[FRACTiLE] | 정적 클래스 기반 Input System과 InputBindingComposite

2025. 4. 14. 22:27·개발/FRACTiLE

개요

'격 자'의 리메이크 프로젝트.

'FRACTiLE' 입니다.

 

기존에 사용하던 New Input System과 SO를 결합하는 대신 정적 클래스로 구조를 새로 다듬어볼겁니다.

 

 


 

 

아이디어 정리

일단 기존에 사용하던 SO 기반 Input System은 이 글에서 보실 수 있습니다.

별로 보지 않는걸 추천합니다. 예전 글이라 그런지 말투가 정돈이 안돼있네요.

 

간단하게 설명하자면 Player Input 컴포넌트를 사용하는 대신 Generate C# Class 옵션을 통해 클래스로 입력을 받고 그 입력을 SO를 통해 호스팅 하는 그러한 구조입니다. 에셋을 통해 관리하고 의존성이 낮다는 장점이 있습니다. 또 인스턴스가 1개 이상일 수 있어서 둘 이상의 플레이어를 관리하기 좋을 수도 있습니다. 이건 안해봐서 잘 모르겠네요.

 

근데 굳이 SO에서 정적 클래스로 전환하려는 이유는 MonoBehaviour가 아닌 클래스에서 Input을 받기 위해선 MonoBehaviour혹은 Input을 호스팅 하기 위한 목적이 아닌 싱글톤을 무조건적으로 참조해야하는게 맘에 안들었기 때문입니다.

그렇다고 Input만을 호스팅 하기 위한 싱글톤을 만들거면 SO로 할 이유도 없죠.

 

그래서 의존성이 좀 많이 끈끈해질 수도 있지만 그냥 정적 클래스로 두고 어디서나 Input을 받을 수 있게 만들려고 합니다. 

 

구조 구상

일단 정적 클래스 하나로는 불가능합니다.

Generate C# Class를 통해 Input을 받기 위해선 생성된 클래스에서 만든 인터페이스를 구현해야하고 그 인터페이스가 구현된 객체가 필요하기 때문이에요.

근데 정적 클래스는 인터페이스 구현도 불가능할 뿐더러 객체 또한 존재하지 않기 때문에 불가능하죠.

 

그래서 인터페이스 구현 및 객체를 만들 클래스 하나를 이런식으로 래핑해줄겁니다. 

 

구현

일단 Unity6에서 프로젝트를 만들면 기본적으로 생성해주는 InputAction의 이름을 Controls로 변경한 뒤에 Generate C# Class를 켜서 클래스를 만들어줍니다.

 

그러면 이런식으로 클래스가 만들어집니다.

 

그리고 정적 클래스인 InputManager를 선언하고 Controls를 생성해줄겁니다.

 public static class InputManager
 {
     private static Controls _Controls;
     
     static InputManager()
     {
     	_Controls = new Controls();
     }
}

Controls를 InputReader가 아닌 InputManager에서 만든 이유는 Input Map을 껐다켰다 해야할 때 이게 더 편해서 그렇습니다.

 

이제 이렇게 만들었으면 그 외의 잡다한 대리자나 Input Map 토글 기능을 추가합니다.

 

그리고 InputManager 안에 private으로 InputReader 클래스를 선언합니다.

이 InputReader는 인터페이스들을 구현합니다.

private class InputReader : Controls.IPlayerActions, Controls.IUIActions
{
	//대충 각종 핸들러들
}

 

그리고 생성자에서 Controls를 받아와 각종 셋업을 하고 소멸자에서 Dispose해줍니다.

private Controls _controls;

public InputReader(Controls controls)
{
    _controls = controls;
    controls.Enable();
    controls.Player.SetCallbacks(this);
    controls.UI.SetCallbacks(this);
}

~InputReader()
{
    _controls.Dispose();
}

 

그리고 구현한 인터페이스 함수들에서 InputManager의 Delegate들을 Invoke 시켜줄겁니다.

public void OnFacing(InputAction.CallbackContext context)
{
    OnFacingEvent?.Invoke(context.ReadValue<Vector2>());
}

public void OnInteract(InputAction.CallbackContext context)
{
    OnInteractEvent?.Invoke();
}

public void OnMove(InputAction.CallbackContext context)
{
    OnMoveEvent?.Invoke(context.ReadValue<Vector2>());
}

 

굳이 왜 InputReader 클래스가 InputManager 안에 있는지 알겠죠?

이 Reader는 InputManager의 생성자에서 초기화합니다.

static InputManager()
{
    _Controls = new Controls();
    _Reader = new InputReader(_Controls);
}

 

 

이렇게 다 되면 이런식으로 InputManager에서 입력을 처리할 수 있게 됩니다.

 

 


 

 

Input의 커스텀

격 자, 그러니까 FRACTiLE의 이동과 공격 방향 설정은 대각선을 지원하지 않습니다.

(1, 0) (-1, 0) (0, 1) (0, -1) 상하좌우만 신경쓰면 된다는거죠.

근데 New Input System에선 그런거 모릅니다. 그냥 8방향으로 때려버리죠. 저로썬 의미가 없기도 하고 처리하기도 곤란한 (1, 1) (-1, 1) (1, -1) (-1, -1) 이라는 Input이 추가로 전달되는거에요.

 

그래서 어떻게 후처리 해줄 방법이 없을까 했는데 InputBindingComposite를 발견했습니다.

모든 기능을 명확히 아는건 아니지만 제가 알기론 Input이 전해주는 인자를 바꿀 수 있고 (context.ReadValue<T>) 그 인자를 어떻게 전할건지에 대한 것도 커스텀이 가능합니다.

 

그래서 전 이걸 써서 상하좌우 입력을 받을때 가장 최근에 들어온 입력 기준으로 Up, Down, Left, Right Vector만 인자로 전해주는걸로 바꿔보기로 했습니다.

 

 

RecentDirectionBindingComposite

InputBindingComposite는 InputBindingComposite<TValue>를 상속하면 만들 수 있어요.

일단 제가 전달해야 하는 인자는 Vector2이니 다음과 같이 코드를 적어줍니다.

[InputControlLayout(displayName = "Recent Direction")]
public class RecentDirectionInputBindingComposite : InputBindingComposite<Vector2>

 

그리고 다음과 같이 바인딩 할 키를 선언합니다. Button으로 두면 키가 눌렸는지 때졌는지의 정보만 받아올 수 있습니다.

 

 [InputControl(layout = "Button")]
 public int up;
 [InputControl(layout = "Button")]
 public int down;
 [InputControl(layout = "Button")]
 public int right;
 [InputControl(layout = "Button")]
 public int left;

 

자료형이 int인 이유는 다음과 같이 키 바인딩을 짰을 때,

위부터 순서대로 인덱스가 매겨지고 전 그 인덱스를 기반으로 값을 받아와야하기 때문이에요.

 

아무튼 이제 간단하게 배열을 2개정도 만들어줄게요.

이전에 특정 키가 눌려있었는지 확인하는 bool 배열와 상하좌우 방향을 담는 Vector2 배열이 있으면 됩니다.

private bool[] _isPressed;
private Vector2[] _directions;

public RecentDirectionInputBindingComposite()
{
    _isPressed = new bool[4] { false, false, false, false };

    _directions = Direction2D.GetDirections(DirectionType.Up, DirectionType.Down, DirectionType.Left, DirectionType.Right);
}

여기서 Direction2D는 그냥 제가 만들어둔 라이브러리 비슷한 코드입니다. 

 

아무튼 _isPressed배열은 제가 잡고있는 키 4개가 이전 프레임에서 눌려있었는지를 체킹하여 표기해주고 _directions배열은 그냥 상하좌우 순서대로 값을 넣어서 Index로 바로 값을 가져올 수 있게 짜줍니다.

 

그리고 [0,1, 2, 3] 의 index를 up, down, right, left로 변환해주는 간단한 함수도 만들어줄게요. 굳이 없어도 되긴 하는데 가독성이 오릅니다.

private int GetPartBinding(int index)
{
    return index switch
    {
        0 => up,
        1 => down,
        2 => left,
        3 => right,
        _ => -1
    };
}

 

 

InputBindingComposite를 상속하면 추상 함수 하나를 구현해야 하는데 그 함수는 제가 바인딩 할 키에 대한 정보를 넘겨줍니다.

 

키가 눌렸는지에 대한 정보는 넘어오는 context에서 ReadValue를 통해 float을 가져오면 알 수 있습니다.

int가 아니라 float인 이유는 잘 모르겠지만 Button이 아닌 다른 타입과의 호환성 때문인 것 같습니다.

 

1은 눌려있는 버튼, 0은 떼어진 버튼이란 뜻입니다.

public override Vector2 ReadValue(ref InputBindingCompositeContext context)
{
	int toMove = -1;
	for (int i = 0; i < 4; i++)
	{
    	float value = context.ReadValue<float>(GetPartBinding(i));
    	if (value == 1 && !_isPressed[i])
    	{
        	toMove = i;
    	}
    	else if (value == 0)
    	{
        	_isPressed[i] = false;
    	}
	}
    if (toMove == -1)
    {
        return Vector2.zero;
    }
    else
    {
        _isPressed[toMove] = true;
        return _directions[toMove];
    }
}

 

이런식으로 _isPressed 배열과 비교해가며 최종적으로 이동할 방향을 가르키는 Index를 구하고 반환합니다.

이러면 이제 InputBindingComposite 세팅 끝입니다.

 

이렇게 만들어진 Composite은 이제 Input Action에서 선택할 수 있습니다.

Action Map -> Action Property -> Binding Property 에서 말이죠.

이렇게 선택해주면 이제 가장 최근 입력만 반영하는 Action 완성입니다.

 

 


 

 

마치는 말

뭔가 글이 중구난방인데 사실 원래 InputBindingComposite를 쓰려고 하진 않아서 그렇습니다.

근데 찾다보니까 나와서 마침 적고 있던 Input System에 붙여 적었죠. 

'개발 > FRACTiLE' 카테고리의 다른 글

FRACTiLE  (2) 2025.04.13
RE : 격 자 : 세번째 프로젝트 # 2 : 맵 구조 수정  (2) 2024.08.03
RE : 격 자 : 세번째 프로젝트 # 1 : 튜토리얼 구조 수정  (2) 2024.07.31
RE : 격 자 : 세번째 프로젝트 # 0 : 리메이크? 리워크?  (1) 2024.07.23
격 자 | 세번째 프로젝트  (0) 2024.03.03
'개발/FRACTiLE' 카테고리의 다른 글
  • FRACTiLE
  • RE : 격 자 : 세번째 프로젝트 # 2 : 맵 구조 수정
  • RE : 격 자 : 세번째 프로젝트 # 1 : 튜토리얼 구조 수정
  • RE : 격 자 : 세번째 프로젝트 # 0 : 리메이크? 리워크?
SundG0162
SundG0162
블로그 프로필은 머핀입니다.
  • SundG0162
    게임개발고수가될거야
    SundG0162
  • 전체
    오늘
    어제
    • 분류 전체보기 (78)
      • 주저리 (3)
        • 잡담 (2)
        • 장현우 (0)
        • 회고록 (1)
      • 개발 (50)
        • CITADEL : 성채 (1)
        • HEXABEAT (1)
        • FRACTiLE (6)
        • UNNAMED (9)
        • Default Defense (10)
        • T-Engine (1)
        • Project EW (0)
        • 졸업작품 (0)
        • Unity (3)
        • C# (4)
        • C++ (13)
        • WinAPI (1)
        • 그 외 (0)
      • 알고리즘 (13)
        • C# (1)
        • C++ (12)
      • 자료구조 (2)
        • C++ (1)
        • C# (0)
        • 공용 (1)
      • 기타 (10)
        • 아트 (6)
        • AI (2)
        • 수학 (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    C#
    LLM
    유니티
    코딩트리조별과제
    코드트리
    코딩
    AI
    코딩테스트
    티스토리챌린지
    생성형ai
    오블완
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
SundG0162
[FRACTiLE] | 정적 클래스 기반 Input System과 InputBindingComposite
상단으로

티스토리툴바