<해야할 일>

더보기
더보기
  1. C++ Pawn 클래스 및 컴포넌트 구성
    • Pawn 클래스 생성: Pawn을 상속받는 새로운 C++ 클래스를 생성합니다.
    • 컴포넌트 추가: 아래 컴포넌트들을 Pawn 클래스에 추가합니다.
      • CapsuleComponent (또는 Box/Sphere 중 택 1)
      • SkeletalMeshComponent
      • SpringArmComponent
      • CameraComponent
    • 계층 구조 설정: 충돌 컴포넌트를 RootComponent로 설정하고, 나머지 컴포넌트들을 부착합니다.
    • DefaultPawn 설정: GameMode 클래스에서 DefaultPawnClass를 본인이 만든 Pawn으로 지정합니다.
    • Physics 설정: 루트 충돌체 및 Mesh의 Simulate Physics를 false로 설정합니다. (물리 대신 코드로 직접 제어)

  1. Enhanced Input 매핑 & 바인딩
    • 입력 액션(IA) 생성: 아래 두 가지 액션을 생성합니다. (타입: Vector2D)
      • IA_Move (WASD 이동용)
      • IA_Look (마우스 회전용)
    • IMC 매핑: 생성한 액션들을 Input Mapping Context에 등록하고 키를 할당합니다.
    • 액션 바인딩: SetupPlayerInputComponent()에서 입력 처리 함수와 액션들을 바인딩합니다.

  1. 이동 및 회전 로직 구현
    • 프레임 독립성: 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 매핑

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 만들어줌 

 

+ Recent posts