일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- 선택정렬
- 애셋로드
- 델리게이트
- map
- 알고리즘
- dataasset
- UE_LOG
- UELOG
- moreeffectiveC++
- 약참조
- UML관련
- 람다사용정렬
- C++
- 람다
- enumasByue
- 언리얼엔진구조체
- 스마트포인터
- unorder_map
- 강참조
- UE4 커스텀로그
- 언리얼가비지컬렉터
- BFS
- 자료구조
- 크리티컬섹션
- 정렬알고리즘
- 데이터애셋
- C++최적화
- 프로그래머스
- stl
- 정렬
- Today
- Total
기억을 위한 기록들
[UNREAL] Asset Manager(애셋 매니저) 란? 본문
에픽게임즈에서 제공하는 Lyra 프로젝트 코드를 분석해 보다가 AssetManager라는 엔진의 프레임워크로 많지 않은 애셋들을 관리하길래 궁금해서 찾아보고 정리를 위해 작성하였습니다.
Asset Manager란?
- 언리얼엔진에서 제공하는 엔진 서브시스템과 같은 싱글톤 UObject 클래스
- 맵 또는 모드별로 존재하지 않음
- Asset Registry를 사용하여 언로딩된 애셋을 분류하고 쿼리 한다.
- 글로벌 애셋 로드 상태 유지
- 쿠킹과 비동기 로딩과 같같은 기존 시스템 통합
- 게임에 의해 오버라이딩되도록 설계
Asset Registry의 기능은?
에디터가 로드되면서 로드되지 않은 애셋에 대한 정보를 비동기적으로 모으는 에디터 서브시스템
- 에디터가 애셋을 로드하지 않고 목록을 만들 수 있도록 메모리에 저장.(Asset Manager를 위한 데이터 저장소 제공)
- 에디터 시작 또는 cook 시작 시 새로고침
- 대부분의 데이터는 패키지 게임에서도 사용할 수 있다.
Asset Manager가 존재하는 이유?
- 기존 ObjectLibrary의 확장
- 로드가능한 인벤토리 아이템 쿼리
- 블루프린트 클래스의 복잡성 줄이기
- 긴 로드 시간 및 높은 메모리 사용량 처리
- 히치 하드 참조에서 비동기 로드된 참조로의 지원 이동
- 다양한 클라/서버/메뉴/ 게임플레이 로딩 상태 처리
- 복잡한 패킹 및 청크 규칙 설정
언리얼엔진 애셋들은 크게 2가지로 Primary Asset과 Secondary Asset으로 나누어진다.
Primary Asset : 애셋 매니저가 직접적으로 처리하고 로딩 가능한 애셋들은 Primary Asset으로 분류된다.
Secondary Asset : 애셋 매니저가 직접적으로 처리하고 로딩 불가능한 애셋들(대부분은 Secondary Asset)이다. (텍스쳐 등 주요 애셋이 아닌 것
Chunking(청킹)이란?
- 개별 애셋을 서로 다른 pak/staged 파일에 할당
- 스테이징 된 파일당 청크 식별자 1개, 0은 기본 값입니다.
- bGenerateChunks 패키징 설정으로 활성화
- ChunkDependencyInfo를 사용하여 계층 생성
- 언어별 다운로드 등 플랫폼별 기능에 필요
- 새 ChunkDownloade 플러그인과 합계 동작.
사용 예 : Lyra 프로젝트 참고
void ALyraGameMode::HandleMatchAssignmentIfNotExpectingOne()
{
FPrimaryAssetId ExperienceId;
FString ExperienceIdSource;
//...중간 생략
// Final fallback to the default experience
if (!ExperienceId.IsValid())
{
if (TryDedicatedServerLogin())
{
// This will start to host as a dedicated server
return;
}
//@TODO: Pull this from a config setting or something
ExperienceId = FPrimaryAssetId(FPrimaryAssetType("LyraExperienceDefinition"), FName("B_LyraDefaultExperience"));
ExperienceIdSource = TEXT("Default");
}
OnMatchAssignmentGiven(ExperienceId, ExperienceIdSource);
}
프로젝트 설정에서 입력된 Primary Asset Type에 "LyraExperienceDefinition"가 어떻게 키보면 데이터 키 역할을 한다
그리고 본격적으로 로드하는 부분
void ULyraExperienceManagerComponent::StartExperienceLoad()
{
check(CurrentExperience != nullptr);
check(LoadState == ELyraExperienceLoadState::Unloaded);
UE_LOG(LogLyraExperience, Log, TEXT("EXPERIENCE: StartExperienceLoad(CurrentExperience = %s, %s)"),
*CurrentExperience->GetPrimaryAssetId().ToString(),
*GetClientServerContextString(this));
LoadState = ELyraExperienceLoadState::Loading;
ULyraAssetManager& AssetManager = ULyraAssetManager::Get();
TSet<FPrimaryAssetId> BundleAssetList;
TSet<FSoftObjectPath> RawAssetList;
BundleAssetList.Add(CurrentExperience->GetPrimaryAssetId());
for (const TObjectPtr<ULyraExperienceActionSet>& ActionSet : CurrentExperience->ActionSets)
{
if (ActionSet != nullptr)
{
BundleAssetList.Add(ActionSet->GetPrimaryAssetId());
}
}
// Load assets associated with the experience
TArray<FName> BundlesToLoad;
BundlesToLoad.Add(FLyraBundles::Equipped);
//@TODO: Centralize this client/server stuff into the LyraAssetManager
const ENetMode OwnerNetMode = GetOwner()->GetNetMode();
const bool bLoadClient = GIsEditor || (OwnerNetMode != NM_DedicatedServer);
const bool bLoadServer = GIsEditor || (OwnerNetMode != NM_Client);
if (bLoadClient)
{
BundlesToLoad.Add(UGameFeaturesSubsystemSettings::LoadStateClient);
}
if (bLoadServer)
{
BundlesToLoad.Add(UGameFeaturesSubsystemSettings::LoadStateServer);
}
TSharedPtr<FStreamableHandle> BundleLoadHandle = nullptr;
if (BundleAssetList.Num() > 0)
{
BundleLoadHandle = AssetManager.ChangeBundleStateForPrimaryAssets(BundleAssetList.Array(), BundlesToLoad, {}, false, FStreamableDelegate(), FStreamableManager::AsyncLoadHighPriority);
}
TSharedPtr<FStreamableHandle> RawLoadHandle = nullptr;
if (RawAssetList.Num() > 0)
{
RawLoadHandle = AssetManager.LoadAssetList(RawAssetList.Array(), FStreamableDelegate(), FStreamableManager::AsyncLoadHighPriority, TEXT("StartExperienceLoad()"));
}
// If both async loads are running, combine them
TSharedPtr<FStreamableHandle> Handle = nullptr;
if (BundleLoadHandle.IsValid() && RawLoadHandle.IsValid())
{
Handle = AssetManager.GetStreamableManager().CreateCombinedHandle({ BundleLoadHandle, RawLoadHandle });
}
else
{
Handle = BundleLoadHandle.IsValid() ? BundleLoadHandle : RawLoadHandle;
}
FStreamableDelegate OnAssetsLoadedDelegate = FStreamableDelegate::CreateUObject(this, &ThisClass::OnExperienceLoadComplete);
if (!Handle.IsValid() || Handle->HasLoadCompleted())
{
// Assets were already loaded, call the delegate now
FStreamableHandle::ExecuteDelegate(OnAssetsLoadedDelegate);
}
else
{
Handle->BindCompleteDelegate(OnAssetsLoadedDelegate);
Handle->BindCancelDelegate(FStreamableDelegate::CreateLambda([OnAssetsLoadedDelegate]()
{
OnAssetsLoadedDelegate.ExecuteIfBound();
}));
}
// This set of assets gets preloaded, but we don't block the start of the experience based on it
TSet<FPrimaryAssetId> PreloadAssetList;
//@TODO: Determine assets to preload (but not blocking-ly)
if (PreloadAssetList.Num() > 0)
{
AssetManager.ChangeBundleStateForPrimaryAssets(PreloadAssetList.Array(), BundlesToLoad, {});
}
}
Loading Best Pracitces
- 스크린 로딩 중에 로드만 동기화하거나, 입력에 대한 빠른 응답
- 모드 전환 중 번들 상태 변경 및 캐시 지우기
- 로드를 동기화 하거나, 메모리에 저장하기 위해 StreamableHandles 사용
- 딜리게이트와 로드 vs Preload + FallBack
- 절대 안전하지 않은 람다를 딜리게이트로 사용하지 마세요.
- 샘플 및 기존 시스템은 개선의 여지가 있다.
참고자료 :
https://www.youtube.com/watch?v=9MGHBU5eNu0