[UE5] C++를 이용한 향상된 입력(Enhanced Input) 설정

kkado·2024년 4월 2일
0

UE5

목록 보기
28/63
post-thumbnail

언리얼엔진 강의를 들으며 공부하다 Enhanced Input이 아닌 구버전 Input을 이용해 폰의 이동을 구현하길래 직접 알아보자 하고 공부했고, 그 내용을 정리하고자 한다.

참고로 UE 5버전 이후 엔진에서 기본 프로젝트를 생성하면 예시 기능이 구현되어 있는데, 그 부분을 많이 참고하여 학습했으니 혹시 이 글을 보시는 분들은 코드를 이해하기 어려우시면 직접 코드를 살펴보면서 옮겨 보시길 추천드립니다.

Enhanced Input?

Input보다 더 많은 기능을 제공하고, 런타임에서 매핑정보를 바꿀 수 있다는 점 등 편리함 측면에서 많은 부분이 개선된 입력 체계이다.

크게 4가지 요소가 있으며 간단하게만 짚고 넘어가고자 한다.

Input Action

  • Digital, 1D, 2D, 3D 등 다양한 타입의 이벤트 타입을 지정 가능

Input Mapping Context

  • 캐릭터가 걸어다니다가 수영을 하거나, 탈것을 타거나 하는 등 환경 변화에 따라 달라지는 키매핑을 런타임에서 쉽게 변경할 수 있도록 해주는 인풋 액션들의 묶음이라고 할 수 있다.

Modifier

  • 인풋 액션으로 발생하는 Raw data를 축을 바꾸거나 부호를 반대로 하는 등 내가 필요한 기능에 맞게 가공하는 역할을 한다. 가령 마우스로 카메라를 이동할 때 Y축 상하반전을 시켜 준다거나, 하나의 액션에서 Swizzle 및 Negate하여 상하좌우 이동을 모두 가능하게 해 준다.

Trigger

Triggered, Pressed, Released 등 입력 시점 및 동작에 따라 보다 더 다양한 이벤트를 발생시킬 수 있다.


IA_Move, IMC_Default 생성

이전에 한번 해 본 바가 있고 워낙 흔한 예시라서 간단히 설정완료한 사진만 첨부한다.


먼저 Blueprint 살펴보기

먼저 블루프린트를 통해서 절차를 보기 쉽게 정리하고 이를 하나하나 C++ 코드로 옮기는 식으로 진행하는 것이 이해하기 쉬울 것이다.

IMC_Default를 player 0에 매핑하는 부분이다.

IA_Move 이벤트가 발생했을 때 그 데이터를 출력하는 부분이다.

그러면 이런 식으로 이벤트가 발생함에 따라 화면에 출력이 잘 되는 모습을 확인할 수 있다.


C++로 구현

Tank.h

#include "CoreMinimal.h"
#include "BasePawn.h"
#include "InputActionValue.h"
#include "Tank.generated.h"

/**
 * 
 */
UCLASS()
class TOONTANKS_API ATank : public ABasePawn
{
	GENERATED_BODY()

public:
	ATank();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

private:
	UPROPERTY(VisibleAnywhere, Category = Input)
	class UInputMappingContext* DefaultContext;

	UPROPERTY(VisibleAnywhere, Category = Input)
	class UInputAction* MoveAction;

protected:
	void Move(const FInputActionValue& Value);

};

IMC_Default를 할당할 UInputMappingContext 클래스 포인터와 IA_Move를 할당할 UInputAction 클래스 포인터를 전방 선언하여 선언했다.

전방 선언이란 특정 클래스 객체를 사용하기 위해 헤더 파일에 그 클래스의 헤더 파일을 include 하지 않고 cpp 파일에만 include 함으로써 컴파일 시간을 줄일 수 있는 방법이다.

그리고 void Move() 함수를 만들었다. 인자로 받을 FInputActionValue 참조자는 전방 선언할 수 없으므로 include 해 주었다.

Tank.cpp

#include "Tank.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "InputMappingContext.h"

ATank::ATank()
{
    static ConstructorHelpers::FObjectFinder<UInputMappingContext>DEFAULT_CONTEXT
	(TEXT("/Game/Inputs/IMC_Default"));
	if (DEFAULT_CONTEXT.Succeeded())
	{
		DefaultContext = DEFAULT_CONTEXT.Object;
	}

	static ConstructorHelpers::FObjectFinder<UInputAction>IA_MOVE
	(TEXT("/Game/Inputs/IA_Move"));
	if (IA_MOVE.Succeeded())
	{
		MoveAction = IA_MOVE.Object;
	}
}

void ATank::BeginPlay()
{
	Super::BeginPlay();
	if (APlayerController* PlayerController = Cast<APlayerController>(GetController()))
	{
		if (UEnhancedInputLocalPlayerSubsystem* SubSystem =
			ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
			SubSystem->AddMappingContext(DefaultContext, 0);
	}
}

void ATank::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

// Called to bind functionality to input
void ATank::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent))
	{
		EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ATank::Move);
	}
}

void ATank::Move(const FInputActionValue& Value)
{
	if (Value.Get<bool>())
		UE_LOG(LogTemp, Warning, TEXT("Input Action Triggered"));
}

코드가 다소 길지만 하나하나 뜯어보면서 알아보자.

생성자

먼저 ConstructorHelpers::FObjectFinder 를 통해 포인터 변수를 채워 넣는다. 괄호 안에 들어가는 Path 부분은 해당 파일에 마우스 오버 해 보면 쉽게 찾을 수 있다.

여기 나와있는 Path 말고 우클릭 -> Copy File Path로 하면 필자처럼 피똥을 쌀 수 있다.

만약 클래스를 가져오는 데 성공했다면 이를 각각 DefaultContext, MoveAction 에 넣어준다.

여기서 헤더파일 InputMappingContextEnhancedInputComponent 가 사용된다.

BeginPlay

블루프린트 이벤트그래프에서 이 부분과 대응되는 부분이다.

  • 먼저 GetController() 을 APlayerController로 캐스트해서 PlayerController를 가져온다.
  • 그리고 PlayerController->GetLocalPlayer() 을 Enhanced Input Local Player Subsystem으로 캐스팅해서 GetSubSystem 한다.
  • 이렇게 가져온 SubSystemDefaultContext 를 매핑한다.

이때 UEnhancedInputLocalPlayerSubSystem 클래스를 사용하기 위해 EnhancedInputSubsystems.h를 추가한다.

SetupPlayerInputComponent

원래 Input 시스템을 사용할 때는 PlayerInputComponent로 바로 매핑이 가능했지만 EnhancedInputComponent로 캐스트해주는 작업을 거쳐야 한다.

이후 EnhancedInputComponent에서 IA_MoveATank::Move 함수에 바인딩 한다.

Move

SetupPlayerInputComponent에 의해 IA_Move 이벤트가 발생하면 실행될 함수이다. 아직은 이동까지 구현해보기 앞서 함수가 실행됐음을 알기 위해 로그만 찍어본다.


결과

WASD키를 누르면 ATank::Move 함수가 호출되어 로그가 잘 찍히는 것을 확인할 수 있다.


참고한 정보

https://www.youtube.com/watch?v=fW1pXOAIviw

profile
베이비 게임 개발자

0개의 댓글