리소스 관리와 최적화
언리얼 엔진에서 게임 리소스를 효과적으로 관리하고 최적화하는 것은 게임의 성능과 품질을 결정짓는 중요한 요소입니다.
이 절에서는 C++ 프로그래밍 관점에서 리소스 관리와 최적화 기법을 살펴보겠습니다.
리소스 로딩 및 언로딩 전략
동기적 로딩
간단하지만 성능에 영향을 줄 수 있습니다.
UStaticMesh* Mesh = LoadObject<UStaticMesh>(nullptr, TEXT("/Game/Meshes/MyMesh"));
if (Mesh)
{
// 메시 사용
}
비동기 로딩
게임플레이를 방해하지 않고 리소스를 로드합니다.
TAsyncLoadingRequest Request = UAssetManager::GetStreamableManager().RequestAsyncLoad(
FSoftObjectPath("/Game/Meshes/MyMesh"),
FStreamableDelegate::CreateUObject(this, &AMyActor::OnMeshLoaded)
);
void AMyActor::OnMeshLoaded()
{
UStaticMesh* Mesh = Cast<UStaticMesh>(Request.GetLoadedAsset());
if (Mesh)
{
// 로드된 메시 사용
}
}
리소스 언로딩
더 이상 필요하지 않은 리소스를 언로드하여 메모리를 확보합니다.
UAssetManager::GetStreamableManager().Unload(FSoftObjectPath("/Game/Meshes/MyMesh"));
리소스 스트리밍 시스템 활용
언리얼 엔진의 스트리밍 시스템을 활용하여 대용량 리소스를 효율적으로 관리할 수 있습니다.
UPROPERTY(VisibleAnywhere, Category = "Streaming")
UStreamableRenderAsset* StreamableAsset;
// 스트리밍 설정
StreamableAsset->bUseCinematicImportance = true;
StreamableAsset->StreamingDistanceMultiplier = 1.5f;
리소스 레퍼런싱 및 캐싱
소프트 레퍼런스 사용
리소스를 직접 로드하지 않고 참조만 유지합니다.
UPROPERTY(EditDefaultsOnly, Category = "Resources")
TSoftObjectPtr<UStaticMesh> MeshReference;
// 사용 시 로드
if (UStaticMesh* Mesh = MeshReference.LoadSynchronous())
{
// 메시 사용
}
리소스 캐싱
자주 사용되는 리소스를 캐시하여 로딩 시간을 단축합니다.
TMap<FName, UObject*> ResourceCache;
UObject* GetCachedResource(FName ResourceName)
{
if (UObject** FoundResource = ResourceCache.Find(ResourceName))
{
return *FoundResource;
}
UObject* NewResource = LoadObject<UObject>(nullptr, *ResourceName.ToString());
ResourceCache.Add(ResourceName, NewResource);
return NewResource;
}
메모리 내 리소스 관리 전략
리소스 풀링
동일한 유형의 리소스를 재사용하여 메모리 할당/해제를 최소화합니다.
class FResourcePool
{
public:
UObject* GetResource()
{
if (Pool.Num() > 0)
{
return Pool.Pop();
}
return NewObject<UObject>();
}
void ReturnResource(UObject* Resource)
{
Pool.Push(Resource);
}
private:
TArray<UObject*> Pool;
};
리소스 생명주기 관리
리소스의 생성, 사용, 해제를 명확히 관리합니다.
class FResourceLifecycleManager
{
public:
void CreateResource(FName ResourceName)
{
// 리소스 생성 및 초기화
}
void UseResource(FName ResourceName)
{
// 리소스 사용
}
void ReleaseResource(FName ResourceName)
{
// 리소스 해제
}
};
대용량 리소스 처리 시 고려사항
- 청크 단위 로딩 : 대용량 리소스를 작은 청크로 나누어 로드
- 우선순위 기반 로딩 : 중요도에 따라 리소스 로딩 순서 조정
- 메모리 버짓 관리 : 전체 메모리 사용량을 모니터링하고 제한
class FMemoryBudgetManager
{
public:
bool CanLoadResource(uint64 ResourceSize)
{
return (CurrentMemoryUsage + ResourceSize) <= MemoryBudget;
}
void UpdateMemoryUsage(uint64 Delta)
{
CurrentMemoryUsage += Delta;
}
private:
uint64 MemoryBudget = 1024 * 1024 * 1024; // 1GB
uint64 CurrentMemoryUsage = 0;
};
리소스 최적화 도구 및 기법
LOD (Level of Detail) 시스템 활용
거리에 따라 메시의 복잡도를 조절합니다.
UPROPERTY(VisibleAnywhere, Category = "Optimization")
UStaticMeshComponent* MeshComponent;
void AMyActor::SetupLOD()
{
if (MeshComponent)
{
MeshComponent->SetForcedLodModel(1); // LOD 레벨 설정
MeshComponent->bOverrideLightMapRes = true;
MeshComponent->OverriddenLightMapRes = 64;
}
}
텍스처 압축
텍스처 크기를 줄여 메모리 사용량과 로딩 시간을 감소시킵니다.
// 텍스처 에셋 설정 (C++에서 직접 설정하는 것은 권장되지 않으므로, 에디터에서 설정)
// MyTexture->CompressionSettings = TC_BC7;
// MyTexture->LODGroup = TEXTUREGROUP_World;
플랫폼별 리소스 최적화 전략
다양한 플랫폼에 대응하기 위해 플랫폼별 최적화 전략을 수립합니다.
void AMyGameMode::OptimizeForPlatform()
{
if (UGameplayStatics::GetPlatformName() == "Android" || UGameplayStatics::GetPlatformName() == "IOS")
{
// 모바일 플랫폼 최적화 설정
UStaticMesh::SetMobileMinLODOverride(2);
}
else
{
// 데스크톱 플랫폼 설정
}
}
리소스 관리가 게임 성능에 미치는 영향
- 로딩 시간 : 효율적인 리소스 관리는 로딩 시간을 크게 단축시킵니다.
- 메모리 사용 : 적절한 리소스 관리로 메모리 사용량을 최적화할 수 있습니다.
- 프레임 레이트 : 리소스의 효율적인 스트리밍과 언로딩으로 프레임 레이트를 개선할 수 있습니다.
- 디스크 I/O : 최적화된 리소스 관리는 디스크 접근을 줄여 I/O 성능을 향상시킵니다.
개발 방법론 및 Best Practices
1. 리소스 프로파일링
- 정기적으로 리소스 사용량을 프로파일링하여 병목 지점을 식별합니다.
void AMyGameMode::ProfileResources()
{
FResourceBroker::LogResourceInfo();
}
2. 동적 리소스 관리
- 게임플레이 상황에 따라 리소스를 동적으로 로드하고 언로드합니다.
void AMyGameMode::EnterNewArea(FName AreaName)
{
UnloadResourcesForPreviousArea();
LoadResourcesForNewArea(AreaName);
}
3. 리소스 번들링
- 관련된 리소스를 함께 번들링하여 로딩 효율성을 높입니다.
void AMyResourceManager::LoadResourceBundle(FName BundleName)
{
TArray<FSoftObjectPath> BundleAssets;
// 번들에 포함된 에셋 경로 추가
UAssetManager::GetStreamableManager().RequestAsyncLoad(BundleAssets);
}
4. 리소스 의존성 관리
- 리소스 간의 의존성을 명확히 하여 불필요한 로딩을 방지합니다.
class FDependencyManager
{
public:
void AddDependency(FName Resource, FName Dependency)
{
Dependencies.FindOrAdd(Resource).Add(Dependency);
}
TArray<FName> GetDependencies(FName Resource)
{
return Dependencies.FindOrAdd(Resource);
}
private:
TMap<FName, TArray<FName>> Dependencies;
};
5. 리소스 프리페칭
- 예측 가능한 리소스를 미리 로드하여 로딩 시간을 분산시킵니다.
void AMyGameMode::PrefetchResources()
{
TArray<FSoftObjectPath> ResourcesForNextLevel;
// 다음 레벨에 필요한 리소스 경로 추가
UAssetManager::GetStreamableManager().RequestAsyncLoad(ResourcesForNextLevel);
}