이번에는 앞서 Input System 과 Player Status 를 이용해서 플레이어 이동을 구현하려고 합니다.
플레이어 움직임에 앞서 카메라의 움직임도 같이 다뤄볼 것입니다.
카메라 시점은“디아블로”라는 게임처럼 위에서 대각선으로 내려다보는 쿼터뷰(Quarter view) 시점입니다.
키보드로 이동하고 마우스로 원하는 대상을 바라보게 하는 *TPS 게임에서 사용되는 플레이어 조작방법을 구현했습니다.
* TPS - Third-Person Shooter의 줄임말로 게임상의 플레이어가 보는 시점이 아닌, 플레이어를 보는 시점 즉, 3인칭 관찰자 시점으로 플레이하는 비디오 게임 중 슈팅 게임 장르입니다.
Player
- 플레이어는 플레이어 캐릭터의 위치와 충돌을 계산하는 오브젝트입니다.
- 플레이어의 매쉬를 담당하는 Player Body, 카메라인 Camera Arm, 바닥 체크를 위한 Foot Pos 자식으로 가지고 있습니다.
Player Body
- 플레이어 바디는 플레이어의 애니메이션과 스탯(FSM)들을 담당합니다.
Camera Arm
- 카메라 Arm은 플레이어를 바라보는 메인 카메라 입니다.
Foot Pos
- Foot Pos는 플레이어 좌표 (0f, 0.1f, 0f) 에서 Raycast를 바닥으로 쏴 Layer가 Ground인지 아닌지 체크합니다.
우선 카메라 움직임 부터 구현하겠습니다.
국내에서는 마우스를 위로 움직이면 위를 쳐다 보고 아래로 움직이면 아래를 쳐다보게 하는 방식을 많이 사용합니다.
그러나, 해외에서는 이 움직임을 반대로 사용하는 경우가 꽤 있습니다.
PlayerController.cs
...
private void LateUpdate()
{
#region LookARound
if (!Cursor.visible)
{
Vector2 mouseDelta = new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"));
Vector3 cameraAngles = _cameraArm.rotation.eulerAngles; // Quternion => euler
float cameraAngleX = (cameraAngles.x - mouseDelta.y);
// 180 보다 작으면 위쪽으로 회전하는 경우
if (cameraAngleX < 180f)
{
cameraAngleX = Mathf.Clamp(cameraAngleX, -1f, 50f);
}
else
{
cameraAngleX = Mathf.Clamp(cameraAngleX, 300f, 361f);
}
_cameraArm.rotation = Quaternion.Euler(cameraAngleX, cameraAngles.y + mouseDelta.x, transform.rotation.z);
}
#endregion
}
...
LateUpdate는 Update의 모든 작업이 완료된 후, 그리고 화면에 그려지기 전에 호출되어, 특히 카메라와 같이 다른 요소들의 업데이트 결과에 의존하는 로직을 처리하는데 적합하므로 카메라 회전은 LateUpdate 매소드에서 구현했습니다.
유니티에서의 회전은 쿼터니언(Quaternion)으로 나타냅니다. 이러한 쿼터니언을 계산하기 위해 오일러(euler) 각도로 변환하는 함수 eularAngles를 사용했습니다.
참고 - https://herfs.tistory.com/7
앞서 말한 방식 중, 저는 국내에서 사용하는 카메라 방식을 사용했습니다. 이러한 방식은 camAngles.x 카메라 X축 값에서 mouseDelta.y인 마우스 y축 이동량 값을 빼서 카메라의 X축 cameraAngleX인 각도를 계산합니다.
그리고 cameraAngleX 값에 제한을 두지 않으면 카메라가 반전되는 현상이 발생합니다. 이러한 문제를 막기 위해서cameraAngleX 값에 제한을 뒀습니다.
플레이어 움직임
플레이어를 이동시키기 위해서는 먼저 플레이어가 이동할 방향을 찾아야 합니다. 이동할 방향으로는 카메라가 바라보는 방향으로 움직임을 구현하는 것이 가장 적합할 것 같습니다.
PlayerController.cs
...
private void FixedUpdate()
{
#region Move
Vector2 moveInput = InputManager.Instance.MoveVector;
bool isMove = (moveInput.magnitude != 0f);
if (isMove && PlayerEntity.StateMachine.CurrentState != PlayerEntity.States[(int)EnumTypes.PlayerState.Die])
{
_myRigid.constraints = RigidbodyConstraints.FreezeRotation;
Vector3 lookForward = new Vector3(_cameraArm.forward.x, 0f, _cameraArm.forward.z);
Vector3 lookRight = new Vector3(_cameraArm.right.x, 0f, _cameraArm.right.z);
Vector3 moveDir = (lookForward * moveInput.y) + (lookRight * moveInput.x);
if (PlayerEntity.StateMachine.CurrentState == PlayerEntity.States[(int)EnumTypes.PlayerState.Run])
{
_myRigid.velocity = new Vector3(moveDir.x * DataManager.Instance.PlayerStatus.RunSpeed, _myRigid.velocity.y, moveDir.z * DataManager.Instance.PlayerStatus.RunSpeed);
}
else
{
_myRigid.velocity = new Vector3(moveDir.x * DataManager.Instance.PlayerStatus.WalkSpeed, _myRigid.velocity.y, moveDir.z * DataManager.Instance.PlayerStatus.WalkSpeed);
}
_playerBody.forward = moveDir;
}
else
{
_myRigid.constraints = RigidbodyConstraints.FreezeRotation | RigidbodyConstraints.FreezePositionX | RigidbodyConstraints.FreezePositionZ;
}
#endregion
...
}
...
플레이어를 이동하기 위한 키입력은 앞에서 구현한 InputManager의 MoveVector를 사용하여 moveInput 벡터를 만듭니다.
그리고 moveInput의 길이인 magnitude를 사용하여 플레이어가 움직였는지 감지합니다.
움직임이 감지되면lookForward, lookRight 처럼 카메라의 방향을 이용해서 플레이어가 나아갈 방향을 정하고, 플레이어가 가지고 있는 Rigidbody velocity에 이동속도만큼 힘을 줍니다.
전체 코드
https://github.com/HerFS/Unity-RPG/blob/main/Assets/_Scripts/Controller/PlayerController.cs
'Unity > 3D Project' 카테고리의 다른 글
3D RPG - 데이터 저장(Json) (0) | 2024.07.03 |
---|---|
3D RPG - 플레이어 상태(FSM) (0) | 2024.07.03 |
3D RPG - 플레이어 입력 (Input System) (0) | 2024.07.01 |
3D RPG - 플레이어 정보(Player Status) (0) | 2024.07.01 |
3D RPG - 프로젝트 시작 (0) | 2024.07.01 |