처음에는 레이어를 상위 상태인 Ground, Air, Ladder 레이어만 만드려고 했었다.
그러나 우리 프로젝트에는 같은 상태임에도 Player가 Item을 갖고 있는지 아닌지에 따라서도 애니메이션이 나뉜다. Idle, Run, Walk, Jump, Air 의 애니메이션 파라미터에서 Item을 갖고 있는 애니메이션으로 전환하는 Idle(have), Run(have), Walk(have), Jump(have), Air(have) 파라미터를 생성하기엔 파라미터가 너무 더러워지기때문에 파라미터 이름은 갖되 레이어를 분리해주기로 했다.
GroundLayer (have / NoHave)
AirLayer (have / NoHave)
LadderLayer
PlayerLadderState : BaseState를 상속받는 State. BaseState의 Update문을 받지 않는다
PlayerLadderMoveState : PlayerLadderState를 상속. 사다리 탈때
private void OnTriggerStay(Collider other)
{
if (ladder == null)
{
ladder = other.GetComponent<LadderItem>();
}
if (ladder != null)
{
Debug.Log("사다리타기 가능");
}
if (other.gameObject.layer == LayerMask.NameToLayer("Ladder"))
{
ladderLayer = true;
}
}
private void OnTriggerExit(Collider other)
{
// 다른 사다리 아이템이 아닌 경우 반환
if (other.GetComponent<LadderItem>() != ladder) return;
ladder = null;
if (other.gameObject.layer == LayerMask.NameToLayer("Ladder"))
{
ladderLayer = false;
}
}
내려가는 사다리가 주변에 있으면 상호작용키를 눌러 Player가 사다리를 직접 타러 가는 로직을 구현하기 위해 충돌한 사다리가 있다면 그 사다리를 벗어나기 전까지는 그 사다리가 갖고 있는 LadderItem 값을 저장한다.
LadderItem 스크립트에는 Player가 사다리를 타러 내려갈때 텔레포트 해주는 위치와 올라갈때 텔레포트 시켜줄 위치와 Player가 바라보는 rotation을 저장한다.
public class PlayerLadderState : PlayerBaseState
{
public PlayerLadderState(PlayerStateMachine playerStateMachine) : base(playerStateMachine)
{
}
public override void Enter()
{
base.Enter();
StartAnimation(stateMachine.Player.AnimationData.ladderParameterHash);
}
public override void Exit()
{
base.Exit();
StopAnimation(stateMachine.Player.AnimationData.ladderParameterHash);
StopAnimation(stateMachine.Player.AnimationData.ladderUpParameterHash);
StopAnimation(stateMachine.Player.AnimationData.ladderDownParameterHash);
}
public override void Update()
{
}
protected override void OnInteractionStarted(InputAction.CallbackContext context)
{
}
}
PlayerBaseState를 상속받으며 BaseState의 Update문 기능인 Move를 더이상 호출 받지 않을것이며 Player가 허공에 있을때 생기는 중력 로직도 상속받지 않을것이다.
따라서 사다리 타기 고유의 move로 이동한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerLadderMoveState : PlayerLadderState
{
bool IsHandle;
public PlayerLadderMoveState(PlayerStateMachine playerStateMachine) : base(playerStateMachine)
{
}
public override void Enter()
{
base.Enter();
stateMachine.Player.Animator.speed = 0.1f;
}
public override void Exit()
{
base.Exit();
StopAnimation(stateMachine.Player.AnimationData.ladderUpParameterHash);
StopAnimation(stateMachine.Player.AnimationData.ladderDownParameterHash);
IsHandle = false;
}
public override void HandleInput()
{
base.HandleInput();
ClimbLadder();
}
private void ClimbLadder()
{
// 이동 입력 처리
if (stateMachine.MovementInput.y > 0)// 'Up'
{
if(stateMachine.Player.ladderCollider.ladder != null)
{
stateMachine.ChangeState(stateMachine.LadderUpState);
}
StartAnimation(stateMachine.Player.AnimationData.ladderUpParameterHash);
StopAnimation(stateMachine.Player.AnimationData.ladderDownParameterHash);
stateMachine.Player.Animator.speed = 1.2f;
Vector3 moveDirection = Vector3.up * stateMachine.MovementInput.y * stateMachine.Player.Data.LadderSpeed;
stateMachine.Player.Controller.Move(moveDirection * Time.deltaTime);
}
else if (stateMachine.MovementInput.y < 0)// 'Down'
{
StartAnimation(stateMachine.Player.AnimationData.ladderDownParameterHash);
StopAnimation(stateMachine.Player.AnimationData.ladderUpParameterHash);
stateMachine.Player.Animator.speed = 1.2f;
Vector3 moveDirection = Vector3.down * Mathf.Abs(stateMachine.MovementInput.y) * stateMachine.Player.Data.LadderSpeed; // y 값이 음수이므로 절댓값 Abs
stateMachine.Player.Controller.Move(moveDirection * Time.deltaTime);
IsHandle = true;
}
else if(stateMachine.MovementInput.y == 0)
{
IsHandle = false;
stateMachine.Player.Animator.speed = 0f;
}
}
public override void PhysicsUpdate()
{
base.PhysicsUpdate();
//땅을 밟으면 멈추게
if (stateMachine.Player.Controller.isGrounded && IsHandle)
{
if(stateMachine.Player.Controller.velocity.y == 0)
{
stateMachine.ChangeState(stateMachine.IdleState); //땅을 밟으면 Idle 상태로 전환
return;
}
}
}
}
기존 Base에서 X좌표와 Z좌표로 움직이는 로직 말고 Up키를 누르면 vector3.Up 방향으로 힘을주고 Down키를 누르면 Vector3.down 방향으로 힘을 준다. Left와 Right키는 설정이 아직 없다.
사다리 애니메이션은 2개로 올라갈때와 내려올때 애니메이션이 존재하며 키를 누르지 않는 상태일때는 애니메이션의 speed를 0으로 바꾸어준다. 그렇게하면 애니메이션이 key를 누르지 않는 상태에서 누르는 상태로 이동할 때 이전 애니메이션을 이어서 내려간다.
그러나 아직까지 사다리를 내려가는 상태에서 올라가는 상태로 교차할때는 어색한 감이 남아있다.
PhysicsUpdate문에서는 IsGround를 체크하여 플레이어가 땅을 밟으면 사다리State를 종료하고 Idle 상태로 이동할 예정이다.
PlayerLadderUpState와 PlayerLadderDownState는 플레이어가 사다리 근처에서 상호작용을 누르거나, 사다리가 더이상 올라갈 부분이 없을때 Idle상태로 전환하기 이전 상태이다.
올라갈때와 내려갈때는 같은 애니메이션이지만 애니메이션을 뒤집어서 올라갈때와 내려갈때를 만들었다.
public override void Enter()
{
base.Enter();
StartAnimation(stateMachine.Player.AnimationData.ladderUpStartParameterHash);
}
public override void Exit()
{
base.Exit();
StopAnimation(stateMachine.Player.AnimationData.ladderUpStartParameterHash);
}
public override void Update()
{
base.Update();
if (stateMachine.Player.StateChange)
{
stateMachine.Player.StateChange = false;
stateMachine.Player.curPlayer.transform.position = stateMachine.Player.ladderCollider.ladder.setUpTrasform;
stateMachine.ChangeState(stateMachine.IdleState);
}
}
PlayerLadderUpState 와 PlayerLadderDownState는 둘다 애니메이션에 이벤트를 추가하여 애니메이션이 끝나면 Idle 상태로 체인지 하는 bool값을 추가했다.
public override void PhysicsUpdate()
{
base.PhysicsUpdate();
//땅을 밟으면 멈추게
if (stateMachine.Player.Controller.isGrounded)
{
stateMachine.ChangeState(stateMachine.IdleState); //땅을 밟으면 Idle 상태로 전환
}
}
플레이어가 사다리를 타다가 바닥에 닿으면 Idle 상태로 전환시키는 로직을 만들기 위해 IsGround == true인지 체크하여 true면 PlayerLadderMoveState 에서 Idle 상태로 전환시키게 하였다.
그러나 바닥에 닿았을때 Idle 상태로 전환되는게 아니라 PlayerLadderDownState 이후 바로 Idle 상태로 전환되는 문제를 확인하였다.
위 의미지의 콜라이더를 보면 PlayerLadderDownState 가장 마지막 애니메이션에서 콜라이더의 바닥 부분이 사다리와 거의 수직으로 접촉되는걸 확인할 수 있다. 애니메이션 때문에 콜라이더가 기울어지면서 생긴 문제였다.
if (stateMachine.Player.Controller.isGrounded && IsHandle)
{
if(stateMachine.Player.Controller.velocity.y == 0)
{
stateMachine.ChangeState(stateMachine.IdleState); //땅을 밟으면 Idle 상태로 전환
return;
}
}
아래키를 누르고 있는지 확인하는 bool값 IsHandle을 추가하고 Down 방향키를 누르고 있는지 먼저 확인한다.
그리고 방향키를 누르고 있음에도 velocity.y 값이 0이라면 Idle 상태로 전환되게 수정한다.
해결해야하는 버그들