* 언리얼 엔진은 플레이어의 정보를 관리하기 위한 용도로  PlayerState라는 클래스를 제동한다. PlayerState를 상속받은 ABPlayerState라는 클래스를 생성한다.

* PlayerState 클래스에는 FString 형의 PlayerName 속성과 float 형의 Score 속성이 이미 설계되 있다.
* 이 속성은 보여질 플레이어의 닉네임과 점수를 관리하기 위한 용도로 사용할 수 있다. 게임의 진행상황을 기록하도록 int32 GameScore라는 속성을 추가하고 , 플레이어의 레벨정보를 표시하기 위한 int32형의 CharaceterLevel이라는 속성을 추가해본다

* 새롭게 생성한 ABPlayerState 클래스를 게임 모드의 PlayerStateClass 속성에 지정하면 엔진에서는 플레이어 컨트롤러가 초기화될 떄 함께 해당 클래스의 인스턴스를 생성하고 그포인터 값을 플레이어 컨트롤러의 PlayerState 속성에 저장한다.
*플레이어 컨트롤러의 구성을 완료하는 시점은 게임 모드의 PostLogin 함수이므로 이떄 함께 ABPlayerState 초기화도 완료해주는 것이 좋다.

#include "ArenaBattle.h"
#include "GameFramework/PlayerState.h"
#include "ABPlayerState.generated.h"

UCLASS()
class ARENABATTLE_API AABPlayerState : public APlayerState
{
	GENERATED_BODY()
	

public:
	AABPlayerState();

	int32 GetGameScore() const;
	int32 GetCharacterLevel() const;

	void InitPlayerData();

protected:
	UPROPERTY(Transient)
	int32 GameScore;

	UPROPERTY(Transient)
	int32 CharacterLevel;
};
#include "ABPlayerState.h"

AABPlayerState::AABPlayerState()
{
	CharacterLevel = 1;
	GameScore = 0;
}


int32 AABPlayerState::GetGameScore() const
{
	return GameScore;
}
int32 AABPlayerState::GetCharacterLevel() const
{
	return CharacterLevel;
}

void AABPlayerState::InitPlayerData()
{
	SetPlayerName(TEXT("Destiny"));
	CharacterLevel = 5;
	GameScore = 0;
}
//AABGameMode.cpp
#include "ABPlayerState.h"

AABGameMode::AABGameMode()
{
	DefaultPawnClass = AABCharacter::StaticClass();
	PlayerControllerClass = AABPlayerController::StaticClass();
	PlayerStateClass = AABPlayerState::StaticClass();
}

void AABGameMode::PostLogin(APlayerController* NewPlayer)
{
	Super::PostLogin(NewPlayer);

	auto ABPlayerState = Cast<AABPlayerState>(NewPlayer->PlayerState);
	ABCHECK(nullptr != ABPlayerState);
	ABPlayerState->InitPlayerData();
}

* 플레이어의 레벨 정보는 실제로 캐릭터에 반영해야 한다. 플레이어 컨트롤러가 캐릭터에 빙의할 때 캐릭터의 PlayerState 속성에 플레이어 스테이트의 포인터를 저장하므로 캐릭터에서도 해당 플레이어 스테이트 정보를 바로 가져올 수 있다.

//ABCharacter.cpp

#include "ABPlayerState.h"

void AABCharacter::SetCharacterState(ECharacterState NewState)
{
	ABCHECK(CurrentState != NewState);
	CurrentState = NewState;

	switch (NewState)
	{
	case ECharacterState::LOADING :
	{
		if (bIsPlayer)
		{
			DisableInput(ABPlayerController);

			auto ABPlayerState = Cast<AABPlayerState>(GetPlayerState());
			ABCHECK(nullptr != ABPlayerState);
			CharacterStat->SetNewLevel(ABPlayerState->GetCharacterLevel());
		}

	}
    ....
 }

 

* 이제 플레이를 누르면 우리가 조종하는 캐릭터는 5레벨로 설정된다.
* 이어서 샘플의 UI 애셋을 이용하여 UserWidger 기본 클래스로 하는  ABHUDWidget 클래스를 새로 생성한다
* 그래프 탭에서 ABHUDWidget 클래스를 부모로 설정한다

반드시 설정해야함

* 플레이어 컨트롤러에서 해당 위젯을 생성한 후 이를 화면에 띄우는 기능을 추가한다. UI 애셋의 레퍼런스를 복사해 클래스 정보를 불러들이고, CreateWidget 함수로 위젯인스턴스를 생성한 후 이를 플레이어의 화면에 씌워준다.

//AABPlayerController.h
#include "ArenaBattle.h"
#include "GameFramework/PlayerController.h"
#include "ABPlayerController.generated.h"

UCLASS()
class ARENABATTLE_API AABPlayerController : public APlayerController
{
	GENERATED_BODY()
	
public:
	AABPlayerController();
	
	virtual void PostInitializeComponents() override;
	virtual void OnPossess(APawn* aPawn) override;
	
	class UABHUDWidget* GetHUDWidget() const;

protected:
	virtual void BeginPlay() override;

	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = UI)
		TSubclassOf<class UABHUDWidget> HUDWidgetClass;

private:
	UPROPERTY()
		class UABHUDWidget* HUDWidget;
};
////AABPlayerController.cpp
#include "ABPlayerController.h"
#include "ABHUDWidget.h"


AABPlayerController::AABPlayerController()
{
	//WidgetBlueprint'/Game/Book/UI/UI_HUD.UI_HUD'
	static ConstructorHelpers::FClassFinder<UABHUDWidget> UI_HUD_C(TEXT("/Game/Book/UI/UI_HUD.UI_HUD_C"));
	if (UI_HUD_C.Succeeded())
	{
		HUDWidgetClass = UI_HUD_C.Class;
	}

}

void AABPlayerController::PostInitializeComponents()
{
	Super::PostInitializeComponents();
	ABLOG_S(Warning);
}

void AABPlayerController::OnPossess(APawn *aPawn)
{
	ABLOG_S(Warning);
	Super::OnPossess(aPawn);
}

void AABPlayerController::BeginPlay()
{
	Super::BeginPlay();

	FInputModeGameOnly InputMode;
	SetInputMode(InputMode);


	HUDWidget = CreateWidget<UABHUDWidget>(this, HUDWidgetClass);
	HUDWidget->AddToViewport();
}

UABHUDWidget * AABPlayerController::GetHUDWidget() const
{
	return HUDWidget;
}

 

 


실행화면

 

* 해당  UI에는 플레이어의 데이터와 캐릭터의 HP 데이터 정보가 함께 표시된다. 따라서 플레이어 스테이트와 캐릭터ㅡ 스탯 컴포넌트 정보를 모두 해당 HUD에 연동해야 한다. 먼저 플레이어 스테이트에 새로운 델리게이트를 정의하고 플레이어 데이터가 변경될 떄 HUD 신호를 보내 HUD가 관련 UI 윗젯을 업데이트 하도록 구현한다.

 

//ABPlayerState.h
DECLARE_MULTICAST_DELEGATE(FOnPlayerStateChangeDelegate);

UCLASS()
class ARENABATTLE_API AABPlayerState : public APlayerState
{
public:
....

	FOnPlayerStateChangeDelegate OnPlayerStateChanged;
};

 

//ABHUDWidget.h
#include "ArenaBattle.h"
#include "Blueprint/UserWidget.h"
#include "ABHUDWidget.generated.h"


UCLASS()
class ARENABATTLE_API UABHUDWidget : public UUserWidget
{
	GENERATED_BODY()
	

public:
	void BindCharacterStat(class UABCharacterStatComponent* CharacterStat);
	void BindPlayerState(class AABPlayerState* PlayerState);
	
	
	//void BindTest(AABPlayerState* point);
protected:
	virtual void NativeConstruct() override;
	void UpdateCharacterStat();
	void UpdatePlayerState();

private:
	TWeakObjectPtr<class UABCharacterStatComponent> CurrentCharacterStat;
	TWeakObjectPtr<class AABPlayerState> CurrentPlayerState;

	UPROPERTY()
		class UProgressBar* HPBar;

	UPROPERTY()
		class UProgressBar* ExpBar;

	UPROPERTY()
		class UTextBlock* PlayerName;

	UPROPERTY()
		class UTextBlock* PlayerLevel;

	UPROPERTY()
		class UTextBlock* CurrentScore;

	UPROPERTY()
		class UTextBlock* HighScore;


};
//ABHUDWidget.cpp
#include "ABHUDWidget.h"
#include "Components/ProgressBar.h"
#include "Components/TextBlock.h"
#include "ABCharacterStatComponent.h"
#include "ABPlayerState.h"


void UABHUDWidget::BindCharacterStat(class UABCharacterStatComponent* CharacterStat)
{
	ABCHECK(nullptr != CharacterStat);
	CurrentCharacterStat = CharacterStat;
	CharacterStat->OnHPChanged.AddUObject(this, &UABHUDWidget::UpdateCharacterStat);
}

void UABHUDWidget::BindPlayerState(class AABPlayerState* PlayerState)
{
	ABCHECK(nullptr != PlayerState);
	CurrentPlayerState = PlayerState;
	PlayerState->OnPlayerStateChanged.AddUObject(this, &UABHUDWidget::UpdatePlayerState);

}



void UABHUDWidget::NativeConstruct()
{
	Super::NativeConstruct();
	HPBar = Cast<UProgressBar>(GetWidgetFromName(TEXT("pbHP")));
	ABCHECK(nullptr != HPBar);

	ExpBar = Cast<UProgressBar>(GetWidgetFromName(TEXT("pbExp")));
	ABCHECK(nullptr != ExpBar);

	PlayerName = Cast<UTextBlock>(GetWidgetFromName(TEXT("txtPlayerName")));
	ABCHECK(nullptr != PlayerName);

	PlayerLevel = Cast<UTextBlock>(GetWidgetFromName(TEXT("txtLevel")));
	ABCHECK(nullptr != PlayerLevel);

	CurrentScore = Cast<UTextBlock>(GetWidgetFromName(TEXT("txtCurrentScore")));
	ABCHECK(nullptr != CurrentScore);

	HighScore = Cast<UTextBlock>(GetWidgetFromName(TEXT("txtHighScore")));
	ABCHECK(nullptr != HighScore);



}

void UABHUDWidget::UpdateCharacterStat()
{
	ABCHECK(CurrentCharacterStat.IsValid());

	HPBar->SetPercent(CurrentCharacterStat->GetHPRatio());

}

void UABHUDWidget::UpdatePlayerState()
{
	ABCHECK(CurrentPlayerState.IsValid());

	PlayerName->SetText(FText::FromString(CurrentPlayerState->GetPlayerName()));

	PlayerLevel->SetText(FText::FromString(FString::FromInt(CurrentPlayerState->GetCharacterLevel())));

	CurrentScore->SetText(FText::FromString(FString::FromInt(CurrentPlayerState->GetGameScore())));

};

* 코드를 완성하면 플레이어 컨트롤러에서 HUD 위젯과 플레이어 스테이트를 연결하고, 캐릭터에서는 HUD 위젯과 캐릭터 스탯 컴포넌트를 연결한다. 

//AABPlayerController.cpp

#include "ABPlayerState.h"



void AABPlayerController::BeginPlay()
{
....

	auto ABPlayerState = Cast<AABPlayerState>(PlayerState);
	ABCHECK(nullptr != ABPlayerState);
	HUDWidget->BindPlayerState(ABPlayerState);
	ABPlayerState->OnPlayerStateChanged.Broadcast();

}
//AABCharacter.cpp

#include "ABHUDWidget.h"

void AABCharacter::SetCharacterState(ECharacterState NewState)
{
.....
	switch (NewState)
	{
	case ECharacterState::LOADING :
	{
		if (bIsPlayer)
		{
			DisableInput(ABPlayerController);

			ABPlayerController->GetHUDWidget()->BindCharacterStat(CharacterStat);

			auto ABPlayerState = Cast<AABPlayerState>(GetPlayerState());
			ABCHECK(nullptr != ABPlayerState);
			CharacterStat->SetNewLevel(ABPlayerState->GetCharacterLevel());
		}
        ....
        }
        

*  이제 플레이어 정보와 캐릭터 스탯 정보가 HUD 위젯에 연동이 되는 것을 확인 할 수 있따.

 


연동된 화면

 

* 이어서 플레이어 데이터에 경험치 정보를 표시하도록 기능을 추가해본다. 플레이어와의 전투에서 NPC가 사망하면 NPC의 레벨이 지정된 경험치를 플레이어에게 전달하고 플레이어가 이를 축적해 레벨업 하는 기능을 구현한다

* UI 에서는 해당 정보를 프로그래스바를 표시하기 떄문에 현재 경험치와 해당 레벨에서의 최대 경험치의 비율 정보를 구하는 작업이 필요하다

* 캐릭터 스탯에 NPC를 위한 경험치 값을 설정하고, 플레이어 스테이트에는 플레이어의 경험치 데이터를 보관하도록 설계를 확장해야 한다. 그리고 캐릭터가 사망할 떄 NPC가 플레이어에게 죽는 지 검사하고, 해당 플레이어 컨트롤러를 통해 플레이어 스테잍르르 업데이트 시키는 로직을 추가해야 한다

* 대미지 프레임워크에서 플레이어 컨트롤러의 정보는 가해자 인자로 전달되므로 이를 사용하면 조금 수월해진다.

//ABPlayerState.h
class ARENABATTLE_API AABPlayerState : public APlayerState
{

public:

	float GetExpRatio() const;
	bool AddExp(int32 IncomeExp);


protected:
	UPROPERTY(Transient)
	int32 Exp;

private:
	void SetCharacterLevel(int32 NewCharacterLevel);
	struct FABCharacterData* CurrentStatData;
};
//AABPlayerState.cpp
#include "ABPlayerState.h"
#include "ABGameInstance.h"

AABPlayerState::AABPlayerState()
{
	CharacterLevel = 1;
	GameScore = 0;
	Exp	= 0;
}


void AABPlayerState::InitPlayerData()
{
	SetPlayerName(TEXT("Destiny"));
	SetCharacterLevel(5);
	GameScore = 0;
	Exp = 0;
}

float AABPlayerState::GetExpRatio() const
{
	if (CurrentStatData->NextExp <= KINDA_SMALL_NUMBER)
		return 0.0f;

	float Result = (float)Exp / (float)CurrentStatData->NextExp;
	return Result;

}

bool AABPlayerState::AddExp(int32 IncomeExp)
{
	if (CurrentStatData->NextExp == -1)
	{
		return false;
	}

	bool DidLevelUp = false;
	Exp = Exp + IncomeExp;
	if (Exp >= CurrentStatData->NextExp)
	{
		Exp -= CurrentStatData->NextExp;
		SetCharacterLevel(CharacterLevel + 1);
		DidLevelUp = true;
	}

	OnPlayerStateChanged.Broadcast();
	return DidLevelUp;
}

void AABPlayerState::SetCharacterLevel(int32 NewCharacterLevel)
{
	auto ABGameInstance = Cast<UABGameInstance>(GetGameInstance());
	ABCHECK(nullptr != ABGameInstance);
	
	CurrentStatData = ABGameInstance->GetABCharacterData(NewCharacterLevel);
	ABCHECK(nullptr != CurrentStatData);

	CharacterLevel = NewCharacterLevel;
}
//UABHUDWidget.cpp
void UABHUDWidget::UpdatePlayerState()
{
	ExpBar->SetPercent(CurrentPlayerState->GetExpRatio());
};
//UABCharacterStatComponent.h
{
public:
.....
	int32 GetDropExp() const;
}

//UABCharacterStatComponent.cpp
int32 UABCharacterStatComponent::GetDropExp() const
{
	return CurrentStatData->DropExp;
}
//AABCharacter.h
{
 public:
    	int32 GetExp() const;
}

//AABCharacter.cpp  
int32 AABCharacter::GetExp() const
{
	return CharacterStat->GetDropExp();
}
//ABPlayerController.h
class ARENABATTLE_API AABPlayerController : public APlayerController
{

public:
......
	void NPCKill(class AABCharacter* KilledNPC) const;

private:
......
	UPROPERTY()
		class AABPlayerState* ABPlayerState;
}


//ABPlayerController.cpp
#include "ABCharacter.h"

void AABPlayerController::BeginPlay()
{
.....
	ABPlayerState = Cast<AABPlayerState>(PlayerState);
	auto ABPlayerState = Cast<AABPlayerState>(PlayerState);
	ABCHECK(nullptr != ABPlayerState);
	HUDWidget->BindPlayerState(ABPlayerState);
	ABPlayerState->OnPlayerStateChanged.Broadcast();

}

void AABPlayerController::NPCKill(class AABCharacter* KilledNPC) const
{
	ABPlayerState->AddExp(KilledNPC->GetExp());
}

//AABCharacter.cpp
float AABCharacter::TakeDamage(float DamageAmount, FDamageEvent const & DamageEvent, AController * EventInstigator, AActor * DamageCauser)
{
	float FinalDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
	ABLOG(Warning, TEXT("Actor : %s took Damage : %f"), *GetName(), FinalDamage);

	CharacterStat->SetDamage(FinalDamage);
	if (CurrentState == ECharacterState::DEAD)
	{
		if (EventInstigator->IsPlayerController())
		{
			auto ABPlayerController = Cast<AABPlayerController>(EventInstigator);
			ABCHECK(nullptr != ABPlayerController);
			ABPlayerController->NPCKill(this);
		}
	}

	return FinalDamage;
}

 

* 이제 NPC를 처치 하면 경험치 관련된 항목이 업데이트 된다.

 


+ Recent posts