어제에 이어 오늘은 플레이어 데이터를 UI와 연동해, 체력바 뿐 아니라 이름, 레벨, 경험치 등의 정보를 화면에 나타내도록 하겠다
사용한 에셋은 링크를 통해 다운 받을 수 있다 오늘은 14장의 Resource를 사용한다
언리얼 엔진은 플레이어의 데이터를 보관하는 용도로 PlayerState 라는 클래스가 존재한다
PlayerState 를 상속하는 ABPlayerState 클래스를 생성해준다
ABPlayerState.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "ArenaBattle.h"
#include "GameFramework/PlayerState.h"
#include "ABPlayerState.generated.h"
DECLARE_MULTICAST_DELEGATE(FOnPlayerStateChangedDelegate);
/**
*
*/
UCLASS()
class ARENABATTLE_API AABPlayerState : public APlayerState
{
GENERATED_BODY()
public:
AABPlayerState();
int32 GetGameScore() const;
int32 GetCharacterLevel() const;
float GetExpRatio() const;
bool AddExp(int32 IncomeExp);
void InitPlayerData();
FOnPlayerStateChangedDelegate OnPlayerStateChanged;
protected:
UPROPERTY(Transient)
int32 GameScore;
UPROPERTY(Transient)
int32 CharacterLevel;
UPROPERTY(Transient)
int32 Exp;
private:
void SetCharacterLevel(int32 NewCharacterLevel);
struct FABCharacterData* CurrentStatData;
};
PlayerState 에는
CharacterName
과Score
변수가 기본적으로 내장되어 있기 때문에 선언할 필요가 없다
ABPlayerState.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "ABPlayerState.h"
#include "ABGameInstance.h"
AABPlayerState::AABPlayerState()
{
CharacterLevel = 1;
GameScore = 0;
Exp = 0;
}
int32 AABPlayerState::GetGameScore() const
{
return GameScore;
}
int32 AABPlayerState::GetCharacterLevel() const
{
return CharacterLevel;
}
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::InitPlayerData()
{
SetPlayerName(TEXT("yoo06"));
SetCharacterLevel(5);
GameScore = 0;
Exp = 0;
}
void AABPlayerState::SetCharacterLevel(int32 NewCharacterLevel)
{
auto ABGameInstance = Cast<UABGameInstance>(GetGameInstance());
CurrentStatData = ABGameInstance->GetABCharacterData(NewCharacterLevel);
CharacterLevel = NewCharacterLevel;
}
설계를 완료했다면, 게임모드의 PostLogin
함수를 통해 초기화를 진행해준다
ABGameMode.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "ABGameMode.h"
#include "ABCharacter.h"
#include "ABPlayerController.h"
#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);
ABPlayerState->InitPlayerData();
}
마지막으로 이 정보를 캐릭터 클래스에서 불러와준다
ABCharacter.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "ABCharacter.h"
#include "ABAnimInstance.h"
#include "DrawDebugHelpers.h"
#include "ABWeapon.h"
#include "ABCharacterStatComponent.h"
#include "Components/WidgetComponent.h"
#include "ABCharacterWidget.h"
#include "ABAIController.h"
#include "ABGameInstance.h"
#include "ABCharacterSetting.h"
#include "Engine/AssetManager.h"
#include "ABPlayerController.h"
#include "ABSection.h"
#include "ABPlayerState.h"
...
void AABCharacter::SetCharacterState(ECharacterState NewState)
{
CurrentState = NewState;
switch (CurrentState)
{
case ECharacterState::LOADING:
{
if (bIsPlayer)
{
DisableInput(ABPlayerController);
auto ABPlayerState = Cast<AABPlayerState>(GetPlayerState());
CharacterStat->SetNewLevel(ABPlayerState->GetCharacterLevel());
}
SetActorHiddenInGame(true);
HPBarWidget->SetHiddenInGame(true);
SetCanBeDamaged(false);
break;
}
...
}
이제 조종하는 캐릭터에게 데이트를 설정하였으니, 이를 확인할 수 있는 HUD UI 를 부착하고 캐릭터의 정보를 표시하도록 구현한다
먼저 다운받은 에셋을 UI 폴더에 넣어준다
다음 UserWidget 을 상속하는 ABHUDWidget 클래스를 생성해준다
ABHUDWidget 을 구현하기 전, 먼저 UIHUD의 부모를 _ABHUDWidget 로 설정해준다
이제 ABHUDWidget 를 구현하도록 하자
ABHUDWidget.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#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);
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
// Fill out your copyright notice in the Description page of Project Settings.
#include "ABHUDWidget.h"
#include "Components/ProgressBar.h"
#include "Components/TextBlock.h"
#include "ABCharacterStatComponent.h"
#include "ABPlayerState.h"
void UABHUDWidget::BindCharacterStat(UABCharacterStatComponent* CharacterStat)
{
CurrentCharacterStat = CharacterStat;
CharacterStat->OnHPChanged.AddUObject(this, &UABHUDWidget::UpdateCharacterStat);
}
void UABHUDWidget::BindPlayerState(AABPlayerState* PlayerState)
{
CurrentPlayerState = PlayerState;
PlayerState->OnPlayerStateChanged.AddUObject(this, &UABHUDWidget::UpdatePlayerState);
}
void UABHUDWidget::NativeConstruct()
{
Super::NativeConstruct();
HPBar = Cast<UProgressBar>(GetWidgetFromName(TEXT("pbHP")));
ExpBar = Cast<UProgressBar>(GetWidgetFromName(TEXT("pbExp")));
PlayerName = Cast<UTextBlock> (GetWidgetFromName(TEXT("txtPlayerName")));
PlayerLevel = Cast<UTextBlock> (GetWidgetFromName(TEXT("txtLevel")));
CurrentScore = Cast<UTextBlock> (GetWidgetFromName(TEXT("txtCurrentScore")));
HighScore = Cast<UTextBlock> (GetWidgetFromName(TEXT("txtHighScore")));
}
void UABHUDWidget::UpdateCharacterStat()
{
HPBar->SetPercent(CurrentCharacterStat->GetHPRatio());
}
void UABHUDWidget::UpdatePlayerState()
{
ExpBar->SetPercent(CurrentPlayerState->GetExpRatio());
PlayerName ->SetText(FText::FromString(CurrentPlayerState->GetPlayerName()));
PlayerLevel ->SetText(FText::FromString(FString::FromInt(CurrentPlayerState->GetCharacterLevel())));
CurrentScore->SetText(FText::FromString(FString::FromInt(CurrentPlayerState->GetGameScore())));
}
코드를 완성하면, 플레이어 컨트롤러에서 HUD 위젯과 플레이어 스테이트를 연결하고,
캐릭터에서 HUD 위젯과 캐릭터 스탯 컴포넌트를 연결해준다
ABPlayerController.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#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;
void NPCKill(class AABCharacter* KilledNPC) const;
protected:
virtual void BeginPlay() override;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = UI)
TSubclassOf<class UABHUDWidget> HUDWidgetClass;
private:
UPROPERTY()
class UABHUDWidget* HUDWidget;
UPROPERTY()
class AABPlayerState* ABPlayerState;
};
ABPlayerController.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "ABPlayerController.h"
#include "ABHUDWidget.h"
#include "ABPlayerState.h"
#include "ABCharacter.h"
AABPlayerController::AABPlayerController()
{
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);
}
UABHUDWidget* AABPlayerController::GetHUDWidget() const
{
return HUDWidget;
}
void AABPlayerController::NPCKill(AABCharacter* KilledNPC) const
{
ABPlayerState->AddExp(KilledNPC->GetExp());
}
void AABPlayerController::BeginPlay()
{
Super::BeginPlay();
FInputModeGameOnly InputMode;
SetInputMode(InputMode);
HUDWidget = CreateWidget<UABHUDWidget>(this, HUDWidgetClass);
HUDWidget->AddToViewport();
ABPlayerState = Cast<AABPlayerState>(PlayerState);
HUDWidget->BindPlayerState(ABPlayerState);
ABPlayerState->OnPlayerStateChanged.Broadcast();
}
ABCharacter.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "ABCharacter.h"
#include "ABAnimInstance.h"
#include "DrawDebugHelpers.h"
#include "ABWeapon.h"
#include "ABCharacterStatComponent.h"
#include "Components/WidgetComponent.h"
#include "ABCharacterWidget.h"
#include "ABAIController.h"
#include "ABGameInstance.h"
#include "ABCharacterSetting.h"
#include "Engine/AssetManager.h"
#include "ABPlayerController.h"
#include "ABSection.h"
#include "ABPlayerState.h"
...
void AABCharacter::SetCharacterState(ECharacterState NewState)
{
CurrentState = NewState;
switch (CurrentState)
{
case ECharacterState::LOADING:
{
if (bIsPlayer)
{
DisableInput(ABPlayerController);
auto ABPlayerState = Cast<AABPlayerState>(GetPlayerState());
CharacterStat->SetNewLevel(ABPlayerState->GetCharacterLevel());
}
SetActorHiddenInGame(true);
HPBarWidget->SetHiddenInGame(true);
SetCanBeDamaged(false);
break;
}
...
float AABCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
float FinalDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
CharacterStat->SetDamage(FinalDamage);
if (CurrentState == ECharacterState::DEAD)
{
if (EventInstigator->IsPlayerController())
{
auto TempABPlayerController = Cast<AABPlayerController>(EventInstigator);
TempABPlayerController->NPCKill(this);
}
}
return FinalDamage;
}
...
}
마지막으로 연동이 잘 되었는지 최종적으로 확인해준다
UI에 정보가 잘 입력되어있다
체력바와 HUD UI가 동일하게 깎이게 된다
적을 죽이면 경험치또한 오르게 된다