드디어 언리얼 에디터 상에서 캐릭터를 움직여보려고 한다
오늘은 애니메이션 적용까지는 하지않고 간단한 이동까지만 진행했다.
먼저 캐릭터를 만들기 전에 알아야하고, 캐릭터 이동에 쓰이는 이론들이 있기 때문에 내가 이해한대로 쉽게 정리를 해보았다.
월드 상의 무언가를 움직일 수 있게 하는 클래스
월드 상에서 움직임을 구현하는 클래스 중에서는 Pawn Class가 있고, Character Class가 있다.

이렇게 클래스를 검색했을 때 Pawn이 Character보다 더 상위 클래스인 것을 알 수 있다.
이것만 보고 예상할 수 있는것은 Character 클래스는 Pawn 클래스보다 제공하는 함수가 더 많으려나?하고 예상을 해보았다.
Pawn Class
- 플레이어가 소유할 수 있는 가장 부모의 클래스
- 하나하나 손수 로직을 짜줘야함
Character Class
- 캐릭터를 움직이기 위한 여러 컴포넌트 붙어져 있음
- 이족보행의 인간형 캐릭터만 적용할 수 있음
- 장점
- Jump, StopJump, Sprint 같은 캐릭터 이동을 구현할 때 필수적으로 구현해야하는 함수들이 내장되어있어 빠르고 간편하게 구현 가능함
PlayerComtroller Class
- 만들어둔 캐릭터에 PlayerController가 빙의하는 것
- 캐릭터가 3개가 있다고 할 때, PlayerController에 따라 어떤 캐릭터를 조작하는지 결정됨
- 주요기능
- Enhanced Input Sysytem - 입력처리(언리얼 엔진 5)
- Input Mapping System (IMC)
- IA들을 총괄해서 관리하는 역할
- Input Action (IA) - 특정 동작 추상화(특정 동작 1개에 대해서만 전문적으로 처리) ← 한 동작 하나하나를 따로따로 저장하는 방식
- 점프 → IA_Jump → Jump 함수
- 마우스 회전 → IA_Look → Look 함수
- 이동 → IA_Move → Move 함수
- Enhanced Input Sysytem 장점
- IA로 동작별로 독립적으로 구현해놓고, IMC로 총괄하면 나중에 키 입력을 A → F로 변경하고 싶을 때 IA의 내부로직을 건드리지 않고도 IMC에서 간편하게 변경(관리) 가능
- 캐릭터가 자동차를 탔을 때 자동차의 입력으로 변경되어야하는 상황에서 IMC만 갈아끼울 수 있음(간단하게)
- 사람 전용 IMC
- 자동차 전용 IMC
- 이렇게 있는데 if 문으로 조건에 맞게 활성화/비활성화 하는 방식
- Input Mapping System (IMC)
- 카메라 제어 로직
- UI 상호작용
- 플레이어가 버튼을 누르거나 드래그 앤 드랍 이벤트가 발생했거나 하는 상황(어떤 상황인지 잘 이해안됨)
- Possess / Unpossess - 빙의
- 해당 캐릭터나 폰을 움직일 수 있게 해줌
- Enhanced Input Sysytem - 입력처리(언리얼 엔진 5)
예제 코드
#include "MyCharacter.h"
#include "MyPlayerController.h"
#include "EnhancedInputComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Camera/CameraComponent.h"
AMyCharacter::AMyCharacter()
{
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;
NormalSpeed = 600.0f;
SprintSpeedMultiplier = 1.7f;
SprintSpeed = NormalSpeed * SprintSpeedMultiplier;
GetCharacterMovement()->MaxWalkSpeed = NormalSpeed;
}
void AMyCharacter::BeginPlay()
{
Super::BeginPlay();
}
void AMyCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
if (UEnhancedInputComponent* EnhancedInput = Cast<UEnhancedInputComponent>(PlayerInputComponent))
{
if (AMyPlayerController* PlayerController = Cast<AMyPlayerController>(GetController()))
{
if (PlayerController->MoveAction)
{
EnhancedInput->BindAction(PlayerController->MoveAction, ETriggerEvent::Triggered, this, &AMyCharacter::Move);
}
if (PlayerController->JumpAction)
{
EnhancedInput->BindAction(PlayerController->JumpAction, ETriggerEvent::Triggered, this, &AMyCharacter::StartJump);
EnhancedInput->BindAction(PlayerController->JumpAction, ETriggerEvent::Completed, this, &AMyCharacter::StopJump);
}
if (PlayerController->LookAction)
{
EnhancedInput->BindAction(PlayerController->LookAction, ETriggerEvent::Triggered, this, &AMyCharacter::Look);
}
if (PlayerController->SprintAction)
{
EnhancedInput->BindAction(PlayerController->SprintAction, ETriggerEvent::Triggered, this, &AMyCharacter::StartSprint);
EnhancedInput->BindAction(PlayerController->SprintAction, ETriggerEvent::Completed, this, &AMyCharacter::StopSprint);
}
}
}
}
void AMyCharacter::Move(const FInputActionValue& value)
{
if (!Controller) return;
const FVector2D MoveInput = value.Get<FVector2D>();
if (!FMath::IsNearlyZero(MoveInput.X))
{
AddMovementInput(GetActorForwardVector(), MoveInput.X);
}
if (!FMath::IsNearlyZero(MoveInput.Y))
{
AddMovementInput(GetActorRightVector(), MoveInput.Y);
}
}
void AMyCharacter::StartJump(const FInputActionValue& value)
{
if (value.Get<bool>())
Jump();
}
void AMyCharacter::StopJump(const FInputActionValue& value)
{
if (!value.Get<bool>())
StopJumping();
}
void AMyCharacter::Look(const FInputActionValue& value)
{
FVector2D LookInput = value.Get<FVector2D>();
AddControllerYawInput(LookInput.X);
AddControllerPitchInput(LookInput.Y); // ia 만들 때 y값은 반전시켜놓은 상태
}
void AMyCharacter::StartSprint(const FInputActionValue& value)
{
if (GetCharacterMovement())
{
GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
}
}
void AMyCharacter::StopSprint(const FInputActionValue& Value)
{
if (GetCharacterMovement())
{
GetCharacterMovement()->MaxWalkSpeed = NormalSpeed;
}
}
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MyCharacter.generated.h"
class USpringArmComponent;
class UCameraComponent;
struct FInputActionValue;
UCLASS()
class PRACTICE_API AMyCharacter : public ACharacter
{
GENERATED_BODY()
public:
AMyCharacter();
protected:
virtual void BeginPlay() override;
virtual void Tick(float DeltaTime) override;
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
USpringArmComponent* SpringArmComp;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
UCameraComponent* CameraComp;
UFUNCTION()
void Move(const FInputActionValue& value); // 참조로 가져오는 이유 : 구조체는 엄청 큰 사이즈이기 때문에 성능, 복사 비용이 크다. 그래서 참조로 가져옴
UFUNCTION()
void StartJump(const FInputActionValue& value); // Trigger로 받는 애들은 시작과 끝을 나눠주는 것이 좋음(EnhancedInputSystem의 취지와도 맞음)
UFUNCTION()
void StopJump(const FInputActionValue& value);
UFUNCTION()
void Look(const FInputActionValue& value);
UFUNCTION()
void StartSprint(const FInputActionValue& value);
UFUNCTION()
void StopSprint(const FInputActionValue& value);
private:
float NormalSpeed;
float SprintSpeedMultiplier; // 몇 배 곱해줄건지
float SprintSpeed;
};
이렇게 구현을 해보았다. 지금은 Character 클래스를 상속받아 구현하는 중이다.
결과

자세가 좀 경직되어있지만 잘 움직이는 것을 볼 수 있다.
마지막 말
Character 클래스가 Jump나 여러 함수를 제공해줘서 여러모로 편했다.
Pawn 클래스를 상속받아 하나하나 구현해보면 더욱 실력이 늘 것 같다.
또한, 언리얼의 장점인 오픈소스답게 Character 클래스에서 제공하는 Jump 함수는 어떤 로직을 사용하는지, 엔진 내부 코드들을 하나하나 뜯어볼 수 있어서 재밌었던 것 같다.
'C++' 카테고리의 다른 글
[ C++ ] 프로그래머스 비밀지도 (0) | 2025.04.25 |
---|---|
[C++ ] C++의 꽃, 다형성에 대한 모든 개념 정리 (0) | 2025.01.21 |
[ C++ ] Text_RPG 팀플 완료 (1) | 2025.01.17 |
[Visual Studio] 한글 깨짐 현상 (0) | 2025.01.15 |
[C++] 진짜 랜덤값? 가짜 랜덤값? (0) | 2025.01.14 |