게임 속 애니메이션이 많아질 경우.. 모든 애니메이션을 bool로 상태를 파악해서 if~else문으로 출력해주는 코드는 매우 비효율적이며 특히 Blending이 필요한 애니메이션이 많다면 더욱 피곤하다.. 뿐만 아니라, 상태 분기가 많아질수록 버그 발생 확률도 높아지게 된다.
💡 그래서, State 패턴을 사용한다!
ㄴ 각 상태에 따른 행동들 출력하는 함수 생성
[전체 코드]
public class PlayerController : MonoBehaviour
{
[SerializeField] float _speed = 10.0f;
Vector3 _destinationPos;
void Start()
{
Managers.Input.MouseAction -= OnMouseClicked;
Managers.Input.MouseAction += OnMouseClicked;
}
float wait_run_ratio = 0f;
public enum PlayerState
{
Die,
Moving,
Idle,
}
PlayerState _state = PlayerState.Idle;
private void UpdateIdle()
{
//애니메이션
Animator anim = GetComponent<Animator>();
wait_run_ratio = Mathf.Lerp(wait_run_ratio, 0, 10.0f * Time.deltaTime);
anim.SetFloat("wait_run_ratio", wait_run_ratio);
anim.Play("WAIT_RUN");
}
private void UpdateMoving()
{
//player 이동 및 회전
Vector3 dir = _destinationPos - transform.position;
if (dir.magnitude < 0.00001f) //player가 destination에 도착하면
{
_state = PlayerState.Idle;
}
else
{
//목적지로 이동
float moveDist = Math.Clamp(_speed * Time.deltaTime, 0, dir.magnitude);
transform.position += dir.normalized * moveDist;
//목적지를 주시하며 이동 (자연스러운 회전)
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 10 * Time.deltaTime);
}
//애니메이션
Animator anim = GetComponent<Animator>();
wait_run_ratio = Mathf.Lerp(wait_run_ratio, 1, 10.0f * Time.deltaTime);
anim.SetFloat("wait_run_ratio", wait_run_ratio);
anim.Play("WAIT_RUN");
}
private void UpdateDie()
{
//아무것도 못함
}
void Update()
{
//애니메이션 실행
switch (_state)
{
case PlayerState.Die:
UpdateDie();
break;
case PlayerState.Moving:
UpdateMoving();
break;
case PlayerState.Idle:
UpdateIdle();
break;
}
}
void OnMouseClicked(Define.MouseEvent evt)
{
if (_state == PlayerState.Die)
return;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Debug.DrawRay(Camera.main.transform.position, ray.direction * 100.0f, Color.red, 1.0f);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 100.0f, LayerMask.GetMask("Wall")))
{
//클릭한 땅 위치로 player가 이동
_destinationPos = hit.point;
_state = PlayerState.Moving;
}
}
}
현재 내 상태(state)에서 적용할 수 있는 코드를 분리해서 관리할 수 있다는 장점!
지금은 상태에 따른 애니메이션 출력을 코드로 해주고 있다. 하지만 이것을 Animator창에서 노드를 활용하여 하나의 State Machine으로 사용하면 훨씬 용이하다. ㄴ 한 애니메이션(상태)에서 마우스 우클릭 - Make Transition 클릭 - 노드로 다른 애니메이션(상태) 연결 ㄴ 이렇게 노드로 연결해주면 굳이 Blend값을 주지 않아도 자동으로 Blending 모드로 세팅된다. (인스펙터창에서 조절 가능)
ㄴ즉, Exit Time으로 지정한 시간이 끝나면 전환을 할지 말지의 여부 결정.
단, JUMP가 Transitions에서 첫 번째 순서라고 하더라도 RUN -> JUMP 노드의 Has Exit Time이 체크해제되어 있다면? ㄴ RUN이 끝난 후 WAIT으로 변환되어 버린다!
실행했을 때 RUN의 경우 계속 반복해서 애니메이션이 출력되고, JUMP의 경우는 한 번만 출력된 후 가만히 있는다. 이는 애니메이션 파일의 Loop Time 체크 여부에 따라 결정된다.
자동으로 변환되면 안되므로 연결 노드 모두 Has Exit Time 체크해제
조건 삽입 ㄴ float로 변수를 선언했기 때문에 Conditions에 비교문이 Greater와 Less밖에 없다 (Equal X) 그러므로 1을 기준으로 1보다 크면 RUN출력, 1보다 작으면 WAIT 출력으로 조건을 넣어준다.
-> 즉, 코드와 Animator가 업무를 분담하여 애니메이션을 관리한다.
Animator에서 조건을 설정하여 사용한다면 코드에서 직접 애니메이션을 호출하지 않고, 그저 speed 값만 지정한 다음, Animator가 해당 speed 값에 해당하는 애니메이션을 출력해주게 된다.
📄 참고 자료
[인프런] C#과 유니티로 만드는 MMORPG 게임 개발 시리즈_State패턴
애니메이션 변환_Has Exit Time_Unity 공식 문서