TIL_055: PlayerState 구현, 성능적 사고

김펭귄·2025년 10월 29일

Today What I Learned (TIL)

목록 보기
55/111

오늘 학습 키워드

  • PlayerState

1. PlayerState

  • 플레이어의 주요 정보(체력, 경험치, 점수 등)를 모아 관리하는 데이터 구조

  • 플레이어 상태를 한 곳에 모으고, 상태를 변경하는 함수를 포함

  • 멀티플레이의 경우, 서버가 각 PlayerState를 관리하고 각 클라이언트는 복제하여 읽음

  • 레벨이 넘어가도 보통 사라지지 않고 유지됨

EXP 컴포넌트

  • 이전엔 체력과 EXP를 컴포넌트로 구현하여 관리하려 하였음

  • 체력 컴포넌트는 NPC도 재사용하니 괜찮음

  • 그러나 점수, 경험치, 레벨 등은 플레이어에게만 필요한 것이므로 재사용되지 않아 컴포넌트로 따로 만드는 것은 좋지 않다고 생각하였음

  • 따라서 PlayerState에 저장 및 관리

헤더

// ITPPlayerState.h

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnLevelUp);

UCLASS()
class INTOTHEPARADOX_API AITPPlayerState : public APlayerState
{
	GENERATED_BODY()

public:
	AITPPlayerState();
	FOnLevelUp OnLevelUp;

	UFUNCTION(BlueprintPure, Category = "PlayerState|Score")
	int32 GetPlayerScore() const { return PlayerScore; }
	UFUNCTION(BlueprintCallable, Category = "PlayerState|Score")
	void AddPlayerScore(int32 Amount);

	UFUNCTION(BlueprintPure, Category = "PlayerState|EXP")
	float GetCurrentEXP() const { return CurrentEXP; }
	UFUNCTION(BlueprintPure, Category = "PlayerState|EXP")
	float GetMaxEXP() const { return MaxEXP; }
	UFUNCTION(BlueprintCallable, Category = "PlayerState|EXP")
	void AddEXP(float Amount);
	UFUNCTION(BlueprintCallable, Category = "PlayerState|Level")
	int32 GetPlayerLevel() const { return PlayerLevel; }

protected:
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "PlayerState|Score")
	int32 PlayerScore;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "PlayerState|EXP")
	float CurrentEXP;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "PlayerState|EXP")
	float MaxEXP;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "PlayerState|Level")
	int32 PlayerLevel;

	UFUNCTION(BlueprintCallable, Category = "PlayerState|Level")
	void LevelUp();
};
  • Delegate는 전역변수같이 사용되므로 중복 선언이 불가능함
    • 이전 EXPComponent를 지우지 않고 구현하다보니, FOnLevelUp키워드가 겹쳐서 에러가 발생하였음.
    • 하나만 있어야 하므로, EXP Component파일 제거하여 해결

cpp

// ITPPlayerState.cpp
#include "ITPPlayerState.h"

AITPPlayerState::AITPPlayerState() : 
	PlayerScore(0),
	CurrentEXP(0.f),
	MaxEXP(100.f),
	PlayerLevel(1)
{}

void AITPPlayerState::AddPlayerScore(int32 Amount)
{
	if (Amount > 0)
		PlayerScore += Amount;
}

void AITPPlayerState::AddEXP(float Amount)
{
	if (Amount <= 0)
		return;
	CurrentEXP += Amount;
	
	while (CurrentEXP >= MaxEXP)
	{
		CurrentEXP -= MaxEXP;
		LevelUp();
	}
}

void AITPPlayerState::LevelUp()
{
	PlayerLevel++;
	MaxEXP += 50;
	OnLevelUp.Broadcast();
}
  • 로직은 이전 EXP Component와 비슷

  • 마찬가지로 Broadcast를 이용하여 delegate 사용

2. 캐릭터에 PlayerState 적용

// MyCharacter.h

class AITPPlayerState;

UCLASS()
class INTOTHEPARADOX_API AMyCharacter : public ACharacter
{
	// ... //
private:
	AITPPlayerState* ITPPlayerState;
};
  • PlayerState관련 함수를 호출할 때마다 GetPlayerState()로 가져오는 것은 overhead라 판단하였음

  • 따라서 그냥 캐릭터 객체의 멤버변수로 PlayerState를 참조하기로 하였음

// MyCharacter.cpp
void AMyCharacter::BeginPlay()
{
	Super::BeginPlay();
	
	ITPPlayerState = GetPlayerState<AITPPlayerState>();
	if (ITPPlayerState)
	{
		ITPPlayerState->OnLevelUp.AddDynamic(this, &AMyCharacter::HandleLevelUp);
	}
}

void AMyCharacter::AddEXP(float Amount)
{
	if (ITPPlayerState)
	{
		ITPPlayerState->AddEXP(Amount);
	}
}
  • 게임 시작시, PlayerState 가져와 설정해주고, Delegate도 이용

  • 이후 플레이어 스텟 관련 함수는 PlayerState 이용해서 처리

경고 발생

  • 지금까지의 과정에서 2번의 C4458 클래스 멤버를 숨깁니다.라는 경고가 발생
  1. ITPPlayerState에서 Score 멤버변수를 사용하였을 때
  2. MyCharacter에서 PlayerState 변수를 사용하였을 때
  • 원인으로는 둘 다 해당 변수가 부모 클래스에도 동일한 이름으로 변수가 있어 발생.
    1. ITPPlayerState의 부모인 PlayerStateScore 변수가 있었음
    2. Pawn객체에는 PlayerState가 존재
  • 따라서 자식 클래스의 변수가 부모 클래스의 변수를 shading하는 현상이 발생하여 이름을 바꾸어 해결하였음

이름 한 번에 바꾸기

  • ctrl + R + R : 해당 단어를 원하는 범위내에서 원하는 단어로 싹 바꿔줌

3. FClassFinder vs FObjectFinder

ConstructorHelpers::FObjectFinder

  • FObjectFinder는 해당 경로의 에셋 객체를 가져옴 (mesh, material..)

ConstructorHelpers::FClassFinder

  • FClassFinder는 해당 경로의 블루프린트 에셋의 클래스를 가져옴 (MyCharacter, PlayerState..)

4. 성능적 사고

  • 게임의 성능을 더 끌어올리는 방법(최적화)
  1. 측정 먼저: 추측하지 말고 실제로 측정하라
    성능이 안 좋을 것 같은 코드를 추측하기도 어려울 뿐더러, 추측해서 고쳤다한들 아니었다면 시간손해도 아주 큼. 직접 측정해서 나쁜 성능인 코드를 찾을 것

  2. 핫스팟 집중: 전체 시간의 80%를 차지하는 20% 코드를 찾아라
    성능 좋지 않은 코드는 전체 코드에서 작은 부분을 차지함.
    그리고 이 부분을 고쳤을 때 성능면에서도 효과가 아주 뛰어남

  3. 개선 후 재측정: 최적화가 실제로 효과가 있는지 확인하라

profile
반갑습니다

0개의 댓글