Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

(SOLVED) Playable for Looping Animations - How to smoothly restart?

Discussion in '5.4 Beta' started by SpreadcampShay, Jul 11, 2016.

  1. SpreadcampShay

    SpreadcampShay

    Joined:
    Dec 9, 2010
    Posts:
    180
    I'm trying to turn the Example Playable from the Manual to something that can loop through my set of Animations. But when it reaches the end of the animation and resets the Index to start over, I get a T-Pose for a moment instead of resuming Playback smoothly. I can get around this if I access at Index -1 and set the clip time to 0, but then I receive this error: NullReferenceException: (null)
    UnityEngine.Experimental.Director.Playable.CastTo[AnimationClipPlayable]

    Is this a bug with the API, or am I overlooking something?

    The code I'm using (sorry that formatting is a little broken, I had just copied it to a Notepad for savekeeping from that awful forum we shall best forget)
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEngine.Experimental.Director;
    4.  
    5. [AddComponentMenu("Debug/Animations/Cycle Animations (Mecanim)")]
    6. public class CycleAnimationsMecanim : MonoBehaviour
    7. {
    8.   [Header("Animations")]
    9.   public AnimationClip[] myAnimations;
    10.  
    11.   [Header("Animation Settings")]
    12.   public bool restartAllAtEnd = true;  // if false, will stop playback at the end of the queue
    13.  
    14.   [Range(0, 5)]
    15.   public int loopRepeats = 0; // how many times a looping anim will play (cliplength * amount)
    16.    
    17.   private Animator animator;
    18.   private CrossFadeQueuePlayable m_Mixer;
    19.  
    20.    void Start ()
    21.   {
    22.   animator = GetComponent<Animator>();
    23.  
    24.   m_Mixer = Playable.Create<CrossFadeQueuePlayable>();
    25.   m_Mixer.SetInputs(myAnimations);
    26.   m_Mixer.InitVariables(loopRepeats, restartAllAtEnd);
    27.    
    28.   // Bind the playable graph to the player
    29.   animator.Play(m_Mixer);
    30.    }
    31. }
    32.  
    33. public class CrossFadeQueuePlayable : CustomAnimationPlayable
    34. {
    35.   public AnimationMixerPlayable m_Mixer;
    36.   public int m_CurrentClipIndex = -1;
    37.   public float m_TimeToNextClip;
    38.   public bool m_RestartQueue;
    39.  
    40.   private int m_LoopAmount = 1;
    41.  
    42.   public CrossFadeQueuePlayable()
    43.   {
    44.   m_Mixer = AnimationMixerPlayable.Create();
    45.   AddInput(m_Mixer);
    46.   }
    47.  
    48.   public void InitVariables(int loopAmount, bool queueRestart)
    49.   {
    50.   m_RestartQueue = queueRestart;
    51.   m_LoopAmount = loopAmount;
    52.   }
    53.  
    54.   public void SetInputs(AnimationClip[] clipsToPlay)
    55.   {
    56.   foreach(AnimationClip clip in clipsToPlay)
    57.   {
    58.   m_Mixer.AddInput(AnimationClipPlayable.Create(clip));
    59.   }
    60.   }
    61.    
    62.   public override void PrepareFrame(FrameData info)
    63.   {
    64.   // Advance to next clip if necessary
    65.   m_TimeToNextClip -= (float)info.deltaTime;
    66.   if (m_TimeToNextClip <= 0.0f)
    67.   {  
    68.   m_CurrentClipIndex++;
    69.   if (m_CurrentClipIndex < m_Mixer.inputCount)
    70.   {
    71.   var currentClip = m_Mixer.GetInput(m_CurrentClipIndex).CastTo<AnimationClipPlayable>();
    72.  
    73.   // Reset the time so that the next clip starts at the correct position
    74.   currentClip.time = 0;
    75.  
    76.   // Make a looping clip go a bit longer
    77.   if(currentClip.clip.isLooping)
    78.   m_TimeToNextClip = currentClip.clip.length * (1 + m_LoopAmount);
    79.   else
    80.   m_TimeToNextClip = currentClip.clip.length;
    81.   }
    82.   else
    83.   {
    84.   // Restart playback if wished
    85.   if (m_RestartQueue)
    86.   {
    87.   m_CurrentClipIndex = -1;
    88.  
    89.   // Smooth Transition to beginning. Causes error, but works...
    90.   var currentClip = m_Mixer.GetInput(m_CurrentClipIndex).CastTo<AnimationClipPlayable>();
    91.   currentClip.time = 0;
    92.  
    93.   // Works without error, but skips what we need to reset
    94.   //if (m_Mixer.GetInput(m_CurrentClipIndex) != Playable.Null)
    95.   //{
    96.   //  var currentClip = m_Mixer.GetInput(m_CurrentClipIndex).CastTo<AnimationClipPlayable>();
    97.   //  currentClip.time = 0;
    98.   //}
    99.   }
    100.   // Pause when queue is complete
    101.   else
    102.   state = PlayState.Paused;
    103.   }
    104.   }
    105.    
    106.   // Adjust the weight of the inputs
    107.   for (int a = 0; a < m_Mixer.inputCount; a++)
    108.   {
    109.   if (a == m_CurrentClipIndex)
    110.   m_Mixer.SetInputWeight(a, 1.0f);
    111.   else
    112.   m_Mixer.SetInputWeight(a, 0.0f);
    113.   }
    114.   }
    115. }
    116.  
     
  2. SpreadcampShay

    SpreadcampShay

    Joined:
    Dec 9, 2010
    Posts:
    180
    SOLVED. @catherineproulx sent me a message containing a fix to my problem on the "new" (old?) Forums today.

    For those interested, it is about changing the iteration block like this:
    Code (csharp):
    1.  
    2.   if (m_TimeToNextClip <= 0.0f)
    3.   {
    4.   m_CurrentClipIndex = (m_CurrentClipIndex + 1) < m_Mixer.inputCount ? m_CurrentClipIndex + 1 : 0;
    5.  
    6.   var currentClip = m_Mixer.GetInput(m_CurrentClipIndex).CastTo<AnimationClipPlayable>();
    7.  
    8.   // Reset the time so that the next clip starts at the correct position
    9.   currentClip.time = 0;
    10.  
    11.   // Make a looping clip go a bit longer
    12.   if (currentClip.clip.isLooping)
    13.   m_TimeToNextClip = currentClip.clip.length * (1 + m_LoopAmount);
    14.   else
    15.   m_TimeToNextClip = currentClip.clip.length;
    16.  
    17.   }
    18.  
    Really simple in the end. No need for "-1" out of range stuff. I changed where to set the Index a bit to retain my pausing functionality like this:
    Code (csharp):
    1.  
    2.   if((m_CurrentClipIndex + 1) < m_Mixer.inputCount)
    3.   {
    4.   m_CurrentClipIndex = m_CurrentClipIndex + 1;
    5.   }
    6.   else
    7.   {
    8.   if (m_RestartQueue)
    9.   m_CurrentClipIndex = 0;
    10.   else
    11.   state = PlayState.Paused;
    12.   }
    13.  
    Voilá nice looping animations for Testing by the Animator