이제 Pawn, Character, Controller가 게임플레이를 구성하는 핵심 요소임을 이해했습니다. 이들이 실제로 움직이고 상호작용하려면 플레이어의 명령, 즉 입력(Input)이 필요합니다. 언리얼 엔진은 매우 강력하고 유연한 입력 처리 시스템을 제공하며, 이를 통해 키보드, 마우스, 게임패드 등 다양한 장치의 입력을 여러분의 게임 로직과 연결할 수 있습니다. 이번 절에서는 언리얼 엔진의 입력 매핑 시스템에 대해 자세히 알아보고, 여러분의 C++ 캐릭터가 플레이어의 명령에 반응하도록 만들어 보겠습니다.
핵심 흐름: 입력 장치 신호를 Input Action으로 추상화하고 Input Mapping Context에서 매핑한 뒤, UEnhancedInputLocalPlayerSubsystem 등록과 SetupPlayerInputComponent() 바인딩으로 캐릭터 동작까지 연결합니다.
언리얼 엔진은 크게 두 가지 방식의 입력 매핑을 제공합니다.
향상된 입력 시스템 (Enhanced Input System): 언리얼 엔진 5에서 도입된 최신 방식입니다. 더 유연하고 강력하며, 복잡한 입력 처리에 훨씬 유리합니다. 이 책에서는 이 방식을 주로 다룰 것입니다.
레거시 입력 시스템 (Legacy Input System): 언리얼 엔진 4부터 사용되던 기존 방식입니다. 아직도 많은 프로젝트에서 사용되지만, 새로운 프로젝트에서는 향상된 입력 시스템을 사용하는 것이 권장됩니다.
우리는 새로운 프로젝트이므로, 향상된 입력 시스템을 기준으로 설명하겠습니다.
향상된 입력 시스템은 몇 가지 주요 에셋과 개념으로 구성됩니다.
Input > Input Action을 선택하여 생성합니다.Input > Input Mapping Context를 선택하여 생성합니다.IMC를 등록하고 활성화하며, 입력 이벤트를 처리하는 엔진의 핵심 컴포넌트입니다. APlayerController를 통해 접근하여 사용합니다.APlayerController의 BeginPlay() 같은 초기화 단계에서 IMC를 등록하고 푸시(Push)하여 활성화합니다.APawn 또는 ACharacter와 같은 액터에서 특정 입력 액션이 발생했을 때 호출될 함수를 바인딩(Bind)하는 역할을 합니다.SetupPlayerInputComponent() 함수 내에서 InputComponent를 통해 바인딩을 수행합니다.특히 UI 메뉴, 차량 탑승, 일반 플레이처럼 입력 의미가 달라지는 상황에서는 Mapping Context 우선순위를 설계해 두면 충돌을 줄일 수 있습니다.
이제 실제 코드를 통해 향상된 입력 시스템을 설정해 보겠습니다. 우리의 캐릭터가 앞뒤로 움직이고 좌우로 회전하는 기능을 추가해 봅시다.
콘텐츠 브라우저에서 Content 폴더 안에 Input이라는 새 폴더를 만들고, 그 안에 다음 두 가지 Input Action 에셋을 생성합니다.
IA_MoveValue Type을 Axis2D (Vector2D)로 설정합니다. (X는 좌우, Y는 앞뒤 이동을 위해)IA_LookValue Type을 Axis2D (Vector2D)로 설정합니다. (X는 좌우 시점 변경, Y는 상하 시점 변경을 위해)IA_JumpValue Type을 Bool로 설정합니다. (점프는 눌렀는지 여부만 알면 되므로)Input 폴더 안에 IMC_Default라는 새 Input Mapping Context 에셋을 생성합니다. 더블 클릭하여 엽니다.
이제 이 IMC_Default에 IA_Move, IA_Look, IA_Jump를 매핑해 봅시다.
IA_Move 매핑IA_Move를 선택합니다.Swizzle Input Axis > Y to 1D (앞으로 이동)Swizzle Input Axis > Y to 1D Magnitude: -1.0 (뒤로 이동)Swizzle Input Axis > X to 1D Magnitude: -1.0 (왼쪽으로 이동)Swizzle Input Axis > X to 1D (오른쪽으로 이동)Swizzle Input Axis > Y-axis and X-axis (게임패드 스틱으로 이동)IA_Look 매핑IA_Look를 선택합니다.Swizzle Input Axis > X to 1D (좌우 시점)Modifiers 에 Invert를 추가하여 필요에 따라 반전시킬 수 있습니다.Swizzle Input Axis > Y to 1D (상하 시점)Modifiers 에 Invert를 추가하여 필요에 따라 반전시킬 수 있습니다.Swizzle Input Axis > X-axis and Y-axis (게임패드 스틱으로 시점 변경)IA_Jump 매핑IA_Jump를 선택합니다.IMC 설정을 마치면 저장합니다.
작성한 APlayerController 클래스 또는 커스텀 AGameMode에서 지정한 기본 PlayerController와 ACharacter 클래스를 수정하여 이 입력 시스템을 연동합니다.
먼저, 프로젝트의 DefaultPawn 또는 ACharacter 클래스 (예: AMyAwesomeActor 또는 새롭게 만든 AMyCharacter)에 입력 액션을 받을 함수를 선언하고 정의해야 합니다.
AMyCharacter.h (또는 여러분의 Character/Pawn 헤더)
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "InputAction.h" // FInputActionValue를 위해 포함
#include "MyCharacter.generated.h"
// 미리 선언 (Forward Declarations)
class UInputMappingContext;
class UInputAction;
UCLASS()
class MYFIRSTCPPPROJECT_API AMyCharacter : public ACharacter
{
GENERATED_BODY()
public:
AMyCharacter();
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
// 입력 설정 함수를 오버라이드
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
protected:
// =============== Input System 관련 UPROPERTY 변수들 ===============
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
UInputMappingContext* DefaultMappingContext;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
UInputAction* MoveAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
UInputAction* LookAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
UInputAction* JumpAction;
// =============== Input Action 바인딩될 함수들 ===============
void Move(const FInputActionValue& Value);
void Look(const FInputActionValue& Value);
void Jump();
void StopJumping();
};AMyCharacter.cpp (또는 여러분의 Character/Pawn 소스)
#include "MyCharacter.h"
#include "EnhancedInputSubsystems.h" // 핵심 서브시스템 포함
#include "EnhancedInputComponent.h" // EnhancedInputComponent를 위해 포함
#include "GameFramework/SpringArmComponent.h" // 카메라 컴포넌트 추가 시 필요
AMyCharacter::AMyCharacter()
{
PrimaryActorTick.bCanEverTick = true;
// CharacterMovementComponent 설정 (ACharacter는 기본으로 가짐)
GetCharacterMovement()->bOrientRotationToMovement = true; // 이동 방향으로 회전
bUseControllerRotationYaw = false; // 컨트롤러 회전 사용 안 함 (캐릭터가 직접 회전하도록)
// ... 다른 컴포넌트 설정 ...
}
void AMyCharacter::BeginPlay()
{
Super::BeginPlay();
// PlayerController를 가져와서 Enhanced Input Subsystem에 접근
if (APlayerController* PlayerController = Cast<APlayerController>(Controller))
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
{
// DefaultMappingContext가 유효하다면 추가
if (DefaultMappingContext)
{
Subsystem->AddMappingContext(DefaultMappingContext, 0); // 0은 우선순위 (높을수록 우선)
}
}
}
}
// InputComponent에 입력 액션 바인딩
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// EnhancedInputComponent로 캐스팅
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent))
{
// Move Action 바인딩
if (MoveAction)
{
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AMyCharacter::Move);
}
// Look Action 바인딩
if (LookAction)
{
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AMyCharacter::Look);
}
// Jump Action 바인딩
if (JumpAction)
{
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &AMyCharacter::Jump);
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &AMyCharacter::StopJumping);
}
}
}
// 이동 함수 구현
void AMyCharacter::Move(const FInputActionValue& Value)
{
// 입력 값 (Vector2D)을 가져옵니다.
FVector2D MovementVector = Value.Get<FVector2D>();
if (Controller != nullptr)
{
// 컨트롤러의 YAW 회전을 기준으로 앞/뒤 방향을 가져옵니다.
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
// 앞 방향 (Forward Vector)
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
AddMovementInput(ForwardDirection, MovementVector.Y);
// 오른쪽 방향 (Right Vector)
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
AddMovementInput(RightDirection, MovementVector.X);
}
}
// 시점 변경 함수 구현
void AMyCharacter::Look(const FInputActionValue& Value)
{
FVector2D LookAxisVector = Value.Get<FVector2D>();
if (Controller != nullptr)
{
AddControllerYawInput(LookAxisVector.X);
AddControllerPitchInput(LookAxisVector.Y);
}
}
// 점프 함수 구현
void AMyCharacter::Jump()
{
Super::Jump(); // ACharacter의 기본 점프 함수 호출
}
void AMyCharacter::StopJumping()
{
Super::StopJumping(); // ACharacter의 기본 점프 정지 함수 호출
}아래 다이어그램은 Move() 함수에서 FVector2D 입력값을 컨트롤러 Yaw 기준의 월드 축으로 변환해 AddMovementInput에 전달하는 과정을 정리한 것입니다.
이제 C++ 코드를 컴파일(Ctrl + Shift + B 또는 언리얼 에디터의 컴파일 버튼)한 후, 여러분의 BP_MyAwesomeActor (또는 새로 만든 BP_MyCharacter) 블루프린트 에디터를 엽니다.
클래스 기본값(Class Defaults) 설정: 블루프린트 에디터에서 클래스 기본값(Class Defaults) 또는 디테일 패널에서 여러분이 C++에서 선언한 Input 관련 UPROPERTY 변수들을 찾습니다.
Default Mapping Context에 이전에 생성한 IMC_Default에셋을 할당합니다.Move Action에 IA_Move에셋을 할당합니다.Look Action에 IA_Look에셋을 할당합니다.Jump Action에 IA_Jump에셋을 할당합니다.게임 모드 설정 (선택 사항이지만 권장): 월드에 배치된 캐릭터가 플레이어에 의해 제어되도록 하려면, 게임 모드의 기본 폰 클래스(Default Pawn Class)와 플레이어 컨트롤러 클래스(Player Controller Class)를 설정해야 합니다.
BP_MyGameMode)를 열거나, 새로 만듭니다.Default Pawn Class를 여러분의 BP_MyAwesomeActor 또는 BP_MyCharacter로 설정합니다.Player Controller Class를 기본 PlayerController 클래스(또는 커스텀 APlayerController를 만들었다면 그것으로)로 설정합니다.Window > World Settings)에서 GameMode Override를 여러분의 BP_MyGameMode로 설정합니다.입력 에셋을 만들었는데 캐릭터가 움직이지 않는다면, 에셋 할당과 게임 모드 연결, 런타임 등록 순서를 함께 확인해야 합니다.
모든 설정이 완료되었다면 언리얼 에디터에서 Play 버튼을 눌러 게임을 실행합니다. 이제 WASD 키로 캐릭터가 앞뒤좌우로 이동하고, 마우스를 움직여 시점을 변경하며, 스페이스바를 눌러 점프하는 것을 확인할 수 있을 것입니다!
향상된 입력 시스템은 처음에는 다소 복잡하게 느껴질 수 있지만, 한 번 익숙해지면 매우 강력하고 유연하게 입력 처리를 관리할 수 있게 해줍니다. Input Action으로 행동의 개념을, Input Mapping Context로 실제 키 매핑을 분리하여 관리함으로써, 게임의 입력 설정을 쉽게 변경하거나, 여러 입력 장치를 지원하거나, 특정 상황에 따른 다른 입력 동작을 구현하는 데 큰 이점을 제공합니다.
Input Action 에셋부터 C++ 바인딩까지 끊기기 쉬운 연결 지점을 한 번 더 묶어 보겠습니다.