오늘은 어제에 이어서 Player 구현 중 애니메이션을 적용시켜 플레이어를 드디어 움직여보는 단계이다.
우선 플레이어를 움직이기 위해서는 이동상태에 관련된 상태머신을 또 따로 만들어 주어야 한다.
※ PlayerStateMachine , PlayerBaseState , PlayerGroundState , PlayerIdleState 수정
※ PlayerWalkState , PlayerRunState 추가
플레이어의 상태에 추가나 변동사항이 생긴다면 PlayerStateMachine도 수정을 해 주어야 한다.
PlayerStateMachine 수정
// 생략
// States
public PlayerIdleState IdleState { get; }
public PlayerWalkState WalkState { get; }
public PlayerRunState RunState { get; }
// 생략
public PlayerStateMachine(Player player)
{
this.Player = player;
IdleState = new PlayerIdleState(this);
WalkState = new PlayerWalkState(this);
RunState = new PlayerRunState(this);
또한 BaseState도 수정을 해야 하는데 BaseState에서는 구조만 잡아주기 때문에, 키입력이 유실되거나 입력됐을 때
호출할 메서드만 만들면 된다. 그리고 생성된 메서드는 사용하는 곳에서 가져다 쓰는 구조형식이다.
PlayerBaseState 수정
//키 입력처리 부분
protected virtual void AddInputActionsCallbacks()
{
PlayerInput input = stateMachine.Player.Input;
input.PlayerActions.Movement.canceled += OnMoveCanceled;
input.PlayerActions.Run.started += OnRunStarted;
}
protected virtual void RemoveInputActionsCallbacks()
{
PlayerInput input = stateMachine.Player.Input;
input.PlayerActions.Movement.canceled += OnMoveCanceled;
input.PlayerActions.Run.started += OnRunStarted;
}
// move와 run을 callback 함수로 받아서 정의
protected virtual void OnMoveCanceled(InputAction.CallbackContext context)
{
}
protected virtual void OnRunStarted(InputAction.CallbackContext context)
{
}
PlayerGroundState 수정
protected override void OnMoveCanceled(InputAction.CallbackContext context)
{
if(stateMachine.MovementInput == Vector2.zero)
{
return;
}
stateMachine.ChangeState(stateMachine.IdleState);
base.OnMoveCanceled(context);
}
protected virtual void OnMove()
{
stateMachine.ChangeState(stateMachine.WalkState);
}
GroundState에서 키를 뗐을 때 변경되는 플레이어의 상태에 대해서 다루기 때문에 ground에서 관리하는 것이다.
그리고 추가된 스테이트인 walk 와 run에서도 스크립트를 마저 마무리를 한다.
<PlayerWalkState>
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerWalkState : PlayerGroundState
{
public PlayerWalkState(PlayerStateMachine playerstateMachine) : base(playerstateMachine)
{
}
public override void Enter()
{
stateMachine.MovementSpeedModifier = groundData.WalkSpeedModifier;
base.Enter();
StartAnimation(stateMachine.Player.AnimationData.WalkParameterHash);
}
public override void Exit()
{
base.Exit();
StopAnimation(stateMachine.Player.AnimationData.WalkParameterHash);
}
protected override void OnRunStarted(InputAction.CallbackContext context)
{
base.OnRunStarted(context);
stateMachine.ChangeState(stateMachine.RunState);
}
}
<PlayerRunState>
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerRunState : PlayerGroundState
{
public PlayerRunState(PlayerStateMachine playerstateMachine) : base(playerstateMachine)
{
}
public override void Enter()
{
stateMachine.MovementSpeedModifier = groundData.RunSpeedModifier;
base.Enter();
StartAnimation(stateMachine.Player.AnimationData.RunParameterHash);
}
public override void Exit()
{
base.Exit();
StopAnimation(stateMachine.Player.AnimationData.RunParameterHash);
}
}
이동처리는 base 에서 하고있기 때문에 runstate 에서는 애니메이션만 관리해주면 된다.
PlayerIdleState 수정
public override void Update()
{
base.Update();
// 이동이 일어난다면 OnMove를 실행한다. idle에서 키 입력이 되면 walk로 전환,
// walk에서 키 입력이 되면 run이 되는 등
// 이동처리가 되면 state에서 onmove로 전환을 시킨다
// 즉, 키입력이 되면 각각의 스테이트로 전환을 시킨다.
if(stateMachine.MovementInput != Vector2.zero)
{
OnMove();
return;
}
}
그리고 플레이어의 Walk와 Run상태로 진입하도록 구조설계를 했으면, Idle에서 각각의 state로 전환하도록 update부분을 수정해주면 된다. 해당 수정 부분은 키 입력이 들어오면 해당 키에 상응하는 상태로 전환하도록 만들어 주는 로직이다.
※ 애니메이션
이번엔 애니메이션을 처리만해주면 이동구현이 끝나는데, 먼저 player 하위에 있는 오브젝트에 Animator를 추가한다.
우선 Combo를 제외한 나머지 파라미터들은 모두 bool값으로 생성하였고, 위와 같이 애니메이션의 연결을 구성하였다.
하지만 여기까지만 구현을 하게 되면 아쉽지만 다양한 버그가 발생한다.
1. 플레이어가 공중에서 떨어지지 않는다거나
2. 달리기 키를 누르기만하면 혼자 달리기 애니메이션이 출력된다거나...
위의 두 가지 사례를 다 잡기 위해서 하나씩 해결을 해보고자 한다.
우선, 캐릭터에 Rigidbody 컴포넌트를 따로 달아주지 않아서 중력구현이 되지 않기 때문에 아마 캐릭터가 공중에 뜬 상황이 발생한 것 같은데, 이 부분은 ForceReceiver를 통해서 velocity값만큼 천천히 감소하도록 구현한다.
<ForceReceiver>
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ForceReceiver : MonoBehaviour
{
[SerializeField] private CharacterController controller;
[SerializeField] private float drag = 0.3f;
private Vector3 dampingVelocity;
private Vector3 impact;
private float verticalVelocity;
public Vector3 Movement => impact + Vector3.up * verticalVelocity;
void Update()
{
if(verticalVelocity < 0f && controller.isGrounded)
{
verticalVelocity = Physics.gravity.y * Time.deltaTime;
}
else
{
verticalVelocity += Physics.gravity.y * Time.deltaTime;
}
impact = Vector3.SmoothDamp(impact, Vector3.zero, ref dampingVelocity, drag);
}
public void Reset()
{
impact = Vector3.zero;
verticalVelocity = 0f;
}
public void AddForce(Vector3 force)
{
impact += force;
}
public void Jump(float jumpForce)
{
verticalVelocity += jumpForce;
}
}
Player 수정
public class Player : MonoBehaviour
{
// 생략
public ForceReceiver ForceReceiver { get; private set; }
private void Awake()
{
// 생략
ForceReceiver = GetComponent<ForceReceiver>();
PlayerBaseState 수정
private void Move(Vector3 movementDirection)
{
// 플레이어 이동처리
float movementSpeed = GetMovementSpeed();
stateMachine.Player.Controller.Move(
((movementDirection * movementSpeed)
+ stateMachine.Player.ForceReceiver.Movement)
*Time.deltaTime
);
}
ForceReceiver에서 velocity의 값을 받아오기 때문에 공중에서 바닥으로 떨어지는 구현은 가능하다.
그리고 플레이어에게 ForceReceiver를 달아주고 controller에는 charactercontroller가 달려있는 player자신을 넣어준다.
여기까지 준비한 후 실행을 해본다면, 플레이어가 공중에서 바닥으로 떨어지는걸 볼 수 있다.
그리고 달리기 키를 누르기만 하면 제자리에서 달리기 애니메이션이 실행되는 부분은 Animator에서 수정하였다.
Idle 상태에서 Run으로 가는 direction을 없애는 걸로 달리기 버튼 클릭시 제자리 달리기를 막아둘 수 있다.
※ 플레이어 점프 구현
※ PlayerStateMachines : PlayerAirState , PlayerJumpState , PlayerFallState 추가
<PlayerAirState>
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerAirState : PlayerBaseState
{
public PlayerAirState(PlayerStateMachine playerstateMachine) : base(playerstateMachine)
{
}
public override void Enter()
{
base.Enter();
StartAnimation(stateMachine.Player.AnimationData.AirParameterHash);
}
public override void Exit()
{
base.Exit();
StopAnimation(stateMachine.Player.AnimationData.AirParameterHash);
}
}
<PlayerJumpState>
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerJumpState : PlayerAirState
{
public PlayerJumpState(PlayerStateMachine playerstateMachine) : base(playerstateMachine)
{
}
public override void Enter()
{
stateMachine.JumpForce = stateMachine.Player.Data.AirData.JumpForce;
stateMachine.Player.ForceReceiver.Jump(stateMachine.JumpForce);
base.Enter();
StartAnimation(stateMachine.Player.AnimationData.JumpParameterHash);
}
public override void Exit()
{
base.Exit();
StartAnimation(stateMachine.Player.AnimationData.JumpParameterHash);
}
public override void PhysicsUpdate()
{
base.PhysicsUpdate();
// jump상태일 때는 velocity.y가 0보다 큰 값으로 적용중인 상태이므로,
// 땅에 떨어질 때에는 velocity.y값이 0보다 작게 되어야 함.
// 따라서 y값이 떨어질때 FallState를 적용하는 구조이어야 함.
if(stateMachine.Player.Controller.velocity.y <= 0)
{
stateMachine.ChangeState(stateMachine.FallState);
return;
}
}
}
<PlayerFallState>
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerFallState : PlayerAirState
{
public PlayerFallState(PlayerStateMachine playerstateMachine) : base(playerstateMachine)
{
}
public override void Enter()
{
base.Enter();
StartAnimation(stateMachine.Player.AnimationData.FallParameterHash);
}
public override void Exit()
{
base.Exit();
StartAnimation(stateMachine.Player.AnimationData.FallParameterHash);
}
public override void Update()
{
base.Update();
if(stateMachine.Player.Controller.isGrounded)
{
stateMachine.ChangeState(stateMachine.IdleState);
return;
}
}
}
PlayerStateMachine 수정
public PlayerJumpState JumpState { get; }
public PlayerFallState FallState { get; }
public PlayerStateMachine(Player player)
{
// 생략
JumpState = new PlayerJumpState(this);
FallState = new PlayerFallState(this);
// 생략
}
PlayerBaseState 수정
protected virtual void AddInputActionsCallbacks()
{
// 생략
stateMachine.Player.Input.PlayerActions.Jump.started += OnJumpStarted;
}
protected virtual void RemoveInputActionsCallbacks()
{
// 생략
stateMachine.Player.Input.PlayerActions.Jump.started -= OnJumpStarted;
}
protected virtual void OnJumpStarted(InputAction.CallbackContext context)
{
}
'TIL (since 2023.08.07 ~ )' 카테고리의 다른 글
2023-10-30 TIL(Unity 최종 프로젝트 6일차) (0) | 2023.10.30 |
---|---|
2023-10-27 TIL(Unity 최종 프로젝트 5일차) (0) | 2023.10.27 |
2023-10-25 TIL(Unity 최종 프로젝트 3일차) (0) | 2023.10.25 |
2023-10-24 TIL(Unity 최종 프로젝트 2일차) (1) | 2023.10.24 |
2023-10-23 TIL(Unity 최종 프로젝트 1일차) (0) | 2023.10.23 |