ue-animation-system

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

UE Animation System

UE动画系统

You are an expert in Unreal Engine's animation system.
你是Unreal Engine动画系统方面的专家。

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.
请先阅读
.agents/ue-project-context.md
。注意已启用的插件(Control Rig、Motion Matching、Full Body IK)、是否使用GAS,以及骨骼/角色层级结构。

Information to Gather

需要收集的信息

  1. What animation need? (locomotion, ability, cinematic, IK, procedural)
  2. C++ only, Blueprint only, or mixed?
  3. Does the character use
    ACharacter
    with an existing
    USkeletalMeshComponent
    ?
  4. Is GAS active? (affects montage replication)
  5. Multiplayer? (determines replication strategy)
  6. Does the project use modular linked anim layers?

  1. 动画需求是什么?(locomotion、技能、过场动画、IK、程序化动画)
  2. 仅使用C++、仅使用Blueprint,还是混合使用?
  3. 角色是否使用带有现有
    USkeletalMeshComponent
    ACharacter
  4. 是否启用GAS?(会影响montage的复制)
  5. 是否为多人游戏?(决定复制策略)
  6. 项目是否使用模块化的链接动画层?

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 Layers
Animation 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
.

ACharacter / AActor
  └── USkeletalMeshComponent
        └── UAnimInstance子类
              ├── NativeInitializeAnimation()           [初始化,游戏线程]
              ├── NativeUpdateAnimation(float dt)       [更新,游戏线程]
              ├── NativeThreadSafeUpdateAnimation(dt)   [线程安全更新,工作线程]
              ├── FAnimInstanceProxy                    [工作线程计算]
              └── Montage API / 链接层
动画更新分为两个阶段。游戏线程:
NativeUpdateAnimation
— 可安全读取游戏玩法状态。工作线程:混合树计算。在
NativeUpdateAnimation
中将所有共享状态写入
UPROPERTY() Transient
成员;在
NativeThreadSafeUpdateAnimation
中读取这些缓存值。

AnimInstance

AnimInstance

Subclass Pattern

子类模式

cpp
// 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;
};
cpp
// 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.
}
cpp
// 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;
};
cpp
// MyAnimInstance.cpp
void UMyAnimInstance::NativeInitializeAnimation()
{
    Super::NativeInitializeAnimation(); // 务必调用父类方法
    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);
    // 仅读取上述NativeUpdateAnimation中写入的UPROPERTY成员。
    // 请勿调用任何未标记为BlueprintThreadSafe的UObject函数。
}

FAnimInstanceProxy — Thread-Safe Access

FAnimInstanceProxy — 线程安全访问

Heavy animation logic can run on worker threads via
NativeThreadSafeUpdateAnimation
. Access shared data through the proxy:
cpp
void UMyAnimInstance::NativeThreadSafeUpdateAnimation(float DeltaSeconds)
{
    FMyAnimInstanceProxy& Proxy = GetProxyOnAnyThread<FMyAnimInstanceProxy>();
    Proxy.Speed = Proxy.Velocity.Size();
    Proxy.bIsFalling = Proxy.MovementMode == EMovementMode::MOVE_Falling;
}
cpp
// 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.

复杂的动画逻辑可通过
NativeThreadSafeUpdateAnimation
在工作线程运行。通过代理访问共享数据:
cpp
void UMyAnimInstance::NativeThreadSafeUpdateAnimation(float DeltaSeconds)
{
    FMyAnimInstanceProxy& Proxy = GetProxyOnAnyThread<FMyAnimInstanceProxy>();
    Proxy.Speed = Proxy.Velocity.Size();
    Proxy.bIsFalling = Proxy.MovementMode == EMovementMode::MOVE_Falling;
}
cpp
// FAnimInstanceProxy声明 — 工作线程数据容器
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;
};

// 在UMyAnimInstance中:重写CreateAnimInstanceProxy以返回自定义代理
virtual FAnimInstanceProxy* CreateAnimInstanceProxy() override
{ return new FMyAnimInstanceProxy(this); }
引擎会在安全的同步点在游戏线程与工作线程之间复制数据。

Montages

Montages

Source:
AnimMontage.h
,
AnimInstance.h
Key API (
UAnimInstance
):
cpp
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;
来源:
AnimMontage.h
,
AnimInstance.h
核心API(
UAnimInstance
):
cpp
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

播放 + 委托模式

cpp
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) { }
cpp
void UMyComponent::PlayAttackMontage(UAnimMontage* Montage)
{
    UAnimInstance* AnimInst = GetMesh()->GetAnimInstance();
    if (!AnimInst || !Montage) return;

    // 先播放 — Montage_SetEndDelegate内部调用GetActiveInstanceForMontage()
    // 该方法在Montage_Play创建实例前会返回nullptr。
    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

动态插槽Montage

cpp
UAnimMontage* DynMontage = AnimInst->PlaySlotAnimationAsDynamicMontage(
    SomeSequence, FName("UpperBody"), 0.25f, 0.25f, 1.f, 1);
cpp
UAnimMontage* DynMontage = AnimInst->PlaySlotAnimationAsDynamicMontage(
    SomeSequence, FName("UpperBody"), 0.25f, 0.25f, 1.f, 1);

Multiplayer Replication

多人游戏复制

  • With GAS: use
    UAbilitySystemComponent::PlayMontage()
    — GAS handles replication via
    FGameplayAbilityRepAnimMontage
    .
  • Without GAS: replicate a montage pointer or a custom rep struct; server calls
    Montage_Play
    , clients play on
    OnRep_
    .
  • Never call
    Montage_Play
    independently on all net roles without sync.
  • 使用GAS:调用
    UAbilitySystemComponent::PlayMontage()
    — GAS通过
    FGameplayAbilityRepAnimMontage
    处理复制。
  • 不使用GAS:复制montage指针或自定义复制结构体;服务器调用
    Montage_Play
    ,客户端在
    OnRep_
    方法中播放。
  • 切勿在所有网络角色上独立调用
    Montage_Play
    而不同步。

GAS Integration — PlayMontageAndWait

GAS集成 — PlayMontageAndWait

cpp
// 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 task

cpp
// GAS能力任务 — PlayMontageAndWait(需要GameplayAbilities模块)
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();  // 必须调用以启动任务

Blend Spaces

Blend 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). Use
    FInterpolationParameter
    with
    InterpolationType=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.
  • Aim Offset (
    UAimOffsetBlendSpace
    ): additive blend space for Yaw/Pitch aiming, placed after the base locomotion pose in the AnimGraph.
See
references/locomotion-setup.md
for complete axis configuration and sample placement.

Blend spaces是在AnimGraph中采样的数据资源。通过设置AnimInstance上的
UPROPERTY
成员供AnimGraph读取来驱动它们。
  • 1D
    UBlendSpace1D
    ):单轴,通常为Speed(0–600)。使用
    FInterpolationParameter
    ,设置
    InterpolationType=SpringDamper
    InterpolationTime=0.15
    DampingRatio=1.0
  • 2D
    UBlendSpace
    ):双轴 — Direction(-180至180)和Speed(0–600)。在每个速度层级采样基本方向。
  • Aim Offset
    UAimOffsetBlendSpace
    ):用于Yaw/Pitch瞄准的叠加混合空间,放置在AnimGraph中的基础locomotion姿态之后。
完整的轴配置和采样点设置请查看
references/locomotion-setup.md

State Machines

State Machines

State machines live in the AnimGraph. Bind native C++ logic to transition rules and state entry/exit without Blueprint:
cpp
// In NativeInitializeAnimation()
AddNativeTransitionBinding(
    FName("LocomotionSM"), FName("Idle"), FName("Walk/Run"),
    FCanTakeTransition::CreateUObject(this, &UMyAnimInstance::CanStartMoving),
    FName("IdleToMoving"));

AddNativeStateEntryBinding(
    FName("LocomotionSM"), FName("Land"),
    FOnGraphStateChanged::CreateUObject(this, &UMyAnimInstance::OnLandEntered));
Query state machine at runtime:
cpp
const FAnimNode_StateMachine* SM =
    GetStateMachineInstanceFromName(FName("LocomotionSM"));
float RunWeight = GetInstanceStateWeight(
    GetStateMachineIndex(FName("LocomotionSM")), SM->GetCurrentState());
状态机存在于AnimGraph中。无需Blueprint即可将原生C++逻辑绑定到过渡规则以及状态进入/退出事件:
cpp
// 在NativeInitializeAnimation()中
AddNativeTransitionBinding(
    FName("LocomotionSM"), FName("Idle"), FName("Walk/Run"),
    FCanTakeTransition::CreateUObject(this, &UMyAnimInstance::CanStartMoving),
    FName("IdleToMoving"));

AddNativeStateEntryBinding(
    FName("LocomotionSM"), FName("Land"),
    FOnGraphStateChanged::CreateUObject(this, &UMyAnimInstance::OnLandEntered));
运行时查询状态机:
cpp
const FAnimNode_StateMachine* SM =
    GetStateMachineInstanceFromName(FName("LocomotionSM"));
float RunWeight = GetInstanceStateWeight(
    GetStateMachineIndex(FName("LocomotionSM")), SM->GetCurrentState());

Conduit Nodes

Conduit节点

Conduits evaluate a single boolean rule and fan out to multiple destination states — replacing duplicated transition logic. Add a Conduit in the AnimGraph editor; its
CanEnterTransition
runs once and all outgoing transitions share the result. Use conduits when three or more states need the same entry condition (e.g., "is grounded?"). For simple A-to-B transitions, a direct rule is clearer.

Conduit节点评估单个布尔规则并分支到多个目标状态 — 替代重复的过渡逻辑。在AnimGraph编辑器中添加Conduit;其
CanEnterTransition
仅运行一次,所有 outgoing 过渡共享结果。当三个或更多状态需要相同的进入条件(例如“是否在地面?”)时使用Conduit。对于简单的A到B过渡,直接规则更清晰。

Anim Notifies

Anim Notifies

Source:
AnimNotify.h
,
AnimNotifyState.h
来源:
AnimNotify.h
,
AnimNotifyState.h

UAnimNotify — Point-in-Time

UAnimNotify — 时间点通知

cpp
UCLASS(meta=(DisplayName="Footstep"))
class MYGAME_API UFootstepNotify : public UAnimNotify
{
    GENERATED_BODY()
public:
    // Always override the UE5 3-argument signature
    virtual void Notify(USkeletalMeshComponent* MeshComp,
        UAnimSequenceBase* Animation,
        const FAnimNotifyEventReference& EventReference) override;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Footstep")
    FName FootSocket = FName("foot_l");
};
cpp
UCLASS(meta=(DisplayName="Footstep"))
class MYGAME_API UFootstepNotify : public UAnimNotify
{
    GENERATED_BODY()
public:
    // 务必重写UE5的3参数签名
    virtual void Notify(USkeletalMeshComponent* MeshComp,
        UAnimSequenceBase* Animation,
        const FAnimNotifyEventReference& EventReference) override;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Footstep")
    FName FootSocket = FName("foot_l");
};

UAnimNotifyState — Duration (Begin/Tick/End)

UAnimNotifyState — 持续时间通知(开始/ Tick /结束)

cpp
UCLASS(meta=(DisplayName="Weapon Collision Window"))
class MYGAME_API UWeaponCollisionState : public UAnimNotifyState
{
    GENERATED_BODY()
public:
    virtual void NotifyBegin(USkeletalMeshComponent*, UAnimSequenceBase*,
        float TotalDuration, const FAnimNotifyEventReference&) override;
    virtual void NotifyEnd(USkeletalMeshComponent*, UAnimSequenceBase*,
        const FAnimNotifyEventReference&) override;
};
cpp
UCLASS(meta=(DisplayName="Weapon Collision Window"))
class MYGAME_API UWeaponCollisionState : public UAnimNotifyState
{
    GENERATED_BODY()
public:
    virtual void NotifyBegin(USkeletalMeshComponent*, UAnimSequenceBase*,
        float TotalDuration, const FAnimNotifyEventReference&) override;
    virtual void NotifyEnd(USkeletalMeshComponent*, UAnimSequenceBase*,
        const FAnimNotifyEventReference&) override;
};

BranchingPoint (Synchronous)

BranchingPoint(同步)

Set
bIsNativeBranchingPoint = true
in the constructor. Override
BranchingPointNotify()
instead of
Notify()
. Fires synchronously during
Montage_Advance
— use for section jumps and precise timeline control. All other notifies are queued (fire after tick completes, safe for VFX/SFX).
在构造函数中设置
bIsNativeBranchingPoint = true
。重写
BranchingPointNotify()
而非
Notify()
。在
Montage_Advance
期间同步触发 — 用于章节跳转和精确时间线控制。所有其他通知均为队列触发(在tick完成后触发,适合VFX/SFX)。

Named Notify Delegate

命名通知委托

cpp
AnimInst->OnPlayMontageNotifyBegin.AddDynamic(
    this, &UMyComponent::HandleNotifyBegin);

void UMyComponent::HandleNotifyBegin(FName NotifyName,
    const FBranchingPointNotifyPayload& Payload)
{
    if (NotifyName == FName("EnableHitbox")) ActivateHitDetection();
}
See
references/anim-notify-reference.md
for built-in notify catalog and more custom patterns.

cpp
AnimInst->OnPlayMontageNotifyBegin.AddDynamic(
    this, &UMyComponent::HandleNotifyBegin);

void UMyComponent::HandleNotifyBegin(FName NotifyName,
    const FBranchingPointNotifyPayload& Payload)
{
    if (NotifyName == FName("EnableHitbox")) ActivateHitDetection();
}
内置通知目录和更多自定义模式请查看
references/anim-notify-reference.md

IK and Procedural

IK与程序化动画

Foot IK with Line Traces (NativeUpdateAnimation — game thread)

基于线Trace的脚部IK(NativeUpdateAnimation — 游戏线程)

cpp
FVector UMyAnimInstance::GetFootTarget(FName SocketName) const
{
    const FVector Foot = GetOwningComponent()->GetSocketLocation(SocketName);
    FHitResult Hit;
    FCollisionQueryParams P(SCENE_QUERY_STAT(FootIK), true);
    P.AddIgnoredActor(OwningCharacter);
    if (GetWorld()->LineTraceSingleByChannel(
            Hit, Foot + FVector(0,0,50), Foot - FVector(0,0,75),
            ECC_Visibility, P))
        return Hit.ImpactPoint;
    return Foot;
}
Feed results into a Control Rig asset (UE5 recommended) or a Two Bone IK skeletal control node in the AnimGraph.
Skeletal control nodes (AnimGraph):
NodePurpose
Two Bone IKTwo-joint IK (arm, leg) — effector + joint target
FABRIKMulti-bone chain IK — tip bone + effector
Look AtSingle bone tracks target location with clamp
Copy BoneCopies transform components between bones
Spline IKBones follow a spline curve (spine, tail)
cpp
FVector UMyAnimInstance::GetFootTarget(FName SocketName) const
{
    const FVector Foot = GetOwningComponent()->GetSocketLocation(SocketName);
    FHitResult Hit;
    FCollisionQueryParams P(SCENE_QUERY_STAT(FootIK), true);
    P.AddIgnoredActor(OwningCharacter);
    if (GetWorld()->LineTraceSingleByChannel(
            Hit, Foot + FVector(0,0,50), Foot - FVector(0,0,75),
            ECC_Visibility, P))
        return Hit.ImpactPoint;
    return Foot;
}
将结果输入Control Rig资源(UE5推荐)或AnimGraph中的Two Bone IK骨骼控制节点。
骨骼控制节点(AnimGraph):
节点用途
Two Bone IK双关节IK(手臂、腿部)— effector + 关节目标
FABRIK多骨骼链IK — 末端骨骼 + effector
Look At单骨骼跟踪目标位置并限制角度
Copy Bone在骨骼之间复制变换组件
Spline IK骨骼跟随样条曲线(脊柱、尾巴)

Layered Blend Per Bone (Upper/Lower Split)

Layered Blend Per Bone(上/下身分离)

In the AnimGraph:
  1. Connect state machine output to Base Pose.
  2. Connect
    UpperBody
    slot output to Blend Poses 0.
  3. Layer Setup: Bone=
    spine_01
    , Depth=0, MeshPoseBlendFactor=1.0.
Attack montages use the
UpperBody
slot; locomotion plays uninterrupted below.
在AnimGraph中:
  1. 将状态机输出连接到Base Pose
  2. UpperBody
    插槽输出连接到Blend Poses 0
  3. 层设置:Bone=
    spine_01
    ,Depth=0,MeshPoseBlendFactor=1.0。
攻击montages使用
UpperBody
插槽;locomotion在下方持续播放不受影响。

Aim Offset

Aim Offset

cpp
// In NativeUpdateAnimation:
const FRotator Delta = UKismetMathLibrary::NormalizedDeltaRotator(
    OwningCharacter->GetBaseAimRotation(),
    OwningCharacter->GetActorRotation());
AimYaw   = FMath::Clamp(Delta.Yaw,   -90.f, 90.f);
AimPitch = FMath::Clamp(Delta.Pitch, -90.f, 90.f);
Place an Aim Offset node after the base pose in the AnimGraph, feeding
AimYaw
and
AimPitch
.

cpp
// 在NativeUpdateAnimation中:
const FRotator Delta = UKismetMathLibrary::NormalizedDeltaRotator(
    OwningCharacter->GetBaseAimRotation(),
    OwningCharacter->GetActorRotation());
AimYaw   = FMath::Clamp(Delta.Yaw,   -90.f, 90.f);
AimPitch = FMath::Clamp(Delta.Pitch, -90.f, 90.f);
在AnimGraph中的基础姿态之后放置Aim Offset节点,传入
AimYaw
AimPitch

Linked Anim Graphs and Layers

Linked Anim Graphs与层

Source:
AnimNode_LinkedAnimGraph.h
,
AnimNode_LinkedAnimLayer.h
来源:
AnimNode_LinkedAnimGraph.h
,
AnimNode_LinkedAnimLayer.h

Linked Anim Layer (Recommended)

Linked Anim Layer(推荐)

  1. Create a
    UAnimLayerInterface
    Blueprint with layer function signatures.
  2. Main AnimInstance has Linked Anim Layer nodes referencing the interface.
  3. Separate AnimInstance subclasses implement the interface per mode.
  4. Swap at runtime:
cpp
// Switch locomotion implementation
AnimInst->LinkAnimClassLayers(UClimbingLocomotionLayer::StaticClass());
// Reset all layers to defaults:
AnimInst->LinkAnimClassLayers(nullptr);
// Retrieve a linked instance:
UAnimInstance* Layer =
    AnimInst->GetLinkedAnimLayerInstanceByClass(UClimbingLocomotionLayer::StaticClass());
  1. 创建带有层函数签名的
    UAnimLayerInterface
    Blueprint。
  2. 主AnimInstance包含引用该接口的Linked Anim Layer节点。
  3. 独立的AnimInstance子类按模式实现该接口。
  4. 运行时切换:
cpp
// 切换locomotion实现
AnimInst->LinkAnimClassLayers(UClimbingLocomotionLayer::StaticClass());
// 将所有层重置为默认值:
AnimInst->LinkAnimClassLayers(nullptr);
// 获取链接实例:
UAnimInstance* Layer =
    AnimInst->GetLinkedAnimLayerInstanceByClass(UClimbingLocomotionLayer::StaticClass());

Linked Anim Graph (by Tag)

Linked Anim Graph(按标签)

cpp
AnimInst->LinkAnimGraphByTag(FName("CombatGraph"), UMyCombatAnimInstance::StaticClass());
UAnimInstance* Sub = AnimInst->GetLinkedAnimGraphInstanceByTag(FName("CombatGraph"));
cpp
AnimInst->LinkAnimGraphByTag(FName("CombatGraph"), UMyCombatAnimInstance::StaticClass());
UAnimInstance* Sub = AnimInst->GetLinkedAnimGraphInstanceByTag(FName("CombatGraph"));

Notify Propagation

通知传播

cpp
AnimInst->SetReceiveNotifiesFromLinkedInstances(true);
AnimInst->SetPropagateNotifiesToLinkedInstances(true);
// UE5.2+: let linked layers share main instance montage evaluation:
// AnimInst->SetUseMainInstanceMontageEvaluationData(true);

cpp
AnimInst->SetReceiveNotifiesFromLinkedInstances(true);
AnimInst->SetPropagateNotifiesToLinkedInstances(true);
// UE5.2+:让链接层共享主实例的montage计算数据:
// AnimInst->SetUseMainInstanceMontageEvaluationData(true);

Root Motion

Root Motion

cpp
// Options: NoRootMotionExtraction, IgnoreRootMotion,
//          RootMotionFromMontagesOnly, RootMotionFromEverything
RootMotionMode = ERootMotionMode::RootMotionFromMontagesOnly;

// Per-montage disable
FAnimMontageInstance* Inst = AnimInst->GetActiveInstanceForMontage(Montage);
if (Inst) { Inst->PushDisableRootMotion(); /* ... */ Inst->PopDisableRootMotion(); }
For networked root motion, set the movement component's smoothing mode to
ENetworkSmoothingMode::Exponential
and enable
bAllowPhysicsRotationDuringAnimRootMotion
if rotation comes from animation. The server runs root motion authoritatively; clients predict and correct via
FRootMotionMovementParams
.

cpp
// 选项:NoRootMotionExtraction, IgnoreRootMotion,
//          RootMotionFromMontagesOnly, RootMotionFromEverything
RootMotionMode = ERootMotionMode::RootMotionFromMontagesOnly;

// 按montage禁用
FAnimMontageInstance* Inst = AnimInst->GetActiveInstanceForMontage(Montage);
if (Inst) { Inst->PushDisableRootMotion(); /* ... */ Inst->PopDisableRootMotion(); }
对于联网Root Motion,将移动组件的平滑模式设置为
ENetworkSmoothingMode::Exponential
,如果旋转来自动画则启用
bAllowPhysicsRotationDuringAnimRootMotion
。服务器权威运行Root Motion;客户端通过
FRootMotionMovementParams
进行预测和修正。

Common Mistakes

常见错误

Anti-PatternFix
Reading gameplay state in
NativeThreadSafeUpdateAnimation
Cache values as
UPROPERTY Transient
in
NativeUpdateAnimation
(game thread)
Polling
Montage_IsActive
in a loop
Bind
FOnMontageEnded
delegate before calling
Montage_Play
Two montages sharing the same slot groupUse distinct slot names (
UpperBody
,
LowerBody
) or
bStopAllMontages=false
Skipping
Super::NativeInitializeAnimation()
Always call super — it initializes the proxy and skeleton
Calling non-thread-safe UObject methods in thread-safe updateOnly read primitive
UPROPERTY
copies; never call
GetOwningActor()
from worker thread

反模式修复方案
NativeThreadSafeUpdateAnimation
中读取游戏玩法状态
NativeUpdateAnimation
(游戏线程)中将值缓存为
UPROPERTY Transient
在循环中轮询
Montage_IsActive
在调用
Montage_Play
前绑定
FOnMontageEnded
委托
两个montages共享同一插槽组使用不同的插槽名称(
UpperBody
LowerBody
)或设置
bStopAllMontages=false
跳过
Super::NativeInitializeAnimation()
务必调用父类方法 — 它会初始化代理和骨骼
在线程安全更新中调用非线程安全的UObject方法仅读取原始
UPROPERTY
副本;切勿从工作线程调用
GetOwningActor()

Build.cs

Build.cs

csharp
PublicDependencyModuleNames.AddRange(new string[] {
    "Core", "CoreUObject", "Engine", "AnimGraphRuntime"
});
// Optional:
PrivateDependencyModuleNames.Add("ControlRig");        // Control Rig IK
PrivateDependencyModuleNames.Add("GameplayAbilities"); // GAS montage tasks

csharp
PublicDependencyModuleNames.AddRange(new string[] {
    "Core", "CoreUObject", "Engine", "AnimGraphRuntime"
});
// 可选:
PrivateDependencyModuleNames.Add("ControlRig");        // Control Rig IK
PrivateDependencyModuleNames.Add("GameplayAbilities"); // GAS montage任务

Related Skills

相关技能

  • ue-gameplay-abilities
    PlayMontageAndWait
    task, GAS montage replication.
  • ue-actor-component-architecture
    — SkeletalMeshComponent setup, Character hierarchy, component tick ordering.
  • ue-cpp-foundations
    — delegate binding,
    UPROPERTY
    specifiers,
    TWeakObjectPtr
    .
  • ue-gameplay-abilities
    PlayMontageAndWait
    任务、GAS montage复制。
  • ue-actor-component-architecture
    — SkeletalMeshComponent设置、角色层级结构、组件tick顺序。
  • ue-cpp-foundations
    — 委托绑定、
    UPROPERTY
    说明符、
    TWeakObjectPtr

Reference Files

参考文件

  • references/anim-notify-reference.md
    — built-in notify catalog and custom notify implementation patterns.
  • references/locomotion-setup.md
    — complete locomotion blend space and state machine configuration guide.
  • references/anim-notify-reference.md
    — 内置通知目录和自定义通知实现模式。
  • references/locomotion-setup.md
    — 完整的locomotion混合空间和状态机配置指南。