내일배움캠프 Unity 21일차 TIL - Unity 게임 개발 입문 1주차

Wooooo·2023년 11월 27일
0

내일배움캠프Unity

목록 보기
23/94

오늘의 키워드

오늘은 개인과제에 어제 강의에 나온 new InputSystem을 적용해서 플레이어 캐릭터의 이동을 구현하던 중에 강의에 나온 방법은 SendMessage로 호출을 하는데, 나는 SendMessage 방식은 조금 느릴 것 같아서 다른 방법으로 구현해봤다.


PlayerInput.Behavior

PlayerInput에는 Behavior라는 프로퍼티가 있다. 사용자가 바인딩 된 키를 입력하면 바인딩 된 키에 알맞는 메서드를 호출해야하는데, 호출방식을 어떻게 할지 정하는 프로퍼티이다.

SendMessage, BroadcastMessage

SendMessageBroadcastMessageObject 클래스를 상속받는 클래스들에게 string 매개변수로 넘어간 메서드 이름과 같은 메서드가 있는지 탐색하고, 호출해주는 방식이다. 이 과정을 C#에선 리플렉션이라고 하고, 연산이 조금 걸린다.

InvokeUnityEvents, InvokeCSharpEvents

InvokeUnityEventsInvokeCSharpEventsdelegate를 이용한 방식이다.
InvokeUnityEvents를 이용하면 인스펙터 창에서 드래그앤드랍 딸깍딸깍으로 호출될 이벤트를 지정해 줄 수 있고,
InvokeCSharpEvents를 이용하면 스크립트 상에서 코드로 호출될 이벤트를 구독시켜줘야한다. 귀찮은 만큼 이 녀석이 제일 빠르다.


ActionMap Interface 상속받아서 호출하기

PlayerInput 컴포넌트를 상속받아서 InvokeCSharpEvents 방식으로 호출해도 되겠지만, 이런 방식도 있다.

1. InputAction Asset → Generate C# Class

InputAction Asset에는 Generate C# Class라는 항목이 있다. 이걸 누르면 자동으로 다음과 같은 C# 스크립트가 생성된다.

  • Class Name : Input Action Asset 파일명과 동일
  • ActionMap Interface Name : I + (ActionMap 이름) + Actions

2. ActionMap Interface 상속받기

나는 Input Action 파일명은 Player Input Action이고, Action Map의 이름은 PlayerContol이므로 인터페이스를 상속 받을 때 다음과 같이 상속 받으면 된다.

public class PlayerController : MonoBehaviour, PlayerInputAction.IPlayerControlActions

이 인터페이스를 상속받으면, 내가 ActionMap에 등록했던 액션들을 호출하기 위한 함수를 구현하라고 알려준다.

나는 이동하기 위한 Move 액션과 마우스를 바라보게 하기 위한 Look 액션을 만들어뒀는데, 그 둘을 호출하기 위한 OnMove 메서드와 OnLook 메서드를 구현하라고 알려주는 모습.

InputAction.CallbackContext 구조체

나는 Action 만들 때 Vector2를 받아오게 했는데 InputAction.CallbackContext라는 뜬금없는 구조체를 매개변수로 넘겨준다고한다. 이 녀석은 ReadValue<T>() 메서드로 변환해서 쓰면 되겠다.

    public event Action<Vector2> OnMoved;

    public virtual void OnMove(InputAction.CallbackContext context)
    {
        // ReadValue로 InputActions asset에서 설정한 값을 읽어온다.
        OnMoved?.Invoke(context.ReadValue<Vector2>());
    }

3. SetCallbacks

인터페이스만 구현했다고 끝이 아니다. 아직 해야할 일이 몇 가지 남아있다.

  • PlayerInput 컴포넌트를 오브젝트에 추가하는 대신, Generate C# Class로 만든 클래스의 객체를 생성해줘야한다.
  • 생성한 객체에 Callback을 설정해준다.
  • 생성한 객체 Enable
    PlayerInputAction inputAction;
	
    protected virtual void Start()
    {
        //if (inputAction == null)
        //    inputAction = new();
        inputAction ??= new();

        // IPlayerControlActions를 상속받아 구현했으므로 this로 Callback을 설정할 수 있다.
        inputAction.PlayerControl.SetCallbacks(this);
        inputAction.Enable();
    }

4. Event 구독

사용자 입력에 따른 이벤트 호출은 모두 구현했으니, 이제 알맞는 메서드들을 구독시켜주기만 하면 된다.

public class CharacterController : PlayerController
{
    Rigidbody2D _rigidBody;
    SpriteRenderer _spriteRenderer;
    [SerializeField] float moveSpeed = 2f;

    Vector2 moveDirection;

    protected override void Start()
    {
        base.Start();
        _rigidBody = GetComponent<Rigidbody2D>();
        _spriteRenderer = transform.GetChild(0).GetComponent<SpriteRenderer>();
        OnMoved += SetMoveDirection;
        OnLooked += SetLookFlip;
    }
    
    void SetMoveDirection(Vector2 dir)
    {
        moveDirection = dir.normalized;
    }

    void SetLookFlip(Vector2 mouseWorldPosition)
    {
        var lookDir = (mouseWorldPosition - (Vector2)transform.position).normalized;
        float angle = Mathf.Atan2(lookDir.y, lookDir.x) * Mathf.Rad2Deg;
        if (Mathf.Abs(angle) > 90f)
            _spriteRenderer.flipX = true;
        else
            _spriteRenderer.flipX = false;
    }
}

나는 PlayerController를 상속받는 CharacterController라는 클래스를 만들어서 OnMoved에는 SetMoveDirection 메서드를, OnLooked에는 SetLookFlip 메서드를 구독시켜줬다.


추가로 알아보면 좋을 것

PlayerInput 컴포넌트와 비교해서 장단점?

둘의 장단점에 대해 토론하는 포럼 글이 있다. 시간 날 때 읽어보면 좋을 것 같다.


참고 자료

https://forum.unity.com/threads/input-system-generate-c-code-what-its-good-for.995674/#post-7073584
https://openmynotepad.tistory.com/76

코드 전문

PlayerController.cs

using System;
using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerController : MonoBehaviour, PlayerInputAction.IPlayerControlActions
{
    PlayerInputAction inputAction;

    public event Action<Vector2> OnMoved;
    public event Action<Vector2> OnLooked;

    public virtual void OnMove(InputAction.CallbackContext context)
    {
        // ReadValue로 InputActions asset에서 설정한 값을 읽어온다.
        OnMoved?.Invoke(context.ReadValue<Vector2>());
    }

    public virtual void OnLook(InputAction.CallbackContext context)
    {
        // convert world position
        var pos = Camera.main.ScreenToWorldPoint(context.ReadValue<Vector2>());
        OnLooked?.Invoke(pos);
    }

    protected virtual void Start()
    {
        //if (inputAction == null)
        //    inputAction = new();
        inputAction ??= new();

        // IPlayerControlActions를 상속받아 구현했으므로 this로 Callback을 설정할 수 있다.
        inputAction.PlayerControl.SetCallbacks(this);
        inputAction.Enable();
    }
}

CharacterContoller.cs

using UnityEngine;

public class CharacterController : PlayerController
{
    Rigidbody2D _rigidBody;
    SpriteRenderer _spriteRenderer;
    [SerializeField] float moveSpeed = 2f;

    Vector2 moveDirection;

    protected override void Start()
    {
        base.Start();
        _rigidBody = GetComponent<Rigidbody2D>();
        _spriteRenderer = transform.GetChild(0).GetComponent<SpriteRenderer>();
        OnMoved += SetMoveDirection;
        OnLooked += SetLookFlip;
    }

    void SetMoveDirection(Vector2 dir)
    {
        moveDirection = dir.normalized;
    }

    void SetLookFlip(Vector2 mouseWorldPosition)
    {
        var lookDir = (mouseWorldPosition - (Vector2)transform.position).normalized;
        float angle = Mathf.Atan2(lookDir.y, lookDir.x) * Mathf.Rad2Deg;
        if (Mathf.Abs(angle) > 90f)
            _spriteRenderer.flipX = true;
        else
            _spriteRenderer.flipX = false;
    }

    void FixedUpdate()
    {
        Move(moveDirection);
    }

    public void Move(Vector2 moveDirection) => _rigidBody.MovePosition(_rigidBody.position + moveSpeed * Time.fixedDeltaTime * moveDirection);
}
profile
game developer

0개의 댓글