Unity Input System 기본 이해

Unity의 Input System은 기존 Input.GetKey(), Input.GetAxis() 방식보다 한 단계 추상화된 입력 처리 시스템이다.

핵심은 딱 하나다.

“키보드의 W를 눌렀다”가 아니라 “이동이라는 입력이 발생했다”를 다루는 것.

이 관점만 잡으면 Input System 구조가 상당히 단순해진다.


전체 구조

Unity Input System은 크게 다음 관계로 이해하면 된다.

실제 입력 장치
    ↓
Keyboard / Mouse / Gamepad / Touch
    ↓
Input Action
    ↓
Input Action Map
    ↓
PlayerInput 또는 C# 코드
    ↓
게임 로직

예를 들어 플레이어 이동을 만든다고 하자.

W / A / S / D
        ↓
    Move Action
        ↓
   Player Action Map
        ↓
 PlayerController
        ↓
     이동

여기서 중요한 것은 W 자체를 게임 코드가 알 필요가 없다는 것이다.


Input System을 사용하는 이유

기존 방식에서는 보통 이렇게 작성한다.

if (Input.GetKey(KeyCode.W))
{
    Move(Vector3.forward);
}

이 코드에는 문제가 있다.

게임 로직이 키보드에 직접 결합되어 있다.

게임 로직
  ↓
Keyboard
  ↓
W

게임패드로 바꾸려면?

if (Input.GetKey(KeyCode.W))

를 뜯어고쳐야 한다.

Input System에서는 이렇게 분리한다.

게임 로직
  ↑
Move
  ↑
Input Action
  ↑
WASD / Gamepad Stick / Joystick / ...

따라서 게임 로직 입장에서는

“W가 눌렸다”

가 아니라

“Move 입력값이 들어왔다”

만 알면 된다.


Input Action

가장 중요한 개념이다.

Input Action = 게임에서 발생하는 입력의 의미

예를 들면:

Move
Jump
Attack
Interact
Dash
Pause
Look
Fire

이런 것들이 Action이다.

예를 들어:

Move

라는 Action에

WASD
Left Stick

을 연결할 수 있다.

그러면 게임 코드는 장치가 무엇인지 신경 쓸 필요가 없다.


Input Action Asset

Unity에서 Input System을 사용하면 보통

PlayerInputActions.inputactions

같은 파일을 만든다.

이 파일이 입력 설정 데이터다.

대략 이런 구조가 된다.

PlayerInputActions
│
├── Player
│   ├── Move
│   ├── Look
│   ├── Jump
│   ├── Attack
│   └── Interact
│
└── UI
    ├── Navigate
    ├── Submit
    └── Cancel

여기서 Player, UI 같은 것이 Action Map이다.


Action Map

Action을 묶어놓은 그룹이다.

예를 들어:

Player
 ├── Move
 ├── Look
 ├── Jump
 └── Attack

그리고

UI
 ├── Navigate
 ├── Submit
 └── Cancel

이렇게 만들 수 있다.

왜 굳이 나누냐?

게임 플레이 중에는

Player Action Map 활성화
UI Action Map 비활성화

메뉴를 열면

Player Action Map 비활성화
UI Action Map 활성화

같은 처리가 가능하기 때문이다.

즉 입력 컨텍스트를 관리하는 단위라고 보면 된다.


Binding

Action에 실제 입력 장치를 연결하는 것이 Binding이다.

예를 들어:

Move

라는 Action이 있다면:

Move
 ├── W
 ├── A
 ├── S
 ├── D
 └── Gamepad Left Stick

이런 식으로 Binding을 설정할 수 있다.

좀 더 정확히는 WASD를 하나의 2D Vector Composite로 묶는다.

Move
 └── 2D Vector
      ├── Up       → W
      ├── Down     → S
      ├── Left     → A
      └── Right    → D

그러면 최종적으로 코드에서는:

Vector2 moveInput

하나만 받으면 된다.


Control

Control은 실제 입력 장치의 개별 입력 요소다.

예를 들어 키보드:

Keyboard
 ├── w
 ├── a
 ├── s
 └── d

게임패드:

Gamepad
 ├── leftStick
 ├── rightStick
 ├── buttonSouth
 └── buttonNorth

마우스:

Mouse
 ├── position
 ├── delta
 ├── leftButton
 └── rightButton

즉 관계를 정리하면:

Device
   ↓
Control
   ↓
Binding
   ↓
Action
   ↓
Action Map

Action의 Input Value

Action은 입력값도 가진다.

예를 들어 Jump:

Jump
→ Button
→ bool

Move:

Move
→ Vector2

Look:

Look
→ Vector2

Trigger:

Fire
→ float

대표적으로:

ActionType
JumpButton
AttackButton
MoveVector2
LookVector2
TriggerAxis / Float

가장 중요한 부분: Input Action의 생명주기

Input System에는 Action을 감지하는 대표적인 이벤트가 있다.

started
performed
canceled

예를 들어 버튼을 누른다면:

누르기 시작
   ↓
started

입력 발생
   ↓
performed

손을 뗌
   ↓
canceled

다만 모든 Action이 정확히 이런 식으로만 동작한다고 단순화하면 안 된다. Action의 타입과 Interaction 설정에 따라 발생 시점이 달라진다.

일단 기본적으로는 이렇게 이해하면 충분하다.


실제 코드

예를 들어 Move Action을 만든다고 하자.

using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerController : MonoBehaviour
{
    [SerializeField]
    private InputActionReference moveAction;

    private void OnEnable()
    {
        moveAction.action.Enable();
    }

    private void OnDisable()
    {
        moveAction.action.Disable();
    }

    private void Update()
    {
        Vector2 input = moveAction.action.ReadValue<Vector2>();

        Vector3 movement = new Vector3(
            input.x,
            0f,
            input.y
        );

        transform.position += movement * Time.deltaTime;
    }
}

여기서 핵심은:

Vector2 input = moveAction.action.ReadValue<Vector2>();

이다.

게임 코드는 이제

W인지
A인지
Gamepad인지
Joystick인지

를 전혀 모른다.

그냥

Vector2

를 받는다.


Callback 방식

Input System은 이벤트 방식으로도 받을 수 있다.

private void OnEnable()
{
    moveAction.action.performed += OnMove;
    moveAction.action.canceled += OnMove;
}

private void OnDisable()
{
    moveAction.action.performed -= OnMove;
    moveAction.action.canceled -= OnMove;
}

private void OnMove(InputAction.CallbackContext context)
{
    Vector2 input = context.ReadValue<Vector2>();

    Debug.Log(input);
}

이 방식은

입력 발생
↓
콜백 호출
↓
게임 코드 실행

이라는 구조다.


PlayerInput

그리고 Unity에는 PlayerInput이라는 컴포넌트가 있다.

이걸 사용하면 입력 이벤트를 좀 더 쉽게 연결할 수 있다.

예를 들어:

Player
 └── PlayerInput

그리고 Input Actions를 연결한다.

PlayerInput은 내부적으로 Action Map과 Action을 관리하면서 게임 오브젝트의 스크립트에 입력을 전달할 수 있다.

대표적인 방식이:

Send Messages
Unity Events
C# Events

등이다.

하지만 실무에서는 PlayerInput을 무조건 써야 하는 것은 아니다.


InputActionReference

개인적으로 Unity 개발에서 이해해두면 좋은 것이 이것이다.

[SerializeField]
private InputActionReference moveAction;

Inspector에서 특정 Action을 직접 연결할 수 있다.

예:

PlayerInputActions
 └── Player
      └── Move

를

PlayerController
 └── Move Action

에 연결한다.

그러면 코드에서:

moveAction.action.ReadValue<Vector2>();

로 읽을 수 있다.

이 방식은 코드와 Input Asset의 결합을 적절하게 유지하면서도 구조가 명확하다.


Input System을 이해하는 가장 좋은 예

FPS 게임을 생각해보자.

Input Actions:

Player
├── Move
├── Look
├── Jump
├── Fire
├── Reload
└── Interact

Binding:

Move
├── WASD
└── Gamepad Left Stick

Look
├── Mouse Delta
└── Gamepad Right Stick

Jump
├── Space
└── Gamepad South Button

Fire
├── Mouse Left Button
└── Gamepad Right Trigger

게임 코드는:

Move(Vector2 input)
Look(Vector2 input)
Jump()
Fire()

만 생각한다.

즉:

             ┌── Keyboard
             │
             ├── Mouse
             │
             └── Gamepad
                    ↓
                 Binding
                    ↓
                 Action
                    ↓
              PlayerController
                    ↓
                Game Logic

이게 Input System의 핵심 구조다.


그리고 여기서 한 단계 더 중요한 것

Input System은 입력을 처리하는 시스템이지 게임플레이 로직 자체가 아니다.

좋은 구조는:

Input System
     ↓
Input Reader
     ↓
Player Controller
     ↓
Character / Gameplay

예를 들어:

public Vector2 MoveInput { get; private set; }

private void Update()
{
    MoveInput = moveAction.action.ReadValue<Vector2>();
}

그리고 PlayerController가:

Vector2 input = inputReader.MoveInput;

을 사용한다.

그러면 나중에 AI가 플레이어를 조작하거나 리플레이 시스템을 만들 때도:

Keyboard
      ↓
Input Reader
      ↓
Player

뿐만 아니라

AI
 ↓
Player

또는

Replay Data
 ↓
Player

같은 구조로 확장하기 쉬워진다.

결국 Input System의 본질은 키보드 입력을 받는 API가 아니라 물리적인 입력 장치와 게임 로직 사이를 추상화하는 계층이다.

이걸 이해한 상태에서 Input Action / Action Map / Binding / Control / Interaction / Processor / PlayerInput을 순서대로 보면 Unity Input System이 거의 다 정리된다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다