[TIL] 2026-01-14 | UE Pawn 클래스로 3D 캐릭터 만들기

2026. 1. 14. 21:19·내배캠Unreal_TIL/UE
C++와 Unreal Engine으로 3D 게임 개발 과제 7

1. WHY

  • Pawn 클래스 구조 이해
    • 언리얼 엔진에서 Pawn은 PlayerController가 조종할 수 있는 최소 단위입니다.
    • CharacterMovementComponent 없이 직접 이동 로직을 구현해봅시다.
  • Enhanced Input & 3인칭 카메라
    • Enhanced Input 액션을 생성하여 키보드와 마우스 입력을 처리합니다.
    • SpringArmComponent 및 CameraComponent를 사용해 3인칭 시점을 구현하며, 마우스 움직임으로 카메라를 회전시킵니다.
  • 직접 이동 로직 구현
    • AddActorLocalOffset, AddActorLocalRotation 등을 활용하여 WASD와 마우스 입력에 따라 Pawn을 움직이도록 만듭니다.

 

2. HOW

- 1단계: C++ Pawn 클래스 생성

실수로 만든 캐릭터 컴포넌트를 뜯어보며 Pawn 클래스로 구현을 했다.

 

// MyPawn.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "MyPawn.generated.h"

class UCapsuleComponent;
class UArrowComponent;
class USpringArmComponent;
class UCameraComponent;

UCLASS()
class NBC_UE_P7_API AMyPawn : public APawn
{
	GENERATED_BODY()

public:
	// Sets default values for this pawn's properties
	AMyPawn();

protected:
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Comp")
	UCapsuleComponent* CapsuleComp;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Comp")
	UArrowComponent* ArrowComp;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Comp")
	USkeletalMeshComponent* SkeletalMeshComp;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
	USpringArmComponent* SpringArmComp;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
	UCameraComponent* CameraComp;

public:	

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

};
// MyPawn.cpp

#include "MyPawn.h"
#include "Components/CapsuleComponent.h"
#include "Components/ArrowComponent.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"

// Sets default values
AMyPawn::AMyPawn()
{
	// 캡슐 컴포넌트 루트로 설정
    CapsuleComp = CreateDefaultSubobject<UCapsuleComponent>(TEXT("CapsuleRoot"));
    SetRootComponent(CapsuleComp);

	if (CapsuleComp->IsSimulatingPhysics())
	{
		CapsuleComp->SetSimulatePhysics(false);
	}

	// arrow 컴포넌트
	ArrowComp = CreateDefaultSubobject<UArrowComponent>(TEXT("Arrow"));
	ArrowComp->SetupAttachment(CapsuleComp);

	// 스켈레톤 매쉬 컴포넌트 추가
	SkeletalMeshComp = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("SkeletalMesh"));
	SkeletalMeshComp->SetupAttachment(CapsuleComp);

	if (SkeletalMeshComp->IsSimulatingPhysics())
	{
		SkeletalMeshComp->SetSimulatePhysics(false);
	}

	// 스프링 암
	SpringArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
	SpringArmComp->SetupAttachment(CapsuleComp);
	SpringArmComp->TargetArmLength = 300.0f;
	SpringArmComp->bUsePawnControlRotation = true;

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

// Called to bind functionality to input
void AMyPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
}

블루프린트에서 확인하면 잘 생성되었다.

 

메쉬 설정, 캡슐 크기, 카메라 위치는 블루프린트에서 조절해줬다.

 

// MyGameMode.cpp

#include "MyGameMode.h"
#include "MyPawn.h"
#include "MyPlayerController.h"

AMyGameMode::AMyGameMode()
{
    DefaultPawnClass = AMyPawn::StaticClass();
    PlayerControllerClass = AMyPlayerController::StaticClass();
}

MyPawn을 DefaultPawn으로 설정해준다.

 

실행시키면 오른쪽 창에서 GameMode에 설정해둔 인스턴스를 확인 가능하다.

 

- 2단계 : Enhanced Input 액션 설정

Move (WASD용 - Vector2D 타입)

Look (마우스 회전용 - Vector2D 타입)

 

IMC에 키를 매핑해줬다.

 

// MyPlayerController.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "MyPlayerController.generated.h"

class UInputMappingContext;
class UInputAction;

UCLASS()
class NBC_UE_P7_API AMyPlayerController : public APlayerController
{
	GENERATED_BODY()

public:
	AMyPlayerController();

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Input")
	UInputMappingContext* InputMappingContext;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
	UInputAction* MoveAction;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
	UInputAction* LookAction;
	
	virtual void BeginPlay() override;
};
// MyPlayerController.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);
            }
        }
    }
}

PlayerController에서 IA와 IMC를 연결해준다.

 

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

	if (UEnhancedInputComponent* EnhancedInput = Cast<UEnhancedInputComponent>(PlayerInputComponent))
	{
		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
				);
			}
		}
	}
}

SetupPlayerInputComponent()에서 액션들을 입력 처리 함수에 바인딩한다.

 

- 3단계 : Pawn 이동 로직 작성

void AMyPawn::Move(const FInputActionValue& value) 
{
	if (!Controller) return;
	const FVector2D MoveInput = value.Get<FVector2D>();

	const float Speed = 300.0f;
	const float Delta = GetWorld()->GetDeltaSeconds();

	if (!FMath::IsNearlyZero(MoveInput.X))
	{
		// 캐릭터가 바라보는 방향(정면)으로 X축 이동
		//AddMovementInput(GetActorForwardVector(), MoveInput.X);
		AddActorLocalOffset(FVector(MoveInput.X,0.f,0.f) * Speed * Delta, true);

	}

	if (!FMath::IsNearlyZero(MoveInput.Y))
	{
		// 캐릭터의 오른쪽 방향으로 Y축 이동
		//AddMovementInput(GetActorRightVector(), MoveInput.Y);
		AddActorLocalOffset(FVector(0.f,MoveInput.Y,0.f) * Speed * Delta, true);
	}
}

AddActorLocalOffset() 을 통해 이동을 구현했다.

 

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

	const float Speed = 90.0f;
	const float Delta = GetWorld()->GetDeltaSeconds();

	// 좌우
	float CameraYaw = LookInput.X * Speed * Delta;
	AddActorLocalRotation(FRotator(0.f, CameraYaw, 0.f));
	
	// 상하
	CameraPitch -= LookInput.Y * Speed * Delta;
	CameraPitch = FMath::Clamp(CameraPitch, -80.f, 80.f);
	SpringArmComp->SetRelativeRotation(FRotator(CameraPitch, 0.f, 0.f));
}

상하 움직임은 SpringArmComp를 회전하고

좌우 움직임은 액터가 회전하도록 했다.

 

 

- 도전 과제 1번

  • 6축 이동 및 회전 액션 구현
    • 이동
      • 전/후 (W/S) - 로컬 X축 이동
      • 좌/우 (A/D) - 로컬 Y축 이동
      • 상/하 (Space/Shift) - 로컬 Z축 이동
    • 회전
      • Yaw - 좌우 회전, 마우스 X축 이동
      • Pitch - 상하 회전, 마우스 Y축 이동
      • Roll - 기울기 회전, 마우스 휠 또는 별도 키
  • Orientation 기반 이동 구현
    • 현재 Pawn의 회전 상태에 따라 이동 방향이 결정되는 비행체 움직임을 구현합니다.
    • 단순 월드 좌표계 이동이 아닌, Pawn의 로컬 좌표계 기준 이동을 구현합니다.
실제 비행기 조종 원리 (요크 / 스틱 기준)

비행기 조종간은 카메라 시점 조작이 아니라 기체 자세 조작이야.

조종간을 앞으로 민다 → 엘리베이터가 아래로 움직임 → 기수(Nose)가 내려감 → 하강
조종간을 뒤로 당긴다 → 엘리베이터가 위로 움직임 → 기수가 올라감 → 상승
void AMyPlane::Move(const FInputActionValue& value)
{
	if (!Controller) return;
	const FVector3d MoveInput = value.Get<FVector3d>();
	const float Speed = 300.0f;
	const float Delta = GetWorld()->GetDeltaSeconds();

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

	if (!FMath::IsNearlyZero(MoveInput.Y))
	{
		// 캐릭터의 오른쪽 방향으로 Y축 이동
		//AddMovementInput(GetActorRightVector(), MoveInput.Y);
		AddActorLocalOffset(FVector(0.f, MoveInput.Y, 0.f) * Speed * Delta, true);
	}
    
	if (!FMath::IsNearlyZero(MoveInput.Z))
	{
		// 캐릭터의 위쪽 방향으로 Z축 이동
		AddActorLocalOffset(FVector(0.f, 0.f, MoveInput.Z) * Speed * Delta, true);
	}

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

	const float Speed = 90.0f;
	const float Delta = GetWorld()->GetDeltaSeconds();

	// 좌우
	float CameraYaw = LookInput.X * Speed * Delta;
	AddActorLocalRotation(FRotator(0.f, CameraYaw, 0.f));

	// 상하
	float CameraPitch = LookInput.Y * Speed * Delta;
	AddActorLocalRotation(FRotator(CameraPitch, 0.f, 0.f));

	// 기울기
	float CameraRoll = LookInput.Z * Speed * Delta;
	AddActorLocalRotation(FRotator(0.f, 0.f, CameraRoll));
}

기존 코드에서 간단하게 수정 및 추가 해주면 된다.

 

Roll은 E,Q 키로 매핑하고 상하 이동은 Space bar, Left Shift 로 했다.

 

시연 영상에서 필수 과제와 도전 과제가 같이 보였으면 좋겠어서 트리거 액터를 통해 폰 클래스를 교체할 수 있도록 했다.

// PawnSwitchTrigger.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "GameFramework/Pawn.h"
#include "PawnSwitchTrigger.generated.h"

class UBoxComponent;
class APawn;

UCLASS()
class NBC_UE_P7_API APawnSwitchTrigger : public AActor
{
    GENERATED_BODY()

public:
    APawnSwitchTrigger();
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pawn Switch")
    TSubclassOf<APawn> SwitchPawnClass;

protected:
    virtual void BeginPlay() override;

    UPROPERTY(VisibleAnywhere)
    UBoxComponent* Box;

    UFUNCTION()
    void OnOverlapBegin(
        UPrimitiveComponent* OverlappedComponent,
        AActor* OtherActor,
        UPrimitiveComponent* OtherComp,
        int32 OtherBodyIndex,
        bool bFromSweep,
        const FHitResult& SweepResult
    );
private:
    bool bIsSwitching = false;
};
// PawnSwitchTrigger.cpp

#include "PawnSwitchTrigger.h"
#include "Components/BoxComponent.h"
#include "MyGameMode.h"
#include "Kismet/GameplayStatics.h"

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

    Box = CreateDefaultSubobject<UBoxComponent>(TEXT("Box"));
    RootComponent = Box;

    Box->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
    Box->SetCollisionResponseToAllChannels(ECR_Ignore);
    Box->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap);
}

void APawnSwitchTrigger::BeginPlay()
{
    Super::BeginPlay();
    Box->OnComponentBeginOverlap.AddDynamic(this, &APawnSwitchTrigger::OnOverlapBegin);
}

void APawnSwitchTrigger::OnOverlapBegin(
    UPrimitiveComponent*,
    AActor* OtherActor,
    UPrimitiveComponent*,
    int32,
    bool,
    const FHitResult&
)
{
    if (bIsSwitching) 
        return;
    bIsSwitching = true;
    APawn* Pawn = Cast<APawn>(OtherActor);
    if (!Pawn) return;

    AMyGameMode* GM = Cast<AMyGameMode>(UGameplayStatics::GetGameMode(this));
    if (!GM) return;

    GM->SwitchPlayerPawn(SwitchPawnClass);
}

 

3. RESULT

https://github.com/yoonseo4343/NBC_UE_P7

 

GitHub - yoonseo4343/NBC_UE_P7: 내일배움캠프 언리얼7기 과제 7

내일배움캠프 언리얼7기 과제 7. Contribute to yoonseo4343/NBC_UE_P7 development by creating an account on GitHub.

github.com

 

https://youtu.be/o74q-V1pCUQ

 

'내배캠Unreal_TIL > UE' 카테고리의 다른 글

[TIL] 2026-01-16 | UE 애니메이션 리타겟팅  (0) 2026.01.16
[TIL] 2026-01-15 | UE 회전 발판과 움직이는 장애물  (0) 2026.01.15
[TIL] 2026-01-13 | State Machine 설계를 통한 캐릭터 애니메이션 적용  (0) 2026.01.13
[TIL] 2026-01-12 | UE C++ 캐릭터 컨트롤  (0) 2026.01.12
[TIL] 2026-01-09 | C++ 문자 종류 판단, Actor 라이프 사이클, Transform, 리플렉션 시스템  (0) 2026.01.09
'내배캠Unreal_TIL/UE' 카테고리의 다른 글
  • [TIL] 2026-01-16 | UE 애니메이션 리타겟팅
  • [TIL] 2026-01-15 | UE 회전 발판과 움직이는 장애물
  • [TIL] 2026-01-13 | State Machine 설계를 통한 캐릭터 애니메이션 적용
  • [TIL] 2026-01-12 | UE C++ 캐릭터 컨트롤
윤윤씨
윤윤씨
🎮 내일배움캠프 Unreal 7기
  • 윤윤씨
    컴퓨터온열맛사지
    윤윤씨
  • 전체
    오늘
    어제
    • 분류 전체보기 (62)
      • 내배캠Unreal_TIL (62)
        • C++ (23)
        • UE (31)
        • 팀프로젝트 (7)
      • etc (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • Github
    • Solved.ac
    • YouTube
  • 태그

    프로그래머스
    오토마타
    디자인패턴
    언리얼과제
    gas
    ta
    머티리얼
    스테이트머신
    코드카타
    STL
    챌린지
  • 최근 댓글

  • hELLO· Designed By정상우.v4.10.6
윤윤씨
[TIL] 2026-01-14 | UE Pawn 클래스로 3D 캐릭터 만들기
상단으로

티스토리툴바