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();
}

 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);
}