[Unity] 3D 플랫포머 게임 - 개인 프로젝트 #2 인풋시스템, 움직임 구현

김소연·2025년 3월 7일

인풋시스템

1) window - package매니저- input system 설치 후 애셋에서 우클릭하여 최하단에 위치한 InputSystems를 생성한다.

2) 액션 맵을 생성하고, Actions을 지정한다

3) 플레이어의 움직임을 담당하는 Move는 타입 value, Vector2 값을 받아오고, 각각 WASD를 할당한다

4) 플레이어의 시야를 담당하는 'Look'은 타입 'delta`
mouse 델타값을 바인딩해준다.

5) 나머지는 버튼 타입. (인벤토리(tab), 공격(좌클릭), 상호작용(E) 등)

6) 인풋 시스템을 적용할 게임 오브젝트에 Player Input 컴포넌트를 달아주고
Actions에 방금 생성한 Input Sytem을 넣어준다.
이때 Behavior은 각자 사용할 방식으로 설정한다.

이번 프로젝트에 사용할 방식은 Invoke Unity Events


움직임 구현

플레이어에 붙여줄 스크립트 <PlayerController.cs>에서 플레이어의 움직임을 구현하자.

먼저 Rigidbody를 가져온다.

  private Rigidbody rigidbody;

  private void Awake()
  {
      rigidbody = GetComponent<Rigidbody>();
  }

1. 이동 관련 함수


private void FixedUpdate()
{
    Move();
}

// 이동 입력
public void OnMove(InputAction.CallbackContext context)
    {
        if (context.phase == InputActionPhase.Performed)
        {
            // 키를 계속 누르면, 값을 불러와서 계속 움직이기
            curMovementInput = context.ReadValue<Vector2>();
        }
        else if(context.phase == InputActionPhase.Canceled)
        {
            // 키를 떼면, 멈추기
            curMovementInput = Vector2.zero;
        }
    }



    // 실제 이동
private void Move()
    {
        // 방향 (앞/뒤 + 좌/우)
        Vector3 dir = transform.forward * curMovementInput.y + transform.right * curMovementInput.x;

        // 대각선 이동 시 속도 균일화
        if (dir.magnitude > 1f)
            dir.Normalize();

        // 속력 = 방향 x 속도
        dir *= moveSpeed;

        // 기존 y 속도 유지
        dir.y = rigidbody.velocity.y;

        // Rigidbody 속도 적용
        rigidbody.velocity = dir;
    }

1
curMovementInput.y → 전진/후진 (W/S 키, 즉 Z축 이동!)
curMovementInput.x → 좌우 이동 (A/D 키, 즉 X축 이동!)
✅ curMovementInput.y는 "전후 움직임 값"으로 사용됨 (실제로는 Z축 이동)
✅ curMovementInput.x는 "좌우 움직임 값"으로 사용됨 (실제로는 X축 이동)

✅ 월드 좌표계 기준 (Unity 기본 좌표)
X → 좌/우
Y → 위/아래 (점프, 중력)
Z → 앞/뒤

캐릭터는 월드 좌표계가 아닌 로컬 좌표계를 따라 방향을 결정한다.
✅ 로컬 좌표계 기준
transform.forward → 현재 캐릭터가 바라보는 방향(앞)
transform.right → 현재 캐릭터 기준으로 오른쪽

따라서 여기서 curMovementInput.y는 "월드의 Y축 이동"이 아니라, "캐릭터 기준 전진/후진(Z축 이동)"


dir.x → 좌우(A/D) 입력에 따라 변하며, 캐릭터가 회전하면 값이 달라질 수 있음
dir.z → 앞뒤(W/S) 입력에 따라 변하며, 캐릭터가 회전하면 값이 달라질 수 있음
dir.y → 초기값이 0이고, 중력/점프를 위해 rigidbody.velocity.y로 유지해야 함

2

dir.y = rigidbody.velocity.y;
rigidbody.velocity = dir;

을 하는 이유는,
캐릭터가 점프 중이거나 낙하할 때 중력이 계속 적용하기 위해 dir.y를 기존 rigidbody.velocity.y 값으로 유지하는 것

  • rigidbody.velocity.y
    땅에 있을 때 → 0 (혹은 작은 값)
    점프할 때 → 양수 (위로 가는 속도)
    떨어질 때 → 음수 (아래로 가는 속도)이다.
Vector3 dir = transform.forward * curMovementInput.y + transform.right * curMovementInput.x;

현재 방향을 가진 벡터 dir의 y값은 0이기 때문에,
이것이 점프에도 적용되면 중력에 의해 떨어져야 하는 캐릭터가 갑자기 y축 속도가 0이 되어버려서 공중에서 멈출 것
따라서 기존의 y 속도 (rigidbody.velocity.y)를 유지하기 위해 dir.y에 대입하고,
이렇게 개선된 dir을 최종적으로 대입해주면

rigidbody.velocity = dir;

기존의 Y축 중력속도, 점프속도가 제대로 유지되고,
X, Z축 속도는 우리가 조작한 값으로 변경된다!

2. 회전 관련 함수

// 회전 입력값
public void OnLook(InputAction.CallbackContext context)
{
    // 마우스는 계속해서 값이 유지되므로
    mouseDelta = context.ReadValue<Vector2>();
}

// 실제 회전
void CameraLook()
{
    camCurXRot += mouseDelta.y * lookSensitivy;
    // 회전값이 최소값, 최대값을 벗어나지 않게
    camCurXRot = Mathf.Clamp(camCurXRot, minLook, maxLook);

    cameraContainer.localEulerAngles = new Vector3(-camCurXRot, 0, 0);
    // 캐릭터의 각도
    //playerPosition.eulerAngles += new Vector3(0, mouseDelta.x * lookSensitivy, 0);
    transform.eulerAngles += new Vector3(0, mouseDelta.x * lookSensitivy, 0);
}
  1. 회전값을 구할 때 mouseDelta.y값을 사용하는 이유

마우스를 왼쪽으로 움직이면 mouseDelta.x는 음수, 캐릭터(카메라)는 왼쪽을 바라봄
마우스를 오른쪽으로 움직이면 mouseDelta.x은 양수,캐릭터는 오른쪽을 바라봄
❗ 마우스의 좌우 이동은 캐릭터의 Y축(Yaw) 회전에 영향을 준다

마우스를 위로 움직이면 mouseDelta.y는 양수, 캐릭터는 아래를 바라봄
마우스를 아래로 움직이면 mouseDelta.y는 음수, 캐릭터가 위를 바라봄
❗ 마우스의 수직 이동은 카메라의 X축(Pitch) 회전에 영향을 준다

✅ 따라서
mouseDelta.y → X축(Pitch) 회전을 조절해서 카메라가 위/아래를 바라보게 만듦
mouseDelta.x → Y축(Yaw) 회전을 조절해서 캐릭터가 좌우를 회전하게 만듦

Y축(Yaw) 회전- y축 잡고 돌려보면 이해가 쉬움

  1. eulerAngles의 역할
    참고링크
    https://blog.naver.com/tlsqudgns6/222193108722

3. 점프 관련 함수

// 점프 입력
void OnJump(InputAction.CallbackContext context)
{
    if (context.phase == InputActionPhase.Started && isGrounded())
    {
        rigidbody.AddForce(Vector2.up * jumpPower, ForceMode.Impulse);
    }
}

// 현재 플레이어가 땅에 있는지, 공중에 있는지 확인하는 함수
bool isGrounded()
{
    // Ray 생성
    Ray[] rays = new Ray[4]
    {
        new Ray(transform.position + (transform.forward * 0.2f) + (transform.up  *0.01f), Vector3.down),
        new Ray(transform.position + (-transform.forward * 0.2f) + (transform.up  *0.01f), Vector3.down),
        new Ray(transform.position + (transform.right * 0.2f) + (transform.up * 0.01f), Vector3.down),
        new Ray(transform.position + (-transform.right * 0.2f) + (transform.up * 0.01f), Vector3.down)
    };

    // Ray 검출
    for(int i = 0; i<rays.Length; i++)
    {
        // groundLayerMask 검출, player만 제외해야 함
        if (Physics.Raycast(rays[i], 0.1f, groundLayerMask))
        {
            return true; // 걸리면 True 반환
        }
    }

    return false; // 아무것도 안 걸리면 False 반환
} 
  1. AddForce(방향*힘의크기,forcemode. )
    forcemode 설정하지 않으면, 디폴트 : forcemode.force
    forcemode.acceration - 질량의 영향을 받지 않음


3인칭 구현

1인칭 시점이 아닌 3인칭 시점을 구현하기 위해, 카메라의 위치를 플레이어 프리팹 뒤에 적절히 배치하면 된다.

실제 플레이할 때 보이는 화면

0개의 댓글