https://github.com/K-Tia20/Basic-Actor-Control-Project.git

과제 조건

  • Pawn 클래스 생성: Pawn을 상속받는 새로운 C++ 클래스를 생성합니다.
  • 컴포넌트 추가: 아래 컴포넌트들을 Pawn 클래스에 추가합니다.
  • `CapsuleComponent
  • SkeletalMeshComponent
  • SpringArmComponent
  • CameraComponent
  • 계층 구조 설정: 충돌 컴포넌트를 RootComponent로 설정하고, 나머지 컴포넌트들을 부착합니다.
  • DefaultPawn 설정: GameMode 클래스에서 DefaultPawnClass를 본인이 만든 Pawn으로 지정합니다.
  • Physics 설정: 루트 충돌체 및 Mesh의 Simulate Physicsfalse로 설정합니다. (물리 대신 코드로 직접 제어)
  • 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() 등 엔진 기본 제공 함수는 사용하지 않습니다.
  • 제한 사항: 평면 상에서의 이동과 회전만 처리하며, 중력이나 낙하 효과는 고려하지 않습니다.

  • 6축 액션 구현: 아래 방향에 대한 이동 및 회전을 모두 구현합니다.
  • 이동: 전/후(W, S), 좌/우(A, D), 상/하(Space, Shift)
  • 회전: Yaw(마우스 X), Pitch(마우스 Y), Roll(마우스 휠 또는 별도 키) 회전: Yaw(마우스 X), Pitch(마우스 Y), Roll(마우스 휠 또는 별도 키)
  • Local 기반 이동: 단순 월드 좌표 이동이 아닌, Pawn의 현재 회전 상태(로컬 좌표계)를 기준으로 이동 방향이 결정되도록 구현합니다.
  • 인공 중력: Tick() 함수에서 매 프레임 중력 가속도를 직접 계산하여 적용합니다. (예: -980 cm/s²)
  • 지면 충돌 감지: LineTrace 또는 SweepTrace를 사용하여 지면과의 충돌을 체크합니다.
  • 상태 초기화: 지면에 착지하는 순간 Z축 낙하 속도를 0으로 초기화합니다.
  • 이동 속도 제한: 공중에 떠 있는 상태에서는 지상 이동 속도의 30~50% 수준으로 제한합니다.
  • 상태별 로직 분기: 지상 상태와 공중 상태를 구분하여 각각 자연스러운 움직임이 나타나도록 구현합니다.

OkPlayerController.h

// OkPlayerController.h
 
#pragma once
 
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "OkPlayerController.generated.h"
 
class UInputMappingContext;
class UInputAction;
 
UCLASS()
class P3P4_API AOkPlayerController : public APlayerController
{
	GENERATED_BODY()
	
public:
	AOkPlayerController();
 
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
	UInputMappingContext* InputMappingContext;
 
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
	UInputAction* MoveAction;
 
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
	UInputAction* LookAction; 
 
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
	UInputAction* MoveUpAction; 
 
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
	UInputAction* MoveDownAction; 
 
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
	UInputAction* RollAction; 
 
protected:
	virtual void BeginPlay() override;
};
 
 

OkPlayerController.cpp

 
// OkPlayerController.cpp
 
 
#include "Controller/OkPlayerController.h"
#include "EnhancedInputSubsystems.h"
 
AOkPlayerController::AOkPlayerController()
	: InputMappingContext(nullptr),
	  MoveAction(nullptr),
	  LookAction(nullptr),
	  MoveUpAction(nullptr),
	  MoveDownAction(nullptr),
	  RollAction(nullptr)
{
}
 
void AOkPlayerController::BeginPlay()
{
	Super::BeginPlay();
 
	if (ULocalPlayer* LocalPlayer = GetLocalPlayer())
	{
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
		{
			if (InputMappingContext)
			{
				Subsystem->AddMappingContext(InputMappingContext, 0);
			}
		}
	}
}
 

OkPawn.h

 
// OkPawn.h
 
#pragma once
 
#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "OkPawn.generated.h"
 
class UCapsuleComponent;
class USkeletalMeshComponent;
class USpringArmComponent;
class UCameraComponent;
struct FInputActionValue;
 
UCLASS()
class P3P4_API AOkPawn : public APawn
{
	GENERATED_BODY()
 
public:
	AOkPawn();
 
protected:
	virtual void BeginPlay() override;
	virtual void Tick(float DeltaTime) override;
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
 
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
	UCapsuleComponent* CapsuleComp;
 
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
	USkeletalMeshComponent* MeshComp;
 
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
	USpringArmComponent* SpringArmComp;
 
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
	UCameraComponent* CameraComp;
 
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
	float MoveSpeed = 1000.0f; 
 
	FVector MoveInputVector; 
	FVector LookInputVector; 
 
	float VerticalVelocity = 0.0f; 
	const float Gravity = -300.0f; 
	bool bIsGrounded = false;  
 
	UFUNCTION()
	void Move(const FInputActionValue& Value);
 
	UFUNCTION()
	void MoveUp(const FInputActionValue& Value);
 
	UFUNCTION()
	void MoveDown(const FInputActionValue& Value);
 
	UFUNCTION()
	void Look(const FInputActionValue& Value);
 
	UFUNCTION()
	void RollAxis(const FInputActionValue& Value);
};
 

OkPawn.cpp

 
// OkPawn.cpp
 
 
#include "Pawn/OkPawn.h"
#include "Controller/OkPlayerController.h"
#include "Components/CapsuleComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "EnhancedInputComponent.h"
 
AOkPawn::AOkPawn()
{
	PrimaryActorTick.bCanEverTick = true;
	
	CapsuleComp = CreateDefaultSubobject<UCapsuleComponent>(TEXT("CapsuleComp"));
	SetRootComponent(CapsuleComp);
	CapsuleComp->InitCapsuleSize(34.0f, 88.0f);
	CapsuleComp->SetSimulatePhysics(false); 
 
	MeshComp = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("MeshComp"));
	MeshComp->SetupAttachment(RootComponent);
	MeshComp->SetSimulatePhysics(false);
 
	SpringArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComp"));
	SpringArmComp->SetupAttachment(RootComponent);
	SpringArmComp->TargetArmLength = 300.0f;
	SpringArmComp->bUsePawnControlRotation = false;
 
	CameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComp"));
	CameraComp->SetupAttachment(SpringArmComp, USpringArmComponent::SocketName);
	CameraComp->bUsePawnControlRotation = false;
 
	MoveInputVector = FVector::ZeroVector;
	LookInputVector = FVector::ZeroVector;
}
 
void AOkPawn::BeginPlay()
{
	Super::BeginPlay();
	
}
 
void AOkPawn::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	
    FHitResult HitResult;
    FVector StartLocation = GetActorLocation();
 
    float TraceLength = CapsuleComp->GetScaledCapsuleHalfHeight() + 5.0f;
    FVector EndLocation = StartLocation - FVector(0.0f, 0.0f, TraceLength);
 
    FCollisionQueryParams TraceParams;
    TraceParams.AddIgnoredActor(this);
 
    bool bHit = GetWorld()->LineTraceSingleByChannel(HitResult, StartLocation, EndLocation, ECC_Visibility, TraceParams);
 
    if (bHit)
    {
        bIsGrounded = true;
        if (VerticalVelocity < 0.0f)
        {
            VerticalVelocity = 0.0f;
        }
    }
    else
    {
        bIsGrounded = false;
        VerticalVelocity += Gravity * DeltaTime;
    }
 
    float CurrentSpeed = bIsGrounded ? MoveSpeed : MoveSpeed * 0.4f;
 
    FVector InstantMove = MoveInputVector * CurrentSpeed * DeltaTime;
    
    AddActorLocalOffset(InstantMove, true);
 
    AddActorWorldOffset(FVector(0.0f, 0.0f, VerticalVelocity * DeltaTime), true);
 
    FRotator PawnRotation = FRotator(0.0f, LookInputVector.X, LookInputVector.Z);
    AddActorLocalRotation(PawnRotation);
 
    FRotator NewArmRotation = SpringArmComp->GetRelativeRotation();
    NewArmRotation.Pitch = FMath::Clamp(NewArmRotation.Pitch + LookInputVector.Y, -60.0f, 60.0f); 
    SpringArmComp->SetRelativeRotation(NewArmRotation);
 
    MoveInputVector = FVector::ZeroVector;
    LookInputVector = FVector::ZeroVector;
}
 
 
void AOkPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);
 
    if (UEnhancedInputComponent* EnhancedInput = Cast<UEnhancedInputComponent>(PlayerInputComponent))
    {
        if (AOkPlayerController* PC = Cast<AOkPlayerController>(GetController()))
        {
            if (PC->MoveAction)
                EnhancedInput->BindAction(PC->MoveAction, ETriggerEvent::Triggered, this, &AOkPawn::Move);
            
            if (PC->LookAction)
                EnhancedInput->BindAction(PC->LookAction, ETriggerEvent::Triggered, this, &AOkPawn::Look);
 
            if (PC->MoveUpAction)
                EnhancedInput->BindAction(PC->MoveUpAction, ETriggerEvent::Triggered, this, &AOkPawn::MoveUp);
 
            if (PC->MoveDownAction)
                EnhancedInput->BindAction(PC->MoveDownAction, ETriggerEvent::Triggered, this, &AOkPawn::MoveDown);
 
            if (PC->RollAction)
                EnhancedInput->BindAction(PC->RollAction, ETriggerEvent::Triggered, this, &AOkPawn::RollAxis);
        }
    }
}
 
void AOkPawn::Move(const FInputActionValue& Value)
{
    FVector2D Input = Value.Get<FVector2D>();
    MoveInputVector.X = Input.Y; 
    MoveInputVector.Y = Input.X;
}
 
void AOkPawn::MoveUp(const FInputActionValue& Value)
{
    MoveInputVector.Z = 1.0f;
}
 
void AOkPawn::MoveDown(const FInputActionValue& Value)
{
    MoveInputVector.Z = -1.0f;
}
 
void AOkPawn::Look(const FInputActionValue& Value)
{
    FVector2D Input = Value.Get<FVector2D>();
    LookInputVector.X = Input.X; 
    LookInputVector.Y = -Input.Y; 
}
 
void AOkPawn::RollAxis(const FInputActionValue& Value)
{
    LookInputVector.Z = Value.Get<float>() * 5.0f; 
}
 

OkGameMode.h

 
// OkGameMode.h
 
#pragma once
 
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "OkGameMode.generated.h"
 
 
UCLASS()
class P3P4_API AOkGameMode : public AGameModeBase
{
	GENERATED_BODY()
	
public:
	AOkGameMode();
};
 
 

OkGameMode.cpp

 
// OkGameMode.cpp
 
 
#include "GameMode/OkGameMode.h"
#include "Pawn/OkPawn.h"
#include "Controller/OkPlayerController.h"
 
AOkGameMode::AOkGameMode()
{
	DefaultPawnClass = AOkPawn::StaticClass();
    
	PlayerControllerClass = AOkPlayerController::StaticClass();
}
 

자세한 설명

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

작성 방법

1. 에디터에서 입력 에셋(IA, IMC) 생성하기

콘텐츠 브라우저의 원하는 폴더에서 우클릭한 뒤 Input(입력) 메뉴를 통해 생성합니다.

  1. Input Action(입력 액션) 생성 (총 5개)
    • IA_Move 생성 더블클릭 후 Value TypeAxis2D (Vector2D)로 변경
    • IA_Look 생성 더블클릭 후 Value TypeAxis2D (Vector2D)로 변경
    • IA_MoveUp 생성 Value Type은 기본값인 Digital (bool) 유지
    • IA_MoveDown 생성 Value Type은 기본값인 Digital (bool) 유지
    • IA_Roll 생성 더블클릭 후 Value TypeAxis1D (float)로 변경
  2. Input Mapping Context(입력 매핑 컨텍스트) 생성
    • 이름은 IMC_Ok 등으로 생성합니다.
2. IMC에 키 매핑하기 (중요)

IMC_Ok를 더블클릭해 열고, 위에서 만든 액션들을 등록한 뒤 키를 매핑합니다. 과제 조건(6축)을 만족하려면 아래 설정을 똑같이 맞춰야 합니다.

IA_Move (WASD 이동)

Mappings에 IA_Move를 추가하고 키를 4개 등록합니다.

  • W 키: Modifiers에 Swizzle Input Axis Values 추가 (Y축 전진으로 변경하기 위함)
  • S 키: Modifiers에 Swizzle Input Axis ValuesNegate 둘 다 추가 (Y축 후진)
  • D 키: 아무 Modifier도 넣지 않음 (기본 X축 우측 이동)
  • A 키: Modifiers에 Negate 추가 (X축 좌측 이동)
상하 이동 (IA_MoveUp / IA_MoveDown)
  • IA_MoveUp 추가 Space Bar 지정
  • IA_MoveDown 추가 Left Shift 지정
IA_Look (마우스 회전)

Mappings에 IA_Look을 추가합니다.

  • Mouse XY 2D-Axis 지정
  • Modifiers에 Negate를 추가하고, 내부 화살표를 열어 Y축만 체크합니다. (마우스를 올렸을 때 화면이 위를 보게 만드는 세팅입니다.)
IA_Roll (휠 회전)

Mappings에 IA_Roll을 추가합니다.

  • Mouse Wheel Axis 지정
3. 블루프린트 클래스 만들고 에셋 연결하기

C++ 코드는 뼈대일 뿐이므로, 에디터에서 블루프린트 클래스로 상속받아 에셋들을 변수에 심어주어야 합니다.

① OkPlayerController 블루프린트 만들기
  1. OkPlayerController C++ 클래스를 우클릭하고 [Create Blueprint class based on OkPlayerController]를 선택합니다.
  2. 이름은 BP_OkPlayerController로 만듭니다.
  3. 블루프린트를 열어 우측 Details(디테일) 패널의 Input 카테고리를 찾습니다.
  4. 우리가 만들었던 C++ 변수들(Input Mapping Context, Move Action, Look Action 등)이 보일 겁니다. 여기에 방금 에디터에서 만든 IMC_OkIA_... 에셋들을 전부 하나씩 매칭하여 넣어줍니다.
  5. 컴파일 후 저장합니다.
② OkPawn 블루프린트 만들기
  1. OkPawn C++ 클래스를 우클릭하고 [Create Blueprint class based on OkPawn]을 선택합니다.
  2. 이름은 BP_OkPawn으로 만듭니다.
  3. 뷰포트나 컴포넌트 창에서 MeshComp를 선택한 뒤, 디테일 창에서 원하는 Skeletal Mesh(캐릭터 외형)를 지정해 줍니다.
  4. 컴파일 후 저장합니다.
③ OkGameMode 블루프린트 만들기 (또는 월드 세팅 수정)
  1. OkGameMode C++ 클래스를 우클릭하고 BP_OkGameMode를 생성합니다.
  2. BP_OkGameMode를 열어 디테일 패널의 Classes 항목을 확인합니다.
  3. Default Pawn Class를 우리가 방금 만든 BP_OkPawn으로 변경합니다.
  4. Player Controller Class를 우리가 방금 만든 BP_OkPlayerController로 변경합니다.
4. 최종 게임에 적용하기

마지막으로 내가 만든 레벨(맵)이 이 게임모드를 사용하도록 지정해야 합니다.

  1. 에디터 상단 메뉴에서 Window(창) World Settings(월드 세팅)을 켭니다.
  2. 우측에 월드 세팅 창이 뜨면 GameMode Override 항목을 찾습니다.
  3. 여기에 방금 만든 BP_OkGameMode를 지정해 줍니다.