언리얼 엔진에서 효율적인 에셋 관리와 로딩은 게임의 성능과 사용자 경험에 큰 영향을 미칩니다.
이 절에서는 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 ();
}
}
};
Copy
동적 에셋 로딩 구현
런타임에 에셋을 동적으로 로딩하는 방법
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);
}
}
}
Copy
비동기 에셋 로딩 기법
대용량 에셋을 비동기적으로 로딩하여 게임 성능 향상
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)
{
// 로드된 메시 사용
}
}
Copy
에셋 번들링 전략
에셋 번들을 사용하여 관련 에셋을 그룹화하고 효율적으로 관리하는 방법
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)
);
}
Copy
스트리밍 레벨과 에셋 관리
스트리밍 레벨을 사용하여 대규모 월드의 에셋을 효율적으로 관리하는 방법
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 ()
{
// 레벨 로드 완료 후 처리
}
};
Copy
메모리 내 에셋 캐싱 기법
자주 사용되는 에셋을 메모리에 캐싱하여 로딩 시간 단축하는 방법
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;
}
};
Copy
대용량 에셋 처리 전략
대용량 에셋을 효율적으로 처리하기 위한 전략
에셋 분할 : 대용량 에셋을 더 작은 단위로 분할하여 관리
LOD (Level of Detail) 시스템 활용 : 거리에 따라 다른 상세도의 에셋 사용
스트리밍 텍스처 : 필요에 따라 텍스처 해상도를 동적으로 조정
UCLASS ()
class MYGAME_API UMyStreamingTexture : public UStreamableRenderAsset
{
GENERATED_BODY ()
public:
virtual void BeginDestroy () override
{
Super :: BeginDestroy ();
// 스트리밍 리소스 정리
}
virtual void CancelPendingStreamingRequest () override
{
// 진행 중인 스트리밍 요청 취소
}
};
Copy
에셋 로딩 시 성능 최적화 기법
백그라운드 로딩 : 게임플레이에 영향을 주지 않도록 별도 스레드에서 에셋 로딩
에셋 프리로딩 : 예상되는 에셋을 미리 로딩하여 로딩 시간 단축
에셋 언로딩 : 불필요한 에셋을 적시에 언로딩하여 메모리 사용 최적화
void AMyGameMode :: PreloadAssets ()
{
TArray<FSoftObjectPath> AssetsToLoad;
// AssetsToLoad에 프리로드할 에셋 경로 추가
UAssetManager :: GetStreamableManager (). RequestAsyncLoad (
AssetsToLoad,
FStreamableDelegate :: CreateUObject ( this , & AMyGameMode ::OnAssetsPreloaded)
);
}
Copy
다양한 플랫폼에 따른 에셋 관리 전략
모바일 : 메모리 제약을 고려한 에셋 최적화 (텍스처 압축, 메시 단순화 등)
콘솔 : 플랫폼별 특화된 에셋 포맷 및 로딩 전략 사용
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
}
Copy
에셋 버전 관리
에셋 버전 관리를 통해 에셋 업데이트 및 호환성을 유지하는 방법
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 ())
{
// 버전에 따른 로딩 로직
}
}
};
Copy
에셋 의존성 처리
에셋 간 의존성을 관리하여 일관성 있는 에셋 로딩 보장하기
UCLASS ()
class MYGAME_API UMyAssetManager : public UAssetManager
{
GENERATED_BODY ()
public:
virtual void StartInitialLoading () override
{
Super :: StartInitialLoading ();
// 의존성 그래프 구축
BuildAssetDependencyGraph ();
}
private:
void BuildAssetDependencyGraph ()
{
// 에셋 간 의존성 분석 및 그래프 구축
}
};
Copy
런타임 에셋 생성 및 파괴
동적으로 에셋을 생성하고 파괴하는 베스트 프랙티스
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 ();
}
}
};
Copy
효율적인 에셋 관리와 로딩은 게임의 성능과 사용자 경험에 크게 기여합니다. 에셋 레퍼런스 시스템을 이해하고 활용하며, 동적 및 비동기 로딩 기법을 적절히 사용하는 것이 중요합니다. 에셋 번들링, 스트리밍 레벨, 메모리 캐싱 등의 전략을 통해 대규모 게임에서도 효율적인 에셋 관리가 가능합니다.
플랫폼별 특성을 고려한 에셋 관리 전략을 수립하고, 버전 관리와 의존성 처리를 통해 에셋의 일관성과 호환성을 유지해야 합니다. 런타임 에셋 생성 및 파괴 시에는 메모리 관리에 주의를 기울여야 합니다.
이러한 기법들을 적절히 조합하여 사용하면, 다양한 규모와 플랫폼의 게임에서 효율적이고 안정적인 에셋 관리 시스템을 구축할 수 있습니다.