플레이어가 공중에서 지면을 향해 떨어지도록 만들어보자.
현재 Player는 Rigidbody를 사용하고 있지 않기 때문에 Player가 힘의 영향을 받도록 스크립트 처리를 해주자.
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;
private void Update()
{
// 현재 플레이어가 땅에 위치해 있으면서 상태가 Ground라면
if(verticalVelocity < 0f && controller.isGrounded)
{
// Physics.gravity.y : 중력 가속도 -9.7을 의미. 따라서 verticalVelocity는 위로 솟아날 경우를 제외하고 항상 음수일 것이다.
verticalVelocity = Physics.gravity.y * Time.deltaTime;
}
else
{
// 현재 플레이어가 공중에 있으면 Physics.gravity.y * Time.deltaTime 만큼 가속.
verticalVelocity += Physics.gravity.y * Time.deltaTime;
}
// impact가 Vector3.zero가 될 때까지 감소된다.
impact = Vector3.SmoothDamp(impact, Vector3.zero, ref dampingVelocity, drag);
}
public void Reset()
{
// 플레이어가 땅에 닿으면 impact, verticalVelocity 초기화
impact = Vector3.zero;
verticalVelocity = 0f;
}
public void AddForce(Vector3 force)
{
impact += force;
}
public void Jump(float jumpForce)
{
verticalVelocity += jumpForce;
}
}
Player에서 ForceReceiver를 활용할 수 있도록 선언해주자.
''' 생략
public ForceReceiver ForceReceiver { get; private set; }
''' 생략
private void Awake()
{
''' 생략
ForceReceiver = GetComponent<ForceReceiver>();
}
위에서 만든 ForceReceiver를 활용하기 위해 PlayerBaseState를 수정해주자.
private void Move(Vector3 movementDirection)
{
float movementSpeed = GetMovementSpeed();
stateMachine.Player.Controller.Move(
((movementDirection * movementSpeed) + stateMachine.Player.ForceReceiver.Movement)
* Time.deltaTime
);
}
PlayerAirState, PlayerJumpState, PlayerFallState 스크립트를 만들어 준다.
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);
}
}
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();
StopAnimation(stateMachine.Player.AnimationData.JumpParameterHash);
}
public override void PhysicsUpdate()
{
base.PhysicsUpdate();
// 플레이어가 점프를 한 후, 공중에서 velocity.y가 0보다 작아질 때, 즉 플레이어가 떨어지기 시작할 때
// FallState로 전환하기
if (stateMachine.Player.Controller.velocity.y <= 0)
{
stateMachine.ChangeState(stateMachine.FallState);
return;
}
}
}
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();
StopAnimation(stateMachine.Player.AnimationData.FallParameterHash);
}
public override void Update()
{
base.Update();
// 플레이어가 땅에 닿으면 Idle 상태로 전환
if (stateMachine.Player.Controller.isGrounded)
{
stateMachine.ChangeState(stateMachine.IdleState);
return;
}
}
}
위에서 생성한 JumpState, FallState를 추가해주자.
'''생략
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)
{
}
PlayerBaseState에서 점프 키 Input을 받아오기 위해 AddInputActionsCallbacks에 OnJumpStarted라는 이벤트를 구독해준다.
이후, OnJumpStarted는 플레이어가 지상에 있을 때 점프를 하도록 만들어주기 위해 PlayerBaseState를 상속받는 PlayerGroundedState에서 사용하도록 virtual로 만들어준다.
이제 플레이어가 지상에서 점프를 하도록 만들어보자.
'''생략
protected override void OnJumpStarted(InputAction.CallbackContext context)
{
stateMachine.ChangeState(stateMachine.JumpState);
}
점프 키가 입력되었을 때 플레이어 상태를 JumpState로 변경해준다.
플레이어의 점프, 낙하 시 애니메이션을 만들어보자.
플레이어의 애니메이션 컨트롤러에 들어가서 새로운 Sub-StateMachine을 만들어주고 이름을 Air로 변경한다.
이후 Ground와 Air 간의 Transition을 만들어주는데, Air → Ground로 갈 때, StateMachine → Ground로 연결해주어야 한다.
그 다음에는 Air의 애니메이션들을 추가해준다. Jump와 Fall 애니메이션을 넣어준 뒤, 아래와 같이 Transition을 만들어준다.
점프가 되는 모습!