- 프로그래머스 CodeKata
- Git 복습
- UE5 인벤토리 시스템(개인 공부)
이번에는 인벤토리 시스템을 공부하는 도중 FInstancedStruct에 대한 내용이 나와서 추후에 자주 사용될 것 같아 정리해보려고 한다.
FInstancedStruct는 UE5부터 제공하는 USTRUCT이다.
일반적인 USTRUCT와 달리, 구조체의 인스턴스를 동적으로 관리할 수 있다는 점이 특징이다.
즉, 다형성을 사용할 수 있을 뿐더러 가비지 컬렉터(GC)의 영향을 받지 않고 데이터만 저장하는 구조체의 특징을 지닌다는 점에서 UObject와 차이가 있다.
사용하기 이전에, FInstancedStruct는 'StructUtils' 모듈을 사용하기 때문에 Build.cs에 다음 코드를 추가해야 한다.
(5.5 이후 위 모듈이 엔진 기본 모듈인 'CoreUObject'로 통합되어 추가하지 않아도 된다.)
PublicDependencyModuleNames.AddRange(new string[] { /* ... */ , "StructUtils" });
아래는 여러가지 아이템의 정보를 담고 있는 구조체이다.
// InvenctoryComponent.h
#pragma once
#include "CoreMinimal.h"
#include "StructUtils/InstancedStruct.h" // 필수 헤더
#include "InventoryComponent.generated.h"
USTRUCT(BlueprintType)
struct FItemBase // 기본 클래스
{
GENERATED_BODY()
UPROPERTY(EditAnywhere)
FName ItemName = "Unknown";
};
USTRUCT(BlueprintType)
struct FWeaponItem : public FItemBase // 무기 구조체
{
GENERATED_BODY()
UPROPERTY(EditAnywhere)
float AttackDamage = 50.0f;
};
USTRUCT(BlueprintType)
struct FPotionItem : public FItemBase // 포션 구조체
{
GENERATED_BODY()
UPROPERTY(EditAnywhere)
float HealAmount = 30.0f;
};
이 구조체를 바탕으로 현재 저장된 아이템의 리스트들을 출력해보는 코드를 작성해본다고 하자.
먼저 USTRUCT를 사용했을 때, 코드를 작성해보면 다음과 같다.
// InvenctoryComponent.h
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class UInventoryComponent : public UActorComponent
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere)
TArray<FItemBase> InventorySlots; // 저장된 아이템 리스트
void PrintInventory(); // 아이템 출력
/* 아이템 추가/삭제 함수 등등... */
};
// InvenctoryComponent.cpp
/* InventorySlots에 대검(FWeaponItem), 체력 회복 포션(FPotionItem)이 저장되었다고 가정
void UInventoryComponent::PrintInventory()
{
for (const FItemBase& Item : NormalInventory)
{
UE_LOG(LogTemp, Log, TEXT("아이템 이름: %s"), *Item.ItemName.ToString());
}
}
// 결과
// LogTemp: 아이템 이름: 대검
// LogTemp: 아이템 이름: 체력 회복 포션
위와 같이, FWeaponItem에 있는 AttackDamage와 FPotionItem에 있는 HealAmount가 슬라이싱되어 출력이 안된 모습을 볼 수 있다.
FInstancedStruct를 사용하면 다음과 같은 코드를 작성할 수 있다.
// InvenctoryComponent.h
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class UInventoryComponent : public UActorComponent
{
GENERATED_BODY()
public:
// BaseStruct 메타 지정자로 블루프린트 에디터에서 FItemBase 유형의 데이터만 할당하도록 세팅
UPROPERTY(EditAnywhere, meta = (BaseStruct = "/Script/YourProjectName.ItemBase")
TArray<FInstancedStruct> InventorySlots; // 저장된 아이템 리스트
void PrintInventory(); // 아이템 출력
/* 아이템 추가/삭제 함수 등등... */
};
void UInventoryComponent::PrintInstancedInventory()
{
for (const FInstancedStruct& ItemBox : InventorySlots)
{
if (const FWeaponItem* Weapon = ItemBox.GetPtr<FWeaponItem>())
{
UE_LOG(LogTemp, Warning, TEXT("무기: %s | 데미지: %f"), *Weapon->ItemName.ToString(), Weapon->AttackDamage);
}
else if (const FPotionItem* Potion = ItemBox.GetPtr<FPotionItem>())
{
UE_LOG(LogTemp, Warning, TEXT("포션: %s | 회복량: %f"), *Potion->ItemName.ToString(), Potion->HealAmount);
}
}
}
// 결과
// LogTemp: 무기: 대검 | 데미지: 15
// LogTemp: 포션: 체력 회복 포션 | 회복량: 20.000000
FInstancedStruct를 사용하면 GetPtr() 함수를 통해 래핑된 메모리 주소를 가져올 수 있기 때문에 데이터 손실을 방지할 수 있다.
FInstancedStruct가 아이템이나 스킬 유형과 같이 데이터 정보를 많이 요구할 때 꽤나 유용하기 때문에 나중에 RPG 프로젝트를 구현하게 될 때 매우 유용할 것 같다.
- 참고 자료
Unreal Engine 5 FInstancedStruct 문서: https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/CoreUObject/FInstancedStruct