Download

Animation LOD or how to not use AnimUpdateRateTick

Ok, short version: since switching to UE 4.22 I got some weird animation related debug output on the screen. Asking here several times was to no avail no answer as usual, so I did some more digging. Turns out there is a flagin a skinned mesh component bDisplayDebugUpdateRateOptimizations that turns this on or off. You can do that through BP or in C++. While researching that I came across the framework to have some sort of anim LOD meaning that anims are updated only every X frames. The tooltip help points to a “AnimUpdateRateTick” function that we should read up on, but there is absolutely no documentation. Hasn’t been added by Epic in several years now. There are many posts here and on the answer hub that ask for help regarding this feature, to no avail.

I did some experimenting and I found a solution that does what I want it to do, that is calling the animation tick not every frame. Now, this may or may not be a canonical solution. I have no idea if these functions were meant to be used this way due to the lack of documentation. If anybody has a different opinion about how to do this, by all means, share.

Also there is an experimental feature here (Animation Budget Allocator | Unreal Engine Documentation) that seems to make my solution obsolete, however since I already fell falt on my face by using an experimental feature that was experimental for years and then was completely changed by epic (3D widgets) I don’t use those features anymore since I assume they WILL be changed at some point.

So here is my solution in rough brush strokes. This is based on the assumption you have a C++ character base class from which your BP character classes are derived.

First you need to get your camera position into C++ which is not a simple task in itself. I work around this by having the BP part store the camera position in a static variable in my C++ character baseclass every frame. The static variable is


static FVector s_Camera_Location;

Then you need a function in your character that turns the custom update feature on or off. This following function contains some of my other code in order to determine if the character this is called on is my player character (which I want to run at full animation speed), you have to supply your own version for that


void AClientCharacter::InitializeCustomAnimationUpdate(bool UseCustomUpdate, bool ShowDebugOutput)
{

    // ###########################################################################
    // Don't allow manual animation update on the player character
    // ###########################################################################
    if (GetObjectID() != UBaseGameInstance::GetGameInstance().GetObjectManager()->GetPlayerCharacterID())
    {
        TArray<UActorComponent*> PrimitiveComponents = GetComponentsByClass(USkinnedMeshComponent::StaticClass());
        for (UActorComponent* PrimitiveActorComponent : PrimitiveComponents)
        {
            USkinnedMeshComponent* Component = CastChecked<USkinnedMeshComponent>(PrimitiveActorComponent);

            if (Component)
            {
                Component->EnableExternalTickRateControl(UseCustomUpdate);
                Component->bEnableUpdateRateOptimizations        = UseCustomUpdate;
                Component->bDisplayDebugUpdateRateOptimizations = ShowDebugOutput;
            }
        }
        IsUsingCustomAnimationUpdate            = UseCustomUpdate;
        AcummulatedFrameDeltaForAnimationUpdate = 0.0f;
    }
}


The variables at the bottom are character member variables. Finally add a Tick functino that performs the update something like this:



void AClientCharacter::Tick(float DeltaSeconds)
{
    Super::Tick(DeltaSeconds);



    if(IsUsingCustomAnimationUpdate)
    {
        // Is used to inform the animaton about the total time since last update
        AcummulatedFrameDeltaForAnimationUpdate += DeltaSeconds;

        // Determine the distance from the camera and then determine the time between
        // two updates based on this distance. This could be turned into parameters if 
        // necessary but for our project, constants work just fine.
        const float DistanceFromCamera = (GetActorLocation() - s_Camera_Location).Size();
        float TriggerIntervalSeconds = 0.0f;

        if (DistanceFromCamera > 5000)
            TriggerIntervalSeconds = 1.5f;
        else
        {
            if (DistanceFromCamera > 2500)
                TriggerIntervalSeconds = 0.75f;
            else
            {
                if (DistanceFromCamera > 1000)
                    TriggerIntervalSeconds = 0.3f;
            }
        }

        TArray<UActorComponent*> PrimitiveComponents = GetComponentsByClass(USkinnedMeshComponent::StaticClass());
        if (AcummulatedFrameDeltaForAnimationUpdate >= TriggerIntervalSeconds)
        {
            for (UActorComponent* PrimitiveActorComponent : PrimitiveComponents)
            {
                USkinnedMeshComponent* Component = CastChecked<USkinnedMeshComponent>(PrimitiveActorComponent);

                if (Component)
                {
                    Component->EnableExternalUpdate(true);
                    Component->SetExternalDeltaTime(AcummulatedFrameDeltaForAnimationUpdate);
                }
            }
            AcummulatedFrameDeltaForAnimationUpdate = 0.0f;
        }else
        {
            for (UActorComponent* PrimitiveActorComponent : PrimitiveComponents)
            {
                USkinnedMeshComponent* Component = CastChecked<USkinnedMeshComponent>(PrimitiveActorComponent);

                if (Component)
                {
                    Component->EnableExternalUpdate(false);
                }
            }
        }
    }

}

Alter the constant distance and frame rate values to your taste or make them parameters. This will tick the animation per character only every X seconds depending on the distance to the camera.