Anim instances in C++

Course day 10 (AnimInstance setup) · WarriorBaseAnimInstance → WarriorCharacterAnimInstance → WarriorHeroAnimInstance, plus WarriorHeroLinkedAnimLayer · 10 min

In one breath

The Animation Blueprint's event graph moves into C++. C++ works out a few numbers each frame (speed, "is accelerating", "been idle long enough to relax"), and the anim graph in the Blueprint just reads them to drive the blend space and state machine. Doing that gathering in the thread-safe update lets it run off the game thread.

The class ladder

ClassAddsWhy a separate level
UWarriorBaseAnimInstanceNothing yetCommon parent for every anim class, including linked layers.
UWarriorCharacterAnimInstanceOwning character + movement component, GroundSpeed, bHasAccelerationHeroes and enemies both need locomotion data.
UWarriorHeroAnimInstanceIdle timer → bShouldEnterRelaxStateOnly the hero has a "relax" idle.
UWarriorHeroLinkedAnimLayerNothing yetParent for linked anim layers, e.g. separate unarmed and axe locomotion swapped in at runtime.

Linked anim layers: the main anim BP calls a "layer" (e.g. Locomotion) defined by an Animation Layer Interface. Different anim classes implement it, one for unarmed and one for the axe, and you swap them with LinkAnimClassLayers when the weapon changes. The main graph never changes. (This is Lyra's approach too.)

The three functions

FunctionWhenThreadYour use
NativeInitializeAnimationOnce, when the instance is createdGameCache OwningCharacter and its movement component.
NativeUpdateAnimationEvery frame, firstGameHero: idle timer.
NativeThreadSafeUpdateAnimationEvery frame, after the aboveWorker thread (when multithreaded anim update is on)Ground speed, acceleration.
void UWarriorCharacterAnimInstance::NativeInitializeAnimation() {
    OwningCharacter = Cast<AWarriorBaseCharacter>(TryGetPawnOwner());
    if (OwningCharacter) OwningMovementComponent = OwningCharacter->GetCharacterMovement();
}

void UWarriorCharacterAnimInstance::NativeThreadSafeUpdateAnimation(float DeltaSeconds) {
    if (!OwningCharacter || !OwningMovementComponent) return;
    GroundSpeed = OwningCharacter->GetVelocity().Size();
    bHasAcceleration = OwningMovementComponent->GetCurrentAcceleration().SizeSquared2D() > 0.f;
}

The relax state

if (bHasAcceleration) { IdleElapsedTime = 0.f; bShouldEnterRelaxState = false; }
else { IdleElapsedTime += DeltaSeconds; bShouldEnterRelaxState = (IdleElapsedTime >= RelaxThreshold); }

A timer built from DeltaSeconds: accumulate while idle, reset on input. RelaxThreshold is EditDefaultsOnly, so the anim BP can tune it (5 s by default). IdleElapsedTime has no UPROPERTY: it's a plain float, so the GC doesn't care, and nobody needs to see it in the editor.

Three things worth noticing (good interview material)

1. Frame order. The hero reads bHasAcceleration in NativeUpdateAnimation, but the parent writes it in NativeThreadSafeUpdateAnimation, which runs later in the frame. So the hero always sees last frame's value. For a 5-second relax timer nobody will ever notice. It's the same kind of bug as the Niagara drift question (Tick and frame order), so spotting it shows you understand frame order.

2. GetVelocity().Size() includes falling speed. Mid-jump, "ground speed" climbs as you fall. Size2D() is the usual choice for a locomotion blend space.

3. Reading the character from a worker thread is what the course does and works fine here. Epic's stricter advice is to copy what you need on the game thread (or use Property Access in the anim graph) and have the thread-safe function only read your own data. Know that this is the "proper" answer if asked.

Quiz yourself

Why does NativeThreadSafeUpdateAnimation exist? What's the benefit?

It can run on a worker thread in parallel, taking animation work off the game thread. With many animated characters, that's real frame time saved.

Why can TryGetPawnOwner() return null, and when?

In the editor's animation preview (no pawn exists), or before the instance is attached to a pawn. That's why every update null-checks first.

Why use acceleration, not velocity, to decide "the player is moving"?

Acceleration reflects input straight away. Velocity lags (he's still sliding after release, and still near zero on the first frame of input).

Spot the frame-order issue in the hero's relax logic.

NativeUpdateAnimation (hero) reads bHasAcceleration before NativeThreadSafeUpdateAnimation (parent) writes it this frame. It's always one frame stale. Harmless here, but the same kind of bug as a follower lagging one frame behind.

What's the point of UWarriorCharacterAnimInstance being separate from the hero one?

Enemies need the same locomotion data (speed, acceleration) but not the hero-only relax state. Shared code sits in the shared parent.

What are linked anim layers for in this project?

Swapping whole chunks of animation (e.g. unarmed vs axe locomotion) at runtime via LinkAnimClassLayers, without changing the main anim graph.

Why is SizeSquared2D() > 0 used instead of Size2D() > 0?

It skips a square root. For a zero check the answer is the same.