icon안동민 개발노트

리소스 관리와 최적화


 언리얼 엔진에서 게임 리소스를 효과적으로 관리하고 최적화하는 것은 게임의 성능과 품질을 결정짓는 중요한 요소입니다.

 이 절에서는 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)
    {
        // 리소스 해제
    }
};

대용량 리소스 처리 시 고려사항

  1. 청크 단위 로딩 : 대용량 리소스를 작은 청크로 나누어 로드
  2. 우선순위 기반 로딩 : 중요도에 따라 리소스 로딩 순서 조정
  3. 메모리 버짓 관리 : 전체 메모리 사용량을 모니터링하고 제한
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
    {
        // 데스크톱 플랫폼 설정
    }
}

리소스 관리가 게임 성능에 미치는 영향

  1. 로딩 시간 : 효율적인 리소스 관리는 로딩 시간을 크게 단축시킵니다.
  2. 메모리 사용 : 적절한 리소스 관리로 메모리 사용량을 최적화할 수 있습니다.
  3. 프레임 레이트 : 리소스의 효율적인 스트리밍과 언로딩으로 프레임 레이트를 개선할 수 있습니다.
  4. 디스크 I / O : 최적화된 리소스 관리는 디스크 접근을 줄여 I / O 성능을 향상시킵니다.

개발 방법론 및 Best Practices

  1. 리소스 프로파일링 정기적으로 리소스 사용량을 프로파일링하여 병목 지점을 식별합니다.
void AMyGameMode::ProfileResources()
{
    FResourceBroker::LogResourceInfo();
}
  1. 동적 리소스 관리 게임플레이 상황에 따라 리소스를 동적으로 로드하고 언로드합니다.
void AMyGameMode::EnterNewArea(FName AreaName)
{
    UnloadResourcesForPreviousArea();
    LoadResourcesForNewArea(AreaName);
}
  1. 리소스 번들링 관련된 리소스를 함께 번들링하여 로딩 효율성을 높입니다.
void AMyResourceManager::LoadResourceBundle(FName BundleName)
{
    TArray<FSoftObjectPath> BundleAssets;
    // 번들에 포함된 에셋 경로 추가
    UAssetManager::GetStreamableManager().RequestAsyncLoad(BundleAssets);
}
  1. 리소스 의존성 관리 리소스 간의 의존성을 명확히 하여 불필요한 로딩을 방지합니다.
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;
};
  1. 리소스 프리페칭 예측 가능한 리소스를 미리 로드하여 로딩 시간을 분산시킵니다.
void AMyGameMode::PrefetchResources()
{
    TArray<FSoftObjectPath> ResourcesForNextLevel;
    // 다음 레벨에 필요한 리소스 경로 추가
    UAssetManager::GetStreamableManager().RequestAsyncLoad(ResourcesForNextLevel);
}

 효과적인 리소스 관리와 최적화는 언리얼 엔진 기반 게임 개발에서 핵심적인 부분입니다. 리소스의 로딩, 언로딩, 스트리밍을 효율적으로 관리함으로써 게임의 성능을 크게 향상시킬 수 있습니다. 비동기 로딩, 리소스 캐싱, 메모리 풀링 등의 기법을 적절히 활용하여 리소스 관리를 최적화해야 합니다.

 대용량 리소스 처리 시에는 청크 단위 로딩, 우선순위 기반 로딩 등의 전략을 고려해야 하며, LOD 시스템과 텍스처 압축 등의 최적화 기법을 적극 활용해야 합니다. 또한, 다양한 플랫폼에 대응하기 위해 플랫폼별 특성을 고려한 최적화 전략을 수립해야 합니다.

 리소스 관리는 게임의 로딩 시간, 메모리 사용량, 프레임 레이트 등 전반적인 성능에 직접적인 영향을 미치므로, 개발 초기 단계부터 리소스 관리 전략을 수립하고 지속적으로 최적화해 나가는 것이 중요합니다. 정기적인 프로파일링, 동적 리소스 관리, 리소스 번들링 등의 기법을 적용하여 효율적인 리소스 관리 시스템을 구축해야 합니다.

 마지막으로, 리소스 관리는 게임 개발의 모든 단계에서 지속적으로 고려되어야 할 사항입니다. 아티스트, 디자이너, 프로그래머 간의 긴밀한 협력을 통해 최적화된 리소스를 제작하고 효율적으로 활용하는 것이 성공적인 게임 개발의 핵심 요소입니다.