원문: Input System Update, Input System Preview Repository, NEW INPUT SYSTEM in Unity, Input System Documentation
참고용 예제 저장소: Unity Input System (Sungkuk Park)
최종 업데이트: 2018년 12월 30일

개요 (Overview)

본문은 현재 Unity가 실험적인 기능(Experimental Feature)이자 프리뷰 패키지(preview package)로 제공하는 신규 Input System을 앞서 알아보는 포스팅이다.

본 포스팅은 Unity 2018.3.0f2를 기준으로 작성되었다는 사실을 미리 알린다. 다른 버전에서의 Input System은 본문에서 말하는 것과 그 동작이 다를 수 있다.

또한, 12월 20일 기준으로 현재 본문에서 활용한 preview 빌드는 최신 버전이 아니라고 한다. 만약 최신 Input System을 가장 먼저 도입하고 싶다면 Input System Preview Repositorydevelop 브랜치를 활용하도록 한다.

신규 Input System 워크플로우 소개

신규 Input System을 도입하기 위해서는, 먼저 Package Manager에서 Show preview packages 토글을 활성화한 뒤, “Input System”을 검색해 신규 Input System 패키지를 설치한다. (보다 상세한 설치 가이드는 Installation Guide를 참조)

1. InputAction 어셋의 생성

InputAction은 어셋 형태로 저장되는 입력 설정(configuration) 데이터이다. 일단 아래의 스크린샷처럼 해당 파일을 생성하게 되면 이름을 지정하게 되며, 해당 파일의 확장자는 “.inputactions”이다.

Create_InputActions

2. InputAction 어셋의 편집

Inspector 창에서 Edit asset 버튼을 누르거나 InputAction 어셋을 더블-클릭한다.

ClickEditAsset

3. InputManager 창

InputManager 창은 다음 세 가지 탭(tabs)으로 이루어져 있다.

  • ActionMaps: 입력의 단위인 Action의 상위 범주
  • Actions: 입력의 단위인 Action
  • Properties: 입력의 단위인 Action을 구성하는 속성

InputManagerWindow

4. CreateActionMap의 생성

CreateActionMap은 입력의 단위인 Action의 상위 범주에 해당한다. ActionMaps 탭 오른쪽의 + 버튼을 눌러 ActionMap을 추가한다.

CreateActionMap

5. Action의 생성

Action은 입력의 단위인 Action이며, event처럼 동작한다. Actions 탭 오른쪽의 + 버튼을 눌러 Action을 추가한다. 이때, Input Systems Version 0.1.2를 기준으로 Action이 생성되면 EmptyBinding이라는 Binding이 자동으로 생성된다.

CreateAction

6. Binding의 편집

Binding은 구체적인 입력을 할당받는 역할을 한다. Binding 가능한 입력은 다음 세 가지 범주로 나뉜다:

  • Usages
  • Abstract Devices
  • Specific Devices
본 예제에서는 Abstract Devices Keyboard에 속한 space를 Binding에 할당했다.

BindingInput

이런 식으로, 하나의 Action에 여러 개의 Binding을 손쉽게 추가할 수 있다. 즉, 쉽게 확장가능하다.

CreateMoreBinding

7. Composite Axis 또는 Composite Dpad의 생성

일반적으로 캐릭터의 이동에 해당하는 입력의 경우 주로 전후좌우나 특정 축(axis)을 중심으로 양의 방향(positive)이나 음의 방향(negative)의 입력을 가하는 경우가 많다. 이때 Binding을 2개 또는 4개를 일일이 만들어주는 대신 Composite Axis 또는 Composite Dpad를 생성해 이러한 작업을 간소화할 수 있다.

CreateCompositeDpad

CreatedCompositeDpad

8. ControlScheme의 추가

또한, 키보드나 마우스 입력 이외에 게임패드의 입력을 함께 추가하고 관리하는 경우가 있을 수 있다. 이 경우에는 ControlScheme을 여러 개 추가해 각 ControlScheme마다의 입력 Binding을 따로 추가하고 관리해줄 수 있다.

본 예제에서는 “Keyboard and Mouse”와 “Gamepad”라는 두 가지 종류의 ControlScheme을 추가하도록 하자. 이에 따라 사용자는 키보드와 마우스 입력과 게임패드 입력을 별도로 추가하고 관리할 수 있을 것이다.

AddControlSchemes

AddKeyboardAndMouseScheme

모든 ControlScheme을 추가한 다음, “Keyboard and Mouse” 모든 키보드와 마우스 입력을 체크 박스를 통해 해당 ControlScheme에 할당한다. Input Systems Version 0.1.2를 기준으로 현재는 각 Binding을 일일이 선택해 체크 박스를 활성화해줘야만 한다.

SetControlScheme

9. InputAction 어셋의 저장

모든 설정을 끝낸 뒤, 반드시 InputAction 어셋을 InputManager 창 상단의 “Save Asset” 버튼을 눌러 저장해줘야 한다. 저장을 하지 않고 InputManager 창을 닫으려고 하면 변경 내역을 저장하겠냐고 묻는 모달 윈도우(Modal Window)가 열리게 된다.

SaveInputAction

변경 내역이 성공적으로 저장되어 “Save Asset” 버튼이 비활성화된 것을 볼 수 있다.

SavedInputAction

10. InputAction 어셋에 기반한 InputActionAssetReference 클래스 생성

기존의 UnityEngine.Input에서 지원 중인 입력 시스템은 각 입력과 관련된 코드를 수동으로 직접 추가하고 관리해야만 했다. 그러나 신규 Input System은 위에서 추가한 입력 관련 설정 데이터에 해당하는 InputAction 어셋을 통해 코드를 자동으로 생성해준다.

지금까지 생성하고 변경한 InputMaster 어셋을 선택한 뒤 Inspector 창에서 “Apply” 버튼을 누르면 관련 입력 코드가 자동으로 생성된다. Inspector 창의 다른 입력 필드(input field)를 따로 채워줄 필요는 없다.

GenerateInputMasterClass

아래와 같이 성공적으로 InputMaster 타입의 C# 클래스가 생성된 것을 확인할 수 있다.

GeneratedInputMasterClass

참고용으로 “InputMaster.inputactions” 어셋 파일을 통해 생성된 “InputMaster.cs” 코드 전문을 아래 수록한다. 아래에서 확인할 수 있듯, 해당 클래스는 InputActionAssetReference 클래스를 상속하는 일종의 래퍼(wrapper) 클래스라는 것을 알 수 있다.

// GENERATED AUTOMATICALLY FROM 'Assets/InputActions/InputMaster.inputactions'

using System;
using UnityEngine;
using UnityEngine.Experimental.Input;


[Serializable]
public class InputMaster : InputActionAssetReference
{
    public InputMaster()
    {
    }
    public InputMaster(InputActionAsset asset)
        : base(asset)
    {
    }
    private bool m_Initialized;
    private void Initialize()
    {
        // Player
        m_Player = asset.GetActionMap("Player");
        m_Player_Jump = m_Player.GetAction("Jump");
        m_Player_Movement = m_Player.GetAction("Movement");
        m_Initialized = true;
    }
    private void Uninitialize()
    {
        m_Player = null;
        m_Player_Jump = null;
        m_Player_Movement = null;
        m_Initialized = false;
    }
    public void SetAsset(InputActionAsset newAsset)
    {
        if (newAsset == asset) return;
        if (m_Initialized) Uninitialize();
        asset = newAsset;
    }
    public override void MakePrivateCopyOfActions()
    {
        SetAsset(ScriptableObject.Instantiate(asset));
    }
    // Player
    private InputActionMap m_Player;
    private InputAction m_Player_Jump;
    private InputAction m_Player_Movement;
    public struct PlayerActions
    {
        private InputMaster m_Wrapper;
        public PlayerActions(InputMaster wrapper) { m_Wrapper = wrapper; }
        public InputAction @Jump { get { return m_Wrapper.m_Player_Jump; } }
        public InputAction @Movement { get { return m_Wrapper.m_Player_Movement; } }
        public InputActionMap Get() { return m_Wrapper.m_Player; }
        public void Enable() { Get().Enable(); }
        public void Disable() { Get().Disable(); }
        public bool enabled { get { return Get().enabled; } }
        public InputActionMap Clone() { return Get().Clone(); }
        public static implicit operator InputActionMap(PlayerActions set) { return set.Get(); }
    }
    public PlayerActions @Player
    {
        get
        {
            if (!m_Initialized) Initialize();
            return new PlayerActions(this);
        }
    }
    private int m_KeyboardandMouseSchemeIndex = -1;
    public InputControlScheme KeyboardandMouseScheme
    {
        get

        {
            if (m_KeyboardandMouseSchemeIndex == -1) m_KeyboardandMouseSchemeIndex = asset.GetControlSchemeIndex("Keyboard and Mouse");
            return asset.controlSchemes[m_KeyboardandMouseSchemeIndex];
        }
    }
    private int m_GamepadSchemeIndex = -1;
    public InputControlScheme GamepadScheme
    {
        get

        {
            if (m_GamepadSchemeIndex == -1) m_GamepadSchemeIndex = asset.GetControlSchemeIndex("Gamepad");
            return asset.controlSchemes[m_GamepadSchemeIndex];
        }
    }
}

10. InputMaster 타입을 참조하는 Player 입력 스크립트 작성

이제 해당 InputMaster 타입을 참조해 Player 컴포넌트에 입력을 위한 스크립트를 작성해보자.

using UnityEngine;
using UnityEngine.Experimental.Input;

public class Player : MonoBehaviour
{
    public float UpForce = 150f;
    
    public InputMaster control;

    private Rigidbody _rigidBody;
    
    private void Awake()
    {
        Debug.Assert(control != null);
        control.Player.Jump.performed += Jump;
    }

    /// <summary>
    /// 신규 Input System은 모듈러(Modular)하게 설계되어 있기 때문에 사용하기 전에 반드시 프로그래머가 활성화(enable)해줘야 한다.
    /// </summary>
    private void OnEnable()
    {
        control.Enable();
    }

    private void OnDisable()
    {
        control.Disable();
    }

    private void Start()
    {
        _rigidBody = GetComponent<Rigidbody>();
        Debug.Assert(_rigidBody != null);
    }

    private void Jump(InputAction.CallbackContext ctx)
    {
        Debug.Assert(_rigidBody != null);
        _rigidBody.AddForce(Vector3.up * UpForce);
    }
}

마지막으로, 게임을 실행하기 전에 공개 변수인 control에 아래처럼 InputAction 어셋에 해당하는 “InputMaster.inputactions” 파일을 반드시 할당하도록 한다.

11. 입력 시스템 테스팅

이제 “InputMaster.inputactions”에서 지금까지 할당했던 설정에 해당하는 입력들을 수행(perform)해보자. 일례로, “space [Keyboard]”로 바인딩(Binding)했던 것처럼 스페이스 키를 누르면 플레이어에 해당하는 큐브(Cube)가 점프하는 것을 확인할 수 있다.

JumpingPlayer

결론

기존의 UnityEngine.Input에서 지원 중인 입력 시스템은 각 입력과 관련된 코드를 수동으로 직접 추가하고 관리해야만 했다. 그러나 신규 Input System은 다음과 같은 4단계를 거쳐 입력 설정을 하드코딩하는 수고를 덜고, 입력을 추가 및 관리가 더 용이하도록 지원한다.

  1. 입력 설정 데이터에 해당하는 InputAction 어셋을 생성한다. 이 파일의 확장자는 “.inputactions”이다.
  2. Input Manager 창을 통해 입력의 단위인 Action을 생성하고 각종 입력을 바인딩(Binding)한다.
  3. InputAction 어셋을 참조하는 래퍼(wrapper) 클래스를 구성하는 코드를 자동으로 생성한다.
  4. 마지막으로, 위에서 생성한 래퍼 클래스에 이벤트(event)를 연결해 원하는 동작을 수행한다.

신규 Input System과 관련된 최신 정보를 구독하려면 Input System Unity Forum에 올라오는 글들을 참고하거나, 고정 포스트로 등록된 Input System Update의 댓글들을 확인하면 된다.

Input System에 대한 문서에 해당하는 Input System Documentation도 존재하지만, 현재 문서화가 충분히 되어 있지 않은 상태이므로 이를 염두에 두고 부분적으로 참고하도록 한다.

<끝>