Quick answer: Mark gameplay-critical notifies as branching points in the Montage editor so they fire even when playback rate skips past their time. Prefer UAnimNotifyState with a window for effects that span multiple frames. Check Montage PlayRate is not pushing the notify outside any tick.
Here is how to fix Unreal AnimNotify not firing at non-standard playback rate. You set up a punch montage with a “hit” notify at the contact frame. At normal speed it fires every time. Speed the montage to 2.0x for a hasted ability and the notify disappears at random — hit damage does not apply, but the animation plays through. Slow it to 0.3x and notify fires twice. Unreal’s notify system is driven by tick time, so sub-tick-sized windows can be stepped over entirely.
The Symptom
A punch or ability plays visually but its damage, sound, or VFX notify is missing. You reproduce by setting PlayRate above 1.5 or below 0.5. At normal rate, 100% reliable. At odd rates, 50 to 90% reliable. The montage finishes, the Montage_Ended event fires, but the expected state change never happened.
Variant: the notify fires twice in a row during slow playback. Or it fires at the wrong time, triggering damage before the visual hit.
What Causes This
Single-frame notify steps skipped. AnimNotifies without duration are point-in-time events. Unreal checks PreviousPosition < NotifyTime <= CurrentPosition each tick. At high PlayRate, PreviousPosition and CurrentPosition can bracket the notify time but also bracket a second one — both fire. At very slow rates, the same notify can be bracketed over multiple ticks and fire repeatedly unless tick-deduplicated.
Montage NotifyTriggerFilter. Montages can have a filter that only triggers notifies in the highest-weighted section. If your montage blends with another (montage blending in and out), weight can drop below threshold at the notify time.
Notify tick type wrong. UAnimNotify has a tick type that decides whether it fires on game thread, animation thread, or both. The default works for most cases, but custom notifies implemented wrong can silently skip ticks.
Network replication. AnimMontages replicate differently from AnimSequences. Montage_Play on the server replicates, but the notify you placed might have bTriggerOnDedicatedServer = false which skips server-side firing. Damage logic on the server never runs.
Skeletal mesh not rendering. If the mesh is culled (VisibilityBasedAnimTickOption = OnlyTickPoseWhenRendered), the anim graph does not tick and notifies do not fire.
The Fix
Step 1: Make critical notifies branching points. In the Montage editor, right-click the notify and choose Montage Branching Point. Branching points are checked every frame and guaranteed to fire if the montage crosses their time, independent of PlayRate.
// In C++, subclass UAnimNotify and set this flag
UAnimNotify_HitFrame::UAnimNotify_HitFrame()
{
// Make the notify a branching point - gameplay-critical
// (Set in montage editor under Notify Track properties)
}
For blueprint montages, the notify properties in the track have a Branching Point checkbox. Enable it for damage, projectile spawns, and section transitions.
Step 2: Use AnimNotifyState for windows. Instead of a single hit point, use a NotifyState spanning the contact window:
class UAN_DamageWindow : public UAnimNotifyState
{
GENERATED_BODY()
public:
virtual void NotifyBegin(USkeletalMeshComponent* Mesh, UAnimSequenceBase* Anim,
float TotalDuration, const FAnimNotifyEventReference& Ref) override
{
// Called when the window starts - enable damage collision
if (ACharacter* Owner = Cast<ACharacter>(Mesh->GetOwner()))
{
Owner->EnableWeaponCollision(true);
}
}
virtual void NotifyEnd(USkeletalMeshComponent* Mesh, UAnimSequenceBase* Anim,
const FAnimNotifyEventReference& Ref) override
{
if (ACharacter* Owner = Cast<ACharacter>(Mesh->GetOwner()))
{
Owner->EnableWeaponCollision(false);
}
}
};
NotifyState Begin/End fire bracket-checked — even at 4x PlayRate, Begin fires once when crossing the start and End fires once when crossing the end.
Step 3: Avoid playing montages at extreme rates. If you need a 2x or 3x speed animation, consider authoring it at that speed. Rate-scaled montages risk notify issues, root motion oddity, and physics interpolation glitches.
// Play at 1.25x is usually safe
AnimInstance->Montage_Play(AttackMontage, 1.25f);
// Play at 3.0x is risky - expect notifies to skip
AnimInstance->Montage_Play(AttackMontage, 3.0f);
Step 4: Verify VisibilityBasedAnimTickOption. On the SkeletalMeshComponent, set:
MeshComponent->VisibilityBasedAnimTickOption =
EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones;
Default is OnlyTickPoseWhenRendered, which skips notifies when off-screen. For invisible actors that still need notifies (AI attacks behind camera), switch to AlwaysTick.
Montage Section Switches
If a notify precedes a section jump (Montage_JumpToSection), the jump happens on anim tick and can reorder notifies. For deterministic order, put the section transition as its own branching-point notify after the gameplay notify.
Server-Side Notifies
For multiplayer, ensure notifies fire on server. In the notify’s properties, check Trigger On Dedicated Server. Without it, the server-side montage runs but the damage notify never triggers, and no damage is applied.
“Branching points for gameplay. NotifyState for windows. Single-frame notifies are for sound and VFX only.”
Related Issues
For replication-related notify issues, see Actor Replication Not Working. For broader animation issues, Physics Constraint Drive Not Working covers related simulation topics.
Branching point for critical. NotifyState for windows. Keep PlayRate reasonable.