본문 바로가기

게임 개발/이득우의 언리얼 C++

[이득우의 언리얼 C++ 게임개발의 정석] #11. 게임 데이터와 UI 위젯

반응형

이득우의-언리얼-C++-게임개발의-정석-표지

엑셀 데이터로부터 캐릭터의 능력치를 관리할 수 있도록
테이블 데이터를 언리얼 엔진에 불러들이는 방법을 알아본다.

게임 데이터를 효과적으로 관리하는 게임 인스턴스 클래스,
방대한 로직을 분산시킬 수 있도록 액터 컴포넌트를 설계해 액터에 적용

캐릭터의 현재 스탯을 HP바로 표시하는 기능 구현

<1>. 엑셀 데이터의 활용

엑셀에 저장돼 있는 캐릭터의 스탯 데이터 테이블을 언리얼 엔진에 불러들여보자.

 

스탯 데이터는 변하지 않는 데이터이므로, 보통 게임 앱이 초기화될 때 불러들인다.

→ 게임 앱을 관리하기 위해 '게임 인스턴스'라는 언리얼 오브젝트가 제공된다.

→ "ABGameInstance" 게임 인스턴스 클래스를 생성하고, 맵&모드 탭에 있는 GameInstance를 바꿔준다.

→ GameInstance의 Init 함수를 호출해 본다.

//ABGameInstance.h

#include "ArenaBattle.h"

class ARENABATTLE_API UABGameInstance : public UGameInstance
{
public:
	UABGameInstance();
    
    virtual void Init() override;
}

//ABGameInstance.cpp

void UABGameInstance::Init()
{
	Super::Init();
	ABLOG_S(Warning);
}

 

※게임을 시작하는 과정

 

게임 앱의 초기화  → 레벨에 속한 액터의 초기화 → 플레이어의 로그인 → 게임의 시작

UGameInstance::Init →  AActor::PostInitializeComponents →  AGameMode::PostLogin → AGameMode::StartPlay

                                                                                                                                                 AActor::BeginPlay

 

게임 데이터를 관리할 게임 인스턴스를 설정했으니, 게임 데이터를 프로젝트에 임포트 하고 이를 불러들여보자.  

→ 엑셀 파일 형식은 안되고, CSV 파일 형식으로 변환한다.

CSV-파일형식

 

이 데이터를 불러들이려면, 테이블 데이터의 각 열의 이름과 유형이 동일한 구조체를 선언해야 한다.

※ 구조체를 생성할 때 규칙 : 1. USTRUCT 매크로를 구조체 선언 윗줄에 넣어준다.

                                              2. 구조체 내부에 GENERATED_BODY() 매크로를 선언해 준다.

                                              3. Name 열 데이터는 자동으로 키 값으로 사용되기 때문에 선언에서 제외한다.

//ABGameInstance.h

#include "Engine/DataTable.h"

USTRUCT(BlueprintType)
struct FABCharacterData : public FTableRowBase //데이터 테이블을 정의할때 사용하는 기본 구조체
{
	GENERATED_BODY();
    
public:
	FABCharacterData() : Level(1), MaxHP(100.0f), Attack(10.0f), DropExp(10), NextExp(30){}
    //각 멤버 변수 초기화
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Data")
    int32 Level;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Data")
    float MaxHP;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Data")
    float Attack;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Data")
    int32 DropExp;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Data")
    int32 NextExp;
};

 

이렇게 FABCharacterData가 컴파일 됐으면, CSV데이터를 임포트 한다.

→ 데이터 테이블 옵션에서 'ABCharacterData' 선택

데이터-테이블
<CSV 파일이 임포트된 화면>

게임 데이터 에셋을 성공적으로 임포트 했다면, 게임 인스턴스에서 이를 로딩하는 기능을 구현해 보자.

 

언리얼 엔진은 테이블 데이터를 관리하도록 'DataTable'이라는 언리얼 오브젝트를 제공한다.

→ DataTable을 게임 인스턴스의 멤버 변수로 선언하고 레퍼런스를 복사한 후, 데이터를 불러들이는 기능을 구현한다. 

 

※ 잘 로딩됐는지 파악하기 위해 20 레벨 데이터를 출력해 보자

//ABGameInstance.h

class ARENABATTLE_API UABGameInstance:public UGameInstance
{
public:
	FABCharacterData* GetABCharacterData(int32 Level);
private:
	UPROPERTY()
    class UDataTable* ABCharacterTable;
};

//ABGameInstance.cpp

UABGameInstance::UABGameInstane()
{
	FString CharacterDataPath = TEXT("(경로)");
    static ConstructorHelpers::FObjectFinder<UDataTable> DT_ABCHARACTER(*CharacterDataPath);
    ABCHECK(DT_ABCHARACTER.Succeeded());
    ABCharacterTable = DT_ABCHARACTER.Object;
}

void UABGameInstance::Init()
{
	ABLOG(Warning, TEXT("%d"), GetABCharacterDate(20)->DropExp); //레벨20의 Exp수치 출력
}

FABCharacterData* UABGameInstance::GetABCharacterData(int32 Level)
{
	return ABCharacterTable->FindRow<FABCharacterData>(*FString::FromInt(Level),TEXT(""));
    //정수를 문자열로 변환한 후 데이터 테이블에서 해당 행을 찾음
    //FromInt는 FString 클래스의 함수로, 정수를 FString으로 변환하는 기능을 함
}

 

<2>. 액터 컴포넌트의 제작

이번엔 액터 컴포넌트 클래스를 생성하고 이를 캐릭터에 부착해 캐릭터 스탯에 대한 관리를 액터 컴포넌트가 일임하도록 기능을 구현해 본다.

→ 'ActorComponent' 클래스를 만든다 ("ABCharacterStatComponent") → ABCharacter 클래스에 멤버 변수로 선언한다.

//ABCharacter.h

UPROPERTY(VisibleAnywhere, Category=Stat)
class UABCharacterStatComponent* CharacterStat;

//ABCharacter.cpp

#include "ABCharacterStatComponent.h"

AABCharacter::AABCharacter()
{
	CharacterStat = CreateDefaultSubobject<UABCharacterStatComponent>(TEXT("CHARACTERSET"));
}

 

액터 컴포넌트 클래스에는 자동으로 제공되는 템플릿 코드에 BeginPlay, TickComponent 함수가 있다.

→ 스탯에 변경이 일어날 때만 데이터를 처리하므로, Tick 로직은 필요 없다 → TickComponent 삭제

→ 액터의 PostInitializeComponents = 액터 컴포넌트의 InitializeComponent 

                                                                →이 함수를 호출하려면 생성자에서 bWantsInitializeComponent = true 해줘야 함.

//ABCharacterStatComponent.h

#include "ArenaBattle.h"

protected:
	virtual void InitializeComponent() override;
    
//ABCharacterStatComponent.cpp

UABCharacterStatComponent::UABCharacterStatComponent()
{
	PrimaryComponentTick.bCanEverTick = false;
    bWantsInitializeComponent = true;
}

void UABCharacterStatComponent::InitializeComponent()
{
	Super::InitializeComponent();
}

 

  • 레벨, HP 등 모든 스탯을 스탯 컴포넌트에서 관리하도록 한다.
  • 게임 인스턴스에서 데이터를 초기화하고 레벨이 변경되면 해당 스탯이 바뀌도록 한다.
  • 캐릭터가 대미지를 받은 만큼 CurrentHP에서 차감하고 0 이하가 되면 캐릭터가 죽는 애니메이션 재생.
  • 캐릭터의 TakeDamge 함수에서 처리하지 말고, ABCharacterStatComponent에 SetDamage 함수를 설정하고 TakeDamage함수를 호출해 액터 컴포넌트가 처리하도록 구성을 변경한다.

<3>. 캐릭터 위젯 UI 제작

캐릭터의 HP값이 시각적으로 보이도록 UI위젯을 제작, 캐릭터에 부착해 보자.

→ '위젯 블루프린트'에서 "UI_HPBar"라는 UI에셋을 생성한다.

 

1. Canvas Panel을 제거한다. 이 패널은 위젯을 전체 스크린 기준으로 부착할 때 좋다.

2.  Progress Bar를 계층 구조에 드래그하고 "PB_HPBar"라고 해준다.

3. Vertical Box로 감싸준다.

4. 윈도 프리미티브 그룹에서 Spacer 컨트롤을 Vertical Box로 드래그해, HPBar 위아래에 추가해 준다.

5. 3개의 영역을 40%, 20%, 40%로 배분해 준다.

 

<4>. 모듈과 빌드 설정

UI를 캐릭터에 부착하는 기능을 구현해 보자.

액터에 UI 위젯을 부착할 수 있도록 'UWidgetComponent' 클래스가 제공된다.

→위젯 컴포넌트 기능은 'ArenaBattle.Build.cs 파일의 기본 모듈에는 없기 때문에, UMG 모듈을 추가해줘야 한다.

 

위젯 컴포넌트가 캐릭터 머리 위로 오도록 하고, 항상 플레이어를 향해 보도록 Screen 모드로 지정한다.

//ABCharacter.cpp

#include "Components/WidgetComponent.h"

AABCharacter::AABCharacter()
{
	HPBarWidget = CreateDefaultSubobject<UWidgetComponent>(TEXT("HPBARWIDGET"));
    HPBarWidget->SetAttachment(GetMesh());
    
    HPBarWidget->SetRelativeLocation(FVector(0.0f, 0.0f, 180.0f)); // 머리 위
    HPBarWidget->SetWidgetSpace(EWidgetSpace::Screen); //플레이어를 향해 보도록
    static ConstructorHelpers::FClassFinder<UUserWidget>UI_HUD(TEXT("(경로)_C"));
    
    if(UI_HUD.Succeeded())
    {
    	HPBarWidget->SetWidgetClass(UI_HUD.Class);
        HPBarWidget->SetDrawSize(FVector2D(150.0f, 50.0f));
    }
}

<5>. UI와 데이터의 연동

캐릭터의 스탯이 변경되면 이를 UI에 전달해 ProgressBar가 변경되도록 구현한다.

 

애니메이션 설계 작업을 애님 그래프에서 한 것처럼, UI 작업은 디자이너라는 공간에서 진행한다.

하지만, UI의 로직은 애님 인스턴스와 유사하게 C++ 클래스에서 만들 수 있다.

※ 위젯 블루프린트가 사용하는 C++ 클래스는 UserWidget이다. → 생성된 "ABCharacterWidget" 클래스는 "ABCharacterStatComponent"와 연동하여 HP 바를 업데이트할 것이다. → 의존성을 가지지 않게 델리게이트를 선언하자.

 

UI 생성은 플레이어 컨트롤러의 BeginPlay에서 호출됨.

→ 앞에서 위젯 초기화 시점을 PostInitializeComponents로 함.

→ 이 초기화 명령은 적용되지 않음 → UI 시스템이 준비되면 호출되는 NativeConstruct 함수에서 위젯 업데이트 로직을 구현하자.

//ABCharacterWidget.h

UCLASS()
class ARENABATTLE_API UABCharacterWidget : public UUserWidget
{
protected:
    virtual void NativeConstruct() override;
    void UpdateHPWidget();
private:
	UPROPERTY()
    class UProgressBar* HPProgressBar;

//ABCharacterWidget.cpp

#include "Components/ProgressBar.h" //UMG 모듈의 Public/Components 폴더에 있음

void UABCharacterWidget::BindCharacterStat(UABCharacterStatComponent* NewCharacterStat)
{
	CurrentCharacterStat = NewCharacterStat;
    NewCharacterStat->OnHPChanged.AddUObject(this, &UABCharacterWidget::UpdateHPWidget);
}

void UABCharacterWidget::NativeConstruct()
{
	Super::NativeConstruct();
    HPProgressBar = Cast<UProgressBar>(GetWidgetFromName(TEXT("PB_HPBar")));
    UpdateHPWidget();
}
void UABCharacterWidget::UpdateHPWidget()
{
	if(CurrentCharacterStat.IsValid())
    {
    	if(nullptr != HPProgressBar)
        {
        	HPProgressBar->SetPercent(CurrentCharacterStat->GetHPRatio());
        }
    }
}

 

 

 

 

[이득우의 언리얼 C++ 게임개발의 정석] #12. AI 컨트롤러와 비헤이비어 트리

비헤이비어 트리 모델을 사용해 컴퓨터 인공지능을 설계하고인공지능에 의해 스스로 정찰하고 플레이어를 쫓아와 공격하는 NPC 제작. AIController와 내비게이션 시스템언리얼 엔진에는 컴퓨터가

lbto.tistory.com

 

반응형