본문 바로가기
Unity 강의/Unity Course(2) - 절대강좌! 유니티

[Unity Course 2] 14. Input System - Player Input 컴포넌트

by 첨부엉. 2024. 6. 29.
위키북스 출판사 이재현 저자님의 '절대강좌! 유니티' 책을 참고하여 필기한 내용입니다.

 

Player Input 컴포넌트

: Input Actions 에서 정의한 액션이 발생했을 때 코드와 연결해 해당 로직을 실행시킬 수 있는 기능을 처리

주인공 캐릭터를 선택하고 Player Input 컴포넌트를 추가한다.

 

Action 속성

미리 만들어 놓은 MainActions Actions 를 연결한다.

Actions 속성에 Input Actions 에셋을 연결하면 바로 밑에 Default Scheme과 Default Map 항목이 표시 됨

 

Behavior 속성

: Input Actions 에셋에 정의한 액션이 발생했을 때 코드의 함수를 어떻게 실행시킬 것인지를 결정하는 속성

Behavior 옵션 설명
Send Message Player Input 컴포넌트가 있는 게임오브젝트에 SendMessage 함수를 이용하여 호출
Broadcast Message Send Message 와 동일하지만 BroadcastMessage 함수를 이용해 하위에 있는 게임오브젝트의 함수도 호출
Invoke Unity Events 액션별로 이벤트 연결 인터페이스가 생성되고 각각 호출하려는 함수를 연결해 사용
Invoke C Sharp Event C# 스크립트에서 이벤트를 직접 연결해 사용

 

위 네가지의 Behavior 속성은 입력장치의 연결, 해지, 변경사항에 대해 다음과 같은 이벤트를 기본으로 호출

  • OnDeviceLost
  • OnDeviceRegained
  • OnControlsChanged

 

Behavior - Send Messages 옵션

void On{액션명}()

 

PlayerCtrl이라는 새로운 스크립트를 생성하고 아래와 같이 작성한다.

#pragma warning disable IDE0051

using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerCtrl : MonoBehaviour
{
    void OnMove(InputValue value)
    {
        Vector2 dir = value.Get<Vector2>();
        Debug.Log($"Move = ({dir.x},{dir.y})");
    }

    void OnAttack()
    {
        Debug.Log("Attack");
    }
}

 

#pragma 전처리기를 사용하여 IDE0051 경고문구를 비활서오하 하는 구문을 추가

IDE0051은 함수나 변수를 정의하고 다른 코드에서 사용하지 않았을 때 Visual Studio 에서 경고를 표시함

#pragma warning disable IDE0051

 

OnMove 함수의 파라미터 타입은 InputValue 클래스로 Get(), Get<T>() 형식으로 값을 전달 받음 

Move 은 Vector2 로 정의 했기 때문에 Get<Vector 2>()를 사용함

void OnMove(InputValue value)
{
    Vector2 dir = value.Get<Vector2>();
    Debug.Log($"Move = ({dir.x},{dir.y})");
}

 

OnAttack 함수는 Button을 설정했기 때문에 키가 눌리면 호출되어 인자 없음

void OnAttack()
{
    Debug.Log("Attack");
}

 

정상적으로 작동 되는 것이 확인 되었다면 애니메이션과 연결한 스크립트를 새로 작성한다.

#pragma warning disable IDE0051

using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerCtrl : MonoBehaviour
{

    private Animator anim;
    private new Transform transform;
    private Vector3 moveDir;

    private void Start()
    {
        anim = GetComponent<Animator>();
        transform = GetComponent<Transform>();
    }

    private void Update()
    {
        if(moveDir != Vector3.zero)
        {
            // 진행 방향으로 회전
            transform.rotation = Quaternion.LookRotation(moveDir);
            // 회전한 후 전진 방향으로 이동
            transform.Translate(Vector3.forward * Time.deltaTime * 4.0f);
        }
    }

    void OnMove(InputValue value)
    {
        Vector2 dir = value.Get<Vector2>();
        // 2차원 좌표를 3차원 좌표로 변환
        moveDir = new Vector3(dir.x, 0, dir.y);

        // Warrior _Run 애니메이션 실행
        anim.SetFloat("Movement", dir.magnitude);
        Debug.Log($"Move = ({dir.x},{dir.y})");
    }

    void OnAttack()
    {
        Debug.Log("Attack");
        anim.SetTrigger("Attack");
    }
}

 

Behavior - Invoke Unity Events 옵션

Player 의 Input 의 Behavior 속성을 Invoke Unity Events로 선택하면 정의한 두 개의 액션인 Move, Attack이 Unity Event 타입의 속성으로 표시됨

공통으로 발생하는 3개의 이벤트도 같이 표시된다. 

Events 의 Move, Attack 액션 명이 표시되며 괄호 안에 CallbackContext 라는 파라미터 타입이 표시됨

 

앞서 만들었떤 함수를 연결할 수 없기 때문에 다음과 같은 형식의 새로운 함수를 정의해야됨

void On{액션명}(InputAction.CallbackContext context)

 

#pragma warning disable IDE0051

using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerCtrl : MonoBehaviour
{

    private Animator anim;
    private new Transform transform;
    private Vector3 moveDir;

    private void Start()
    {
        anim = GetComponent<Animator>();
        transform = GetComponent<Transform>();
    }

    private void Update()
    {
        if(moveDir != Vector3.zero)
        {
            // 진행 방향으로 회전
            transform.rotation = Quaternion.LookRotation(moveDir);
            // 회전한 후 전진 방향으로 이동
            transform.Translate(Vector3.forward * Time.deltaTime * 4.0f);
        }
    }

    #region SEND_MESSAGE
    void OnMove(InputValue value)
    {
        Vector2 dir = value.Get<Vector2>();
        // 2차원 좌표를 3차원 좌표로 변환
        moveDir = new Vector3(dir.x, 0, dir.y);

        // Warrior _Run 애니메이션 실행
        anim.SetFloat("Movement", dir.magnitude);
        Debug.Log($"Move = ({dir.x},{dir.y})");
    }

    void OnAttack()
    {
        Debug.Log("Attack");
        anim.SetTrigger("Attack");
    }
    #endregion

    #region UNITY_EVENTS
    public void OnMove(InputAction.CallbackContext ctx)
    {
        Vector2 dir = ctx.ReadValue<Vector2>();

        // 2차원 좌표를 3차원 좌표로 벼환
        moveDir = new Vector3(dir.x, 0, dir.y);

        // Warrior_Run 애니메이션 실행
        anim.SetFloat("Movement", dir.magnitude);
    }
    public void OnAttack(InputAction.CallbackContext ctx)
    {
        Debug.Log($"ctx.phase={ctx.phase}");

        if (ctx.performed)
        {
            Debug.Log("Attack");
            anim.SetTrigger("Attack");
        }

    }
    #endregion
}

 

#region ~ #endregion 전처리기는 코드의 영역을 정의하는 것

특정 영역을 Collapse 할 수 있음

코드의 라인이 많을 때나 특정 로직을 그룹화 할 때 편리한 방법

Invoke Unity Events 옵션을 선택했을 때 넘어오는 파라미터는 InputAction.CallvackContext 타입으로 입력값은 ReadValue<T>() 함수를 이용하여 전달 받음

InputActions에서 Move 액션을 Vector2 타입으로 정의했기 때문에 2차원 벡터 타입으로 받아와야됨

public void OnMove(InputAction.CallbackContext ctx)
{
    Vector2 dir = ctx.ReadValue<Vector2>();

    // 2차원 좌표를 3차원 좌표로 벼환
    moveDir = new Vector3(dir.x, 0, dir.y);

    // Warrior_Run 애니메이션 실행
    anim.SetFloat("Movement", dir.magnitude);
}

 

Invoke Unity Events 옵셩르 사용해 연결한 함수는 총 3번 호출됨

Input Actions 에 정의한 액션은 시작, 실행, 취소의 콜백 함수를 가각 한 번씩 호출함

어떤 상태로 호출됐는지에 대한 정보는 CallbackContext.phase 속성을 통해 알 수 있음

InputActionPhase 열거형으로 다음과 같이 5개가 정의 됨

InputActionPhase.Started
InputActionPhase.Performed
InputActionPhase.Canceled
InputActionPhase.Disabled
InputActionPhase.Waiting

OnAttack 함수에서 InputActionPhase.Performed 일때만 공격 애니메이션을 실행하도록 처리하지 않으면 공격 애니메이션이 3번 발생하게 됨

 

Performed 속성은 실행 상태일 때 true값을 반환함 

3번 호출하는지 확인하기 위해서 Debug로 확인

public void OnAttack(InputAction.CallbackContext ctx)
{
    Debug.Log($"ctx.phase={ctx.phase}");

    if (ctx.performed)
    {
        Debug.Log("Attack");
        anim.SetTrigger("Attack");
    }
}

이렇게 연결하고 실행해보기

Behavior - Invoke C Sharp Events

 C# 스크립트에서 직접 이벤트를 연결해 사용하는 방식

Player Input 컴포넌트를 사용할 수도 있고 Input Actions 에셋과 Player Input 컴포넌트 없이  모두 다 스크립트로 처리 가능함

PlayerCtrl 스크립트 수정

#pragma warning disable IDE0051

using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerCtrl : MonoBehaviour
{

    private Animator anim;
    private new Transform transform;
    private Vector3 moveDir;

    private PlayerInput playerInput;
    private InputActionMap mainActionMap;
    private InputAction moveAction;
    private InputAction attackAction;

    private void Start()
    {
        anim        = GetComponent<Animator>();
        transform   = GetComponent<Transform>();
        playerInput = GetComponent<PlayerInput>();

        // ActionMap 추출
        mainActionMap = playerInput.actions.FindActionMap("PlayerActions");

        // Move, Attack 액션 추출
        moveAction      = mainActionMap.FindAction("Move");
        attackAction    = mainActionMap.FindAction("Attack");

        // Move 액션의 performed 이벤트 연결
        moveAction.performed += ctx =>
        {
            Vector2 dir = ctx.ReadValue<Vector2>();
            moveDir = new Vector3(dir.x, 0, dir.y);
            // Warrior_Run 애니메이션 실행
            anim.SetFloat("Movement", dir.magnitude);
        };
        // Move 액션의 canceled 이벤트 연결
        moveAction.canceled += ctx =>
         {
             moveDir = Vector3.zero;
            // Warrior_Run 애니메이션 정지
            anim.SetFloat("Movement", 0.0f);
         };
        // Attack 액션의 performed 이벤트 연결
        attackAction.performed += ctx =>
        {
            Debug.Log("Attack by c# event");
            anim.SetTrigger("Attack");
        };

    }

    private void Update()
    {
        if(moveDir != Vector3.zero)
        {
            // 진행 방향으로 회전
            transform.rotation = Quaternion.LookRotation(moveDir);
            // 회전한 후 전진 방향으로 이동
            transform.Translate(Vector3.forward * Time.deltaTime * 4.0f);
        }
    }

    #region SEND_MESSAGE
    void OnMove(InputValue value)
    {
        Vector2 dir = value.Get<Vector2>();
        // 2차원 좌표를 3차원 좌표로 변환
        moveDir = new Vector3(dir.x, 0, dir.y);

        // Warrior _Run 애니메이션 실행
        anim.SetFloat("Movement", dir.magnitude);
        Debug.Log($"Move = ({dir.x},{dir.y})");
    }

    void OnAttack()
    {
        Debug.Log("Attack");
        anim.SetTrigger("Attack");
    }
    #endregion

    #region UNITY_EVENTS
    public void OnMove(InputAction.CallbackContext ctx)
    {
        Vector2 dir = ctx.ReadValue<Vector2>();

        // 2차원 좌표를 3차원 좌표로 벼환
        moveDir = new Vector3(dir.x, 0, dir.y);

        // Warrior_Run 애니메이션 실행
        anim.SetFloat("Movement", dir.magnitude);
    }
    public void OnAttack(InputAction.CallbackContext ctx)
    {
        Debug.Log($"ctx.phase={ctx.phase}");

        if (ctx.performed)
        {
            Debug.Log("Attack");
            anim.SetTrigger("Attack");
        }
    }
    #endregion
}

 

변수 선언부에 PlayerInput 컴포넌트, 액션 맵, 액션을 저장할 변수 선언 후 Start 함수에서 정보 대입

private PlayerInput playerInput;
private InputActionMap mainActionMap;
private InputAction moveAction;
private InputAction attackAction;

 

playerInput 변수에는 GetComponent로 PlayerInput 컴포넌트를 저장하고 액션 맵을 FindActionMap함수를 사용하여 가져옴

FindActionMap 함수에 전달할 파라미터는 MainActions 에서 정의한 액션맵으로 문자열로 전달함

playerInput = GetComponent<PlayerInput>();

// ActionMap 추출
mainActionMap = playerInput.actions.FindActionMap("PlayerActions");

 

액션 맵을 추출했으면 해당 액션 맵에 정의한 액션(Move,Attack)을 FindAction ("액션 명") 함수를 사용하여 변수에 대입함

// Move, Attack 액션 추출
moveAction      = mainActionMap.FindAction("Move");
attackAction    = mainActionMap.FindAction("Attack");

 

추출한 Move 액션의 performed 이벤트와 canceled 이벤트가 발생했을 때의 로직을 람다식으로 지정함

// Move 액션의 performed 이벤트 연결
moveAction.performed += ctx =>
{
    Vector2 dir = ctx.ReadValue<Vector2>();
    moveDir = new Vector3(dir.x, 0, dir.y);
    // Warrior_Run 애니메이션 실행
    anim.SetFloat("Movement", dir.magnitude);
};
// Move 액션의 canceled 이벤트 연결
moveAction.canceled += ctx =>
 {
     moveDir = Vector3.zero;
    // Warrior_Run 애니메이션 정지
    anim.SetFloat("Movement", 0.0f);
 };

 

Attack 액션의 performed 이벤트만 사용함

// Attack 액션의 performed 이벤트 연결
attackAction.performed += ctx =>
{
    Debug.Log("Attack by c# event");
    anim.SetTrigger("Attack");
};

 

Direct Binding

PlayerInput 컴포넌트 없이 직접 스크립트에서 InputAction을 생성하고 액션을 정의하는 방식

 

새로운 스크립트인 PlayerCtrlByEvent를 생성

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerCtrlByEvent : MonoBehaviour
{
    private InputAction moveAction;
    private InputAction attackAction;

    private Animator anim;
    private Vector3 moveDir;

    private void Start()
    {
        anim = GetComponent<Animator>();

        // Move 액션 생성 및 타입 설정
        moveAction = new InputAction("Move", InputActionType.Value);

        // Move 액션의 복합 바인딩 정보 정의
        moveAction.AddCompositeBinding("2DVector")
                                                .With("Up", "<Keyboard>/w")
                                                .With("Down", "<Keyboard>/s")
                                                .With("Left", "<Keyboard>/a")
                                                .With("Right", "<Keyboard>/d");

        // Move 액션의 performed, canceled 이벤트 연결
        moveAction.performed += ctx =>
        {
            Vector2 dir = ctx.ReadValue<Vector2>();
            moveDir = new Vector3(dir.x, 0, dir.y);
            // Warrior_Run 애니메이션 실행
            anim.SetFloat("Movement", dir.magnitude);
        };
        // Move 액션의 canceled 이벤트 연결
        moveAction.canceled += ctx =>
        {
            moveDir = Vector3.zero;
            // Warrior_Run 애니메이션 정지
            anim.SetFloat("Movement", 0.0f);
        };

        // Move 액션의 활성화
        moveAction.Enable();

        // Attack 액션 생성
        attackAction = new InputAction("Attack", InputActionType.Button, "<Keyboard>/space");
        
        // Attack 액션의 performed 이벤트 연결
        attackAction.performed += ctx =>
        {
            Debug.Log("Attack by c# event");
            anim.SetTrigger("Attack");
        };
        // Attack 액션의 활성화
        attackAction.Enable();
    }
    private void Update()
    {
        if(moveDir != Vector3.zero)
        {
            // 진행 방향으로 회전
            transform.rotation = Quaternion.LookRotation(moveDir);
            // 회전한 후 전진 방향으로 이동
            transform.Translate(Vector3.forward * Time.deltaTime * 4.0f);
        }
    }
}

 

InputActions 창에서 정의했던 Move, Attack 액션을 직접 생성함

액션 = new InputAction("액션명", 액션_타입)
// Move 액션 생성 및 타입 설정
moveAction = new InputAction("Move", InputActionType.Value);

 

WASD 같은 복합적인 바인딩은  아래의 함수르 사용하여 구성하고 .With() 구문으로 필요한 만큼 추가 가능

AddCompositeBinding("복합 바인딩 타입").With("바인딩 명","바인딩 정보")
// Move 액션의 복합 바인딩 정보 정의
moveAction.AddCompositeBinding("2DVector")
                                        .With("Up", "<Keyboard>/w")
                                        .With("Down", "<Keyboard>/s")
                                        .With("Left", "<Keyboard>/a")
                                        .With("Right", "<Keyboard>/d");

 

복합 바인딩 타입을 다음과 같다.

  • 1DAxis(또는 Axis)
  • 2DVector(또는 Dpad)
  • 3DVector
  • OneModifier
  • TwoModifiers

Input Bindings | Input System | 1.1.1 (unity3d.com)

 

Input Bindings | Input System | 1.1.1

Input Bindings An InputBinding represents a connection between an Action and one or more Controls identified by a Control path. An Action can have an arbitrary number of Bindings pointed at it. Multiple Bindings can reference the same Control. Each Binding

docs.unity3d.com

 

preformed, canceled 이벤트를 연결하는 방법은 이전과 동일함

 

액션의 선언과 이벤트 연결을 완료했으면 반드시 해당 액션을 활성화 해야됨

// Move 액션의 활성화
moveAction.Enable();

 

작성을 완료 했다면 Warrior_bindpos 모델을 하나 더 추가하고 방금 작성한 스크립트와 Animator를 연결해준다.

 

Input Debug

입력 장치로부터 전달되는 값을 모니터링할 수 있는 기능

에디트 모드에서 다양한 외부 입력 장치의 정보를 확인 가능함

입력값을 녹화, 저장, 로드 할 수 있음

책에는 적혀있지 않았는데 

Window - Analysis - InputDebugger 를 통해 들어가면확인 가능하다.