플레이어의 주요 정보(체력, 경험치, 점수 등)를 모아 관리하는 데이터 구조
플레이어 상태를 한 곳에 모으고, 상태를 변경하는 함수를 포함
멀티플레이의 경우, 서버가 각 PlayerState를 관리하고 각 클라이언트는 복제하여 읽음
레벨이 넘어가도 보통 사라지지 않고 유지됨
이전엔 체력과 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();
};
FOnLevelUp키워드가 겹쳐서 에러가 발생하였음.// 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 사용
// 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 이용해서 처리
C4458 클래스 멤버를 숨깁니다.라는 경고가 발생ITPPlayerState에서 Score 멤버변수를 사용하였을 때MyCharacter에서 PlayerState 변수를 사용하였을 때ITPPlayerState의 부모인 PlayerState에 Score 변수가 있었음Pawn객체에는 PlayerState가 존재ctrl + R + R : 해당 단어를 원하는 범위내에서 원하는 단어로 싹 바꿔줌측정 먼저: 추측하지 말고 실제로 측정하라
성능이 안 좋을 것 같은 코드를 추측하기도 어려울 뿐더러, 추측해서 고쳤다한들 아니었다면 시간손해도 아주 큼. 직접 측정해서 나쁜 성능인 코드를 찾을 것
핫스팟 집중: 전체 시간의 80%를 차지하는 20% 코드를 찾아라
성능 좋지 않은 코드는 전체 코드에서 작은 부분을 차지함.
그리고 이 부분을 고쳤을 때 성능면에서도 효과가 아주 뛰어남
개선 후 재측정: 최적화가 실제로 효과가 있는지 확인하라