Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Animation lags by 1 frame when activating object via Timeline signal callback

Discussion in 'Timeline' started by huulong, Jun 10, 2022.

  1. huulong

    huulong

    Joined:
    Jul 1, 2013
    Posts:
    224
    Context

    In my project, I define my FX as prefabs, each with their own Animator. The Animator Controller defines a single state which is also the Entry state, and that plays a simple animation (changing the Sprite Renderer sprite).

    So, to play an FX, I just have to instantiate an FX prefab, or, when pooling, activate a sleeping FX instance, which will automatically play the Entry animation.

    However, to visualize what an FX prefab corresponds to, I generally set the SpriteRenderer default sprite reference to some representative sprite. It helps me previewing the prefab when navigating assets, or when dragging them on the scene for editing.

    Now, I want to play some FX as part of a Timeline (for a battle sequence), so I defined a Timeline signal connected to a callback on some Signal Receiver component, and added that signal on my Signal Track.

    Issue

    When playing the TImeline, the FX appears for 1 frame (the frame when the signal is hit) with its representative sprite (generally a sprite in the middle of the animation). Starting next frame, the FX plays the Entry animation properly from its first sprite to the end. However, player can still perceive a visual glitch with the representative sprite flashing for 1 frame at the beginning.

    Possible cause

    It seems that this is due to Timeline signal callbacks being called late in the frame.

    According to https://forum.unity.com/threads/bug...e-does-not-work-properly.769796/#post-5126627, this happens between Update and LateUpdate (not sure how it's related to FixedUpdate). Doc and SEO panel don't say explicitly how TImeline updates relatively to other components like Animator, but I suspect that the Animator is updated first, then the Timeline with signal callbacks, then the Sprite Renderer is renderer. This means that at the end of the first frame (when signal is sent), the Entry animation hasn't started, and the Sprite Renderer shows the representative sprite set on the FX prefab (or, if the prefab instance is reused for pooling, whatever sprite was set last time).

    If so, this would also explain another issue: https://forum.unity.com/threads/timeline-signal-frame-accuracy-fix.1293702/ where an Animation track switches to the next animation 1 frame too late.

    The first thread (about Play On Awake) suggests to use PlayableDirector.Evaluate(). This should work to make sure the 1st keyframes are properly used immediately. Unfortunately, as noticed in https://forum.unity.com/threads/tim...-sent-in-playabledirector-manual-mode.711494/, using Manual Update Mode with Evaluate (typically in our own FixedUpdate to ensure correct timing with animation) doesn't work for signals and will not have any signal callbacks called.

    In fact, that thread suggests two solutions: "a FixedUpdate timeline update" that would guarantee FixedUpdate-time Timeline signal callback call, which would effectively be before Animator update (I can see it because delaying the FX activation to next FixedUpdate fixes the issue), and an "Advance" method on PlayableDirector that would advance the timeline while sending signals, which would also fix the issue (plugging it in FixedUpdate with Manual Update Mode). Therefore, this thread may contain the key to solving my issue, but I wanted to open a different thread to handle the issue of Update timing and lag rather than signals with manual update.

    Workarounds

    I found several workarounds, some tackling generic lag, some specific to animations:

    # Animation-specific

    a. When you instantiates/activates the object (in my case, the FX animated sprite), manually set any animated property to their first key value in the Entry animation (in my case, spriteRenderer.sprite = null). This is like the suggestion to Evaluate(), except it works with a signal too (in counterpart you need to know in advance what kind of stuff your animation will change)
    a1. Do this in the same method that instantiates/activates the object
    a2. Do this on the Awake or OnEnable method of some component on that object (Awake if you only need to initialize properly the first time, OnEnable if you need to do it every time e.g. if object is pooled and reused and there is no guarantee that it ends with a null sprite at the end of each animation)
    => this works but preserves the existing lag of 1 frame
    => needs more knowledge of context, but if you have few different types of objects animated like this, you can fix the problem once and for all with a2

    b. Call animator.Update(Time.deltaTime) after instantiation/activation of the object to manually advance to the first frame (animator.Update(0) will also hide the glitchy representative sprite on first frame, but will show the 1st frame of the animation for 2 frames)
    => this works and is the only solution I found that removes the animation lag entirely
    => but a bit hacky

    # Generic

    c. Delay instantiation/activation of the object to the next frame update/fixed update.
    c1. Via StartCoroutine + coroutine method that starts with yield return null
    c2. Via async void method that starts with Task.Yield()
    c3. By setting a flag consumed on next FixedUpdate, and on consumption, instantiate/active the object
    => this works but preserves the existing lag of 1 frame
    => c1/c2 need more maintenance, I had to create a variant of each method called via Timeline signal just to add the Task.Yield(). c3 needs even more maintenance with the extra flag member.

    Suggestions for Unity

    https://forum.unity.com/threads/tim...-sent-in-playabledirector-manual-mode.711494/ probably already contains the good ideas, but to sum-up:

    A. Add an option to update Timeline, and specifically send signals, on FixedUpdate (or any time before Animator update this frame). This would effectively allow us to activate the object with the correct animation sprite on the signal frame, without delay.
    B. Add an option to delay signal callback to next Update/FixedUpdate. This would keep the existing lag of the first animation frame, but at least it won't show the glitch on the signal frame just before.
    C. Add an Advance method to PlayableDirector that works with signals so we can use manual Update mode and basically do whatever we want, in this case send the signal on the next Fixed Update. This would preserve the existing lag like B.

    EDIT: I tested Activation Track to compare, and activation an object like my FX via Activation Track properly starts the animation immediately, without lag. So this only occurs for signals apparently. So this is not something related directly to Timeline update timing. I suspect that event callbacks are only processed at the end of frame.
     
    Last edited: Jun 10, 2022
    whatahell and Snowdrama like this.
  2. Snowdrama

    Snowdrama

    Joined:
    May 10, 2014
    Posts:
    28
    Exactly, I recently did a test where I intentionally made my frames take 300ms, and the timeline will happily skip any Time.deltaTime forward and trigger all signals within that 300ms timeline tick. It's not necessarily just a single frame of lag, but that it will be called after the timeline has been evaluated at whatever the current time is, no matter how many ticks later into the timeline you are. I updated my post with more info on that but you're spot on that's what I see, the tracks are evaluated BEFORE the signals, it's either at the end of this frame/update loop or it happens during the next update loop, but definitely after tracks are evaluated. And for at least me, I'd very much prefer they were executed before the timeline was evaluated, this would prevent my animation stutter issue.
     
    Erveon likes this.