* 언리얼 엔진은 플레이어의 정보를 관리하기 위한 용도로 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를 처치 하면 경험치 관련된 항목이 업데이트 된다.
'Unreal > Game 1 (C++)' 카테고리의 다른 글
22. 게임의 완성 1 (게임 데이터의 저장과 로딩) (0) | 2019.05.10 |
---|---|
21. 게임플레이 제작 3 (게임 데이터의 관리) (0) | 2019.05.09 |
21. 게임플레이 제작 1 (캐릭터 스테이트) (0) | 2019.05.08 |
20. 프로젝트이 설정과 무한 맵의 제작 4 (타이머기능 + 네비게이션 메시 시스템 설정) (0) | 2019.05.08 |
20. 프로젝트이 설정과 무한 맵의 제작 3 (무한맵 생성+메시소켓장착) (0) | 2019.05.08 |