icon안동민 개발노트

간단한 폰(Pawn) 및 캐릭터(Character) 클래스 구현


 언리얼 엔진에서 폰(Pawn)과 캐릭터(Character)는 플레이어나 AI가 제어할 수 있는 게임 내 객체를 나타냅니다.

 이 절에서는 이들의 개념과 C++에서의 구현 방법을 살펴보겠습니다.

APawn과 ACharacter 클래스의 차이점

 1. APawn

  • 기본적인 이동 가능 객체
  • 물리 기반 이동이나 비표준 이동에 적합
  • 예 : 차량, 비행체, 로봇 등

 2. ACharacter

  • APawn을 상속받아 확장한 클래스
  • 기본적인 이족 보행 기능 포함
  • CharacterMovementComponent 내장
  • 예 : 플레이어 캐릭터, 인간형 NPC 등

기본 폰 클래스 구현

 폰 클래스 선언

// MyPawn.h
##pragma once
 
##include "CoreMinimal.h"
##include "GameFramework/Pawn.h"
##include "MyPawn.generated.h"
 
UCLASS()
class MYPROJECT_API AMyPawn : public APawn
{
    GENERATED_BODY()
 
public:
    AMyPawn();
 
    virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
 
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components")
    UStaticMeshComponent* MeshComponent;
 
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components")
    UFloatingPawnMovement* MovementComponent;
 
private:
    void MoveForward(float Value);
    void MoveRight(float Value);
};

폰 클래스 구현

// MyPawn.cpp
##include "MyPawn.h"
##include "Components/StaticMeshComponent.h"
##include "GameFramework/FloatingPawnMovement.h"
 
AMyPawn::AMyPawn()
{
    PrimaryActorTick.bCanEverTick = true;
 
    MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComponent"));
    SetRootComponent(MeshComponent);
 
    MovementComponent = CreateDefaultSubobject<UFloatingPawnMovement>(TEXT("MovementComponent"));
}
 
void AMyPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);
 
    PlayerInputComponent->BindAxis("MoveForward", this, &AMyPawn::MoveForward);
    PlayerInputComponent->BindAxis("MoveRight", this, &AMyPawn::MoveRight);
}
 
void AMyPawn::MoveForward(float Value)
{
    if (Value != 0.0f)
    {
        AddMovementInput(GetActorForwardVector(), Value);
    }
}
 
void AMyPawn::MoveRight(float Value)
{
    if (Value != 0.0f)
    {
        AddMovementInput(GetActorRightVector(), Value);
    }
}

캐릭터 클래스 구현

 캐릭터 클래스 선언

// MyCharacter.h
##pragma once
 
##include "CoreMinimal.h"
##include "GameFramework/Character.h"
##include "MyCharacter.generated.h"
 
UCLASS()
class MYPROJECT_API AMyCharacter : public ACharacter
{
    GENERATED_BODY()
 
public:
    AMyCharacter();
 
    virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
 
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Camera")
    class UCameraComponent* CameraComponent;
 
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Camera")
    class USpringArmComponent* SpringArmComponent;
 
private:
    void MoveForward(float Value);
    void MoveRight(float Value);
    void Turn(float Value);
    void LookUp(float Value);
    void StartJump();
    void StopJump();
};

 캐릭터 클래스 구현

// MyCharacter.cpp
##include "MyCharacter.h"
##include "Camera/CameraComponent.h"
##include "GameFramework/SpringArmComponent.h"
##include "GameFramework/CharacterMovementComponent.h"
 
AMyCharacter::AMyCharacter()
{
    PrimaryActorTick.bCanEverTick = true;
 
    SpringArmComponent = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComponent"));
    SpringArmComponent->SetupAttachment(RootComponent);
    SpringArmComponent->TargetArmLength = 300.0f;
    SpringArmComponent->bUsePawnControlRotation = true;
 
    CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
    CameraComponent->SetupAttachment(SpringArmComponent);
 
    GetCharacterMovement()->JumpZVelocity = 400.0f;
    GetCharacterMovement()->AirControl = 0.2f;
}
 
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);
 
    PlayerInputComponent->BindAxis("MoveForward", this, &AMyCharacter::MoveForward);
    PlayerInputComponent->BindAxis("MoveRight", this, &AMyCharacter::MoveRight);
    PlayerInputComponent->BindAxis("Turn", this, &AMyCharacter::Turn);
    PlayerInputComponent->BindAxis("LookUp", this, &AMyCharacter::LookUp);
 
    PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &AMyCharacter::StartJump);
    PlayerInputComponent->BindAction("Jump", IE_Released, this, &AMyCharacter::StopJump);
}
 
void AMyCharacter::MoveForward(float Value)
{
    if (Value != 0.0f)
    {
        AddMovementInput(GetActorForwardVector(), Value);
    }
}
 
void AMyCharacter::MoveRight(float Value)
{
    if (Value != 0.0f)
    {
        AddMovementInput(GetActorRightVector(), Value);
    }
}
 
void AMyCharacter::Turn(float Value)
{
    AddControllerYawInput(Value);
}
 
void AMyCharacter::LookUp(float Value)
{
    AddControllerPitchInput(Value);
}
 
void AMyCharacter::StartJump()
{
    Jump();
}
 
void AMyCharacter::StopJump()
{
    StopJumping();
}

AIController 연동

 AIController를 폰이나 캐릭터와 연동하려면

  1. AIController 클래스 생성
  2. 폰/캐릭터의 AIControllerClass 속성 설정
  3. Possess 함수에서 AI 로직 초기화

 예시

// MyAIController.h
UCLASS()
class MYPROJECT_API AMyAIController : public AAIController
{
    GENERATED_BODY()
 
public:
    virtual void OnPossess(APawn* InPawn) override;
};
 
// MyAIController.cpp
void AMyAIController::OnPossess(APawn* InPawn)
{
    Super::OnPossess(InPawn);
    // AI 로직 초기화
}
 
// 폰/캐릭터 클래스에서
AMyCharacter::AMyCharacter()
{
    // ...
    AIControllerClass = AMyAIController::StaticClass();
}

애니메이션 기초 설정

  1. AnimInstance 클래스 생성
  2. 캐릭터의 Mesh 컴포넌트에 AnimInstance 설정
// MyAnimInstance.h
UCLASS()
class MYPROJECT_API UMyAnimInstance : public UAnimInstance
{
    GENERATED_BODY()
 
public:
    virtual void NativeUpdateAnimation(float DeltaSeconds) override;
 
    UPROPERTY(BlueprintReadOnly, Category = "Movement")
    float Speed;
 
    UPROPERTY(BlueprintReadOnly, Category = "Movement")
    bool bIsInAir;
};
 
// MyAnimInstance.cpp
void UMyAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
    Super::NativeUpdateAnimation(DeltaSeconds);
 
    APawn* Pawn = TryGetPawnOwner();
    if (Pawn)
    {
        Speed = Pawn->GetVelocity().Size();
        bIsInAir = Pawn->GetMovementComponent()->IsFalling();
    }
}
 
// 캐릭터 클래스에서
AMyCharacter::AMyCharacter()
{
    // ...
    GetMesh()->SetAnimInstanceClass(UMyAnimInstance::StaticClass());
}

네트워크 멀티플레이어 준비

  1. 리플리케이션 설정
  2. RPC (Remote Procedure Calls) 구현
// MyCharacter.h
UCLASS()
class MYPROJECT_API AMyCharacter : public ACharacter
{
    // ...
 
    UFUNCTION(Server, Reliable, WithValidation)
    void ServerPerformAction();
};
 
// MyCharacter.cpp
void AMyCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);
 
    DOREPLIFETIME(AMyCharacter, SomeReplicatedVariable);
}
 
bool AMyCharacter::ServerPerformAction_Validate()
{
    return true;
}
 
void AMyCharacter::ServerPerformAction_Implementation()
{
    // 서버에서 실행될 로직
}

성능 고려사항 및 Best Practices

 1. Tick 함수 최적화

  • 불필요한 경우 비활성화
PrimaryActorTick.bCanEverTick = false;

 2. 네트워크 트래픽 최적화

  • 필요한 정보만 리플리케이션
  • 업데이트 빈도 조절

 3. LOD (Level of Detail) 시스템 활용

  • 거리에 따른 메시 및 애니메이션 복잡도 조절

 4. 물리 시뮬레이션 최적화

  • 필요한 경우에만 물리 시뮬레이션 활성화

 5. 메모리 관리

  • 동적 할당 최소화
  • 객체 풀링 기법 고려

 6. 코드 구조화

  • 기능별 컴포넌트 분리
  • 인터페이스 활용으로 확장성 확보

 7. 입력 처리 최적화

  • 불필요한 입력 체크 제거
  • 입력에 따른 상태 관리 효율화

 8. 애니메이션 시스템 효율적 사용

  • 블렌드 스페이스 및 애님 몽타주 활용
  • 애니메이션 블루프린트 최적화

 폰과 캐릭터 클래스는 게임플레이의 핵심 요소입니다. 적절한 설계와 최적화를 통해 반응성 좋고 효율적인 게임 캐릭터를 구현할 수 있습니다.

 기본적인 이동과 상호작용부터 시작하여 애니메이션, AI, 네트워크 기능을 점진적으로 추가해 나가는 것이 좋습니다.