플레이어에 애니메이션 클립을 적용할 준비를 하기 위한 코드를 작성하는 과정이다. 플레이어에 애니메이터 컴포넌트를 붙이고, 플레이어 애니메이션 컨트롤러를 스크립트로 작성할 것이다.
0. 애니메이션별 상태 분류 및 포함되어야 할 기능
1. 구현 과정
2. 고민되는 부분 및 추후 관련되어 작업해야 할 내용
0. 애니메이션별 상태 분류 및 포함되어야 할 기능
플레이어의 상태는 사용되는 애니메이션을 기준으로 아래와 같이 나눠질 수 있겠다.
* 정지한 상태
= 어떠한 키의 입력도 들어오지 않는 상태
* 이동하는 상태
= 입력키의 입력을 받고 이동하는 상태
* 잠에 드는 상태
= 다음날로 넘어가는 상태
* 잠에서 깨어나는 상태
= 침대에 누워 있다가 깨어나는 상태로, 잠에 드는 상태와 종속적이다.
비주얼 노벨 형식이어서 대화창 옆에 뜨는 플레이어의 표정같은 이미지를 제외하고, 게임 씬 상 도트로 드러나는 부분의 애니메이션은 위와 같다.
여기서 정지한 상태에 대해 좀 더 나눠볼 필요가 있어 내용을 정리해 본다.
* 어떠한 키의 입력 및 이벤트 없이 정지해 있는 상태
* NPC와의 대화 / 독백 등으로 대화창UI가 켜져있는 상태
* 대화창 이외 팝업창과 같은 다른 UI가 켜져있는 상태
(아래 2가지의 경우 InputManager에서 UI가 켜질시 플레이어 이동이 강제 정지되도록 설정했기 때문에 해당 부분을 작성한 OnMove에 플레이어의 speed를 0으로 처리하도록 설정함으로써 해당 상태를 PlayerAnimationController에서 정의하지 않았다.)
정지한 상태에서 위와 같이 UI가 켜져 있는 경우에 대한 예외처리를 작업하면 되는 형식으로 보여진다.
(현재 명확하게 기능이 나오지는 않았지만 대화창의 초상화 표정이 바뀌는 동안 게임상의 플레이어 도트 이미지가 바뀌는 경우가 있을 수도 있기 때문)
1. 구현 과정
1.1. 기본 IPlayerState 구현
public interface IPlayerState
{
void EnterState(PlayerAnimationController playerAnimationController);
void UpdateState(PlayerAnimationController playerAnimationController);
void ExitState(PlayerAnimationController playerAnimationController);
}
= 기본적으로 상태가 시작될 때, 업데이트될 때, 상태가 끝날 때로 설정했다.
1.1. 정지한 상태 구현
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerIdleState : IPlayerState
{
public void EnterState(PlayerAnimationController playerAnimationController)
{
playerAnimationController.animator.SetFloat("Speed", 0);
}
public void UpdateState(PlayerAnimationController playerAnimationController)
{
if(playerAnimationController.InputVector.sqrMagnitude > 0)
{
playerAnimationController.TransitionToState(playerAnimationController.WalkingState);
}
//if(isNextDay == true)
{
playerAnimationController.TransitionToState(playerAnimationController.FallingAsleepState);
}
}
public void ExitState(PlayerAnimationController playerAnimationController)
{
}
}
= 잠에 드는 상태로 넘어갈 때, 이동 상태로 넘어갈 때의 경우에 상태를 바꾼다.
1.2. 이동하는 상태 구현
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerWalkingState : IPlayerState
{
public void EnterState(PlayerAnimationController playerAnimationController)
{
playerAnimationController.animator.SetFloat("Speed", 5);
}
public void UpdateState(PlayerAnimationController playerAnimationController)
{
if(playerAnimationController.InputVector.sqrMagnitude == 0)
{
playerAnimationController.TransitionToState(playerAnimationController.IdleState);
}
}
public void ExitState(PlayerAnimationController playerAnimationController)
{
}
}
= 이동하는 상태에서 잠에 드는 상태, 잠에서 깨는 상태 등으로 이동하려면 일단 정지한 Idle 상태로 되돌아가야 하기 때문에 상태 전환은 Idle 상태로 전환하는 것만 구현했다.
1.3. 잠에 드는 상태
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerFallingAsleepState : IPlayerState
{
public void EnterState(PlayerAnimationController playerAnimationController)
{
//잠에 드는 상태의 애니메이션 재생
}
public void UpdateState(PlayerAnimationController playerAnimationController)
{
//다음날로 이동했을 때, 기본 상태로 연결
//if(isBrightDay == true)
{
playerAnimationController.TransitionToState(playerAnimationController.IdleState);
}
}
public void ExitState(PlayerAnimationController playerAnimationController)
{
}
}
= 잠에 들었다가 다음날로 날짜가 전환되면 잠에서 깨어나는 상태로 전환하였다.
1.4. PlayerAnimationController 구현
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerAnimationController : MonoBehaviour
{
public Animator animator;
public Rigidbody2D rb;
public float moveSpeed = 5f;
private IPlayerState currentState;
public readonly IPlayerState IdleState = new PlayerIdleState();
public readonly IPlayerState WalkingState = new PlayerWalkingState();
public readonly IPlayerState FallingAsleepState = new PlayerFallingAsleepState();
public readonly PlayerController playerController;
public Vector2 InputVector { get; private set; }
private void Start()
{
TransitionToState(IdleState);
}
private void Update()
{
currentState.UpdateState(this);
}
public void SetMoveDirection(Vector2 inputVector)
{
InputVector = inputVector;
if(InputVector.x > 0)
{
transform.localScale = new Vector3(1, 1, 1);
}
else if(InputVector.x < 0)
{
transform.localScale = new Vector3(-1, 1, 1);
}
animator.SetFloat("MoveX", InputVector.x);
animator.SetFloat("MoveY", InputVector.y);
animator.SetBool("IsMoving", InputVector != Vector2.zero);
}
public void TransitionToState(IPlayerState newState)
{
currentState?.ExitState(this);
currentState = newState;
currentState.EnterState(this);
}
}
= 처음 세팅될 때 기본 상태로 세팅하고, 움직일 때 inputVector 값을 받으면 플레이어의 방향을 설정하고 애니메이션에 매개변수를 전달해 원하는 애니메이션을 출력하도록 한다.
= 실시간으로 상태가 설정되므로 Update()를 사용하고, 상태를 변경할 때는 TransitionToState를 사용하도록 하였다.
2. 고민되는 부분 및 추후 작업해야 하는 부분
= 애니메이션 컨트롤러여서 애니메이션이 바뀌는 부분을 기준으로 체크해서 상태를 만들어야 한다고 생각했는데, 해당 기준으로 나누는 게 맞는지 고민이다. 잠에 드는 상태는 애니메이션이 전환되는 부분이고, 그 외 날짜 변경처럼 게임의 전체적인 전환은 씬 관리 또는 게임상 날짜 관리 시스템에서 이루어지기 때문이다.
상태를 잘못 설정하면 생각보다 조잡한 느낌이 들어서 좀 더 고민해봐야하는 사항같다.
= 컨트롤러, 매니저 등은 전부 싱글톤 처리해서 접근을 더 편하게 만들어야겠다.