에셋 관리와 로딩
언리얼 엔진에서 효율적인 에셋 관리와 로딩은 게임의 성능과 사용자 경험에 큰 영향을 미칩니다.
이 절에서는 C++를 사용하여 에셋을 관리하고 로딩하는 다양한 기법을 살펴보겠습니다.
에셋 레퍼런스 시스템 이해 및 활용
언리얼 엔진의 에셋 레퍼런스 시스템은 에셋의 로딩, 언로딩, 가비지 컬렉션을 관리합니다.
UCLASS()
class MYGAME_API AMyActor : public AActor
{
GENERATED_BODY()
public:
// 소프트 레퍼런스 (지연 로딩 가능)
UPROPERTY(EditAnywhere, Category = "Assets")
TSoftObjectPtr<UStaticMesh> MeshAsset;
// 하드 레퍼런스 (즉시 로딩)
UPROPERTY(EditAnywhere, Category = "Assets")
UStaticMesh* LoadedMesh;
void LoadMeshAsset()
{
if (MeshAsset.IsValid())
{
LoadedMesh = MeshAsset.Get();
}
else
{
LoadedMesh = MeshAsset.LoadSynchronous();
}
}
};
동적 에셋 로딩 구현
런타임에 에셋을 동적으로 로딩하는 방법
void AMyActor::LoadDynamicAsset(const FString& AssetPath)
{
UStaticMesh* LoadedAsset = Cast<UStaticMesh>(StaticLoadObject(UStaticMesh::StaticClass(), nullptr, *AssetPath));
if (LoadedAsset)
{
// 로드된 에셋 사용
UStaticMeshComponent* MeshComponent = GetComponentByClass<UStaticMeshComponent>();
if (MeshComponent)
{
MeshComponent->SetStaticMesh(LoadedAsset);
}
}
}
비동기 에셋 로딩 기법
대용량 에셋을 비동기적으로 로딩하여 게임 성능 향상
void AMyActor::LoadAssetAsync(const FSoftObjectPath& AssetPath)
{
TAsyncLoadingRequest<UStaticMesh> AsyncRequest = UAssetManager::GetStreamableManager().RequestAsyncLoad(
AssetPath,
FStreamableDelegate::CreateUObject(this, &AMyActor::OnAssetLoaded)
);
}
void AMyActor::OnAssetLoaded()
{
UStaticMesh* LoadedMesh = Cast<UStaticMesh>(AssetPath.ResolveObject());
if (LoadedMesh)
{
// 로드된 메시 사용
}
}
에셋 번들링 전략
에셋 번들을 사용하여 관련 에셋을 그룹화하고 효율적으로 관리하는 방법
UCLASS()
class MYGAME_API UMyAssetBundle : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Category = "Bundle")
TArray<FSoftObjectPath> BundledAssets;
virtual FPrimaryAssetId GetPrimaryAssetId() const override
{
return FPrimaryAssetId("AssetBundle", GetFName());
}
};
// 에셋 번들 로딩
void AMyGameMode::LoadAssetBundle(const FPrimaryAssetId& BundleId)
{
UAssetManager::Get().LoadPrimaryAsset(
BundleId,
TArray<FName>(),
FStreamableDelegate::CreateUObject(this, &AMyGameMode::OnBundleLoaded)
);
}
스트리밍 레벨과 에셋 관리
스트리밍 레벨을 사용하여 대규모 월드의 에셋을 효율적으로 관리하는 방법
UCLASS()
class MYGAME_API AMyLevelStreamer : public AActor
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable)
void StreamInLevel(const FName& LevelName)
{
FLatentActionInfo LatentInfo;
LatentInfo.CallbackTarget = this;
LatentInfo.ExecutionFunction = "OnLevelLoaded";
LatentInfo.UUID = FGuid::NewGuid().A;
LatentInfo.Linkage = 0;
UGameplayStatics::LoadStreamLevel(this, LevelName, true, true, LatentInfo);
}
UFUNCTION()
void OnLevelLoaded()
{
// 레벨 로드 완료 후 처리
}
};
메모리 내 에셋 캐싱 기법
자주 사용되는 에셋을 메모리에 캐싱하여 로딩 시간 단축하는 방법
UCLASS()
class MYGAME_API UAssetCache : public UObject
{
GENERATED_BODY()
private:
UPROPERTY()
TMap<FString, UObject*> CachedAssets;
public:
template<class T>
T* GetOrLoadAsset(const FString& AssetPath)
{
if (UObject** FoundAsset = CachedAssets.Find(AssetPath))
{
return Cast<T>(*FoundAsset);
}
T* LoadedAsset = Cast<T>(StaticLoadObject(T::StaticClass(), nullptr, *AssetPath));
if (LoadedAsset)
{
CachedAssets.Add(AssetPath, LoadedAsset);
}
return LoadedAsset;
}
};
대용량 에셋 처리 전략
대용량 에셋을 효율적으로 처리하기 위한 전략
- 에셋 분할 : 대용량 에셋을 더 작은 단위로 분할하여 관리
- LOD (Level of Detail) 시스템 활용 : 거리에 따라 다른 상세도의 에셋 사용
- 스트리밍 텍스처 : 필요에 따라 텍스처 해상도를 동적으로 조정
UCLASS()
class MYGAME_API UMyStreamingTexture : public UStreamableRenderAsset
{
GENERATED_BODY()
public:
virtual void BeginDestroy() override
{
Super::BeginDestroy();
// 스트리밍 리소스 정리
}
virtual void CancelPendingStreamingRequest() override
{
// 진행 중인 스트리밍 요청 취소
}
};
에셋 로딩 시 성능 최적화 기법
- 백그라운드 로딩 : 게임플레이에 영향을 주지 않도록 별도 스레드에서 에셋 로딩
- 에셋 프리로딩 : 예상되는 에셋을 미리 로딩하여 로딩 시간 단축
- 에셋 언로딩 : 불필요한 에셋을 적시에 언로딩하여 메모리 사용 최적화
void AMyGameMode::PreloadAssets()
{
TArray<FSoftObjectPath> AssetsToLoad;
// AssetsToLoad에 프리로드할 에셋 경로 추가
UAssetManager::GetStreamableManager().RequestAsyncLoad(
AssetsToLoad,
FStreamableDelegate::CreateUObject(this, &AMyGameMode::OnAssetsPreloaded)
);
}
다양한 플랫폼에 따른 에셋 관리 전략
- 모바일 : 메모리 제약을 고려한 에셋 최적화 (텍스처 압축, 메시 단순화 등)
- 콘솔 : 플랫폼별 특화된 에셋 포맷 및 로딩 전략 사용
- PC : 다양한 하드웨어 사양을 고려한 스케일러블 에셋 관리
##if PLATFORM_ANDROID || PLATFORM_IOS
##define USE_COMPRESSED_TEXTURES 1
##else
##define USE_COMPRESSED_TEXTURES 0
##endif
void AMyGameMode::LoadPlatformSpecificAssets()
{
##if USE_COMPRESSED_TEXTURES
// 압축된 텍스처 로딩
##else
// 비압축 텍스처 로딩
##endif
}
에셋 버전 관리
에셋 버전 관리를 통해 에셋 업데이트 및 호환성을 유지하는 방법
USTRUCT()
struct FAssetVersion
{
GENERATED_BODY()
UPROPERTY()
int32 MajorVersion;
UPROPERTY()
int32 MinorVersion;
};
UCLASS()
class MYGAME_API UVersionedAsset : public UObject
{
GENERATED_BODY()
public:
UPROPERTY()
FAssetVersion Version;
virtual void Serialize(FArchive& Ar) override
{
Super::Serialize(Ar);
Ar << Version;
if (Ar.IsLoading())
{
// 버전에 따른 로딩 로직
}
}
};
에셋 의존성 처리
에셋 간 의존성을 관리하여 일관성 있는 에셋 로딩 보장하기
UCLASS()
class MYGAME_API UMyAssetManager : public UAssetManager
{
GENERATED_BODY()
public:
virtual void StartInitialLoading() override
{
Super::StartInitialLoading();
// 의존성 그래프 구축
BuildAssetDependencyGraph();
}
private:
void BuildAssetDependencyGraph()
{
// 에셋 간 의존성 분석 및 그래프 구축
}
};
런타임 에셋 생성 및 파괴
동적으로 에셋을 생성하고 파괴하는 Best Practices
UCLASS()
class MYGAME_API AAssetGenerator : public AActor
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable)
UStaticMesh* CreateDynamicMesh()
{
UStaticMesh* NewMesh = NewObject<UStaticMesh>(this);
// 메시 데이터 설정
NewMesh->CreateMeshDescription();
NewMesh->BuildFromMeshDescription();
return NewMesh;
}
UFUNCTION(BlueprintCallable)
void DestroyDynamicAsset(UObject* AssetToDestroy)
{
if (AssetToDestroy && !AssetToDestroy->IsUnreachable())
{
AssetToDestroy->ConditionalBeginDestroy();
}
}
};
효율적인 에셋 관리와 로딩은 게임의 성능과 사용자 경험에 크게 기여합니다.
에셋 레퍼런스 시스템을 이해하고 활용하며, 동적 및 비동기 로딩 기법을 적절히 사용하는 것이 중요합니다