비동기 애셋 로딩

실행시간에 애셋을 로드/언로드하는 메서드입니다.

Windows
MacOS
Linux

UE4 에는 애셋 데이터 비동기 로딩을 훨씬 쉽게 할 수 있는 신규 시스템이 다수 도입, UE3 의 기존 시크프리 콘텐츠 패키지의 함수성 대다수를 대체하고 있습니다. 이 신규 시스템은 개발 도중이나 디바이스상에서 쿠킹된 데이터로 실행할 때나 동일하게 작동하므로, 필요에 따라 데이터를 로딩하기 위한 별도의 코드 패스를 유지할 필요가 없습니다. 필요에 따라 데이터를 로드 및 참조에 사용되는 방법은 일반적으로 두 가지 있습니다.

FSoftObjectPaths 와 TSoftObjectPtr

아티스트나 디자이너가 애셋을 참조하는 가장 쉬운 방법은 하드 포인터의 UProperty 를 만든 다음 카테고리를 지정하는 것입니다. UE4 에서 애셋을 참조하는 하드 UObject 포인터 프로퍼티가 있다면, 그 프로퍼티가 포함된 오브젝트가 (맵에 배치되거나, 게임인포같은 것에서 참조되어) 로드될 때 애셋이 로드됩니다. 주의를 기울이지 않으면 게임 시작시 100% 의 애셋이 로드되어버리는 수가 생깁니다. 아티스트/디자이너가 하드 포인터와 같은 UI 를 사용해서 특정 애셋에 대한 레퍼런스를 만들되, 항상 로드되지는 않도록 하려면, FSoftObjectPath 또는 TSoftObjectPtr 을 사용하면 됩니다.

FSoftObjectPath 는 애셋의 전체 이름으로 된 스트링이 들어있는 단순한 구조체입니다. 클래스에 이 유형의 프로퍼티를 만들면, 에디터에는 마치 UObject * 프로퍼티인 양 나타납니다. 쿠킹과 리디렉터도 제대로 처리되므로, SoftObjectPath 가 있다면 디바이스에서의 정상 작동도 보장됩니다. TSoftObjectPtr 은 기본적으로 FSoftObjectPath 를 감싸는 TWeakObjectPtr 이며, 에디터 UI 에서 특정 클래스만 선택되게끔 제한시킬 수 있도록 하기 위해서 특정 클래스로 고정됩니다. 참조된 애셋이 메모리에 존재한다면 TSoftObjectPtr.Get() 은 그것을 반환합니다. 존재하지 않는다면, ToSoftObjectPath() 을 호출하여 참조하는 애셋을 찾아내어 아래 설명된 메서드를 사용해서 로드한 다음 TSoftObjectPtr.Get() 을 다시 호출하여 역참조합니다.

TSoftObjectPtr 과 SoftObjectPath 는 아티스트나 디자이너가 레퍼런스를 수동 셋업하는 경우에는 좋지만, 특정 조건을 만족하는 애셋 질의 작업을 모든 애셋 로드 없이 하려는 경우, 애셋 레지스트리와 오브젝트 라이브러리를 사용하는 것이 좋습니다.

애셋 레지스트리와 오브젝트 라이브러리

애셋 레지스트리는 애셋에 대한 메타데이터를 저장하여 해당 애셋에 대한 검색 및 질의를 가능케 해주는 시스템입니다. 에디터에서는 콘텐츠 브라우저에 정보를 표시하기 위해 사용되나, 게임플레이 코드에서 현재 로드되지 않은 게임플레이 애셋에 대한 메타데이터 질의를 하는 데도 사용할 수 있습니다. 애셋에 대한 데이터를 검색 가능하게 만들려면, 프로퍼티에 "AssetRegistrySearchable" 태그를 추가해 줘야 합니다. 애셋 레지스트리에 대한 질의는 FAssetData 유형 오브젝트를 반환하는데, 여기에는 오브젝트에 대한 정보는 물론 검색가능한 것으로 마킹된 프로퍼티가 들어있는 키->값 짝의 맵도 포함됩니다.

로드되지 않은 애셋 그룹을 가지고 작업하는 가장 쉬운 방법은 ObjectLibrary 입니다. ObjectLibrary 는 로드된 오브젝트와 로드되지 않은 오브젝트의 경우 FAssetData 를 합친 목록이 들어있는 오브젝트로, 공유 베이스 클래스를 상속합니다. 오브젝트 라이브러리에 검색할 경로를 주는 것으로 로드하면, 그 경로에 있는 모든 애셋이 추가됩니다. 이는 매우 유용할 수 있는데, 콘텐츠 폴더 일부분을 각기 다른 유형으로 지정하고, 아티스트/디자이너는 마스터 목록을 수동 편집할 필요 없이 새 애셋을 추가할 수 있기 때문입니다. 오브젝트 라이브러리를 사용해서 AssetData 를 디스크에서 로드하는 방법 예제는 이렇습니다:

if (!ObjectLibrary)
{
       ObjectLibrary = UObjectLibrary::CreateLibrary(BaseClass, false, GIsEditor);
       ObjectLibrary->AddToRoot();
}
ObjectLibrary->LoadAssetDataFromPath(TEXT("/Game/PathWithAllObjectsOfSameType");
if (bFullyLoad)
{
       ObjectLibrary->LoadAssetsFromAssetData();
}

이 예제에서는 새 오브젝트 라이브러리를 생성하고, 베이스 클래스를 할당하면, 주어진 경로의 모든 애셋 데이터를 로드합니다. 그리고서 옵션을 통해 실제 애셋을 로드할 수도 있습니다. 애셋이 작은 경우 애셋을 전체 로드할 수도 있고, 쿠킹중인 경우 모두 쿠킹되도록 할 수도 있습니다. 쿠킹 도중 애셋 레지스트리 질의를 하고서 반환된 애셋을 로드하는 한, 오브젝트 라이브러리는 개발중인 데이터든 디바이스에서 쿠킹된 데이터든 똑같이 작동합니다. ObjectLibrary 에 애셋 데이터가 들어있는 상태라면, 질의를 통해 특정 애셋만 선택적으로 로드할 수 있습니다. 질의 방법에 대한 예제는 다음과 같습니다:

TArray<FAssetData> AssetDatas;
ObjectLibrary->GetAssetDataList(AssetDatas);

for (int32 i = 0; i < AssetDatas.Num(); ++i)
{
       FAssetData& AssetData = AssetDatas[i];

       const FString* FoundTypeNameString = AssetData.TagsAndValues.Find(GET_MEMBER_NAME_CHECKED(UAssetObject,TypeName));

       if (FoundTypeNameString && FoundTypeNameString->Contains(TEXT("FooType")))
       {
              return AssetData;
       }
}

이 예제에서는, 오브젝트 라이브러리에서 TypeName 칸에 "FooType" 이 들어있는 것들을 검색하여 처음 찾은 것을 반환합니다. 그 AssetData 가 생긴 후 ToStringReference() 를 호출하여 FSoftObjectPath 으로 변환하고 나면, 다음 시스템을 사용하여 비동기 로드가 가능합니다:

StreamableManager 와 비동기 로딩

이제 디스크의 애셋을 참조하는 FSoftObjectPath 가 생겼으니, 실제 비동기 로드는 어떻게 할까요? 가장 쉬운 방법은 FStreamableManager 입니다. 우선, FStreamableManager 를 만들어 줘야 하는데, 일종의 게임 전역 유일무이(singleton) 오브젝트, 이를테면 DefaultEngine.ini 에서 GameSingletonClassName 에 지정된 오브젝트에 넣는 것이 좋습니다. 그 후 거기에 FSoftObjectPath 를 전달한 다음 로드를 시작합니다. SynchronousLoad 는 단순한 로드 블록 후 오브젝트를 반환할 것입니다. 작은 오브젝트의 경우 이 메서드로 충분할 테지만, 메인 스레드를 너무 오래 붙잡아 둘 가능성이 있습니다. 그러한 경우에는 RequestAsyncLoad 를 사용해 줘야 하는데, 애셋 그룹을 비동기 로드한 다음 완료되면 델리게이트를 호출하는 것입니다. 예제입니다:

void UGameCheatManager::GrantItems()
{
       TArray<FSoftObjectPath> ItemsToStream;
       FStreamableManager& Streamable = UGameGlobals::Get().StreamableManager;
       for(int32 i = 0; i < ItemList.Num(); ++i)
       {
              ItemsToStream.AddUnique(ItemList[i].ToStringReference());
       }
       Streamable.RequestAsyncLoad(ItemsToStream, FStreamableDelegate::CreateUObject(this, &UGameCheatManager::GrantItemsDeferred));
}

void UGameCheatManager::GrantItemsDeferred()
{
       for(int32 i = 0; i < ItemList.Num(); ++i)
       {
              UGameItemData* ItemData = ItemList[i].Get();
              if(ItemData)
              {
                     MyPC->GrantItem(ItemData);
              }
       }
}

이 예제에서 ItemList 는 TArray< TSoftObjectPtr<UGameItem> > 이며, 에디터에서 디자이너에 의해 수정된 것입니다. 코드는 그 리스트에 대해 반복하여 StringReferences 로 변환시킨 다음 로드를 위한 대기열에 등록시킵니다. 그 아이템 전부가 로드되(거나 없어서 실패하)면 전달된 델리게이트를 호출합니다. 그러면 그 델리게이트는 같은 아이템 리스트에 대해 반복하여 그 역참조를 구한 다음 플레이어에게 전해줍니다. StreamableManager 는 델리게이트가 호출될 때까지 로드하는 애셋에 대한 하드 레퍼런스를 유지시켜, 비동기 로드하려 했던 오브젝트의 델리게이트가 호출되기도 전에 가비지 컬렉팅되는 일이 없도록 합니다. 델리게이트가 호출된 이후에는 그 레퍼런스가 해제되므로, 계속해서 남아있도록 하려면 어딘가에 하드 레퍼런스를 해 줘야 합니다.

같은 메서드를 사용해서 FAssetData 를 비동기 로드할 수도 있는데, 그냥 ToStringReference 를 호출한 다음 배열에 추가시키고 델리게이트를 붙여 RequestAsyncLoad 를 호출해 주면 됩니다. 델리게이트는 원하는 무엇이든 될 수 있으므로, 원한다면 페이로드 정보와 함께 전달해 줄 수 있습니다. 위에 언급한 메서드를 조합하면 게임 내 어느 애셋에 대해서도 효율적인 로드가 가능한 시스템을 구축할 수 있을 것입니다. 메모리에 직접 접근하는 게임플레이 코드가 비동기 로드를 처리하도록 변환해 주는 작업에 시간이 조금 걸리겠지만, 그 이후에는 게임에서 발생하는 멈춤 현상이나 차지하는 메모리 양이 훨씬 줄어들 것입니다.

새로운 언리얼 엔진 4 문서 사이트에 오신 것을 환영합니다!

문서 사이트에 대한 의견을 모을 수 있는 피드백 시스템을 포함해서 여러가지 새로운 기능을 준비하고 있습니다. 아래 Documentation Feedback 포럼(영문) 또는 언리얼 엔진 네이버 공식 카페(한글) 중 편하신 곳에 의견이나 문제점을 알려 주세요.

새 시스템이 준비되면 알려 드리겠습니다.

네이버 카페
공식 포럼