게임 플레이 보완 (2) 플레이어 데이터와 UI 연동

유영준·2023년 1월 27일
0
post-thumbnail

어제에 이어 오늘은 플레이어 데이터를 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 에는 CharacterNameScore 변수가 기본적으로 내장되어 있기 때문에 선언할 필요가 없다

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 정보 연동

이제 조종하는 캐릭터에게 데이트를 설정하였으니, 이를 확인할 수 있는 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가 동일하게 깎이게 된다

적을 죽이면 경험치또한 오르게 된다

profile
토비폭스가 되고픈 게임 개발자

0개의 댓글