Search Unity

Resetting AnimationClipPlayable causes events to play multiple times

Discussion in 'Animation' started by bleu, Jan 17, 2019.

  1. bleu

    bleu

    Joined:
    Apr 6, 2013
    Posts:
    41
    Whenever I try to reset a (non-looping) AnimationClipPlayable, the animation plays an event near the end of the clip before starting from the beginning. It also plays an event that occurs near the beginning of the clip twice!

    I have no idea why this is occurring. Below is the script that plays animations. Please focus on PlayAnimation, as that is the function responsible for selecting a clip by setting its weight to 1.0 in the mixer and setting its time to 0.0. I also set the mixer's time to 0.0, but that may not be necessary.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Playables;
    3. using UnityEngine.Animations;
    4. using System.Collections.Generic;
    5.  
    6. /// <summary>
    7. /// Animator for character. Uses Unity's Playable API.
    8. /// </summary>
    9. public class SpriteAnimator : MonoBehaviour {
    10.     [SerializeField]
    11.     private AnimationClip[] animationClips;
    12.     [SerializeField]
    13.     private int defaultClipIndex = 0;
    14.     [SerializeField]
    15.     private SpriteRenderer SpriteRenderer;
    16.  
    17.     private PlayableGraph playableGraph;
    18.     private AnimationMixerPlayable mixerPlayable;
    19.  
    20.     private Dictionary<string, int> clipNameToIndex = new
    21.         Dictionary<string, int>();
    22.     private AnimationClipPlayable[] clipPlayables;
    23.     private int currentClipIndex = 0;
    24.  
    25.     public SpriteRenderer SpriteAnimRenderer { get; private set; }
    26.  
    27.     public void Initialize() {
    28.         for (int clipIndex = 0; clipIndex < animationClips.Length; clipIndex++) {
    29.             var animationClip = animationClips[clipIndex];
    30.             clipNameToIndex.Add(animationClip.name, clipIndex);
    31.         }
    32.         SpriteAnimRenderer = SpriteRenderer;
    33.     }
    34.  
    35.     private void OnEnable() {
    36.         currentClipIndex = defaultClipIndex;
    37.         CreatePlayableGraph();
    38.     }
    39.  
    40.     private void CreatePlayableGraph() {
    41.         playableGraph = PlayableGraph.Create();
    42.         playableGraph.SetTimeUpdateMode(DirectorUpdateMode.GameTime);
    43.  
    44.         var playableOutput = AnimationPlayableOutput.Create(playableGraph,
    45.                 "SpriteAnimationPlayableOutput", GetComponent<Animator>());
    46.    
    47.         int numClips = animationClips.Length;
    48.         mixerPlayable = AnimationMixerPlayable.Create(playableGraph,
    49.             numClips);
    50.         clipPlayables = new AnimationClipPlayable[numClips];
    51.         playableOutput.SetSourcePlayable(mixerPlayable);
    52.  
    53.         for (int clipIndex = 0; clipIndex < numClips; clipIndex++) {
    54.             var animationClip = animationClips[clipIndex];
    55.             // TODO: make this work for non-looping animations
    56.             var clipPlayable = AnimationClipPlayable.Create(playableGraph,
    57.                 animationClip);
    58.             playableGraph.Connect(clipPlayable, 0, mixerPlayable, clipIndex);
    59.             clipPlayables[clipIndex] = clipPlayable;
    60.             mixerPlayable.SetInputWeight(clipIndex, 0.0f);
    61.         }
    62.         mixerPlayable.SetInputWeight(currentClipIndex, 1.0f);
    63.         playableGraph.Play();
    64.     }
    65.  
    66.     private void OnDisable() {
    67.         mixerPlayable.Destroy();
    68.         playableGraph.Destroy();
    69.     }
    70.  
    71.     /// <summary>
    72.     /// Play the requested clip while disabling the rest.
    73.     /// </summary>
    74.     /// <param name="requestedClipName">Requested clip name.</param>
    75.     public void PlayAnimation(string requestedClipName) {
    76.         int requestedClipIndex;
    77.         if (clipNameToIndex.TryGetValue(requestedClipName,
    78.             out requestedClipIndex)) {
    79.             if (currentClipIndex == requestedClipIndex) {
    80.                 return;
    81.             }
    82.  
    83.             for (int clipIndex = 0; clipIndex < animationClips.Length;
    84.                 clipIndex++) {
    85.                 mixerPlayable.SetInputWeight(clipIndex,
    86.                     (clipIndex == requestedClipIndex) ? 1.0f : 0.0f);
    87.             }
    88.          
    89.             mixerPlayable.Pause();
    90.             clipPlayables[requestedClipIndex].SetTime(0.0f);
    91.             Debug.Log("lead time: " + mixerPlayable.GetLeadTime());
    92.             Debug.Log("clip play time: " + clipPlayables[requestedClipIndex].GetTime());
    93.             mixerPlayable.SetTime(0.0);
    94.             mixerPlayable.Play();
    95.             currentClipIndex = requestedClipIndex;
    96.         }
    97.         else {
    98.             Debug.LogWarningFormat("Could not find clip named {0} on " +
    99.                 "sprite animator on game object named {1}.", requestedClipName,
    100.                 this.name);
    101.         }
    102.     }
    103.  
    104.     // TODO: get current time normalized
    105.     public double GetAnimationTime(){
    106.         return mixerPlayable.GetTime();
    107.     }
    108. }
    109.  
    I've searched online and there isn't a whole lot of documentation about this or the bug that is occurring. This question has been asked before -- I don't remember where the specific thread is but it looks like the poster didn't get any responses. Hopefully I have better luck since the playable system seems to be the best way to programmatically control animations. It's either my script or maybe the animation itself is buggy.
     
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    If you're only playing a single clip at a time, and the weights in the mixer for each clip is always exactly 1 for one clip and 0 for the others, I think you're better off just having a single AnimationClipPlayable at a time connected to the graph.

    Anyway, the repeated events seems like a bug, so I would create a bug report for it. I'd also recommend creating your own animation event system that lives in your SpriteAnimator rather than on the animation clips; the animation clip events work in their own mysterious ways, and the interface for editing them is a bit garbage.
     
    vexe likes this.
  3. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    I reported this as a bug a while ago and apparently it's working as intended. One SetTime call causes events to trigger while calling it twice in a row skips over the events.

    You might be interested in trying out my Animancer plugin which provides a much simpler API that wraps the playables. Practically everything in your script would be covered by my LegacyAnimancerController script ("Legacy" as in "similar to Unity's legacy animation system", not meaning "don't use this because it will soon be removed"). From what I can tell, only real differences are that I don't have a default clip index (it just plays the first clip by default) and I don't expose a SpriteRenderer reference (though you could easily inherit from LegacyAnimancerController to add that reference if you want).
     
    vexe likes this.
  4. bleu

    bleu

    Joined:
    Apr 6, 2013
    Posts:
    41
    Thank you for the replies. I think I will simplify my graph as suggested. I was going with events being fired from the animation because they would be synchronized with animation frames...doing it my own way might be less convenient. Kind of a shame that the previous method isn't quite working right for me.
     
  5. bleu

    bleu

    Joined:
    Apr 6, 2013
    Posts:
    41
    Last edited: Jan 18, 2019
  6. Quasimodem

    Quasimodem

    Joined:
    May 21, 2013
    Posts:
    52
    Oh wow, you just saved me a tremendous amount of time! Indeed, it seems that calling SetTime(0) twice "correctly" rewinds the animation and allows events to fire predictably on successive playthroughs. Thank you very much!
     
  7. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Be aware that there are a couple of other issues with Unity's Animation Events when you are using playables.
     
  8. Kustuk

    Kustuk

    Joined:
    May 29, 2016
    Posts:
    22
    Sorry for necro posting, but bug still exists in 2022.3.19f1