카테고리 없음

언리얼 C++) 언리얼 에셋 로딩 최적화 및 데이터 관리 (Soft Reference & DataAsset)

eclipse2 2026. 6. 4. 19:44

1. 에셋 참조 방식: 하드(Hard) vs 소프트(Soft)

① 하드 래퍼런스 (Hard Reference)

  • 특징: UPROPERTY(EditAnywhere)로 블루프린트에서 직접 할당하거나 생성자에서 ConstructorHelpers로 찾는 방식.
  • 장단점: 참조된 에셋을 즉시 사용할 수 있어 편리하지만, 캐릭터가 스폰될 때 들고 있는 무기 수십 개가 메모리에 한꺼번에 로드되므로 메모리 부하 및 로딩 지연이 발생할 수 있습니다.

② 소프트 래퍼런스 (Soft Reference)

  • 특징: 에셋의 경로 정보만 딱 가지고 있다가, 개발자가 '지금 당장 필요해!'라고 부를 때(동기/비동기) 비로소 메모리에 로드하는 방식.
  • 장점: 초기 로딩 속도 개선, 메모리 점유율 최소화, 비동기 로딩을 통한 프레임 드롭(렉) 방지.
  • 사용처: 인벤토리 내 수백 개의 아이콘, 특정 보스 몬스터, 무거운 고해상도 텍스처/사운드 등.
  • 선언 방법:
    • 인스턴스(객체/에셋) 참조: TSoftObjectPtr<UStaticMesh>
    • 클래스(붕어빵 틀) 참조: TSoftClassPtr<AActor>

2. 소프트 참조 로드 방식 (동기 vs 비동기)

① 동기 로드 (Synchronous Load)

메모리에 올릴 때까지 메인 스레드(게임)가 멈춰서 기다리는 방식입니다.

C++
 
UPROPERTY(EditAnywhere)
TSoftObjectPtr<UStaticMesh> MySoftMesh;

void LoadWithSoftPtr()
{
    if (MySoftMesh.IsNull()) return;

    UStaticMesh* FinalMesh = nullptr;

    if (MySoftMesh.IsValid()) {
        // 이미 메모리에 로드된 상태면 바로 가져옴
        FinalMesh = MySoftMesh.Get(); 
    } else {
        // 메모리에 없으면 메인 스레드를 멈추고 강제로 로드!
        FinalMesh = MySoftMesh.LoadSynchronous(); 
    }
}

② 비동기 로드 (Asynchronous Load)

에셋 매니저를 통해 뒷단(서브 스레드)에서 로드하라고 시키고, 게임은 계속 진행하는 방식입니다. 로드가 끝나면 델리게이트를 통해 결과를 돌려받습니다.

C++
 
#include "Engine/AssetManager.h"      
#include "Engine/StreamableManager.h" 

void LoadAsync()
{
    if (MySoftMesh.IsValid()) {
        OnLoadCompleted(); // 이미 로드되어 있으면 바로 함수 실행
        return;
    }

    // 에셋 매니저의 StreamableManager를 통해 비동기 로드 요청
    FStreamableManager& Streamable = UAssetManager::GetStreamableManager();
    
    Streamable.RequestAsyncLoad(
        MySoftMesh.ToSoftObjectPath(), // 로드할 경로
        FStreamableDelegate::CreateUObject(this, &AMySoftReferenceTestActor::OnLoadCompleted) // 완료 시 호출될 델리게이트
    );
}

void OnLoadCompleted()
{
    // 비동기 로드가 끝나면 이 함수가 실행되며 에셋을 꺼내 씀!
    UStaticMesh* FinalMesh = MySoftMesh.Get();
}

3. 데이터 관리: DataTable vs DataAsset

수많은 아이템 데이터를 관리할 때 무엇을 써야 할까요? 상황에 따라 섞어 쓰는 것이 베스트입니다.

구분 DataTable (데이터 테이블) DataAsset (데이터 에셋)
특징 엑셀 형태의 표. 한눈에 보기 좋음. 개별 포장된 상품. 독립된 에셋 파일.
장점 공격력, 드랍률 등 1000개의 수치를 쫙 펴놓고 밸런스 비교/수정하기 좋음. 게임 중 특정 아이템 1개만 콕 집어서 메모리에 비동기 로드하기 좋음.
단점 행 하나만 필요해도 표 전체를 로드해야 함. 아이템이 1000개면 파일도 1000개라 전체 수치 비교/수정이 매우 불편함.

💡 핵심 전략: 기본 스탯은 DataTable로 기획자가 관리하고, 실제 무거운 메쉬나 사운드 같은 리소스는 DataAsset에 TSoftObjectPtr로 연결해 둔 뒤, 테이블에서 데이터 에셋의 ID만 참조하는 식으로 섞어 씁니다!

4. 실전: PrimaryDataAsset 구현 및 로드

에셋 매니저가 고유 ID를 통해 빠르게 에셋을 식별하고 로드할 수 있도록 UPrimaryDataAsset을 상속받아 사용합니다.

① PrimaryDataAsset 세팅 (데이터 구조 만들기)

C++
 
UCLASS()
class UMyItemData : public UPrimaryDataAsset
{
    GENERATED_BODY()

public:
    // 에셋 매니저가 이 파일을 식별할 수 있는 고유 ID 규칙 생성
    virtual FPrimaryAssetId GetPrimaryAssetId() const override
    {
        return FPrimaryAssetId(ItemType, GetFName());
    }

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FPrimaryAssetType ItemType;

    // 무거운 데이터는 반드시 TSoftObjectPtr로 선언!
    // meta = (AssetBundles = "Mesh")를 통해 특정 번들만 골라서 로드 가능
    UPROPERTY(EditAnywhere, meta = (AssetBundles = "Mesh"))
    TSoftObjectPtr<USkeletalMesh> ItemMesh;
};

(주의: 생성 후 프로젝트 세팅 -> Asset Manager에서 지정한 ItemType과 폴더 경로를 꼭 등록해야 엔진이 인식합니다!)

② 에셋 매니저로 특정 데이터 에셋 비동기 로드하기

C++
 
#include "Engine/AssetManager.h"

UPROPERTY(EditAnywhere)
FPrimaryAssetId ItemToLoad; // 에디터에서 로드할 대상의 ID 지정

// 로딩 진행 상황을 쥐고 있을 핸들 (안 들고 있으면 가비지 컬렉터가 날려버림!)
TSharedPtr<FStreamableHandle> LoadingHandle; 

void StartLoading()
{
    if (!ItemToLoad.IsValid()) return;

    UAssetManager& AssetManager = UAssetManager::Get();
    TArray<FName> Bundles; // 특정 번들 이름, 비워두면 전체 로드

    // ID를 주고 로드 요청! (끝나면 OnLoadFinished 호출)
    LoadingHandle = AssetManager.LoadPrimaryAsset(
        ItemToLoad,
        Bundles,
        FStreamableDelegate::CreateUObject(this, &AMyDataAssetTestActor::OnLoadFinished, ItemToLoad)
    );
}

// 로드 완료 후 콜백 함수
void OnLoadFinished(FPrimaryAssetId LoadedId)
{
    // 로드된 ID를 바탕으로 진짜 UMyItemData 객체를 캐스팅해서 꺼내옴
    UMyItemData* LoadedItem = Cast<UMyItemData>(UAssetManager::Get().GetPrimaryAssetObject(LoadedId));

    if (LoadedItem) {
        // 이제 LoadedItem->ItemMesh.Get() 등을 통해 사용 가능!
    }
}