[UE5] Enhanced Input(향상된 입력)
언리얼 엔진 4에서 사용하던 Axis, Action Mapping은 UE5에서 deprecated로
대신 Enhanced Input을 사용하는 것을 권장하고 있다.
Enhanced Input의 주요 컨셉은 아래와 같다.
Input Action: 프로젝트 코드와 Enhanced Input system을 연결해 주는 역할을 하며 캐릭터가 취할 수 있는 점프, 이동, 문 열기 등 Action을 말한다
Input Mapping Contexts: 유저의 입력과 Actions을 매핑하는 역할을 하며 동적으로 추가, 제거가 가능하며 priority 또한 설정이 가능하다. 동일한 키를 입력하였을 때, 문을 열거나, 인벤토리의 아이템을 선택하는 기능을 구현하기 위해서 이전에는 현재의 상태를 정의하는 Boolean 변수를 통해 분기 처리가 필요하였지만 Enhanced Input은 priority를 동적으로 조정할 수 있기 때문에 인벤토리가 열려있을 때는 아이템 선택, 인벤토리가 닫혔을 때는 인벤토리 context의 우선순위를 낮추거나 제거하여 코드단에서의 편의성이 높아졌다
Modifier: 유저 디바이스에서의 raw inputs을 조정하는 역할을 한다. (XYZ -> YXZ 등 축 변환, 양의 방향을 음의 방향으로 변환 등)
Triggers: Modifier가 적용된 유저 input에 대해 어떤 Input Action을 수행할 것인지 말 것인지 결정하는 역할을 한다.
아래는 Input -> InputAction에서 생성한 이동을 담당하는 Move input action이다.
Value type으로는 Digital(bool), Axis1D(float), Axis2D(Vector2D), Axis3D(Vector)가 있다.
상하좌우 입력값을 받기 때문에 Axis2D로 설정하였다.
아래는 Input->Input Mapping Contexts에서 생성한 Input Mapping Contexts다.
앞서 정의한 Input Action을 유저 디바이스의 input과 매핑할 수 있다.
Move의 경우 Modifier를 사용하여 Swizzle Input Axis Values <- 축 변환 / Negate <- 방향 전환 을 적용하였다.
UE5에서 제공하는 삼인칭 예시의 Character 블루프린트를 보면
Pawn의 Controller를 가져와서 PlayerController로 캐스팅, Enhanced Input Local Player Subsystem에 예시에서 정의한 Input Mapping Context인 IMC_Default를 추가하는 것을 볼 수 있다
C++로 생성한 ExampleCharacter.h에 Input Mapping Context, Input Action, Input Action의 동작 구현 함수를 선언하였다.
// ExampleCharacter.h
protected:
// Input
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Enhanced Input")
UInputMappingContext* DefaultMappingContext;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Enhanced Input")
UInputAction* InputToMove;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Enhanced Input")
UInputAction* InputToJump;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Enhanced Input")
UInputAction* InputToLook;
void EnhancedInputMove(const FInputActionValue& Value);
void EnhancedInputLook(const FInputActionValue& Value);
ExampleCharacter.cpp에서 BeginPlay 함수에서 예시 블루프린트의 함수들을 그대로 실행하는 코드를 작성하였다.
Controller를 얻고 -> 이를 PlayerController로 캐스팅 -> 검증 후 Enhanced Input Local Player Subsystem 정의 -> Input Mapping Context 추가
헤더파일에서 정의한 액션 구현 함수 또한 정의하였다.
void AExampleCharacter::BeginPlay()
{
Super::BeginPlay();
APlayerController* PlayerController = Cast<APlayerController>(GetController());
if (PlayerController != nullptr)
{
UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(
PlayerController->GetLocalPlayer());
if (Subsystem != nullptr)
{
Subsystem->AddMappingContext(DefaultMappingContext, 0);
}
}
}
void AExampleCharacter::EnhancedInputMove(const FInputActionValue& Value)
{
FVector2D MovementVector = Value.Get<FVector2D>();
ABLOG_S(Warning);
if (Controller != nullptr)
{
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
const FVector FowardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
AddMovementInput(FowardDirection, MovementVector.Y);
AddMovementInput(RightDirection, MovementVector.X);
}
}
이제 남은 작업은 Input Action과 그 액션을 구현한 함수를 바인딩 해주는 것이다.
이는 Character 클래스의 SetupPlayerInputComponent에서 이루어진다.
PlayerInputComponent를 EnhancedInputComponent로 캐스팅하고 BindAction을 사용하여 바인딩한다.
Jump 액션의 경우 Character 클래스에 정의된 멤버를 그대로 사용하였다.
void AExampleCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent);
if (EnhancedInputComponent != nullptr)
{
EnhancedInputComponent->BindAction(InputToJump, ETriggerEvent::Triggered, this, &ACharacter::Jump);
EnhancedInputComponent->BindAction(InputToJump, ETriggerEvent::Completed, this, &ACharacter::StopJumping);
EnhancedInputComponent->BindAction(InputToLook, ETriggerEvent::Triggered, this, &AExampleCharacter::EnhancedInputLook);
EnhancedInputComponent->BindAction(InputToMove, ETriggerEvent::Triggered, this, &AExampleCharacter::EnhancedInputMove);
}
}
이대로 빌드하여 실행하면 Input Mapping Context가 정의되지 않았기 때문에 아래와 같은 에러메시지가 발생한다.
Warning: Called AddMappingContext with a null Mapping Context! No changes have been applied.
남은 작업은 에디터에서 만든 Input Action, Input Mapping Context를 정의한 Character(Pawn)클래스와 연결하는 것이다.
직접 만든 Character 클래스의 생성자에서 FObjectFinder를 사용하여 에디터에서 정의한 Input Mapping Contexts, Input Actions을 정의한다.
// 생략
#include "InputMappingContext.h"
// 생략
AExampleCharacter::AExampleCharacter() // Constructor
{
// 생략
static ConstructorHelpers::FObjectFinder<UInputMappingContext>InputMappingContext(
TEXT("/GAME/Input/IMC_AB.IMC_AB"));
if (InputMappingContext.Succeeded())
{
DefaultMappingContext = InputMappingContext.Object;
}
static ConstructorHelpers::FObjectFinder<UInputAction>InputActionMove(
TEXT("/GAME/Input/IA_AB_Move.IA_AB_Move"));
if (InputActionMove.Succeeded())
{
InputToMove = InputActionMove.Object;
}
static ConstructorHelpers::FObjectFinder<UInputAction>InputActionJump(
TEXT("/GAME/Input/IA_AB_Jump.IA_AB_Jump"));
if (InputActionJump.Succeeded())
{
InputToJump = InputActionJump.Object;
}
static ConstructorHelpers::FObjectFinder<UInputAction>InputActionLook(
TEXT("/GAME/Input/IA_AB_Look.IA_AB_Look"));
if (InputActionLook.Succeeded())
{
InputToLook = InputActionLook.Object;
}
}
또는 이를 상속한 블루프린트 클래스를 에디터에서 만들고 직접 Enhanced Input에서 선택하여 연결해 주어도 좋다 (추천)