Unity

[Unity] InputSystem 사용방법(PlayerInputComponent와 Generate C# Class)

usingsystem 2024. 12. 27. 15:37
728x90

Unity의 Input System은 기존의 Legacy Input Manager의 한계를 해결하기 위해 설계된 새로운 입력 관리 시스템입니다. 멀티 디바이스 지원, 유연성 향상, 플랫폼 호환성 강화를 목표로 하며, 2019년에 처음 도입되어 Unity의 최신 입력 관리 표준으로 자리 잡았습니다.

기존 Legacy Input Manager의 한계

기본 API가 직관적이고 학습 곡선이 낮지만 Input Manager에서 모든 입력을 사전에 설정해야 하며, 게임 내에서 변경하려면 커스텀 스크립트를 작성해야 하고 이벤트 기반 입력 미지원, 멀티 디바이스 지원의 부족, 폴링(Polling) 기반 입력으로 사용자가 입력한 상태를 매 프레임마다 Update에서 확인해야 되기 때문에 유지보수 및 확장에 어려움이 있습니다.

    public float speed = 5f;

    void Update()
    {
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");

        Vector3 movement = new Vector3(horizontal, 0, vertical);
        transform.Translate(movement * speed * Time.deltaTime);
    }
특성 Legacy Input Manager Input System
입력 처리 방식 폴링 기반 이벤트 기반, 액션 중심
입력 디바이스 지원 제한적 (키보드, 마우스, 기본 게임패드) 다양한 입력 장치 지원 (VR, 모바일 등)
런타임 바인딩 변경 불가능 가능
유연성 낮음 높음
멀티 디바이스 입력 미지원 지원

1. InputSystem 설치

1. Package Manager에서 Input System을 설치한다.

Package Manager에서 Packages를 Unity Registry로 변경 후, InputSystem검색하여 Install 해준다.

InputSystem 설치

 

설치 후 Warning이 뜨는데 InputSystem을 새로 적용하기 위한 안내창으로 Yes 누르면 재시작되고 적용된다.

 

 

2. InputSystem 적용

설치가 완료되었으면 InputSystem이 Unity Editor에 적용되었는지 확인할 수 있다.

Edit > Project Settings > Player > Other Settings > Configuration에서 설정 가능.

 

Avtive Input Handling에서 Input System (New), Both를 선택하면 새로운 InputSystem을 사용할 수 있다.

  • Input Manager (Old) - Legacy Input Manager만 사용.
  • Input System (New) - 새로운 입력 처리 방식으로, 이벤트 기반 입력 및 멀티 디바이스 지원.
  • Both - Input SystemLegacy Input Manager를 동시에 사용.

입력 설정

3. Input Action 에셋 생성

Project창 -> +버튼 or Create -> 하단 Input Actions 클릭

Input Action 생성

 

생성된 InputSystem 이름을 PlayerInputSystem으로 변경했음.

 

생성한 InputSystem

 

3. Input Action 에셋 생성

해당 에셋을 더블클릭하면 Input Actions 설정 창이 나오게 된다.

  • Auto-Save - 클릭하면 설정한 InputSystem에서 설정한 내용이 자동으로 저장된다.
  • Control Scheme - 디바이스를 그룹화한 논리적 단위입니다. 예를 들어 PC, Mobile 등
  • Action Maps - Action Map은 여러 Actions를 논리적으로 그룹화한 것으로 예를 들어, "Player"와 "UI" 같은 서로 다른 컨텍스트에서 입력을 관리할 수 있도록 도와줍니다.
  • Actions -  특정 상황에서만 활성화되는 입력 그룹을 정의, 각 액션 맵은 독립적으로 활성화/비활성화할 수 있음, 다양한 디바이스와 컨텍스트별 입력 처리를 간단히 설정. 예를 들어, "Player" Action Map:Move, Jump, Shoot 등 캐릭터 조작 관련 입력. "UI" Action Map:Navigate, Select, Cancel 등 UI 조작 관련 입력.

Input Action 창

 

현재 Control Scheme 가 없기 때문에 만들어보자.

Control Schemes 클릭 -> Add Control Scheme 클릭 

Control Schemes Add

다음으로 어떤 디바이스에 대한 것 인지에 대한 Scheme의 이름과 입력받아 처리할 컨트롤러를 정의한다.

 

KeyBoard와 Mouse를 추가하고 Save를 해준다.

 

다음으로 Action Maps를 정의한다. 여기서는 PlayerMaps라는 이름으로 정의하였다.

Action Maps 생성

이렇게 Action Maps를 만들어 주게 되면 초기 Actions도 함께 생성된다.

생성된 Actions

Move에 대한 이동을 정의하기 위해 우선 New action의 이름을 Move로 변경하고 하위의 <No Binding>을 삭제해 준다.

이름 변경 및 No Binding 삭제

 

Action Type설명

  • Value - 지속적으로 값(데이터)을 반환하는 액션 타입. 1 프레임 동안 유지되는 값이 아니라, 입력이 활성화된 동안 현재 상태 값을 지속적으로 읽음. 입력 들어온 값에 따라 +면 오른쪽 -면 왼쪽 ex) 캐릭터 이동
  • Button - 버튼과 같이 On/Off 상태를 처리하는 액션 타입. 단순한 상태 확인에 적합하며, 키보드 키나 게임패드 버튼처럼 디지털 입력을 처리. ex) 점프, 발사
  • Pass Through - 입력 데이터를 필터링하지 않고 바로 반환. Value나 Button은 입력 데이터를 일부 필터링하거나 중복 제거를 수행하지만, Pass Through는 이를 무시하고 모든 입력 데이터를 반환.  ex) 입력 데이터를 즉시 처리해야 하는 경우.
특성 Value Button Pass Through
주요 목적 연속적인 값 반환 (축, 위치 등). 단순 On/Off 입력 처리. 필터링 없이 입력 데이터를 즉시 전달.
사용 예 이동, 커서 위치, 트리거 압력 등. 점프, 발사, 대시. 멀티 디바이스 입력, 제스처 등.
반환 데이터 Vector2, Vector3, float 등. bool, float (0 또는 1). 데이터 유형에 따라 다양.
입력 필터링 필터링 및 상태 관리. 상태 이벤트(Started, Performed, Canceled). 필터링 없음.
이벤트 발생 활성화된 동안 지속적으로 반환. 버튼 입력 이벤트 기반. 입력 이벤트 발생 시 즉시 처리.

Action Type

이동 Action을 받기 위해 Action Type을 Value로 변경하고 Control Type을 Vector2로 설정한다.

Control Type은 키가 눌렸을 경우 어떤 자료형 타입으로 리턴 받아 사용할지를 결정한다.

ActionType, ControlType 변경

Actions에 입력 이벤트를 위한 바인딩을 설정해 준다. Add Up\Down\Left\Right Composite를 눌러 Binding을 만들어준다.

Add Up\Down\Left\Right Composite

Add Up\Down\Left\Right Composite에 맞게 Binding이 자동으로 생성되고 이름은 여기서 MoveBinding으로 변경하였다.

아직 어떤 키가 눌렀을 때 Binding이 되는지 정의되지는 않아 NoBinding이라고 되어있다.

자동으로 생성된 바인딩

Up에 Path에 Search에서 W를 검색하여 W [Keyboard]로 설정한다. 동일한 방식으로 Down은 S, Left는 A, Right는 D로 설정한다. 이제 설정은 다됐다.

Binding Path 설정
Binding 끝


4. Player Input Component를 사용한 로직 제어

Input System을 사용하고 싶은 Object에 Add Component -> PlayerInput Component를 붙여준다.

Player Input component 연결
Player Input Component가 있는 Object는 번개모양 표시됨

 

Actions에 우리가 만든 PlayerInputSystem을 연결한다.

Default Scheme도 만들었던 PC로 연결한다.

Default Map도 만들었던 PlayerMaps로 연결한다.

Action, scheme, map 연결

PlayerInput Component를 Script로 제어

우선 제어로직을 작성할 Script를 하나 만들어 준다. 여기서는 MoveController라는 이름의 Script로 만들었다.

MoveController Script 생성 및 연결

우리가 만들었던 입력에 대한 이벤트를 받을 수 있는 방법으로 PlayerInput에 Behavior에 정의된 4가지의 방식으로 입력정보를 전달받을 수 있다. 

Behavior이란 Player Input 컴포넌트에서 제공하는 Behavior 옵션은 입력 이벤트를 처리하는 방식을 정의한다.

  • Send Messages - 입력 이벤트를 GameObject에 있는 메서드에 메시지 방식으로 전달합니다. 메서드 이름이 입력 액션 이름과 일치해야 하며, 해당 메서드가 호출됩니다.
//MoveController 내부
//입력 액션 이름이 Move라면, OnMove라는 이름의 메서드가 호출됩니다.
//키가 눌릴 때 새로운 입력값이 생성되고, 키를 뗐을 때 값이 초기화(0, 0)되므로 두 번 호출됩니다
    enum Status
    {
        None,
        Move
    }

    Vector3 _direction;
    float _speed = 4f;
    Status _status = Status.None;

    void Update()
    {
        if (_status == Status.Move)
        {
            transform.rotation = Quaternion.LookRotation(_direction);
            transform.Translate(Vector3.forward * _speed * Time.deltaTime);
        }
    }

    void OnMove(InputValue value)
    {
        Vector2 input = value.Get<Vector2>();
        if (input != null)
        {
            _status = input != Vector2.zero ? Status.Move : Status.None;

            _direction = new Vector3(input.x, 0f, input.y);
            Debug.Log("OnMove");
        }
    }
  • Unity Events - 입력 액션에 대해 Unity의 UnityEvent 시스템을 사용하여 이벤트를 발생시킵니다. Unity Editor의 Inspector에서 이벤트를 연결하고 관리할 수 있습니다.
  • Invoke C# Events - 입력 액션이 발생하면 C# 이벤트를 호출합니다. 코드에서 이벤트를 직접 구독하여 입력을 처리. 입력 로직을 명확히 관리하고, 코드 기반의 구조를 선호할 때
//MoveController 내부
	enum Status
    {
        None,
        Move
    }

    PlayerInput _playerInput;
    InputAction _moveAction;

    Vector3 _direction;
    float _speed = 4f;
    Status _status = Status.None;

    private void Awake()
    {
        _playerInput = GetComponent<PlayerInput>();
        _moveAction = _playerInput.actions["Move"];
        _moveAction.performed += OnMoveAction_performed;
    }
    void Update()
    {
        if (_status == Status.Move)
        {
            transform.rotation = Quaternion.LookRotation(_direction);
            transform.Translate(Vector3.forward * _speed * Time.deltaTime);
        }
    }
    private void OnMoveAction_performed(InputAction.CallbackContext context)
    {
        Vector2 input = context.ReadValue<Vector2>();

        if (input != null)
        {
            _status = input != Vector2.zero ? Status.Move : Status.None;
            _direction = new Vector3(input.x, 0f, input.y);
            Debug.Log("OnMoveAction_performed");
        }
    }

6. PlayerInput Component를 사용하지 않고 Generate C# Class로 제어

이 방법은 PlayerInput Component를 사용하지 않고 Input Actions에서 Generate C# Class를 만들어 입력을 처리하는 방법으로 직접 코드 제어를 선호하는 경우 적합합니다. 이 방식은 입력 로직을 더 명확히 관리하고, Unity의 컴포넌트 중심 방식에서 독립적으로 작동할 수 있는 구조를 제공합니다.

 

위에서 만들었던 InputActions 파일을 클릭하고 에디터 우측 상단 Generate C# Class를 체크한 후 Apply를 클릭한다.

Generate C# Class 생성

Apply를 하면 InputActions가 있는 경로에 C# Class가 생성된다.

생성된 Generate C# Class

 

생성된 Generate C# Class 내부

 

 

기존에 Cube에 넣었던 PlayerInput 컴포넌트를 삭제해 준다.

Player Input  삭제

 

Input System에서 입력 처리를 하기 위해서는 InputActionMap을 활성화해야 합니다. 이것은 액션 맵 내의 모든 액션이 활성화되어 입력 이벤트를 수신하도록 만드는 중요한 단계입니다. 그래서 OnEnable, OnDisable에서 ActionMap을 활성화 비화성화 시켜줘야 한다. InputActionMap 활성화가 누락되면 입력 이벤트가 호출되지 않고  InputActionMap을 활성화/비활성화하여 효율적인 입력 처리가 가능합니다.
예를 들어 Move, UI 2개의 InputActionMap이 있다고 할 때 UI가 켜져 있을 때는 Move를 비활성화하고 UI는 활성화시켜 동일한 액션이벤트에서 구분되어 로직을 처리할 수 있다.

    PlayerInputSystem _playerInputSystem;
    enum Status
    {
        None,
        Move
    }
    Vector3 _direction;
    float _speed = 4f;
    Status _status = Status.None;

    private void Awake()
    {
        _playerInputSystem = new PlayerInputSystem();
    }
    private void OnEnable()
    {
        // 입력 Action Map 활성화
        _playerInputSystem.PlayerMaps.Enable();

        _playerInputSystem.PlayerMaps.Move.performed += Move_performed;
        _playerInputSystem.PlayerMaps.Move.started += Move_started;
        _playerInputSystem.PlayerMaps.Move.canceled += Move_canceled;
    }

    private void OnDisable()
    {
        // 입력 Action Map 비활성화
        _playerInputSystem.PlayerMaps.Disable();

        _playerInputSystem.PlayerMaps.Move.performed -= Move_performed;
        _playerInputSystem.PlayerMaps.Move.started -= Move_started;
        _playerInputSystem.PlayerMaps.Move.canceled -= Move_canceled;
    }

    void Update()
    {
        if (_status == Status.Move)
        {
            transform.rotation = Quaternion.LookRotation(_direction);
            transform.Translate(Vector3.forward * _speed * Time.deltaTime);
        }
    }
    private void Move_performed(InputAction.CallbackContext obj)
    {
        Vector2 input = obj.ReadValue<Vector2>();

        if (input != null)
        {
            _status = input != Vector2.zero ? Status.Move : Status.None;
            _direction = new Vector3(input.x, 0f, input.y);
            Debug.Log("Move_performed");
        }
    }
    private void Move_started(InputAction.CallbackContext obj)
    {
        Debug.Log("Move_started");
    }
    private void Move_canceled(InputAction.CallbackContext obj)
    {
        Debug.Log("Move_canceled");
    }

 

728x90