이번 프로젝트에서는 플레이어 상태 관리 시스템을 유니티에서 구현하였습니다. 이 시스템은 플레이어의 이동 및 상호작용을 관리하고, 상태 머신을 통해 다양한 상태를 전환하며 플레이어의 행동을 제어합니다. 이 일지에서는 구현된 주요 기능과 코드의 세부적인 설명을 다룹니다.
1. 주요 기능
2. 주요 클래스 및 구조
public class PlayerBaseState : IState
{
protected PlayerStateMachine stateMachine;
protected readonly PlayerSO playerSO;
private Transform civilianTransform;
private GameObject detectedCivilian; // 플레이어와 충돌한 민간인
private Vector2 targetDirection; // 목표 방향
private bool isMoving = false; // 이동 여부 확인
private float mouseButtonDownTime = 0f; // 마우스 버튼이 눌린 시간 추적
private const float requiredHoldTime = 0.2f; // 마우스 버튼을 눌러야 하는 최소 시간
public PlayerBaseState(PlayerStateMachine stateMachine)
{
this.stateMachine = stateMachine;
playerSO = stateMachine.Player.Data;
}
public virtual void Enter()
{
AddInputActionsCallbacks(); // 입력 액션 콜백 추가
PromptManager.Instance.OnPromptClosed += HandlePromptClosed;
}
public virtual void Exit()
{
RemoveInputActionsCallbacks(); // 입력 액션 콜백 제거
PromptManager.Instance.OnPromptClosed -= HandlePromptClosed;
}
protected void StartAnimation(int animatorHash)
{
stateMachine.Player.Animator.SetBool(animatorHash, true);
}
protected void SetAnimation(int animatorHashX, int animatorHashY)
{
stateMachine.Player.Animator.SetFloat(animatorHashX, targetDirection.x);
stateMachine.Player.Animator.SetFloat(animatorHashY, targetDirection.y);
}
protected void StopAnimation(int animatorHash)
{
stateMachine.Player.Animator.SetBool(animatorHash, false);
}
public virtual void HandleInput()
{
ReadMovementInput();
if (stateMachine.Player.Input.IsLeftMouseButtonPressed())
{
mouseButtonDownTime += Time.deltaTime;
if (!isMoving && mouseButtonDownTime >= requiredHoldTime)
{
Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
targetDirection = (mousePosition - (Vector2)stateMachine.Player.transform.position).normalized;
isMoving = true;
}
}
else
{
mouseButtonDownTime = 0f;
isMoving = false;
}
}
public virtual void Update()
{
if (!PromptManager.Instance.promptPanel.activeSelf && isMoving)
{
UpdateTargetDirection();
Move();
}
if (!PromptManager.Instance.isCivilianDetected)
{
CheckForCollisionWithCivilian();
}
else
{
CheckCivilianDistance();
}
}
protected virtual void AddInputActionsCallbacks()
{
PlayerController input = stateMachine.Player.Input;
input.playerActions.MouseDelta.canceled += OnMovementCanceled;
}
protected virtual void RemoveInputActionsCallbacks()
{
PlayerController input = stateMachine.Player.Input;
input.playerActions.MouseDelta.canceled -= OnMovementCanceled;
}
protected virtual void OnMovementCanceled(InputAction.CallbackContext context)
{
if (stateMachine.MovementInput == Vector2.zero) return;
stateMachine.ChangeState(stateMachine.IdleState);
}
private void ReadMovementInput()
{
stateMachine.MovementInput = stateMachine.Player.Input.GetMouseDelta();
}
private void UpdateTargetDirection()
{
Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
targetDirection = (mousePosition - (Vector2)stateMachine.Player.transform.position).normalized;
}
private void Move()
{
if (!stateMachine.Player.Input.IsLeftMouseButtonPressed() || mouseButtonDownTime < requiredHoldTime)
{
stateMachine.ChangeState(stateMachine.IdleState);
return;
}
Transform playerTransform = stateMachine.Player.transform;
playerTransform.position += (Vector3)targetDirection * GetMovementSpeed() * Time.deltaTime;
}
private float GetMovementSpeed()
{
return stateMachine.MovementSpeed * stateMachine.MovementSpeedModifier;
}
private void CheckForCollisionWithCivilian()
{
CapsuleCollider2D capsuleCollider = stateMachine.Player.GetComponent<CapsuleCollider2D>();
if (capsuleCollider == null)
{
Debug.LogError("CapsuleCollider2D 컴포넌트를 찾을 수 없습니다!");
return;
}
Vector2 playerPosition = stateMachine.Player.transform.position;
float radius = capsuleCollider.size.y / 2f;
RaycastHit2D[] hits = Physics2D.CircleCastAll(playerPosition, radius, Vector2.zero);
foreach (var hit in hits)
{
if (hit.collider.CompareTag("Civilian"))
{
PromptManager.Instance.OpenPromptPanel();
stateMachine.ChangeState(stateMachine.IdleState);
PromptManager.Instance.isCivilianDetected = true;
civilianTransform = hit.collider.transform;
detectedCivilian = hit.collider.gameObject;
PromptManager.Instance.SetDetectedCivilian(detectedCivilian);
DialogManager.Instance.sc = hit.collider.gameObject.GetComponent<SpecialCharacter>();
if (!DialogManager.Instance.sc.isCorrectAns)
PromptManager.Instance.dominateBtnColor.color = Color.gray;
else
PromptManager.Instance.dominateBtnColor.color = Color.white;
InteractionEvent intercationEvent = detectedCivilian.transform.GetComponent<InteractionEvent>();
Dialogue[] dialogues = intercationEvent.GetDialogue();
DialogManager.Instance.GetDialogues(dialogues);
break;
}
}
}
private void CheckCivilianDistance()
{
CapsuleCollider2D capsuleCollider = stateMachine.Player.GetComponent<CapsuleCollider2D>();
if (capsuleCollider == null)
{
Debug.LogError("CapsuleCollider2D 컴포넌트를 찾을 수 없습니다!");
return;
}
float radius = capsuleCollider.size.x * 2;
if (civilianTransform == null) return;
float distance = Vector3.Distance(stateMachine.Player.transform.position, civilianTransform.position);
if (distance > radius)
{
PromptManager.Instance.isCivilianDetected = false;
}
}
private void HandlePromptClosed()
{
if (!PromptManager.Instance.isCivilianDetected)
{
stateMachine.ChangeState(stateMachine.WalkState);
}
}
}
PlayerBaseState 클래스는 모든 플레이어 상태의 기본 클래스로, 상태 전환, 애니메이션 관리, 입력 처리, 이동 로직 및 민간인과의 상호작용을 처리합니다.
public class PlayerIdleState : PlayerBaseState
{
public PlayerIdleState(PlayerStateMachine playerStateMachine) : base(playerStateMachine)
{
}
public override void Enter()
{
stateMachine.MovementSpeedModifier = 0f;
base.Enter();
StartAnimation(stateMachine.Player.AnimationData.IdleParameterHash);
}
public override void Exit()
{
base.Exit();
StopAnimation(stateMachine.Player.AnimationData.IdleParameterHash);
}
public override void HandleInput()
{
base.HandleInput();
if (stateMachine.Player.Input.IsLeftMouseButtonPressed())
{
stateMachine.ChangeState(stateMachine.WalkState);
}
}
public override void Update()
{
base.Update();
}
}
PlayerStateMachine 클래스는 상태 머신을 관리하며 상태 전환을 담당합니다.
public class PlayerWalkState : PlayerBaseState
{
public PlayerWalkState(PlayerStateMachine playerStateMachine) : base(playerStateMachine)
{
}
public override void Enter()
{
stateMachine.MovementSpeedModifier = playerSO.WalkSpeedModifier;
base.Enter();
StartAnimation(stateMachine.Player.AnimationData.WalkParameterHash);
SetAnimation(stateMachine.Player.AnimationData.DirXParameterHash, stateMachine.Player.AnimationData.DirYParameterHash);
}
public override void Exit()
{
base.Exit();
StopAnimation(stateMachine.Player.AnimationData.WalkParameterHash);
}
public override void HandleInput()
{
base.HandleInput();
if (!stateMachine.Player.Input.IsLeftMouseButtonPressed())
{
stateMachine.ChangeState(stateMachine.IdleState);
}
}
}
PlayerWalkState 클래스는 플레이어의 Walk 상태를 관리합니다.
public class PlayerController : MonoBehaviour
{
public PlayerInputs playerInputs { get; private set; }
public PlayerInputs.PlayerActions playerActions { get; private set; }
public event Action OnLeftClick;
private void Awake()
{
playerInputs = new PlayerInputs();
playerActions = playerInputs.Player;
playerActions.LeftClick.performed += context => OnLeftClick?.Invoke();
}
private void OnEnable()
{
playerInputs.Enable();
}
private void OnDisable()
{
playerInputs.Disable();
}
public Vector2 GetMouseDelta()
{
return playerActions.MouseDelta.ReadValue<Vector2>();
}
public bool IsLeftMouseButtonPressed()
{
return playerActions.LeftClick.IsPressed();
}
}
PlayerController 클래스는 플레이어의 입력을 처리합니다.
public class Player : MonoBehaviour
{
[field: Header("References")]
[field: SerializeField] public PlayerSO Data { get; private set; }
public PlayerController Input { get; private set; }
private PlayerStateMachine stateMachine;
[field: Header("Animations")]
[field: SerializeField] public AnimationData AnimationData { get; private set; }
public Animator Animator { get; private set; }
private void Awake()
{
AnimationData.Initialize();
Animator = GetComponentInChildren<Animator>();
Input = GetComponent<PlayerController>();
stateMachine = new PlayerStateMachine(this);
}
void Start()
{
stateMachine.ChangeState(stateMachine.IdleState);
}
private void Update()
{
stateMachine.HandleInput();
stateMachine.Update();
}
}
Player 클래스는 플레이어 게임 오브젝트를 관리합니다.
이번 프로젝트를 통해 유니티에서의 플레이어 상태 관리 시스템을 구현하면서 상태 머신의 개념을 깊이 이해하게 되었습니다. 이 시스템을 통해 플레이어의 이동 및 상호작용을 효율적으로 관리할 수 있으며, 향후 더 복잡한 상태 및 기능을 추가할 수 있는 기반을 마련하였습니다.
댓글 영역