PlayerState 클래스
게임 점수 및 플레이어 게임 데이터를 별도의 엑터에서 관리하는 클래스
ABPlayerState 클래스 생성
PlayerState 에는 Playername, Score 속성은 이미 설계
프로젝트에 맞게 GameScore, CharacterLevel 변수 추가
ABPlayerState
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"
#include "ABGameInstance.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;
}
게임 모드에서 PlayerState 지정 시
플레이어 컨트롤러 가 초기화 시점에 해당 클래스 인스턴스를 생성 해
포인터를 플레이어 컨트롤러의 PlayerState 속성에 저장
게임 모드의 PostLogin에서 새로운 플레이어 스테이트 초기화 진행
AABGameMode::AABGameMode()
{
DefaultPawnClass = AABCharacter::StaticClass();
PlayerControllerClass = AABPlayerController::StaticClass();
//플레이어 스테이트 지정
PlayerStateClass = AABPlayerState::StaticClass();
}
void AABGameMode::PostLogin(APlayerController* NewPlayer)
{
ABLOG(Warning, TEXT("PostLogin Begin"));
Super::PostLogin(NewPlayer);
ABLOG(Warning, TEXT("PostLogin End"));
//PostLogin 시점에 PlayerState 초기화 실행
auto ABPlayerState = Cast<AABPlayerState>(NewPlayer->PlayerState);
ABCHECK(nullptr != ABPlayerState);
ABPlayerState->InitPlayerData();
}
ABCharacter
플레이어 레벨 정보를 캐릭터에 반영
컨트롤러가 빙의 시점에 ABPlayer State에 접근 해 데이터 갱신
#include "ABPlayerState.h"
void AABCharacter::SetCharacterState(ECharacterState NewState)
{
//현재 상태와 새로운 상태 비교
ABCHECK(CurrentState != NewState);
CurrentState = NewState;
switch (CurrentState)
{
case ECharacterState::LOADING:
{
if (bIsPlayer)
{
//플레이어 컨트롤러 입력 비활성화
DisableInput(ABPlayerController);
//플레이어 스테이트 접근 해 레벨 지정
auto ABPlayerState = Cast<AABPlayerState>(PlayerState);
ABCHECK(nullptr != ABPlayerState);
CharacterStat->SetNewLevel(ABPlayerState->GetCharacterLevel());
}
//...
}
}
HUD UI 사용
캐릭터 정보 를 화면에 표시 및 구현
UI_HUD.uasset 파일을 컨트롤 하는 UserWidget 기본 클래스로 ABHUDWidget 생성
UI_HUD 애셋을 열어 그래프 탭에서 ABHUDWidget 클래스를 부모로 설정
ABPlayer Controller
ABHUDWidget 를 생성 해 화면에 띄우는 기능 추가
UCLASS()
class ARENABATTLE_API AABPlayerController : public APlayerController
{
GENERATED_BODY()
public:
AABPlayerController();
class UABHUDWidget* GetHUDWidget() const;
protected:
//UI HUD 애셋 레퍼런스 복사한 클래스 정보
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = UI)
TSubclassOf<class UABHUDWidget> HUDWidgetClass;
private:
//위젯 인스턴스
UPROPERTY()
class UABHUDWidget* HUDWidget;
}
#include "ABHUDWidget.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::BeginPlay()
{
Super::BeginPlay();
FInputModeGameOnly InputMode;
SetInputMode(InputMode);
//인스턴스 생성 후 화면에 부착
HUDWidget = CreateWidget<UABHUDWidget>(this, HUDWidgetClass);
HUDWidget->AddToViewport();
}
UABHUDWidget * AABPlayerController::GetHUDWidget() const
{
return HUDWidget;
}
HUD 데이터 연동
플레이어 스테이트 및 캐릭터 스탯 컴포넌트 데이터를 UI 와 연동
ABPlayerState
델리게이트 생성 해 플레이어 데이터가 변경 시 HUD 위젯 업데이트
DECLARE_MULTICAST_DELEGATE(FOnPlayerStateChangedDelegate);
/**
*
*/
UCLASS()
class ARENABATTLE_API AABPlayerState : public APlayerState
{
GENERATED_BODY()
public:
//스테이트 값 변경 시 델리게이트 호출
FOnPlayerStateChangedDelegate OnPlayerStateChanged;
}
ABHUDWidget
UI 컨트롤 할 수 있게 각각의 변수에 연동
플레이어 스테이트 변경 하는 델리게이트에 함수 등록
캐릭터 스탯의 HP 변경 시 호출 되는 델리게이트에 함수 등록
갱신 된 데이터를 UI에 적용
#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;
};
#include "ABHUDWidget.h"
#include "Components/ProgressBar.h"
#include "Components/TextBlock.h"
#include "ABCharacterStatComponent.h"
#include "ABPlayerState.h"
void UABHUDWidget::BindCharacterStat(UABCharacterStatComponent * CharacterStat)
{
//캐릭터 스탯에 있는 HP 체인지 델리게이트에 함수 등록
ABCHECK(nullptr != CharacterStat);
CurrentCharacterStat = CharacterStat;
CharacterStat->OnHPChanged.AddUObject(this, &UABHUDWidget::UpdateCharacterStat);
}
void UABHUDWidget::BindPlayerState(AABPlayerState * PlayerState)
{
//플레이어 스테이스에 있는 스테이스 변경 델리게이트에 함수 등록
ABCHECK(nullptr != PlayerState);
CurrentPlayerState = PlayerState;
PlayerState->OnPlayerStateChanged.AddUObject(this, &UABHUDWidget::UpdatePlayerState);
}
void UABHUDWidget::NativeConstruct()
{
Super::NativeConstruct();
//각각의 UI를 컨트롤 할 수 있게 변수에 저장
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());
//HP bar 값 갱신
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())));
}
Player Controller
HUD 위젯과 플레이어 스테이트 연결
#include "ABPlayerState.h"
void AABPlayerController::BeginPlay()
{
Super::BeginPlay();
FInputModeGameOnly InputMode;
SetInputMode(InputMode);
//인스턴스 생성 후 화면에 부착
HUDWidget = CreateWidget<UABHUDWidget>(this, HUDWidgetClass);
HUDWidget->AddToViewport();
//HUD widget 과 PlayState 연동 후 실행
auto ABPlayerState = Cast<AABPlayerState>(PlayerState);
ABCHECK(ABPlayerState != nullptr);
HUDWidget->BindPlayerState(ABPlayerState);
ABPlayerState->OnPlayerStateChanged.Broadcast();
}
ABCharacter
HUD 위젯 과 캐릭터 스탯 컴포넌트 연결
#include "ABHUDWidget.h"
void AABCharacter::SetCharacterState(ECharacterState NewState)
{
//현재 상태와 새로운 상태 비교
ABCHECK(CurrentState != NewState);
CurrentState = NewState;
switch (CurrentState)
{
case ECharacterState::LOADING:
{
if (bIsPlayer)
{
//플레이어 컨트롤러 입력 비활성화
DisableInput(ABPlayerController);
//HUD Widget 과 캐릭터 스탯 연동
ABPlayerController->GetHUDWidget()->BindCharacterStat(CharacterStat);
//플레이어 스테이트 접근 해 레벨 지정
auto ABPlayerState = Cast<AABPlayerState>(PlayerState);
ABCHECK(nullptr != ABPlayerState);
CharacterStat->SetNewLevel(ABPlayerState->GetCharacterLevel());
}
//캐릭터 및 UI 숨김, 데미지 충돌 안함
SetActorHiddenInGame(true);
HPBarWidget->SetHiddenInGame(true);
bCanBeDamaged = false;
break;
}
//....
}
}
HUD 와 캐릭터 데이터 연동 후 테스트
플레이어 경험치 정보 표시
플레이어와 NPC를 죽이면 NPC 레벨에 따른 경험치를 받아서 레벨 업 하는 기능 구현
경험치를 프로그래스 바로 표시 하기 떄문에 현재 경험치와 최대 경험치의 비율이 필요
경험치 기능 추가
1. 캐릭터 스태에서 NPC를 위한 경험치 값 설정
2. 플레이어 스테이트 에서 플레이어 경험치 보관
3. NPC 사망 시 플레이어 스테이트 업데이트
PlayeerState
플레이어 경험치에 따라 레벨 업 및 경험치 비율 계산
UCLASS()
class ARENABATTLE_API AABPlayerState : public APlayerState
{
GENERATED_BODY()
public:
//현재 경험치와 레벨업에 필요한 경험치 비율 계산 후 리턴
float GetExpRatio() const;
//현재 경험치에 변수 값 만큼 더하고, 레벨업 판단
bool AddExp(int32 IncomeExp);
protected:
//경험치
UPROPERTY(Transient)
int32 Exp;
private:
//새로운 레벨에 맞는 CurrentStatData로 갱신
void SetCharacterLevel(int32 NewCharacterLevel);
struct FABCharacterData* CurrentStatData;
};
// 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;
}
void AABPlayerState::InitPlayerData()
{
SetPlayerName(TEXT("Destiny"));
SetCharacterLevel(5);
GameScore = 0;
Exp = 0;
}
float AABPlayerState::GetExpRatio() const
{
//KINDA_SMALL_NUMBER 작으면 0.0 리턴
if (CurrentStatData->NextExp <= KINDA_SMALL_NUMBER)
return 0.0f;
//비율 계산
float Result = (float)Exp / (float)CurrentStatData->NextExp;
ABLOG(Warning, TEXT("Ratio : %f, Current : %d, Next : %d"), Result, Exp, 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;
}
ABHUDWidget
플레이어 스테이스 변경 시 동작하는 함수에 경험치 비율에 따른 exp bar 갱신 기능 추가
void UABHUDWidget::UpdatePlayerState()
{
//약 포인트 연결 확인
ABCHECK(CurrentPlayerState.IsValid());
//경험치 바 갱신
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())));
}
ABCharacterStatComponent
NCP가 사망 시 경험치 값 지정
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class ARENABATTLE_API UABCharacterStatComponent : public UActorComponent
{
GENERATED_BODY()
public:
int32 GetDropExp() const; // 현재 경혐치 값 리턴
}
int32 UABCharacterStatComponent::GetDropExp() const
{
return CurrentStatData->DropExp;
}
ABCharacter
현재 캐릭터의 ABCharacterStatComponent 접근 해 경험치 변수 값 를 리턴
NPC 죽음 체크
UCLASS()
class ARENABATTLE_API AABCharacter : public ACharacter
{
GENERATED_BODY()
public:
//현재 경험치 값 리턴
int32 GetExp() const;
}
//현재 스태에 가지고 있는 경험치 값 리턴
int32 AABCharacter::GetExp() const
{
return CharacterStat->GetDropExp();
}
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);
//대미지를 받고 DEAD 상태
if (CurrentState == ECharacterState::DEAD)
{
//가해자가 플레이어일 경우
if (EventInstigator->IsPlayerController())
{
//NPC 죽음으로 판단
auto ABPlayerController = Cast<AABPlayerController>(EventInstigator);
ABCHECK(nullptr != ABPlayerController, 0.0f);
ABPlayerController->NPCKill(this);
}
}
return FinalDamage;
}
ABPlayerController
NPC 사망 시 경험치 증가 기능 추가
UCLASS()
class ARENABATTLE_API AABPlayerController : public APlayerController
{
GENERATED_BODY()
public:
//NPC 사망 시 호출
void NPCKill(class AABCharacter* KilledNPC) const;
private:
UPROPERTY()
class AABPlayerState* ABPlayerState;
}
#include "ABCharacter.h"
void AABPlayerController::BeginPlay()
{
Super::BeginPlay();
FInputModeGameOnly InputMode;
SetInputMode(InputMode);
//인스턴스 생성 후 화면에 부착
HUDWidget = CreateWidget<UABHUDWidget>(this, HUDWidgetClass);
HUDWidget->AddToViewport();
//HUD widget 과 PlayState 연동 후 실행
//ABPlayerState 변수로 지정
ABPlayerState = Cast<AABPlayerState>(PlayerState);
ABCHECK(ABPlayerState != nullptr);
HUDWidget->BindPlayerState(ABPlayerState);
ABPlayerState->OnPlayerStateChanged.Broadcast();
}
//현재 스테이트의 경험치에 죽은 NPC 경험치 더하기
void AABPlayerController::NPCKill(AABCharacter* KilledNPC) const
{
ABPlayerState->AddExp(KilledNPC->GetExp());
}
'언리얼 C++ 게임 개발의 정석 > 13. 게임 플레이 제작' 카테고리의 다른 글
13.3 게임 데이터의 관리 (0) | 2024.05.23 |
---|---|
13.1 캐릭터의 스테이스 설정 (0) | 2024.05.18 |