C++와 Unreal Engine으로 3D 게임 개발

C++와 Unreal Engine으로 3D 게임 개발 2-3

jh009 2026. 6. 10. 20:13

2-3. 캐릭터 동작 구현과 입력 처리하기

1. Character 클래스에 액션 바인딩 추가하기

 

캐릭터 클래스에서 입력 액션 연결의 개념

  • PlayerController → SpartaInputMappingContext(IMC)를 활성화
  • 해당 IMC 에는 IA_Move, IA_Jump 등의 UInputAction들이 키보드/마우스와 맵핑이 되어있음
  • 캐릭터가 SetupPlayerInputComponent() 함수를 통해 각 액션이 발생했을 때 어떤 함수를 실행할지를 등록
  • 등록한 함수들이, 실제로 움직이거나 점프하는 등 캐릭터 동작을 수행

캐릭터에 액션 바인딩 추가하기

// SpartaCharacter.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "SpartaCharacter.generated.h"

class USpringArmComponent;
class UCameraComponent;

// Enhanced Input에서 액션 값을 받을 때 사용하는 구조체
struct FInputActionValue;

UCLASS()
class SPARTAPROJECT_API ASpartaCharacter : public ACharacter
{
		GENERATED_BODY()

public:
		ASpartaCharacter();

protected:
		UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
		USpringArmComponent* SpringArmComp;
		UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
		UCameraComponent* CameraComp;
	
		// 입력 바인딩을 처리할 함수
		virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
		       
		// IA_Move와 IA_Jump 등을 처리할 함수 원형
		// Enhanced Input에서 액션 값은 FInputActionValue로 전달됩니다.
		UFUNCTION()
		void Move(const FInputActionValue& value);
        
		UFUNCTION()
		void StartJump(const FInputActionValue& value);
        
		UFUNCTION()
		void StopJump(const FInputActionValue& value);
        
		UFUNCTION()
		void Look(const FInputActionValue& value);
        
		UFUNCTION()
		void StartSprint(const FInputActionValue& value);
        
		UFUNCTION()
		void StopSprint(const FInputActionValue& value);
};

 

void Move(const FInputActionValue& value);

 

FInputActionValue 참조자로 가져오는 이유

  • 구조체 같은 경우 파일이 큼
  • 참조를 안하고 가져오는 경우, 객체의 모든 데이터를 복사해서 가져옴 → 성능 및 복사 비용이 커짐

const

  • 참조된 객체를 수정하지 못하게끔 막아놓는 역할

void Move(const FInputActionValue& value);

 

언리얼 엔진이 사용자가 만든 함수를 외부(입력 시스템)에서 자유롭게 호출하려면, 그 함수가 무엇인지 엔진에게 미리 알려줘야 함

 

리플렉션이 없다면 (UFUNCTION 미사용)

C++ 컴파일러는 Move 함수의 주소만 알게 됨

엔진 입장에서 이 함수가 어떤 클래스에 속해 있는지, 안전하게 호출 가능한지, 어떤 매개변수를 받는지 등을 추적할 수 없음

 

리플렉션이 있다면 (UFUNCTION 사용):

엔진은 리플렉션 데이터를 통해 이 함수가 UObject 시스템 내부에 안전하게 등록된 함수임을 인지함

입력 이벤트 발생 시 엔진이 안전하게 해당 객체의 멤버 함수를 실행(Delegate 호출) 가능


#include "SpartaCharacter.h"
#include "SpartaPlayerController.h"
#include "EnhancedInputComponent.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"

ASpartaCharacter::ASpartaCharacter()
{
		PrimaryActorTick.bCanEverTick = false;

    SpringArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
    SpringArmComp->SetupAttachment(RootComponent);
    SpringArmComp->TargetArmLength = 300.0f;
    SpringArmComp->bUsePawnControlRotation = true;

    CameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
    CameraComp->SetupAttachment(SpringArmComp, USpringArmComponent::SocketName);
    CameraComp->bUsePawnControlRotation = false;
}

void ASpartaCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);

    // Enhanced InputComponent로 캐스팅
    if (UEnhancedInputComponent* EnhancedInput = Cast<UEnhancedInputComponent>(PlayerInputComponent))
    {
        // IA를 가져오기 위해 현재 소유 중인 Controller를 ASpartaPlayerController로 캐스팅
        if (ASpartaPlayerController* PlayerController = Cast<ASpartaPlayerController>(GetController()))
        {
            if (PlayerController->MoveAction)
            {
                // IA_Move 액션 키를 "키를 누르고 있는 동안" Move() 호출
                EnhancedInput->BindAction(
                    PlayerController->MoveAction,
                    ETriggerEvent::Triggered,
                    this,
                    &ASpartaCharacter::Move
                );
            }
            
            if (PlayerController->JumpAction)
            {
                // IA_Jump 액션 키를 "키를 누르고 있는 동안" StartJump() 호출
                EnhancedInput->BindAction(
                    PlayerController->JumpAction,
                    ETriggerEvent::Triggered,
                    this,
                    &ASpartaCharacter::StartJump
                );
                
                // IA_Jump 액션 키에서 "손을 뗀 순간" StopJump() 호출
                EnhancedInput->BindAction(
                    PlayerController->JumpAction,
                    ETriggerEvent::Completed,
                    this,
                    &ASpartaCharacter::StopJump
                );
            }
            
            if (PlayerController->LookAction)
            {
                // IA_Look 액션 마우스가 "움직일 때" Look() 호출
                EnhancedInput->BindAction(
                    PlayerController->LookAction,
                    ETriggerEvent::Triggered,
                    this,
                    &ASpartaCharacter::Look
                );
            }
            
            if (PlayerController->SprintAction)
            {
                // IA_Sprint 액션 키를 "누르고 있는 동안" StartSprint() 호출
                EnhancedInput->BindAction(
                    PlayerController->SprintAction,
                    ETriggerEvent::Triggered, 
                    this, 
                    &ASpartaCharacter::StartSprint
                );
                // IA_Sprint 액션 키에서 "손을 뗀 순간" StopSprint() 호출
                EnhancedInput->BindAction(
                    PlayerController->SprintAction, 
                    ETriggerEvent::Completed, 
                    this, 
                    &ASpartaCharacter::StopSprint
                );
            }    
        }
    }
}

void ASpartaCharacter::Move(const FInputActionValue& value)
{
}

void ASpartaCharacter::StartJump(const FInputActionValue& value)
{
}

void ASpartaCharacter::StopJump(const FInputActionValue& value)
{
}

void ASpartaCharacter::Look(const FInputActionValue& value)
{
}

void ASpartaCharacter::StartSprint(const FInputActionValue& value)
{
}

void ASpartaCharacter::StopSprint(const FInputActionValue& value)
{
}

 

BindAction 함수

EnhancedInput->BindAction(
PlayerController->MoveAction,
ETriggerEvent::Triggered,
this,
&ASpartaCharacter::Move
);
인자 위치 역할 쉽게 말하면
1번째 Action 어떤 버튼 (예: 조이스틱의 이동키)
2번째 Trigger 언제 동작할까 (누르는 중, 떼는 순간, 클릭하는 순간)
3번째 Object 누가 이 일을 수행하나 (보통 자기 자신인 this)
4번째 Function 어떤 행동을 하나 (실제 이동 처리 함수)

2. 캐릭터의 이동 함수 구현하기

Move 함수 구현하기

void ASpartaCharacter::Move(const FInputActionValue& value)
{
    // 컨트롤러가 있어야 방향 계산이 가능
    if (!Controller) return;

    // Value는 Axis2D로 설정된 IA_Move의 입력값 (WASD)을 담고 있음
    // 예) (X=1, Y=0) → 전진 / (X=-1, Y=0) → 후진 / (X=0, Y=1) → 오른쪽 / (X=0, Y=-1) → 왼쪽
    const FVector2D MoveInput = value.Get<FVector2D>();

    if (!FMath::IsNearlyZero(MoveInput.X))
    {
        // 캐릭터가 바라보는 방향(정면)으로 X축 이동
        AddMovementInput(GetActorForwardVector(), MoveInput.X);
    }

    if (!FMath::IsNearlyZero(MoveInput.Y))
    {
        // 캐릭터의 오른쪽 방향으로 Y축 이동
        AddMovementInput(GetActorRightVector(), MoveInput.Y);
    }
}

 

if(!Controller) return; 를 추가 하는 이유

GetActorForwardVector, GetActorRightVector → 컨트롤러가 기본적으로 있어야함

→ 밖에서 컨트롤러 체크를 또 해줘야 됨

 

InputActionValue::Get<FVector2D>()

IA_Move가 Axis2D로 설정되어 있음 → 2차원 벡터 형태로 입력이 들어옴

W(앞) / S(뒤) / D(오른쪽) / A(왼쪽)을 동시에 누를 수도 있으므로, (1,1) 같은 형태도 가능

 

AddMovementInput(방향, 크기)

작성 방법

  • AddMovementInput( 월드 좌표 기준 이동 방향(Forward, Right 등), 이동 스케일(속도) );

월드 좌표 기준 이동 방향(Forward, Right 등)

  • GetActorForwardVector()

이동 스케일(속도)

  • MoveInput.X

내부적으로 CharacterMovementComponent가 이 요청을 받아 속도를 계산, 실제 이동 구현


3. 캐릭터의 점프 함수 구현하기

StartJump, StopJump 함수 구현하기

void ASpartaCharacter::StartJump(const FInputActionValue& value)
{
    // Jump 함수는 Character가 기본 제공
    if (value.Get<bool>())
    {
        Jump();
    }
}

void ASpartaCharacter::StopJump(const FInputActionValue& value)
{
    // StopJumping 함수도 Character가 기본 제공
    if (!value.Get<bool>())
    {
        StopJumping();
    }
}

 

Jump에 if (!Controller) return; 추가 안 한 이유

Jump(), StopJumping() 엔진에서 구현된 코드 내부에 Controller 체크가 한 번 있음

 

value.Get<bool>()

Enhanced Input System에서 전달된 입력 값을 bool 로 가져옴

이 값은 점프 키(예: 스페이스바)가 눌렸는지 여부를 나타냄

  • true: 키가 눌림
  • false: 키가 눌리지 않음

Jump(), StopJumping()

Character 클래스에서 기본 제공되는 함수

캐릭터가 점프를 하거나 멈추도록 만들어줌


4. 캐릭터의 기본 시점 회전 구현하기

Look 함수 구현하기

void ASpartaCharacter::Look(const FInputActionValue& value)
{
    // 마우스의 X, Y 움직임을 2D 축으로 가져옴
    FVector2D LookInput = value.Get<FVector2D>();

    // X는 좌우 회전 (Yaw), Y는 상하 회전 (Pitch)
    // 좌우 회전
    AddControllerYawInput(LookInput.X);
    // 상하 회전
    AddControllerPitchInput(LookInput.Y);
}

 

실제 어느 방향으로 얼마나 회전할지 설정

  • 프로젝트 세팅 → Input → Mouse Sensitivity나 LookInput 에 곱해줄 스케일 (Modifiers)을 통해 조정

5. 스프린트 동작 구현하기

CharacterMovementComponent 에는 MaxWalkSpeed 라는 속성이 있음

MaxWalkSpeed 값을 변경하면, 캐릭터의 이동 속도가 즉시 바뀜

SpartaCharacter.h
SpartaCharacter.cpp


StartSprint, StopSprint 함수 구현하기

void ASpartaCharacter::StartSprint(const FInputActionValue& value)
{
    // Shift 키를 누른 순간 이 함수가 호출된다고 가정
    // 스프린트 속도를 적용
    if (GetCharacterMovement())
    {
        GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
    }
}

void ASpartaCharacter::StopSprint(const FInputActionValue& value)
{
    // Shift 키를 뗀 순간 이 함수가 호출
    // 평상시 속도로 복귀
    if (GetCharacterMovement())
    {
        GetCharacterMovement()->MaxWalkSpeed = NormalSpeed;
    }
}