PART 7 - 강의 4/8

Significance Manager

거리 기반으로 게임플레이 객체의 기능을 동적으로 조절하기

01

Significance Manager란?

중요도 기반 최적화 시스템

Significance Manager는 플레이어로부터의 거리나 가시성에 따라 객체의 중요도(Significance)를 계산하고, 이에 따라 기능을 조절하는 시스템입니다. 대규모 오픈월드에서 수천 개의 NPC나 객체가 있을 때 필수적입니다.

Player

Near: 고품질 | Mid: 중간 | Far: 최소 기능

활성화 방법

프로젝트에서 SignificanceManager 모듈을 활성화해야 합니다. Build.cs에 "SignificanceManager"를 추가하세요.

02

Significance 레벨별 동작

거리에 따른 기능 스케일 다운

HIGH 0.7 ~ 1.0
  • ✓ 풀 프레임 Tick
  • ✓ 고품질 애니메이션
  • ✓ 모든 물리 시뮬레이션
  • ✓ AI 의사결정 활성
  • ✓ 사운드/VFX 활성
MEDIUM 0.3 ~ 0.7
  • ✓ Tick Interval 증가 (0.1s)
  • ☒ 복잡한 IK 비활성화
  • ☒ 물리 단순화
  • ✓ 기본 AI만 활성
  • ☒ 상세 VFX 비활성화
LOW 0.0 ~ 0.3
  • ☒ Tick 비활성화
  • ☒ 애니메이션 최저 품질
  • ☒ 물리 비활성화
  • ☒ AI 휴면 상태
  • ☒ 사운드/VFX 비활성화
03

Significance Manager 설정

Viewport Client에서 업데이트 호출

MyGameViewportClient.h
UCLASS() class UMyGameViewportClient : public UGameViewportClient { GENERATED_BODY() public: virtual void Tick(float DeltaTime) override; };
MyGameViewportClient.cpp
#include "SignificanceManager.h" void UMyGameViewportClient::Tick(float DeltaTime) { Super::Tick(DeltaTime); UWorld* World = GetWorld(); if (!World) return; // Significance Manager 가져오기 USignificanceManager* SignificanceManager = FSignificanceManagerModule::Get(World); if (!SignificanceManager) return; // 모든 플레이어의 위치 수집 TArray<FTransform> ViewpointTransforms; for (FConstPlayerControllerIterator It = World->GetPlayerControllerIterator(); It; ++It) { if (APlayerController* PC = It->Get()) { if (APawn* Pawn = PC->GetPawn()) { ViewpointTransforms.Add(Pawn->GetActorTransform()); } } } // Significance 업데이트 if (ViewpointTransforms.Num() > 0) { SignificanceManager->Update(TArrayView<FTransform>(ViewpointTransforms)); } }
04

Managed Object 구현

Significance 관리 대상 액터 구현

SignificanceAwareActor.h
UCLASS() class ASignificanceAwareActor : public AActor { GENERATED_BODY() public: virtual void BeginPlay() override; virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; protected: // Significance 계산 float CalculateSignificance( USignificanceManager::FManagedObjectInfo* ObjectInfo, const FTransform& Viewpoint); // Significance 변경 후 처리 void PostSignificanceUpdate( USignificanceManager::FManagedObjectInfo* ObjectInfo, float OldSignificance, float Significance, bool bFinal); UPROPERTY(EditAnywhere, Category = "Significance") float MaxSignificanceDistance = 5000.f; UPROPERTY(EditAnywhere, Category = "Significance") float HighSignificanceThreshold = 0.7f; UPROPERTY(EditAnywhere, Category = "Significance") float LowSignificanceThreshold = 0.3f; private: float CurrentSignificance = 1.f; };
SignificanceAwareActor.cpp
#include "SignificanceManager.h" void ASignificanceAwareActor::BeginPlay() { Super::BeginPlay(); if (USignificanceManager* Manager = FSignificanceManagerModule::Get(GetWorld())) { Manager->RegisterObject( this, FName("SignificanceAwareActor"), // Significance 계산 함수 (Lambda) [this](USignificanceManager::FManagedObjectInfo* ObjectInfo, const FTransform& Viewpoint) -> float { return CalculateSignificance(ObjectInfo, Viewpoint); }, // Post Significance 함수 USignificanceManager::EPostSignificanceType::Sequential, [this](USignificanceManager::FManagedObjectInfo* ObjectInfo, float OldSignificance, float Significance, bool bFinal) { PostSignificanceUpdate(ObjectInfo, OldSignificance, Significance, bFinal); } ); } } void ASignificanceAwareActor::EndPlay(const EEndPlayReason::Type EndPlayReason) { if (USignificanceManager* Manager = FSignificanceManagerModule::Get(GetWorld())) { Manager->UnregisterObject(this); } Super::EndPlay(EndPlayReason); } float ASignificanceAwareActor::CalculateSignificance( USignificanceManager::FManagedObjectInfo* ObjectInfo, const FTransform& Viewpoint) { float Distance = FVector::Dist( GetActorLocation(), Viewpoint.GetLocation()); // 거리가 멀수록 Significance가 낮아짐 (0~1) float Significance = 1.0f - FMath::Clamp( Distance / MaxSignificanceDistance, 0.f, 1.f); return Significance; } void ASignificanceAwareActor::PostSignificanceUpdate( USignificanceManager::FManagedObjectInfo* ObjectInfo, float OldSignificance, float Significance, bool bFinal) { CurrentSignificance = Significance; // High Significance: 모든 기능 활성화 if (Significance >= HighSignificanceThreshold) { SetActorTickEnabled(true); PrimaryActorTick.TickInterval = 0.f; // 매 프레임 // 고품질 모드 활성화 OnHighSignificance(); } // Medium Significance: 일부 기능 제한 else if (Significance >= LowSignificanceThreshold) { SetActorTickEnabled(true); PrimaryActorTick.TickInterval = 0.1f; // 10 FPS // 중간 품질 모드 OnMediumSignificance(); } // Low Significance: 최소 기능만 else { SetActorTickEnabled(false); // 저품질/휴면 모드 OnLowSignificance(); } }
05

NPC 예제

실제 NPC에 적용하기

SignificanceNPC.cpp
void ASignificanceNPC::OnHighSignificance() { // 풀 애니메이션 품질 if (SkeletalMesh) { SkeletalMesh->SetComponentTickEnabled(true); SkeletalMesh->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones; } // AI 컨트롤러 활성화 if (AIController) { AIController->SetActorTickEnabled(true); AIController->GetBrainComponent()->RestartLogic(); } // 사운드 활성화 if (AudioComponent) { AudioComponent->SetActive(true); } } void ASignificanceNPC::OnMediumSignificance() { // 애니메이션 단순화 if (SkeletalMesh) { SkeletalMesh->SetComponentTickEnabled(true); SkeletalMesh->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered; } // AI 간소화 (BT 대신 간단한 로직) if (AIController) { AIController->SetActorTickEnabled(true); AIController->PrimaryActorTick.TickInterval = 0.5f; } // 사운드 감소 if (AudioComponent) { AudioComponent->SetVolumeMultiplier(0.5f); } } void ASignificanceNPC::OnLowSignificance() { // 애니메이션 최소화 if (SkeletalMesh) { SkeletalMesh->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::OnlyTickMontagesWhenNotRendered; } // AI 휴면 if (AIController) { AIController->SetActorTickEnabled(false); AIController->GetBrainComponent()->StopLogic("Low Significance"); } // 사운드 비활성화 if (AudioComponent) { AudioComponent->SetActive(false); } }
SUMMARY

핵심 요약

핵심 포인트
  • Significance Manager - 거리 기반으로 객체 중요도를 0~1 사이 값으로 계산
  • RegisterObject - BeginPlay에서 등록, EndPlay에서 해제
  • High Significance - 가까운 객체는 풀 기능 활성화
  • Low Significance - 먼 객체는 Tick 비활성화, AI 휴면
  • 애니메이션 최적화 - VisibilityBasedAnimTickOption 활용
  • 대규모 오픈월드 필수 - 수천 개의 NPC 관리에 필수적
적용 우선순위

NPC AI > Skeletal Mesh 애니메이션 > 물리 시뮬레이션 > 사운드/VFX 순서로 적용하면 가장 큰 성능 향상을 얻을 수 있습니다.

PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: SignificanceManager 기본 설정

USignificanceManager를 프로젝트에 설정하고, RPG NPC에 RegisterObject()로 중요도를 등록하세요. 중요도 함수에서 카메라 거리와 화면 크기를 기반으로 값을 반환하세요.

실습 2: 중요도 기반 LOD 제어

Significance 값에 따라 NPC의 틱 빈도, 애니메이션 LOD, AI 업데이트 빈도를 동적으로 조절하세요. PostSignificanceFunction 콜백에서 컴포넌트별 설정을 변경하세요.

심화 과제: 대규모 RPG 월드 중요도 전략

수백 명의 NPC, 수천 개의 인터랙터블 오브젝트에 중요도 시스템을 적용하세요. 오브젝트 타입별(적, 아군 NPC, 환경, 이펙트) 중요도 버짓을 설계하고, 프레임 타임 목표치 내에서 최적화하세요.