Behaviour

The Events page compares the differences between Unity's inbuilt Animation Events and Animancer Events, but there are several other important details about the way the system works.

Subject Summary
Looping Events on looping animations are triggered on every loop.
Clear Automatically By default, playing a new animation will clear the events from the old animation.
Other Details Various other minor details.

Looping

Events behave differently depending on whether the animation is looping or not:

Type Trigger Conditions
Non-Looping Once on the frame when the animation passes the specified time.
Looping

Every loop on the frame when the animation passes the specified time.

  • If the animation is playing fast enough that multiple loops pass in a single frame, the event will be triggered the appropriate number of times. If you want to ensure that your callback only gets triggered once per frame, you can store the AnimancerPlayable.FrameID and check if it has changed each time your method is called.
  • Looping Events must be within the range of 0 <= normalizedTime < 1 in order to function correctly. Events outside that range will cause an ArgumentOutOfRangeException during the next update.
  • AnimancerEvent.AlmostOne is a constant containing the largest possible float value less than 1 in case you want to set an event to occur right at the end of the loop.
End Events On every frame when the animation has passed the specified time, regardless of whether the animation is looping or not.

Clear Automatically

Calling AnimancerComponent.Play or Stop automatically clears the events of all states unless AnimancerState.AutomaticallyClearEvents is set to false. This ensures that you do not have to worry whether or not other scripts have used the same animation previously. Each script that plays an animation takes responsibility for managing what it expects to happen without worrying about the expectations of other scripts.

For example, if a character has an Attack animation which wants to return to Idle when it finishes but the character gets hit by an enemy in the middle of the Attack, the character will now want to play the Flinch animation and return to Idle after that instead. At that point, we no longer care about the end of the Attack animation. If we want to attack again, we just play the animation and register the callback again. But if the character has a special skill that lets them perform an attack combo which happens to include the same Attack animation followed by several others in sequence, it will not want that animation to still have the old End Event that returns to Idle.

That said, enforcing rules for which animations/actions are allowed to interrupt each other is often very important so that topic is covered in the Interruptions example.

The way that events are cleared differs slightly depending on how a state was played:

Direct Transition
AnimationClip clip;
AnimancerComponent animancer;
var state = animancer.Play(clip);
ClipTransition transition;
AnimancerComponent animancer;
var state = animancer.Play(transition);
Without a Transition, the state will have no events by default. When playing a Transition, the state will be given the AnimancerEvent.Sequence from that Transition.
Accessing its Events will give it a spare AnimancerEvent.Sequence from the ObjectPool which you can then modify on the spot. Accessing its Events will refer directly to the AnimancerEvent.Sequence owned by the Transition so any modifications you make will be retained when you play it again. This means modifications should generally only be performed once on startup rather than being repeated every time you play it.
void PlayAnimation()
{
    var state = animancer.Play(clip);
    state.Events.Add(...
}

This creates Garbage every time unless you cache the event callbacks.

void Awake()
{
    transition.Events.Add(...
}

void PlayAnimation()
{
    animancer.Play(transition);
}

When you play something else, the AnimancerEvent.Sequence will be removed from the state, have its contents cleared, and then be returned to the ObjectPool so it can be reused.

If you create a new AnimancerEvent.Sequence() and assign it manually, then it will not be cleared or pooled.
When you play something else, the AnimancerEvent.Sequence will be removed from the state but since it is owned by the Transition it will not be cleared or given to the ObjectPool.

Other Details

This system has several other details worth mentioning:

  • Events are triggered using the AnimancerEvent.Invoke method which sets the static AnimancerEvent.CurrentEvent and AnimancerEvent.CurrentState properties, allowing anything to access the details of the event and the state that triggered it before being cleared immediately afterwards. The Event Utilities example demonstrates some ways those properties can be used.
  • Changing the AnimancerState.Time (or NormalizedTime) prevents that state from triggering any more events during that frame. If you want events between the old and new time to be triggered, you can use AnimancerState.MoveTime instead.
  • The AnimancerState.Events sequence can not be modified by its own events (i.e. you can't use an event to add another event to the state that triggered it).
  • Animancer Events can be placed on Mixers or on their children depending on whether you want them to be triggered according to the weighted average NormalizedTime of the children or the NormalizedTime of a specific child. The Events section on that page gives more details.
  • They also technically work with Controller States, though they are tied to the overall ControllerState and do not check what the Animator Controller is doing internally so attempting to use them might not give the intended result.
  • If you want to run custom code as part of the animation update, you can implement IUpdatable in your own scripts.