<해야할 일>
더보기
더보기
- C++ Pawn 클래스 및 컴포넌트 구성
Pawn 클래스 생성: Pawn을 상속받는 새로운 C++ 클래스를 생성합니다.컴포넌트 추가: 아래 컴포넌트들을 Pawn 클래스에 추가합니다.CapsuleComponent (또는 Box/Sphere 중 택 1)SkeletalMeshComponentSpringArmComponentCameraComponent
계층 구조 설정: 충돌 컴포넌트를 RootComponent로 설정하고, 나머지 컴포넌트들을 부착합니다.DefaultPawn 설정: GameMode 클래스에서 DefaultPawnClass를 본인이 만든 Pawn으로 지정합니다.Physics 설정: 루트 충돌체 및 Mesh의 Simulate Physics를 false로 설정합니다. (물리 대신 코드로 직접 제어)
- Enhanced Input 매핑 & 바인딩
입력 액션(IA) 생성: 아래 두 가지 액션을 생성합니다. (타입: Vector2D)IA_Move (WASD 이동용)IA_Look (마우스 회전용)
IMC 매핑: 생성한 액션들을 Input Mapping Context에 등록하고 키를 할당합니다.액션 바인딩: SetupPlayerInputComponent()에서 입력 처리 함수와 액션들을 바인딩합니다.
- 이동 및 회전 로직 구현
프레임 독립성: DeltaTime을 사용하여 프레임 속도와 관계없이 일정한 속도로 움직이도록 구현합니다.이동 구현: AddActorLocalOffset() 등을 활용해 WASD 입력에 따라 Pawn이 움직이도록 작성합니다.이동 방향은 Pawn의 Forward/Right 벡터를 기준으로 결정됩니다.
회전 구현: AddActorLocalRotation() 등을 활용해 마우스 입력에 따라 회전하도록 작성합니다.마우스 입력값으로 Yaw와 Pitch를 직접 계산하여 구현합니다.⚠️ 주의: AddControllerYawInput(), AddControllerPitchInput() 등 엔진 기본 제공 함수는 사용하지 않습니다.
제한 사항: 평면 상에서의 이동과 회전만 처리하며, 중력이나 낙하 효과는 고려하지 않습니다.
<구현>
- 맵 설정 및 기본 설정
- 게임모드 생성 및 블루프린트로 감싸기
- 게임모드 프로젝트 전역에 설정
- Pawn 클래스 생성 및 블루프린트로 감싸기
- 컴포넌트 선언하기
// MyPawn.h 안의 클래스 내부 (protected나 public 영역)
protected:
// Box 컴포넌트로 설정!
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
class UBoxComponent* BoxComp;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
class USkeletalMeshComponent* SkeletalMeshComp;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
class USpringArmComponent* SpringArmComp;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
class UCameraComponent* CameraComp;
// MyPlayerPawn.cpp
#include "MyPawn.h" // 본인의 헤더 파일 이름에 맞게 확인
// 컴포넌트 사용을 위한 헤더들 추가 (중요!)
#include "Components/BoxComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
// 생성자 (여기서 부품을 조립합니다)
AMyPawn::AMyPawn()
{
PrimaryActorTick.bCanEverTick = false;
// 1. 박스 컴포넌트 생성 및 루트로 설정
BoxComp = CreateDefaultSubobject<UBoxComponent>(TEXT("BoxComp"));
RootComponent = BoxComp; // 충돌 컴포넌트를 Root로 설정
BoxComp->SetSimulatePhysics(false); // 물리 대신 코드 제어
// 2. 스켈레탈 메시 생성 및 루트에 부착
SkeletalMeshComp = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("SkeletalMeshComp"));
SkeletalMeshComp->SetupAttachment(RootComponent); // 루트(캡슐)에 붙임
SkeletalMeshComp->SetSimulatePhysics(false); // 물리 대신 코드 제어
// 3. 스프링암(셀카봉) 생성 및 루트에 부착
SpringArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
// 스프링 암을 루트 컴포넌트 (CapsuleComponent)에 부착
SpringArmComp->SetupAttachment(RootComponent);
// 캐릭터와 카메라 사이의 거리 기본값 300으로 설정
SpringArmComp->TargetArmLength = 300.0f;
// 컨트롤러 회전에 따라 스프링 암도 회전하도록 설정
SpringArmComp->bUsePawnControlRotation = true;
// 4. 카메라 생성 및 스프링암 끝에 부착
CameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComp"));
// 스프링 암의 소켓 위치에 카메라를 부착
CameraComp->SetupAttachment(SpringArmComp, USpringArmComponent::SocketName);
// 카메라는 스프링 암의 회전을 따르므로 PawnControlRotation은 꺼둠
CameraComp->bUsePawnControlRotation = false;
}
- 스켈레탈 메시 설정 및 컴포넌트 위치 조절
- GameMode에서 DefaultPawnClass 설정
// 게임모드 헤더파일
public:
AMyGameMode();
// 게임 모드 cpp 파일
#include "MyGameMode.h"
#include "MyPawn.h"
AMyGameMode::AMyGameMode()
{
DefaultPawnClass = AMyPawn::StaticClass();
}
- 빌드가 끝난 뒤 언리얼 에디터에서 프로젝트 세팅 -> GameMode Default Pawn Class를 BP_MyPawn으로 다시 설정
- PlayerController 생성 및 블루프린트로 감싸기
- IA 생성 및 IMC 매핑




- PlayerController에서 IMC 활성화하기
<컨트롤러 헤더>
class UInputMappingContext; // IMC 관련 전방 선언
class UInputAction; // IA 관련 전방 선언
public:
AMyPlayerController();
// 에디터에서 세팅할 IMC
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputMappingContext* InputMappingContext;
// IA_Move를 지정할 변수
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputAction* MoveAction;
// IA_Look를 지정할 변수
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputAction* LookAction;
<컨트롤러 cpp>
#include "MyPlayerController.h"
AMyPlayerController::AMyPlayerController()
: InputMappingContext(nullptr),
MoveAction(nullptr),
LookAction(nullptr)
{
}
- 컨트롤러 블루프린트를 열고 에셋 할당

- IMC 활성화 - 플레이어 컨트롤러에서 beginplay 함수를 오버라이드 하여 Blueprint에서 지정해둔 IMC를 활성화하는 코드를 추가
<컨트롤러 헤더>
virtual void BeginPlay() override;
<컨트롤러 cpp>
#include "MyPlayerController.h"
#include "EnhancedInputSubsystems.h"
AMyPlayerController::AMyPlayerController()
: InputMappingContext(nullptr),
MoveAction(nullptr),
LookAction(nullptr)
{
}
void AMyPlayerController::BeginPlay()
{
Super::BeginPlay();
if (ULocalPlayer* LocalPlayer = GetLocalPlayer())
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
{
if (InputMappingContext)
{
Subsystem->AddMappingContext(InputMappingContext, 0);
}
}
}
}
- Pawn 클래스에 액션 바인딩 추가하기
<pawn 헤더 파일>
// Enhanced Input에서 액션 값을 받을 때 사용하는 구조체
struct FInputActionValue;
// 입력 바인딩을 처리할 함수
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
// IA_Move등을 처리할 함수 원형
// Enhanced Input에서 액션 값은 FInputActionValue로 전달됩니다.
UFUNCTION()
void Move(const FInputActionValue& value);
UFUNCTION()
void Look(const FInputActionValue& value);
<pawn cpp 파일>
#include "MyPlayerController.h"
#include "EnhancedInputComponent.h"
void AMyPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// Enhanced InputComponent로 캐스팅
if (UEnhancedInputComponent* EnhancedInput = Cast<UEnhancedInputComponent>(PlayerInputComponent))
{
// IA를 가져오기 위해 현재 소유 중인 Controller를 ASpartaPlayerController로 캐스팅
if (AMyPlayerController* PlayerController = Cast<AMyPlayerController>(GetController()))
{
if (PlayerController->MoveAction)
{
// IA_Move 액션 키를 "키를 누르고 있는 동안" Move() 호출
EnhancedInput->BindAction(
PlayerController->MoveAction,
ETriggerEvent::Triggered,
this,
&AMyPawn::Move
);
}
if (PlayerController->LookAction)
{
// IA_Look 액션 마우스가 "움직일 때" Look() 호출
EnhancedInput->BindAction(
PlayerController->LookAction,
ETriggerEvent::Triggered,
this,
&AMyPawn::Look
);
}
}
}
}
void AMyPawn::Move(const FInputActionValue& value)
{
}
void AMyPawn::Look(const FInputActionValue& value)
{
}
- 이동 구현
void AMyPawn::Move(const FInputActionValue& value)
{
if (!Controller) return;
// 1. Enhanced Input에서 X, Y축 입력값(WASD)을 가져옵니다.
FVector2D MovementVector = value.Get<FVector2D>();
// 2. 프레임 독립성을 위한 DeltaTime 가져오기
// 성능이 좋은 컴퓨터나 나쁜 컴퓨터나 똑같은 속도로 움직이게 해줍니다.
float DeltaTime = GetWorld()->GetDeltaSeconds();
// 3. 이동 속도 설정 (원하는 속도로 조절하세요)
float MoveSpeed = 500.0f;
// 4. 로컬 이동 벡터 계산
FVector LocalOffset = FVector(
MovementVector.X * MoveSpeed * DeltaTime,
MovementVector.Y * MoveSpeed * DeltaTime,
0.0f);
// 5. AddActorLocalOffset을 사용해 폰을 이동
// 'Local'이 붙은 함수를 쓰면 폰이 바라보는 방향(Forward/Right)을 기준으로 자동으로 계산되어 이동합니다.
AddActorLocalOffset(LocalOffset, true);
}
void AMyPawn::Look(const FInputActionValue& value)
{
if (!Controller) return;
// 1. 마우스의 X, Y 이동값을 가져옵니다.
FVector2D LookAxisVector = value.Get<FVector2D>();
// 2. DeltaTime 가져오기
float DeltaTime = GetWorld()->GetDeltaSeconds();
// 3. 회전 속도 설정
float TurnSpeed = 100.0f;
// 4. 로컬 회전값 계산
// 마우스 X축 이동(좌우) -> 폰의 Z축 회전(Yaw)
// 마우스 Y축 이동(상하) -> 폰의 Y축 회전(Pitch)
// 언리얼의 FRotator는 (Pitch, Yaw, Roll) 순서로 값을 넣어야 합니다!
float PitchInput = LookAxisVector.Y * TurnSpeed * DeltaTime;
float YawInput = LookAxisVector.X * TurnSpeed * DeltaTime;
// (선택) 마우스 상하 반전이 필요하다면 PitchInput에 -1을 곱해주세요.
FRotator LocalRotation = FRotator(PitchInput, YawInput, 0.0f);
// 5. AddActorLocalRotation을 사용해 폰 자체를 직접 회전
AddActorLocalRotation(LocalRotation, true);
}
프로젝트 세팅에 가서
- Default GameMode가 본인이 만든 게임모드(예: BP_MyGameMode)로 되어 있는지 확인
- 그 아래쪽의 세부 화살표를 열고, Player Controller Class 항목이 에셋을 할당해둔 BP_MyPlayerController로 지정되어 있는지 확인
<트러블 슈팅>
1. 위, 아래 방향이 안맞음 -> 큐브 컴포넌트라서 x축 방향 맞춰주는 거 깜빡함!
-> 그냥 코드에서 x, y 위치 바꿈...
2.마우스 방향대로 카메라 시선이 바뀌면서 이동 위치도 이상하게 변하는 오류 발생
//MyPawn.cpp 함수 수정
AMyPawn::AMyPawn()
{
// 1. 스프링암이 '절대' 컨트롤러나 폰의 회전을 따르지 않게 합니다.
SpringArmComp->bUsePawnControlRotation = false; // 시선 고정
SpringArmComp->bInheritPitch = false; // 몸이 기울어져도 카메라는 고정
SpringArmComp->bInheritYaw = false; // 몸이 돌아가도 카메라는 고정
SpringArmComp->bInheritRoll = false;
// 2. 카메라 컴포넌트 자체도 회전하지 않게 합니다.
CameraComp->bUsePawnControlRotation = false;
}
시선은 고정되어 있는데 시선은 고정되어 있는데 마우스 방향으로 몸이 돌아가야 한다면, AddActorLocalOffset 대신 '세상(World)의 방향'을 기준으로 움직여야 함
void AMyPawn::Move(const FInputActionValue& value)
{
if (!Controller) return;
FVector2D MovementVector = value.Get<FVector2D>();
float DeltaTime = GetWorld()->GetDeltaSeconds();
float MoveSpeed = 500.0f;
// 카메라 시선과 상관없이 '세상의 절대 방향'으로 이동합니다.
FVector WorldOffset = FVector(MovementVector.X * MoveSpeed * DeltaTime, MovementVector.Y * MoveSpeed * DeltaTime, 0.0f);
AddActorWorldOffset(WorldOffset, true);
}
<작동 방식 이해>
- 자극 수신: 현실의 손가락이 W 키를 누름
- 뇌의 해석 (Controller & IMC): 대뇌(PlayerController)가 신호를 받음. 뇌는 미리 세팅된 IMC(입력 사전)를 뒤져보고, "아하! W 키는 IA_Move 신호(Y축 방향 +1)로 바꾸라는 뜻이구나!" 하고 해석함
- 신호 발송 (IA): IA_Move 신호가 육체를 향해 척수를 타고 내려감.
- 수용체 반응 (BindAction): 육체(Pawn)의 수용체(SetupPlayerInputComponent)가 그 신호를 낚아챔. "어? IA_Move 신호가 왔네? 아까 이거 오면 Move() 함수 실행하기로 했지!"
- 근육 수축 (Move 함수): Move() 함수 안의 로직이 실행. AddActorLocalOffset이 발동하면서 폰의 캡슐(몸통)을 프레임당 정해진 속도(DeltaTime)만큼 앞쪽 좌표로 물리적으로 쓱 밀어냄.
- 클래스의 멤버 변수(UPROPERTY 포인터)를 만들 때는 class UBoxComponent* 처럼 합쳐서 쓰는 것이 깔끔하고 좋음
- 함수의 매개변수(UFUNCTION)에 사용할 때는 오류 방지를 위해 지금처럼 위에 따로 전방 선언을 하거나, 헤더를 직접 포함(include)하는 것이 언리얼 엔진의 규칙
함수 이름 누르고 ctrl + . 누르면 cpp에 definition 만들어줌
'학습 > Unreal' 카테고리의 다른 글
| 언리얼 실무 도구 정리 (Interface, Delegate, FString, 컨테이너) (0) | 2026.04.28 |
|---|---|
| 언리얼 UObject 동작 원리 정리 (CDO, Reflection, GC, Serialization) (0) | 2026.04.27 |
| PlayerController 알아보기 / Enhanced Input System을 활용한 입력 매핑 구현하기 (0) | 2026.04.15 |
| Character 클래스를 활용한 캐릭터 구현하기 (0) | 2026.04.15 |
| [실습] Actor 회전과 이동 기능 구현 (0) | 2026.04.14 |
