[CH3/07] Pawn 클래스로 3D 캐릭터 만들기

김여울·2025년 7월 10일
0

내일배움캠프

목록 보기
41/111

Pawn 클래스로 3D 캐릭터 만들기

🧩 만들 것

  • GameMode

    • 어떤 Pawn이 기본적으로 사용될지 설정(Default Pawn Class )하는 곳
    • Character가 아닌 Pawn이어도 똑같이 필요
    • 게임에서 어떤 객체가 플레이어의 제어를 받을지 설정
  • PlayerController

    • 게임 내 입력을 받아서 Pawn에 전달하는 역할
    • Character 클래스가 아니어도 Pawn을 조종하는 데는 플레이어 컨트롤러가 여전히 필요
  • 제트기(Pawn)

  • Enhanced Input을 통한 이동/카메라/롤 조작

  • 시점 및 컨트롤 문제

1️⃣ GameMode

📄 MyGameMode.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameMode.h"
#include "MyGameMode.generated.h"

UCLASS()
class HW07PROJECT_API AMyGameMode : public AGameMode
{
	GENERATED_BODY()
	
public:
	AMyGameMode();
};

✨ MyGameMode.cpp

#include "MyGameMode.h"
#include "UObject/ConstructorHelpers.h"

AMyGameMode::AMyGameMode()
{

}

2️⃣ PlayerController

📄 MyPlayerController.h

#pragma once

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

class UInputMappingContext;

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

public:
	AMyPlayerController();

protected:
	virtual void BeginPlay() override;	// 플레이어 컨트롤러의 초기화

public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
	UInputMappingContext* InputMappingContext;
};

✨ MyPlayerController.cpp

#include "MyPlayerController.h"
#include "EnhancedInputSubsystems.h"
#include "EnhancedInputComponent.h"

AMyPlayerController::AMyPlayerController()
{
}

void AMyPlayerController::BeginPlay()
{
	Super::BeginPlay();

	// 현재 게임의 로컬 플레이어를 가져오는 함수
	if (ULocalPlayer* LocalPlayer = GetLocalPlayer())
	{
    	// 로컬 플레이어의 Enhanced Input 서브시스템을 가져
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
		{
        	// 입력 맵핑 컨텍스트가 있으면 추가
			if (InputMappingContext)
			{
				Subsystem->AddMappingContext(InputMappingContext, 0);	// 0은 우선 순위
			}
		}
	}
}

3️⃣ Pawn

📄 MyPawn.h

#pragma once

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

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

public:
	AMyPawn();

protected:
	virtual void BeginPlay() override;
	virtual void Tick(float DeltaTime) override;
    // 플레이어 입력을 설정하는 함수
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

	// 플레이어 입력을 설정하는 함수
	void OnMove(const FInputActionValue& Value);
	void OnLook(const FInputActionValue& Value);
	void OnRoll(const FInputActionValue& Value);

private:
	// 컴포넌트 선언
	UPROPERTY(EditAnywhere)
	class UCapsuleComponent* Capsule;

	UPROPERTY(EditAnywhere)
	class USkeletalMeshComponent* JetMesh;

	UPROPERTY(EditAnywhere)
	class USpringArmComponent* SpringArm;

	UPROPERTY(EditAnywhere)
	class UCameraComponent* Camera;

	// 입력 맵핑과 관련된 변수들
	UPROPERTY(EditAnywhere, Category = "Input")
	class UInputMappingContext* InputMapping;

	UPROPERTY(EditAnywhere, Category = "Input")
	class UInputAction* MoveAction;

	UPROPERTY(EditAnywhere, Category = "Input")
	class UInputAction* LookAction;

	UPROPERTY(EditAnywhere, Category = "Input")
	class UInputAction* RollAction;

	// 이동 컴포넌트
	UPROPERTY(VisibleAnywhere, Category = "Movement")
	class UFloatingPawnMovement* MovementComponent;

	// 속도 설정
	UPROPERTY(EditAnywhere)
	float MoveSpeed = 600.f;

	UPROPERTY(EditAnywhere)
	float TurnSpeed = 100.f;

	// 이동, 시점, 롤 제어를 위한 함수들
	UFUNCTION()
	void Move(const FInputActionValue& Value);

	UFUNCTION()
	void Look(const FInputActionValue& Value);

	UFUNCTION()
	void Roll(const FInputActionValue& Value);
};

✨ MyPawn.cpp

#include "MyPawn.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "InputMappingContext.h"
#include "InputAction.h"
#include "InputActionValue.h"
#include "Components/CapsuleComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/FloatingPawnMovement.h"

AMyPawn::AMyPawn()
{
	PrimaryActorTick.bCanEverTick = true;

	bUseControllerRotationYaw = true;

	Capsule = CreateDefaultSubobject<UCapsuleComponent>(TEXT("Capsule"));
	RootComponent = Capsule;
	Capsule->SetSimulatePhysics(false);

	JetMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("JetMesh"));
	JetMesh->SetupAttachment(RootComponent);
	JetMesh->SetSimulatePhysics(false);

	SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
	SpringArm->SetupAttachment(RootComponent);
	SpringArm->TargetArmLength = 1200.f;	// 카메라와의 거리
	SpringArm->SetRelativeLocation(FVector(0.f, 0.f, 200.f));	// 스프링암의 위치 설정
	SpringArm->SetRelativeRotation(FRotator(-15.f, 0.f, 0.f));	// 스프링암의 회전 설정
	SpringArm->bUsePawnControlRotation = true;	// 카메라의 회전이 Pawn의 제어에 의해 결정되도록 설정
	SpringArm->bInheritPitch = true;
	SpringArm->bInheritYaw = true;
	SpringArm->bInheritRoll = true;

	Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
	Camera->SetupAttachment(SpringArm);	// 카메라를 스프링암에 부착

	MovementComponent = CreateDefaultSubobject<UFloatingPawnMovement>(TEXT("MovementComponent"));
}

void AMyPawn::BeginPlay()
{
	Super::BeginPlay();

	// Enhanced Input 시스템 초기화
	if (APlayerController* PlayerController = Cast<APlayerController>(GetController()))
	{
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
		{
			Subsystem->AddMappingContext(InputMapping, 0);
		}
	}
}

void AMyPawn::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
}

void AMyPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	if (UEnhancedInputComponent* Input = Cast<UEnhancedInputComponent>(PlayerInputComponent))
	{
		Input->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AMyPawn::Move);
		Input->BindAction(LookAction, ETriggerEvent::Triggered, this, &AMyPawn::Look);
		Input->BindAction(RollAction, ETriggerEvent::Triggered, this, &AMyPawn::Roll);
	}
}

void AMyPawn::Move(const FInputActionValue& Value)
{
	FVector2D MoveVector = Value.Get<FVector2D>();
	UE_LOG(LogTemp, Warning, TEXT("Move Input: X=%f, Y=%f"), MoveVector.X, MoveVector.Y);

	// 이동 입력이 있을 경우, 그 방향으로 이동
	if (!MoveVector.IsNearlyZero())
	{
		AddMovementInput(GetActorForwardVector(), MoveVector.Y);	// 전후 이동
		AddMovementInput(GetActorRightVector(), MoveVector.X);	// 좌우 이동
	}
}

void AMyPawn::Look(const FInputActionValue& Value)
{
	// 시점 입력 처리 (Yaw, Pitch)
	FVector2D LookVector = Value.Get<FVector2D>();

	// 시점 변경 입력이 있을 경우, 회전 적용
	if (LookVector != FVector2D::ZeroVector)
	{
		AddControllerYawInput(LookVector.X);	// 좌우 회전
		AddControllerPitchInput(LookVector.Y);	// 상하 회전
	}
}

void AMyPawn::Roll(const FInputActionValue& Value)
{
	// 롤 회전 입력 처리
	float RollValue = Value.Get<float>();

	// 롤 회전 값이 있을 경우 회전 적용
	if (!FMath::IsNearlyZero(RollValue))
	{
		FRotator NewRotation = GetActorRotation();
		NewRotation.Roll += RollValue * 2.0f;	// 롤 회전 값에 따른 회전 적용
		SetActorRotation(NewRotation);	// 회전 적용
	}
}

💥 저지른 실수

  • W/S 눌러도 좌우로만 이동됨
    • AddMovementInput(GetActorForwardVector(), MoveVector.Y) 로컬 축 기준으로 수정
  • 마우스로 카메라 회전 안됨
    • PlayerController에서 Look Input 연결 확인 & 마우스 캡처 옵션 확인
  • D 키 눌러도 왼쪽으로 감
    • GetActorRightVector() 방향 반전 문제로 코드 점검
  • 시작할 때 땅에 붙음
    • Pawn 위치 수동으로 올리기 + Use Auto Possess Player 옵션 확인
  • 입력 제대로 안 먹힘
    • Subsystem->AddMappingContext 가 BeginPlay에 들어가는지 확인
  • Roll 입력 중복 함수 존재
    • OnRoll, Roll 중복 제거하고 하나로 통합
  • 실행 중 뷰포트 작음
    • Shift + F11 전체화면 또는 Play 세팅에서 New Editor Window 사용

💡 기억하자

  • 마우스 입력 안 될 때
    → PlayerController 설정 또는 bShowMouseCursor, bEnableClickEvents, bEnableMouseOverEvents 확인
  • Enhanced Input 설정 시
    → InputAction + InputMappingContext 둘 다 연결 필요
  • LogTemp로 디버깅 유용하게 사용 `
    UE_LOG(LogTemp, Warning, TEXT("Move called"));

0개의 댓글