The Significance Manager in Unreal Engine 4

In one of the previous articles, I touched on the topic Significance Manager… Now I want to take a closer look at its settings and integration into your project.

Significance Manager – a single structure that allows you to write flexible, specific code for evaluating and prioritizing objects. Based on the data obtained, it becomes possible to change the behavior of the system, for example, turn off particle / sound systems, reduce the tick frequency, change lodes, etc.

The simplest implementation idea is that from the PlayerController we transfer the position and rotation of the player’s camera to all the actors registered in the Significance Manager and already in them we process and make changes for optimization, for example, Tick, LOD, URO, etc.

Now let’s move on to setting up this beautiful tool, first of all you need to enable the plugin (it is enabled by default).

If the need arises, we can create our own class for the manager and define it in the DefaultEngine.ini:

[/Script/SignificanceManager.SignificanceManager]
SignificanceManagerClassName=/Script/DemoProject.DemoSignificanceManager

Update

The Update function is necessary to assess the importance of each registered object in the manager, relative to the transformation of some object. For the client, it would be great to use the camera in the controller. Let’s write the code that will transfer the camera position to the manager and make it only for the player (IsLocalController ()).

Code in DemoPlayerController
.h 
public:
	virtual void Tick(float DeltaTime) override;

private:
	TWeakObjectPtr<class USignificanceManager> SignificanceManagerPtr;

.cpp
#include "SignificanceManager.h"

void ADemoPlayerController::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
  
	if (!IsLocalController()) return;

	UWorld* WorldPtr = GetWorld();
	if (!WorldPtr) return;

	if (!SignificanceManagerPtr.IsValid())
		SignificanceManagerPtr = FSignificanceManagerModule::Get(WorldPtr);

	if (SignificanceManagerPtr.IsValid())
	{
		FVector ViewLocation;
		FRotator ViewRotation;
		GetPlayerViewPoint(ViewLocation, ViewRotation);

		TArray<FTransform> Viewpoints;
		Viewpoints.Emplace(ViewRotation, ViewLocation, FVector::OneVector);

		SignificanceManagerPtr.Get()->Update(Viewpoints);
	}
}

FSignificanceFunction and FPostSignificanceFunction

Two types of functions are responsible for any processing of an object:

FSignificanceFunction – The main scoring function that creates an algorithm for the distribution of importance. It takes a UObject and transforms an object and returns a float result. For each Update, the function will be called once and will return the highest value (You can change the sorting by creating a successor to the manager). Every registered object must be associated with this function.

FPostSignificanceFunction – A function to process the received importance value. Accepts the old and the new value (if bFinal == true, then the new value will be equal to one). The Signification Manager will call this function based on the object’s settings during registration.

  • None – the function will not be called

  • Concurrent – the function will be called immediately after the importance is evaluated. Functions must be thread safe as they execute in parallel to each other.

  • Sequential – the function will be called immediately after all objects have been evaluated.

RegisterObject / UnregisterObject

Each UObject can be registered with the Significance Manager, thereby adding it to the manager handler. All objects will be grouped and processed based on the specified tag when created. At the time of registration, the first estimate will be created based on the last Update Manager.

Registering directly in the AActor class
.h
#include "SignificanceManager.h"

protected:
    virtual void BeginPlay() override;

    // Significance Manager implementation
    float SignficanceFunction(USignificanceManager::FManagedObjectInfo* ObjectInfo, const FTransform& Viewpoint);
    void PostSignficanceFunction(USignificanceManager::FManagedObjectInfo* ObjectInfo, float OldSignificance, float Significance, bool bFinal);

.cpp
void ADemoActor::BeginPlay()
{
    Super::BeginPlay();
    if (IsNetMode(NM_DedicatedServer)) return;

    USignificanceManager* SignificanceManager = FSignificanceManagerModule::Get(GetWorld());
    if (!SignificanceManager) return;

    //Создаем 2 лямбда функции для FSignificanceFunction и FPostSignificanceFunction
    auto Significance = [&](USignificanceManager::FManagedObjectInfo* ObjectInfo, const FTransform& Viewpoint) -> float
    {
        return SignficanceFunction(ObjectInfo, Viewpoint);
    };

    auto PostSignificance = [&](USignificanceManager::FManagedObjectInfo* ObjectInfo, float OldSignificance, float Significance, bool bFinal)
    {
        PostSignficanceFunction(ObjectInfo, OldSignificance, Significance, bFinal);
    };

    //Регистрируем наш объект в Significance Manager
    SignificanceManager->RegisterObject(this, TEXT("Cube"), Significance, USignificanceManager::EPostSignificanceType::Sequential, PostSignificance);
}

void ADemoActor::EndPlay(const EEndPlayReason::Type Reason)
{
    Super::EndPlay(Reason);

    auto WorldPtr = GetWorld();
    if (!WorldPtr) return;

    // Убираем объект из SignificanceManager
    //Так же можно использовать UnregisterAll(FName(TEXT("Cube")));
    //Для удаления всех по тэгу
    if (auto SignificanceManager = FSignificanceManagerModule::Get(WorldPtr))
        SignificanceManager->Unregister(this);
}

float ADemoActor::SignficanceFunction(USignificanceManager::FManagedObjectInfo* ObjectInfo, const FTransform& Viewpoint)
{
    if (ObjectInfo->GetTag() == TEXT("Cube"))
    {
        ADemoActor* Actor = CastChecked<ADemoActor>(ObjectInfo->GetObject());
        const float Distance = (Actor->GetActorLocation() - Viewpoint.GetLocation()).Size();

        if (Distance >= 2000.f) return 2.f;
        if (Distance >= 1000.f) return 1.f;
    }

    return 0.f;
}

void ADemoActor::PostSignficanceFunction(USignificanceManager::FManagedObjectInfo* ObjectInfo, float OldSignificance, float Significance, bool bFinal)
{
    if (ObjectInfo->GetTag() == TEXT("Cube"))
    {
        if (Significance >= 2.f)
        {
            SetActorTickInterval(0.25f);
        }
        else if (Significance >= 1.f)
        {
            SetActorTickInterval(0.1f);
        }
        else
        {
            SetActorTickInterval(0.f);
        }
    }
}

Conclusion

In such a very simple way, you can add a little more optimization to your project. Changing the ticks without any problems, the ability to move only visible objects, even just use to hide objects (culling) or shadows, or to embody any other your ideas.

And on this I say goodbye to you. Thank you all for your attention and may the power of knowledge be with you.

Similar Posts

One Comment

  1. Very helpful. How can you use this with Animation Budget Allocation? Since it already has Significance Manager built in. Maybe you can do an article on Animation Budget Allocation. Thanks.

Leave a Reply

Your email address will not be published. Required fields are marked *