본문 바로가기
스파르타 내배캠

스파르타 내배캠 Unity 3기 41일차

by LemongO 2024. 2. 23.

 

흑...ㅠ

 

 

 

StateMachine.... 아직 글로 풀어쓰는게 어렵다... 너무 어렵다...

그래서 며칠간 좀 고민만 하다 TIL 도 못썼는데 (다른 것도 있었지만)
아무튼 오늘은 대신 개인과제에 쓰인 걸 써봐야겠다.


CameraStateMachine

 

여기서 내가 구현하고자 한 것은 마우스 커서에 따라 3D 효과를 주는 카메라 연출과

버튼을 눌렀을 때 카메라가 돌아가 반대편 화면을 찍으며 3D 효과는 그대로 가져가는 것이다.

 

기본적으로 역 참조 구조인 것을 활용하였다.

public class TitleSceneCameraController : MonoBehaviour
public class CameraStateMachine : StateMachine

 

public class CameraStateBase : IState

 

 

TitleSceneCameraController 의 FixedUpdate에서 커서를 따라가는 카메라 연출을 담당하고있다.

public class TitleSceneCameraController : MonoBehaviour
{
    public CameraStateMachine StateMachine { get; private set; }

    public bool IsChanging { get; private set; } = false;

    private void Awake()
    {
        StateMachine = new CameraStateMachine(this);
    }

    private void Start()
    {
        StateMachine.ChangeState(StateMachine.mainState);
    }

    private void FixedUpdate()
    {
        StateMachine.FixedUpdate();
    }
}

TitleSceneCameraController 에서 담당한다고 말한 이유는

 

public class CameraStateBase : IState
{
    protected CameraStateMachine _stateMachine;
    
    protected Transform mTrans;
    protected Quaternion mStart;
    protected Vector2 mRot = Vector2.zero;    

    public CameraStateBase(CameraStateMachine stateMachine)
    {
        _stateMachine = stateMachine;
        mTrans = stateMachine.Controller.transform;
        mStart = Quaternion.identity;
    }

    public virtual void Enter() { }    

    public virtual void Exit() { }   

    public virtual void FixedUpdate()
    {
        if (_stateMachine.Controller.IsChanging)
            return;

        Vector3 pos = Input.mousePosition;

        float halfWidth = Screen.width * 0.5f;
        float halfHeight = Screen.height * 0.5f;
        float x = Mathf.Clamp((pos.x - halfWidth) / halfWidth, -1f, 1f);
        float y = Mathf.Clamp((pos.y - halfHeight) / halfHeight, -1f, 1f);
        mRot = Vector2.Lerp(mRot, new Vector2(x, y), Time.deltaTime * 5f);

        mTrans.localRotation = mStart * Quaternion.Euler(-mRot.y * _stateMachine.Range.y, mRot.x * _stateMachine.Range.x, 0f);
    }
}

 

어느 상태이던 관계없이 FixedUpdate로 실행이 되도록 설계했기 때문이다.

위의 코드 중 Quaternion mStart가 카메라 연출의 중심이 되는 Rotation인데

public ModuleTiltState(CameraStateMachine stateMachine) : base(stateMachine)
{
}

public override void Enter()
{
    base.Enter();

    mStart = Quaternion.Euler(15, 180, 0);
    _stateMachine.Controller.ChangeCameraLerp(mStart);        
}

 

 

public MainTiltState(CameraStateMachine stateMachine) : base(stateMachine)
{
}    

public override void Enter()
{
    base.Enter();

    mStart = Quaternion.Euler(0, 0, 0);
    _stateMachine.Controller.ChangeCameraLerp(mStart);
}

 

각 상태에 진입하면 mStart를 변경시켜 해당 Rotation 을 중심으로 cursor Following을 연출했다.

 

State의 동작 순서는 Enter만 사용하여 진입할 때 현재 상태에서 중심이 되는 점을 설정 후,
그 점으로 부드럽게 회전하는 연출을 주었다.

 

이 때, FixedUpdate는 상태에 관계없기 때문에 계속해서 실행이 된다.
그렇기 때문에 중심점 변경 후 카메라 회전중엔 FixedUpdate가 실행이 되면 안 됐기 때문에 이를 조건을 걸 필요가 있었고

이를 코루틴으로 해결하였다.

 

하지만 여기서 문제가 하나 더 있는데, State와 StateMachine은 Monobehavior를 상속받은 구조가 아니기 때문에

코루틴을 사용하지 못한다. 하지만 State, StateMachine, Controller 는 서로를 역 참조 하고 있는 구조라 타고 타고
Monobehavior를 상속받은 Controller에서 해당 코루틴 함수를 호출하는 래핑 함수를 호출했다.

 

public void ChangeCameraLerp(Quaternion changeQuat)
{
    StartCoroutine(CoChangeCameraRoutine(changeQuat));
}

private IEnumerator CoChangeCameraRoutine(Quaternion changeQuat)
{
    IsChanging = true;
    UI_BlockerPopup blocker = Managers.UI.ShowPopupUI<UI_BlockerPopup>();

    float current = 0;
    float percent = 0;

    Quaternion startRot = transform.localRotation;

    while (percent < 1)
    {
        current += Time.deltaTime;
        percent = current / 1f;

        transform.localRotation = Quaternion.Lerp(startRot, changeQuat, curve.Evaluate(percent));

        yield return null;
    }

    IsChanging = false;
    blocker.ClosePopup();
}

 

해결은 어찌어찌 하였지만 사실 중간에 있을 RotationState가 따로 있었다면 좀 더 깔끔한 처리가 가능하지 않았을까?

라는 의문이 든다. 이 부분은 내일 과제를 다듬으며 적용시도를 해보아야겠다.