일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- UE4 커스텀로그
- 람다사용정렬
- 크리티컬섹션
- 자료구조
- 델리게이트
- C++최적화
- 프로그래머스
- 애셋로드
- 강참조
- 언리얼가비지컬렉터
- 알고리즘
- UE_LOG
- 스마트포인터
- stl
- BFS
- UML관련
- 람다
- 언리얼엔진구조체
- 약참조
- map
- 정렬
- moreeffectiveC++
- C++
- unorder_map
- 정렬알고리즘
- enumasByue
- 데이터애셋
- UELOG
- 선택정렬
- dataasset
- Today
- Total
기억을 위한 기록들
[UE4-CPP] [애셋로딩에 관하여-2] 비동기식 애셋로딩 본문
*언리얼엔진4 문서 참조하여 작성
애셋 데이터를 참조하고 요청 시 로드하는 데 사용할 수 있는 일반적인 방법에는 두 가지가 있습니다.
FSoftObjectPath와 TSoftObjectPtr
아티스트나 디자이너가 애셋을 참조하도록 하는 가장 쉬운 방법은 하드 포인터의 UPROPERTY를 만들고 카테고리를 지정하는 것이다. UE4에서 애셋을 참조하는 하드 UObject 포인터 UPROPERTY을 가지고 있다면, 그 애셋은 그 속성을 포함하는 객체를 로드 할 때 로드될 것이다.
조심하지 않으면 게임 시작 시 모든 애셋을 100%를 로드하게 된다.
아티스트 혹은 디자이너가 항상 참조된 애셋을 로드하지 않고 동일한 UI를 하드 포인터와 함께 사용하여 특정 애셋에 대한 참조를 수행할 수 있도록 하려면 FSoftObjectPath 또는 TSoftObjectPtr을 사용해야한다.
FSoftObjectPath 란?
애셋의 전체 이름을 가진 문자열을 포함하는 단순 구조체입니다.
/**
* A struct that contains a string reference to an object, either a top level asset or a subobject.
* @note The full C++ class is located here: Engine\Source\Runtime\CoreUObject\Public\UObject\SoftObjectPath.h
*/
USTRUCT(noexport, BlueprintType, meta=(HasNativeMake="Engine.KismetSystemLibrary.MakeSoftObjectPath", HasNativeBreak="Engine.KismetSystemLibrary.BreakSoftObjectPath"))
struct FSoftObjectPath
{
/** Asset path, patch to a top level object in a package */
UPROPERTY()
FName AssetPathName;
/** Optional FString for subobject within an asset */
UPROPERTY()
FString SubPathString;
};
TSoftObjectPtr 은 기본적으로 FSoftObjectPath 를 감싸는 TWeakObjectPtr 이며, 에디터 UI 에서 특정 클래스만 선택되게끔 제한시킬 수 있도록 하기 위해서 특정 클래스로 고정됩니다.
참조된 애셋이 메모리에 있는 경우 TSoftObjectPtr.Get()가 해당 포인터를 반환한다. 존재하지 않는다면, ToSoftObjectPath() 을 호출하여 참조하는 애셋을 찾아내어 아래 설명된 메서드를 사용해서 로드한 다음 TSoftObjectPtr.Get() 을 다시 호출하여 역참조합니다.
TSoftObjectPtr 과 SoftObjectPath 는 아티스트나 디자이너가 레퍼런스를 수동으로 설정하는 경우에는 좋지만,
특정 요구 사항을 충족하는 자산을 찾기 위해 쿼리 등의 작업을 수행하여 해당 자산을 모두 로드하지 않고 '애셋 레지스트리' 및 '오브젝트 라이브러리'를 사용하는 것이 좋습니다.
애셋 레지스트리와 오브젝트 라이브러리
애셋 레지스트리?
애셋에 대한 메타데이터를 저장하여 해당 애셋에 대한 검색 및 질의를 가능케 해주는 시스템입니다.
에디터에서는 콘텐츠 브라우저에 정보를 표시하기 위해 사용되나, 게임플레이 코드에서 현재 로드되지 않은 게임플레이 애셋에 대한 메타데이터 질의를 하는 데도 사용할 수 있습니다.
애셋에 대한 데이터를 검색 가능하게 만들려면, 프로퍼티에 "AssetRegistrySearchable" 태그를 추가해 줘야 합니다.
애셋 레지스트리에 대한 질의는 FAssetData 유형 오브젝트를 반환하는데, 여기에는 오브젝트에 대한 정보는 물론 검색가능한 것으로 마킹된 프로퍼티가 들어있는 키->값 짝의 맵도 포함됩니다.
오브젝트 라이브러리?
로드되지 않은 애셋 그룹을 가지고 작업하는 가장 쉬운 방법은 오브젝트 라이브러리입니다.
오브젝트 라이브러리
- 로드된 오브젝트
- 공유 기본 클래스에서 상속된 언로드된 오브젝트에 대한 FAssetData목록을 포함하는 오브젝트
검색할 경로를 지정하여 오브젝트 라이브러리를 로드하면 해당 경로에 모든 애셋이 추가됩니다.
이는 매우 유용할 수 있는데, 콘텐츠 폴더 일부분을 각기 다른 유형으로 지정하고, 아티스트/디자이너는 마스터 목록을 수동 편집할 필요 없이 새 애셋을 추가할 수 있기 때문입니다. 오브젝트 라이브러리를 사용해서 AssetData 를 디스크에서 로드하는 방법 예제는 이렇습니다:
if (!ObjectLibrary)
{
ObjectLibrary = UObjectLibrary::CreateLibrary(BaseClass, false, GIsEditor);
ObjectLibrary->AddToRoot();
}
ObjectLibrary->LoadAssetDataFromPath(TEXT("/Game/PathWithAllObjectsOfSameType");
if (bFullyLoad)
{
ObjectLibrary->LoadAssetsFromAssetData();
}
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;
}
}
위 예시에서는 오브젝트라이브러리에서 TEXT("FooType")을 포함하는 TypeName 필드가 있는 모든 항목을 검색한 다음 가장 먼저 찾은 항목을 반환합니다.
FAssetData& AssetData가 생겨서 있으면 ToStringReference()를 호출하여 FSoftObjectPath로 변환한 후 다음 시스템을 사용하여 비동기식으로 로드할 수 있습니다.
StreamableManager 와 비동기 로딩
이제 디스크의 애셋을 참조하는 FSoftObjectPath 가 생겼으니, 실제 비동기 로드는 어떻게 할까요?
가장 쉬운 방법은 FStreamableManager 입니다. 우선, FStreamableManager 를 만들어 줘야 하는데, 일종의 게임 전역 싱글턴 오브젝트, 이를테면 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 를 호출해 주면 됩니다.
델리게이트는 원하는 무엇이든 될 수 있으므로, 원한다면 페이로드 정보와 함께 전달해 줄 수 있습니다. 위에 언급한 메서드를 조합하면 게임 내 어느 애셋에 대해서도 효율적인 로드가 가능한 시스템을 구축할 수 있을 것입니다.
메모리에 직접 접근하는 게임플레이 코드가 비동기 로드를 처리하도록 변환해 주는 작업에 시간이 조금 걸리겠지만, 그 이후에는 게임에서 발생하는 멈춤 현상이나 차지하는 메모리 양이 훨씬 줄어들 것입니다.
void MyComponent::RoadAnimAsset(const FString& path )
{
FSoftObjectPath asset( path );
FStreamableManager assetLoader;
TAssetPtr<UAnimMontage> animMotage = assetLoader.LoadSynchronous( asset );
auto pCharacter = Cast<ACharacter>( GetOwner() );
if( pCharacter )
{
pCharacter->PlayAnimMontage( animMotage.Get() );
}
}
'UnrealEngine > UE4 - CPP' 카테고리의 다른 글
[UE4-CPP] 데이터 유형 형식<CSV> vs <Json> (0) | 2022.02.22 |
---|---|
[UE4 - CPP] TFunctionRef<T>에 관하여 (0) | 2022.02.02 |
[UE4-CPP] TMap에 관하여 (0) | 2021.11.09 |
[UE4-CPP] FHITResult 분석 (0) | 2021.10.07 |
[UE4-CPP] UE4에서 UUID와 Mac Address 가져오기 (0) | 2021.09.13 |