icon안동민 개발노트

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


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

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

APawn과 ACharacter 클래스의 차이점

  1. APawn
  • 기본적인 이동 가능 객체
  • 물리 기반 이동이나 비표준 이동에 적합
  • 예 : 차량, 비행체, 로봇 등
  1. 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;
  1. 네트워크 트래픽 최적화
  • 필요한 정보만 리플리케이션
  • 업데이트 빈도 조절
  1. LOD (Level of Detail) 시스템 활용:
  • 거리에 따른 메시 및 애니메이션 복잡도 조절
  1. 물리 시뮬레이션 최적화
  • 필요한 경우에만 물리 시뮬레이션 활성화
  1. 메모리 관리
  • 동적 할당 최소화
  • 객체 풀링 기법 고려
  1. 코드 구조화
  • 기능별 컴포넌트 분리
  • 인터페이스 활용으로 확장성 확보
  1. 입력 처리 최적화
  • 불필요한 입력 체크 제거
  • 입력에 따른 상태 관리 효율화
  1. 애니메이션 시스템 효율적 사용
  • 블렌드 스페이스 및 애님 몽타주 활용
  • 애니메이션 블루프린트 최적화

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

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

 또한, 프로파일링 도구를 활용하여 지속적으로 성능을 모니터링하고 최적화하는 것이 중요합니다. 폰과 캐릭터 클래스의 효과적인 구현은 플레이어 경험의 질을 크게 향상시키며, 게임의 전반적인 품질에 직접적인 영향을 미칩니다.