DataLoader 클래스를 따로 만드는 이유
- 캐릭터들이 초기화할 때만 사용할 Data Table을 각 캐릭터마다 들고 있는 것은 효율적이지 못함
- 앞으로 추가될 각종 애니메이션 관련 구조체나 여타 구조체 변수들을 추가할 때마다 해당 변수들을 초기화하기 위한 Data Table이 필요
- 그 과정에서 1번에서 말한 것처럼 각 캐릭터들이 필요한 Data Table을 모두 변수 형태로 가지고 있어야 하게 되면 낭비가 더더욱 심해짐
ㅡ> 따라서 각종 Data Table을 가지고 있다가 필요한 시점에 캐릭터들이 호출해서 각 변수들을 초기화할 수 있는 시스템이 필요
Subsystem
수명이 관리되는 자동 인스턴싱 클래스
종류별 Subsystem의 Lifetime
Subsystem | Inherit From |
Engine | UEningeSubsystem |
Editor | UEditorSubsystem |
GameInstance | UGameInstanceSubsystem |
LocalPlayer | ULocalPlayerSubsystem |
Subsystem의 장점
- 프로그래밍 시간 절약
- 생애주기 자동 관리 (원하는 생애주기에 따라 종류 선택)
- Engine 클래스 오버라이드를 회피 가능
- 이미 복잡한 클래스에 API 추가 회피 가능
- 블루프린트 액세스
- 코드베이스의 모듈성과 일관성
- 에디터 스크립팅이나 테스트 코드 작성을 위해 Python 스크립트 액세스 가능
종류 선택
- 게임 구성 상 DataLoader는 게임 내에서 계속 유지되면서 캐릭터들의 BeginPlay가 호출되는 시점에 각종 변수들을 초기화
- 따라서 게임이 시작되는 순간부터 끝날 때까지 유지되는 GameInstance가 적합
ㅡ> UGameInstanceSubsystem 으로 결정
DataLoader
DataLoader.h
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "DataLoaderSubSystem.generated.h"
/**
*
*/
struct FCharacterStats;
UCLASS()
class CCFF_API UDataLoaderSubSystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
UDataLoaderSubSystem();
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
UFUNCTION(BlueprintCallable)
FCharacterStats InitializeStat(const FName CharacterRowName) const;
private:
//CharacterStats Data Table
UPROPERTY()
UDataTable* StatDataTable;
};
DataLoader.cpp
#include "DataLoaderSubSystem.h"
#include "Base/CharacterStats.h"
UDataLoaderSubSystem::UDataLoaderSubSystem()
{
// Find Character Stats Data Table and Initialize Data Table Variable
static ConstructorHelpers::FObjectFinder<UDataTable> DataTableFinder(TEXT("/Game/Character/DataTables/DT_Stats.DT_Stats"));
if (DataTableFinder.Succeeded())
{
StatDataTable=DataTableFinder.Object;
}
}
void UDataLoaderSubSystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
}
FCharacterStats UDataLoaderSubSystem::InitializeStat(const FName CharacterRowName) const
{
if (CharacterRowName.IsValid()&&StatDataTable)
{
UE_LOG(LogTemp, Warning, TEXT("CharacterRowName: %s"), *CharacterRowName.ToString());
if (FCharacterStats* TempStats=StatDataTable->FindRow<FCharacterStats>(CharacterRowName,TEXT("")))
{
UE_LOG(LogTemp, Warning, TEXT("Load Success!"));
return *TempStats;
}
}
return FCharacterStats();
}
- 생성자에서 ConstructorHelpers로 경로를 이용해 데이터 테이블 로드
- 미리 만들어둔 포인터 변수에 찾은 데이터 테이블 할당
- 캐릭터들의 BeginPlay 함수 내에서 호출될 CharacterStats 구조체 변수 초기화용 함수 생성 및 정의
- DataLoader의 생성자에서 할당해둔 데이터 테이블 변수를 이용
- 호출 시점에 함께 전달받은 매개변수 CharacterRowName으로 알맞은 데이터를 찾아 반환
// Called when the game starts or when spawned
void ABeholder::BeginPlay()
{
Super::BeginPlay();
if (UGameInstance* GameInstance=GetGameInstance())
{
if (UDataLoaderSubSystem* Loader=GameInstance->GetSubsystem<UDataLoaderSubSystem>())
{
Stats=Loader->InitializeStat("Beholder");
}
}
}
- GameInstance를 통해 위에서 만든 DataLoader로 접근
- DataLoader의 InitializeStat 함수에 알맞은 Character 이름을 전달하여 반환값을 캐릭터의 Stats(FCharacterStats)에 할당
- 플레이 시작 시점에서 Stats 관련 변수값들 모두 DataLoader를 이용하여 초기화 성공
DataLoader 클래스를 사용함에 따른 이점
- 각 캐릭터들이 초기화할 때만 1회성으로 필요한 Data Table 변수를 가지고 있을 필요가 없음
ㅡ> 메모리 효율 증가 - Subsystem을 통해 글로벌하게 접근 가능한 단일 인스턴스(싱글톤 패턴)
ㅡ> 게임 내에서는 DataLoader 단일 인스턴스만 Data Table을 소지 - Subsystem을 이용한 클래스로 라이프타임이 자동 관리 + 라이프타임이 명확
- 게임 내내 유지되는 UGameInstanceSubsystem이므로 캐릭터가 생성, 삭제될 때마다 Data Table을 다시 찾을 필요 X
참고자료
https://forums.unrealengine.com/t/two-questions-subsystems-and-datatables/258668
Two questions: Subsystems and Datatables
I’m making a subsystem that will manage my rooms (basically a Map with connections being Edges and Rooms being Nodes). Is there a way to view my subsystem in the editor to make sure I’ve loaded in the Map correctly. Which brings me to the second part.
forums.unrealengine.com
https://devjino.tistory.com/338
[UE] Subsystem 활용한 Unreal 스타일 Singleton
Subsystem 활용한 Unreal 스타일 Singleton Singleton 패턴은 많은 짐이 있지만 무시할수 없는 기능성이 있습니다. 다행히도 Unreal Engine은 결점이 적은 방식으로 Singleton의 이점을 제공할 수 있는 방법이 있
devjino.tistory.com
'프로젝트 > CCFF' 카테고리의 다른 글
[Project] 미친 선인장과 분노의 버섯 - #7 공격시 콜리전 생성 방식 결정 + 캐릭터별 알맞은 데이터 로딩 로직 (0) | 2025.04.03 |
---|---|
[Project] 미친 선인장과 분노의 버섯 - #6 구조체 추가 정의 + DataLoader Update (0) | 2025.04.02 |
[Project] 미친 선인장과 분노의 버섯 - #4 데이터 테이블을 이용한 변수 초기화+트러블 슈팅 (0) | 2025.03.31 |
[Project] 미친 선인장과 분노의 버섯 - #3 구조체를 활용한 변수 관리 및 데이터 테이블을 이용한 변수 초기화 (0) | 2025.03.28 |
[Project] 미친 선인장과 분노의 버섯 - #2 서버-클라이언트 구조를 고려한 피격 판정 설계 (0) | 2025.03.27 |