게임 개발/학습 일지

언리얼 내배캠 마스터반 - Subsystem 기반으로 아이템 매니저 만들기

이개 2026. 6. 15. 08:05

총 세 가지의 클래스를 만든다.

  • GameInstanceSubsystem → ItemManagerSubsystem
  • PrimaryDataAsset → ItemDataAsset
  • 플레이하고 아이템 장착 할 액터 → ItemManagerTestCharacter

 

#include "Engine/AssetManager.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnItemLoaded, FPrimaryAssetId, AssetId);


public:
    // 서브시스템 초기화 시 호출
    virtual void Initialize(FSubsystemCollectionBase& Collection) override;

    // 아이템 비동기 로드 요청
    UFUNCTION(BlueprintCallable, Category = "ItemManager")
    void RequestAsyncLoadItem(FPrimaryAssetId AssetId);

    // 로드된 아이템 가져오기 (이미 로드된 경우만 반환)
    UFUNCTION(BlueprintCallable, Category = "ItemManager")
    class UMyItemDataAsset* GetItemData(FPrimaryAssetId AssetId);

    // 아이템 메모리 해제
    UFUNCTION(BlueprintCallable, Category = "ItemManager")
    void UnloadItem(FPrimaryAssetId AssetId);



    //델리게이트
    UPROPERTY(BlueprintAssignable, Category = "ItemManager")
    FOnItemLoaded OnItemLoaded;


private:
    void OnLoadCompleted(FPrimaryAssetId AssetId);

    // 로드된 핸들을 보관하여 가비지 컬렉션을 방지
    TMap<FPrimaryAssetId, TSharedPtr<FStreamableHandle>> LoadingHandles;
void UItemManagerSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);
	UE_LOG(LogTemp, Log, TEXT("Item Manager Subsystem Initialized!"));
}

void UItemManagerSubsystem::RequestAsyncLoadItem(FPrimaryAssetId AssetId)
{
    if (!AssetId.IsValid()) return;

    // 이미 로드 중이거나 로드 완료되었는지 확인
    if (LoadingHandles.Contains(AssetId) && LoadingHandles[AssetId]->IsActive())
    {
        return;
    }

    UAssetManager& Manager = UAssetManager::Get();

    // 비동기 로드 시작
    TSharedPtr<FStreamableHandle> Handle = Manager.LoadPrimaryAsset(
        AssetId,
        TArray<FName>(),
        FStreamableDelegate::CreateUObject(this, &UItemManagerSubsystem::OnLoadCompleted, AssetId)
    );

    LoadingHandles.Add(AssetId, Handle);
}

UMyItemDataAsset* UItemManagerSubsystem::GetItemData(FPrimaryAssetId AssetId)
{
    // 에셋 매니저를 통해 메모리에 올라온 객체 포인터를 즉시 반환
    return UAssetManager::Get().GetPrimaryAssetObject<UMyItemDataAsset>(AssetId);
}

void UItemManagerSubsystem::UnloadItem(FPrimaryAssetId AssetId)
{
    if (LoadingHandles.Contains(AssetId))
    {
        // 핸들을 해제하여 에셋 매니저가 참조를 끊게 함 (GC 대상이 됨)
        LoadingHandles.Remove(AssetId);
        UAssetManager::Get().UnloadPrimaryAsset(AssetId);
        UE_LOG(LogTemp, Log, TEXT("Item Unloaded: %s"), *AssetId.ToString());
    }
}

void UItemManagerSubsystem::OnLoadCompleted(FPrimaryAssetId AssetId)
{
    UE_LOG(LogTemp, Log, TEXT("Item Loaded via Subsystem: %s"), *AssetId.ToString());

    // 로드 완료 알림
    if (OnItemLoaded.IsBound())
    {
        OnItemLoaded.Broadcast(AssetId);
    }
}

 

 

Primary Data Asset

 

 

public:
    // 에셋 매니저에서 식별할 타입 이름 (예: "Weapon")
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item")
    FPrimaryAssetType ItemType;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Stats")
    float AttackDamage;

    // 무거운 에셋은 SoftObjectPtr로 선언해서 로드 시점을 제어합니다.
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Visual")
    TSoftObjectPtr<USkeletalMesh> ItemMesh;

    // ID를 반환하는 함수 오버라이드 (Asset Manager가 식별할 때 사용)
    virtual FPrimaryAssetId GetPrimaryAssetId() const override
    {
        return FPrimaryAssetId(ItemType, GetFName());
    }