Ue Animation System
Use this skill when working with Unreal Engine animation: AnimInstance, montage playback, blend space, state machine, anim notify, IK, AnimGraph, skeletal mesh, or linked anim graphs. See references/anim-notify-reference.md for notify patterns and references/locomotion-setup.md for locomotion setup. For montage integration with GAS, see ue-gameplay-abilities.
- Skill ID
- quodsoler/unreal-engine-skills/ue-animation-system
- Publisher
- quodsoler
- Repository
- unreal-engine-skills
- Installs
- 0
- Files
- 3
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
quodsoler/unreal-engine-skills/ue-animation-systemInstalls these files- SKILL.md
- references/anim-notify-reference.md
- references/locomotion-setup.md
What this skill tells the agent
UE Animation System
You are an expert in Unreal Engine's animation system.
Context Check
Read .agents/ue-project-context.md first. Note which plugins are enabled (Control Rig, Motion Matching, Full Body IK), whether GAS is in use, and the skeleton/character hierarchy.
Information to Gather
- What animation need? (locomotion, ability, cinematic, IK, procedural)
- C++ only, Blueprint only, or mixed?
- Does the character use
ACharacterwith an existingUSkeletalMeshComponent? - Is GAS active? (affects montage replication)
- Multiplayer? (determines replication strategy)
- Does the project use modular linked anim layers?
Architecture
ACharacter / AActor
└── USkeletalMeshComponent
└── UAnimInstance subclass
├── NativeInitializeAnimation() [setup, game thread]
├── NativeUpdateAnimation(float dt) [game thread]
├── NativeThreadSafeUpdateAnimation(dt) [worker thread]
├── FAnimInstanceProxy [worker thread eval]
└── Montage API / Linked LayersAnimation updates run in two phases. Game thread: NativeUpdateAnimation — safe to read gameplay state. Worker thread: blend tree evaluation. Write all shared state as UPROPERTY() Transient members in NativeUpdateAnimation; read those cached values in NativeThreadSafeUpdateAnimation.
AnimInstance
Subclass Pattern
// MyAnimInstance.h
UCLASS()
class MYGAME_API UMyAnimInstance : public UAnimInstance
{
GENERATED_BODY()
virtual void NativeInitializeAnimation() override;
virtual void NativeUpdateAnimation(float DeltaSeconds) override;
virtual void NativeThreadSafeUpdateAnimation(float DeltaSeconds) override;
protected:
UPROPERTY(Transient) TObjectPtr<ACharacter> OwningCharacter;
UPROPERTY(Transient) TObjectPtr<UCharacterMovementComponent> MovementComp;
UPROPERTY(Transient, BlueprintReadOnly, Category="Locomotion")
float Speed = 0.f;
UPROPERTY(Transient, BlueprintReadOnly, Category="Locomotion")
float Direction = 0.f;
UPROPERTY(Transient, BlueprintReadOnly, Category="Locomotion")
bool bIsInAir = false;
};// MyAnimInstance.cpp
void UMyAnimInstance::NativeInitializeAnimation()
{
Super::NativeInitializeAnimation(); // ALWAYS call super
OwningCharacter = Cast<ACharacter>(TryGetPawnOwner());
if (OwningCharacter)
MovementComp = OwningCharacter->GetCharacterMovement();
}
void UMyAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
Super::NativeUpdateAnimation(DeltaSeconds);
if (!OwningCharacter || !MovementComp) return;
const FVector Velocity = MovementComp->Velocity;
Speed = Velocity.Size2D();
bIsInAir = MovementComp->IsFalling();
if (Speed > 3.f)
{
const FRotator ActorRot = OwningCharacter->GetActorRotation();
const FRotator VelocityRot = Velocity.ToOrientationRotator();
Direction = UKismetMathLibrary::NormalizedDeltaRotator(
VelocityRot, ActorRot).Yaw;
}
}
void UMyAnimInstance::NativeThreadSafeUpdateAnimation(float DeltaSeconds)
{
Super::NativeThreadSafeUpdateAnimation(DeltaSeconds);
// Only read UPROPERTY members written in NativeUpdateAnimation above.
// Do NOT call any UObject functions not marked BlueprintThreadSafe.
}FAnimInstanceProxy — Thread-Safe Access
Heavy animation logic can run on worker threads via NativeThreadSafeUpdateAnimation. Access shared data through the proxy:
void UMyAnimInstance::NativeThreadSafeUpdateAnimation(float DeltaSeconds)
{
FMyAnimInstanceProxy& Proxy = GetProxyOnAnyThread<FMyAnimInstanceProxy>();
Proxy.Speed = Proxy.Velocity.Size();
Proxy.bIsFalling = Proxy.MovementMode == EMovementMode::MOVE_Falling;
}// FAnimInstanceProxy declaration — worker thread data container
USTRUCT()
struct FMyAnimInstanceProxy : public FAnimInstanceProxy
{
GENERATED_BODY()
FMyAnimInstanceProxy() = default;
explicit FMyAnimInstanceProxy(UAnimInstance* Instance) : FAnimInstanceProxy(Instance) {}
float Speed = 0.f;
FVector Velocity = FVector::ZeroVector;
TEnumAsByte<EMovementMode> MovementMode = MOVE_None;
virtual void PreUpdate(UAnimInstance* AnimInstance, float DeltaSeconds) override;
virtual void Update(float DeltaSeconds) override;
};
// In UMyAnimInstance: override CreateAnimInstanceProxy to return your proxy
virtual FAnimInstanceProxy* CreateAnimInstanceProxy() override
{ return new FMyAnimInstanceProxy(this); }The engine copies data between game thread and worker thread at safe sync points.
Montages
Source: AnimMontage.h, AnimInstance.h
Key API (UAnimInstance):
float Montage_Play(UAnimMontage*, float PlayRate=1.f,
EMontagePlayReturnType=MontageLength, float StartAt=0.f, bool bStopAll=true);
void Montage_Stop(float BlendOut, const UAnimMontage* Montage=nullptr);
void Montage_Pause(const UAnimMontage* Montage=nullptr);
void Montage_Resume(const UAnimMontage* Montage);
void Montage_JumpToSection(FName Section, const UAnimMontage* Montage=nullptr);
void Montage_SetNextSection(FName From, FName To, const UAnimMontage* Montage=nullptr);
bool Montage_IsActive(const UAnimMontage*) const;
bool Montage_IsPlaying(const UAnimMontage*) const;
FName Montage_GetCurrentSection(const UAnimMontage* Montage=nullptr) const;
float Montage_GetPosition(const UAnimMontage*) const;Playing + Delegate Pattern
void UMyComponent::PlayAttackMontage(UAnimMontage* Montage)
{
UAnimInstance* AnimInst = GetMesh()->GetAnimInstance();
if (!AnimInst || !Montage) return;
// Play FIRST — Montage_SetEndDelegate calls GetActiveInstanceForMontage()
// internally, which returns nullptr until Montage_Play creates the instance.
if (AnimInst->Montage_Play(Montage) <= 0.f) return;
FOnMontageEnded EndDelegate;
EndDelegate.BindUObject(this, &UMyComponent::OnAttackEnded);
AnimInst->Montage_SetEndDelegate(EndDelegate, Montage);
FOnMontageBlendingOutStarted BlendOutDelegate;
BlendOutDelegate.BindUObject(this, &UMyComponent::OnAttackBlendingOut);
AnimInst->Montage_SetBlendingOutDelegate(BlendOutDelegate, Montage);
}
void UMyComponent::OnAttackEnded(UAnimMontage* Montage, bool bInterrupted) { }
void UMyComponent::OnAttackBlendingOut(UAnimMontage* Montage, bool bInterrupted) { }Dynamic Slot Montage
UAnimMontage* DynMontage = AnimInst->PlaySlotAnimationAsDynamicMontage(
SomeSequence, FName("UpperBody"), 0.25f, 0.25f, 1.f, 1);Multiplayer Replication
- With GAS: use
UAbilitySystemComponent::PlayMontage()— GAS handles replication viaFGameplayAbilityRepAnimMontage. - Without GAS: replicate a montage pointer or a custom rep struct; server calls
Montage_Play, clients play onOnRep_. - Never call
Montage_Playindependently on all net roles without sync.
GAS Integration — PlayMontageAndWait
// GAS ability task — PlayMontageAndWait (requires GameplayAbilities module)
UAbilityTask_PlayMontageAndWait* Task =
UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(
this, NAME_None, AttackMontage, 1.f);
Task->OnCompleted.AddDynamic(this, &UMyAbility::OnMontageCompleted);
Task->OnInterrupted.AddDynamic(this, &UMyAbility::OnMontageInterrupted);
Task->ReadyForActivation(); // must call to start the taskBlend Spaces
Blend spaces are data assets sampled in the AnimGraph. Drive them by setting UPROPERTY members on the AnimInstance that the AnimGraph reads.
- 1D (
UBlendSpace1D): single axis, typically Speed (0–600). UseFInterpolationParameterwithInterpolationType=SpringDamper,InterpolationTime=0.15,DampingRatio=1.0. - 2D (
UBlendSpace): two axes — Direction (-180 to 180) and Speed (0–600). Cardinal direction samples at each speed tier.
More from quodsoler
Use this skill in a project
Open any RiverX project, search the Skills panel for quodsoler/unreal-engine-skills/ue-animation-system, and install it in one click.
