공용 스크립트 제작기🎈09 - 인풋 시스템 편

공용 스크립트 제작기🎈09 - 인풋 시스템 편

Unity 프로젝트의 공용으로 사용할 수 있는 스크립트를 제작하면서 어떻게 만들었는지, 어떤 점에 신경썼는지 설명하는 글입니다.

게임을 만들 때 빼놓을 수 없는 것이 바로 입력 시스템이다. 키보드, 마우스, 게임패드 등 다양한 입력을 통일된 방식으로 처리하고, 입력 설정이 변경될 때마다 코드를 자동으로 생성해주면 편리하지 않을까? Unity의 New Input System을 활용하여 .inputactions 파일만 수정하면 자동으로 코드가 생성되고, 이벤트 구독 방식으로 깔끔하게 사용할 수 있는 입력 시스템을 만들어보자.

핵심은 .inputactions 파일 수정 → 자동 코드 생성 → 이벤트 구독/직접 조회 방식의 간편한 사용이다.

💡예시

간단하게 먼저 사용 예시부터 소개해보자면 다음과 같다.

이벤트 구독 방식

public class PlayerController : MonoBehaviour
{
    private void OnEnable()
    {
        // 이동 입력 이벤트 구독
        InputManager.Instance.OnMove += HandleMove;

        // 점프 입력 이벤트 구독
        InputManager.Instance.OnJumpStarted += HandleJump;

        // 공격 입력 이벤트 구독
        InputManager.Instance.OnAttackPerformed += HandleAttack;
    }

    private void OnDisable()
    {
        // 반드시 구독 해제!
        if (InputManager.Instance != null)
        {
            InputManager.Instance.OnMove -= HandleMove;
            InputManager.Instance.OnJumpStarted -= HandleJump;
            InputManager.Instance.OnAttackPerformed -= HandleAttack;
        }
    }

    private void HandleMove(Vector2 input)
    {
        // 이동 처리
        transform.Translate(new Vector3(input.x, 0, input.y) * Time.deltaTime * 5f);
    }

    private void HandleJump()
    {
        // 점프 처리
        Debug.Log("Jump!");
    }

    private void HandleAttack()
    {
        // 공격 처리
        Debug.Log("Attack!");
    }
}

직접 조회 방식

public class PlayerMovement : MonoBehaviour
{
    private void Update()
    {
        // 실시간 입력 값 읽기
        Vector2 moveInput = InputManager.Instance.GetMoveInput();

        if (moveInput != Vector2.zero)
        {
            Vector3 movement = new Vector3(moveInput.x, 0, moveInput.y);
            transform.Translate(movement * 5f * Time.deltaTime);
        }

        // 버튼 상태 확인
        if (InputManager.Instance.IsJumpPressed())
        {
            Jump();
        }
    }
}

입력 제어

public class PauseMenuUI : UIBase
{
    public override void OnShow()
    {
        base.OnShow();

        // 플레이어 입력 비활성화, UI 입력 활성화
        InputManager.Instance.DisablePlayerInput();
        InputManager.Instance.EnableUIInput();
    }

    public override void OnHide()
    {
        base.OnHide();

        // 플레이어 입력 재활성화, UI 입력 비활성화
        InputManager.Instance.EnablePlayerInput();
        InputManager.Instance.DisableUIInput();
    }
}

📊 클래스 다이어그램

InputManager (싱글톤, partial class)
├─ InputManager.cs (수동 작성 부분)
└─ InputManager.Generated.cs (자동 생성 부분)
   ├─ 이벤트 선언 (On{ActionName})
   ├─ 바인딩 메서드 (Bind{MapName}Actions)
   └─ Get 메서드 (Get{ActionName}Input)

InputActionCodeGenerator (자동 생성 에디터 툴)
InputActionsPostprocessor (파일 변경 감지 및 자동 재생성)

📁 시스템 아키텍처

핵심 컴포넌트

InputManager.cs (수동 작성 부분)
  • 역할: 개발자가 직접 작성하는 핵심 로직
  • 주요 기능:
    • InputSystem_Actions 인스턴스 생성
    • Initialize()에서 BindAllActions() 호출
    • Action Map 제어 메서드 제공
      • EnablePlayerInput() / DisablePlayerInput()
      • EnableUIInput() / DisableUIInput()
      • EnableAllInput() / DisableAllInput()
    • OnDestroy()에서 정리 작업
InputManager.Generated.cs (자동 생성 부분)
  • 역할: .inputactions 파일로부터 자동 생성되는 코드
  • 주요 기능:
    • 이벤트 선언
      • Vector2 타입: event Action<Vector2> On{ActionName}
      • Button 타입: event Action On{ActionName}Started/Performed/Canceled
      • Vector3 타입: event Action<Vector3> On{ActionName}
      • Quaternion 타입: event Action<Quaternion> On{ActionName}
    • 바인딩 메서드
      • Bind{MapName}Actions(): 각 Action Map의 액션 바인딩
      • BindAllActions(): 모든 Action Map 바인딩 호출
    • Get 메서드
      • Vector2: Vector2 Get{ActionName}Input()
      • Button: bool Is{ActionName}Pressed()
      • Vector3: Vector3 Get{ActionName}Input()
      • Quaternion: Quaternion Get{ActionName}Input()
InputActionCodeGenerator (에디터 전용)
  • 역할: .inputactions 파일을 파싱하여 C# 코드 생성
  • 주요 기능:
    • JSON 파싱 (JsonUtility.FromJson)
    • InputManager.Generated.cs 생성
    • 메뉴 제공: Tools > Input > Generate Input Manager Code
  • 코드 생성 프로세스:
    1. .inputactions 파일 읽기
    2. JSON 파싱 → InputActionsData
    3. Action Map별 이벤트 선언 생성
    4. Action Map별 바인딩 메서드 생성
    5. BindAllActions 메서드 생성
    6. Action Map별 Get 메서드 생성
    7. 파일 저장 및 AssetDatabase.Refresh()
InputActionsPostprocessor (에디터 전용)
  • 역할: .inputactions 파일 변경 감지 및 자동 재생성
  • 주요 기능:
    • AssetPostprocessor 상속
    • OnPostprocessAllAssets()에서 파일 변경 감지
    • .inputactions 파일 수정 시 자동으로 코드 재생성
  • 동작 방식:
    • .inputactions 파일 저장 시 트리거
    • InputActionCodeGenerator.GenerateCode() 호출

🔄 데이터 흐름

개발 단계 (에디터)
  1. InputSystem_Actions.inputactions 파일 수정
    • Action Map 추가/수정
    • Action 추가/수정
    • Binding 설정
  2. 파일 저장 (Ctrl+S)
  3. [Unity] InputSystem_Actions.cs 자동 생성
  4. [InputActionsPostprocessor] 파일 변경 감지
  5. [InputActionCodeGenerator] JSON 파싱
    • Action Map 및 Action 정보 추출
    • Action 타입 확인 (Vector2, Button, Vector3, Quaternion)
  6. [코드 생성] InputManager.Generated.cs 생성:
    • 이벤트 선언 (On{ActionName})
    • 바인딩 메서드 (Bind{MapName}Actions())
    • BindAllActions 메서드
    • Get 메서드 (Get{ActionName}Input(), Is{ActionName}Pressed())
  7. [AssetDatabase.Refresh] Unity 리컴파일
  8. [완료] InputManager에서 바로 사용 가능
런타임 단계
  1. [게임 시작] InputManager.Awake() 호출
  2. [Initialize] InputSystem_Actions 인스턴스 생성
  3. [BindAllActions] 모든 액션 바인딩:
    • BindPlayerActions()
    • BindUIActions()
    • … (Action Map별 바인딩 메서드 호출)
  4. [각 바인딩 메서드] InputAction 이벤트 구독
  5. [EnablePlayerInput] Player Action Map 활성화
  6. [완료] 입력 시스템 사용 가능
사용자 입력 처리 프로세스
  1. [플레이어 입력] 키보드/마우스/게임패드 입력
  2. [Unity Input System] InputAction 트리거
    • started / performed / canceled 단계별 호출
  3. [InputManager 바인딩] 이벤트 발행:
    • OnMove?.Invoke(input) (Vector2)
    • OnJumpStarted?.Invoke() (Button)
  4. [구독자] 이벤트 핸들러 실행:
    • HandleMove(Vector2 input)
    • HandleJump()
  5. [완료] 입력 처리 완료
Action Map 제어 프로세스
  1. UI 팝업 열림
  2. InputManager.Instance.DisablePlayerInput()
    • inputActions.Player.Disable() 호출
    • Player 입력 비활성화
  3. InputManager.Instance.EnableUIInput()
    • inputActions.UI.Enable() 호출
    • UI 입력 활성화
  4. UI 팝업 닫힘
  5. InputManager.Instance.EnablePlayerInput()
  6. InputManager.Instance.DisableUIInput()
  7. [완료] 원래 입력 상태로 복원

🎯 신경 쓴 부분

자동 재생성 시스템

AssetPostprocessor를 활용한 완전 자동화

public class InputActionsPostprocessor : AssetPostprocessor
{
    private static void OnPostprocessAllAssets(
        string[] importedAssets, ...)
    {
        foreach (string assetPath in importedAssets)
        {
            if (assetPath.EndsWith(INPUT_ACTIONS_FILE))
            {
                // 파일 완전히 임포트될 때까지 대기 후 재생성
                EditorApplication.delayCall += () =>
                {
                    InputActionCodeGenerator.GenerateCode();
                };
                break;
            }
        }
    }
}
  • EditorApplication.delayCall로 Unity의 임포트 프로세스 완료 대기
  • 개발자는 파일만 저장하면 자동으로 코드 생성
  • 수동 메뉴 실행 불필요
이벤트와 Get 메서드 병행 지원

두 가지 사용 방식 모두 지원하여 유연성 제공

// 이벤트 방식 (권장)
InputManager.Instance.OnMove += HandleMove;

// Get 방식 (실시간 조회)
Vector2 input = InputManager.Instance.GetMoveInput();
  • 이벤트 방식: 입력 변경 시에만 호출 (효율적)
  • Get 방식: Update()에서 매 프레임 조회 (간편)
  • 상황에 맞게 선택 가능
Action Map별 입력 제어

Player, UI 등 Action Map별로 독립적인 활성화/비활성화

// 플레이어 입력만 비활성화
InputManager.Instance.DisablePlayerInput();

// UI 입력만 활성화
InputManager.Instance.EnableUIInput();

// 모든 입력 비활성화 (컷씬 등)
InputManager.Instance.DisableAllInput();
  • 상황별로 필요한 입력만 활성화
  • UI 팝업 시 플레이어 입력 차단
  • 컷씬 재생 시 모든 입력 차단

결론

Unity의 New Input System을 활용하여 자동 코드 생성 기반 입력 시스템을 만들어봤다. .inputactions 파일만 수정하면 코드가 자동으로 생성되고, 이벤트 구독 방식으로 깔끔하게 사용할 수 있어 편리하다. AssetPostprocessor로 완전 자동화한 점이 핵심.


© 2022. All rights reserved.