Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Dance Sync, animation loses the synchronization after some time

Discussion in 'Animation' started by polkuj, Sep 10, 2016.

  1. polkuj

    polkuj

    Joined:
    Jul 25, 2016
    Posts:
    5
    Hello :)

    I downloaded a Dance sequence Animation from Mixamo. I adjusted the Animation in Cinema 4d to match the bpm of a song. I imported both the song and the fbx(30 fps) to unity and let them Play together. They do match at the beggining, but after some time, the Animation starts "getting slower" and loses the beat of the song.

    My Goal is to Play an Animation that during the whole song matches the beat, and achieve this by preaparing both data in advance.

    My guess is that the Audio Plays constantly at the same rate, right? and the Animation Speed depends on the Frame rate of the game. BUT my Frame rate accroding to a script i downloaded from asset store is always between 50 and 60 fps....and the Animation accroding to unity is 30fps.

    So am I right that the Animation Speed is not 100% reliable? If so how can i achieve my Goal?

    i tested it without Frame reduction, with Frame reduction and optimal.

    just to double check i opened the Animation with Maya and letting Play there in a Loop does match the song, so i dont know whats Happening inside unity that i can fix.

    Before implementing an unorthodox solution(like restarting the Animation after some ammount of seconds) i wanted to ask you if my theories are right and if there is any solution to it.

    Thank you very much.
     
  2. polkuj

    polkuj

    Joined:
    Jul 25, 2016
    Posts:
    5
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class womanForward : MonoBehaviour
    5. {
    6.     //look at partner
    7.     public Transform LookAt;
    8.     public Animator anim;
    9.  
    10.     //react
    11.     public KeyCode forward;
    12.     public KeyCode backward;
    13.     public KeyCode finish;
    14.  
    15.     bool keyB = false;
    16.     bool keyFinish = false;
    17.     private float PercB;
    18.  
    19.     private bool waitingForF=false;
    20.     private bool waitingForB=false;
    21.  
    22.     public GameObject woman;
    23.     private Vector3 startPos;
    24.     private Vector3 endPos;
    25.  
    26.     public float distance = 0.5f;
    27.     //time from start to end
    28.     public float lerpTime = 2;
    29.     //this will update Lerp time
    30.     private float currentLerptime = 0;
    31.     bool keyF = false;
    32.     private float PercF;
    33.  
    34.     // Use this for initialization
    35.     void Start()
    36.     {
    37.         startPos = woman.transform.position;
    38.         endPos = woman.transform.position + Vector3.forward * distance;
    39.     }
    40.  
    41.     // Update is called once per frame
    42.     void Update()
    43.     {
    44.         anim.speed = 1.0f;//1.568f;
    45.  
    46.         if (Input.GetKeyDown(finish))
    47.         {
    48.             keyFinish = true;
    49.         }
    50.         if (keyFinish == true)
    51.         { anim.SetBool("finish", true); }
    52.         //transform.LookAt(LookAt);
    53.         if (Input.GetKeyDown(forward))
    54.         {
    55.             keyF = true;
    56.         }
    57.         if (keyF == true && waitingForB == false)
    58.         {
    59.             currentLerptime += Time.deltaTime;
    60.             if (currentLerptime >= lerpTime)
    61.             {
    62.                 currentLerptime = lerpTime;
    63.             }
    64.              PercF = currentLerptime / lerpTime;
    65.  
    66.             woman.transform.position = Vector3.Lerp(startPos, endPos, PercF);
    67.             anim.SetBool("forward", true);
    68.             anim.SetBool("backward", false);
    69.  
    70.             if (woman.transform.position == endPos)
    71.             {
    72.                 keyF = false;
    73.                 currentLerptime = 0;
    74.                 PercF = currentLerptime / lerpTime;
    75.                 waitingForB = true;
    76.                 waitingForF = false;
    77.             }
    78.  
    79.             if (keyFinish == true)
    80.             { anim.SetBool("finish", true); }
    81.         }
    82.  
    83.  
    84.         //Backward
    85.         if (Input.GetKeyDown(backward))
    86.         {
    87.             keyB = true; Debug.Log("away1");
    88.         }
    89.         if (keyB == true && waitingForF == false)
    90.         {
    91.             currentLerptime += Time.deltaTime;
    92.             if (currentLerptime >= lerpTime)
    93.             {
    94.                 currentLerptime = lerpTime;
    95.             }
    96.             PercB = currentLerptime / lerpTime;
    97.             woman.transform.position = Vector3.Lerp(endPos, startPos, PercB);
    98.             Debug.Log("away2");
    99.             anim.SetBool("backward", true);
    100.             anim.SetBool("forward", false);
    101.             anim.SetBool("idle_cancel", true);
    102.  
    103.             if (woman.transform.position == endPos)
    104.             {
    105.                 keyB = false;
    106.                 currentLerptime = 0;
    107.             }
    108.             if (woman.transform.position == startPos)
    109.             {
    110.                 keyB = false;
    111.                 currentLerptime = 0;
    112.                 PercB = currentLerptime / lerpTime;
    113.                 waitingForF = true;
    114.                 waitingForB = false;
    115.             }
    116.  
    117.             if (keyFinish == true)
    118.             { anim.SetBool("finish", true); }
    119.         }
    120.      
    121.     }
    122. }
    there is also the possibility that i am calling the Animation in an inefficient way, so here is my Code (just a normal press key). i m aodin that here: { anim.SetBool("finish", true);
     
  3. polkuj

    polkuj

    Joined:
    Jul 25, 2016
    Posts:
    5
    i just noticed the inspector of my Animation says: length 4,767....30 fps. but in fact...when playing it takes around 6 seconds :(
     
  4. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    Hi polkuj,

    The desynchronization is expected, the audio is ticked with a DSP and most of the rest of the engine with the internal timer.

    Right now the only way to make this work would be to manually tick your animator with audio DSP time.
    https://docs.unity3d.com/ScriptReference/AudioSettings-dspTime.html
    So the audio become the master and the animator the audio slave

    To make it work you will need to disable the animator, use PlayInFixedTime and then tick manually the animator

    Code (CSharp):
    1.  
    2. public class AnimatorTicker : MonoBehaviour
    3. {
    4.     private AudioSource audioSource;
    5.     private Animator animator;
    6.  
    7.     void Start()
    8.     {
    9.         animator = GetComponent<Animator>();
    10.         audioSource = GetComponent<AudioSource>();
    11.         animator.enable = false;
    12.     }
    13.  
    14.     void Update()
    15.     {
    16.         // Change time for currently played state
    17.         animator.PlayInFixedTime(0, 0, audioSource.time);
    18.         animator.Update(0.0f);
    19.     }
    20. }
     
  5. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    So I just had a chat with our audio team leader

    He proposed a different solution that will give you better result because audioSource.time is not as precise as the dspTime.

    So basically he proposed to start your audio clip with PlayScheduled and save the time at which you start your audio clip as the dspStartTime.
    And then at each update compute the diff between the current dspTime and the dspStartTime and use this diff to play your animation clip in sync with audio with PlayInFixedTime.
    https://docs.unity3d.com/ScriptReference/AudioSource.PlayScheduled.html
     
  6. mohamedheaderccc

    mohamedheaderccc

    Joined:
    Oct 11, 2016
    Posts:
    3
    In case someone is facing the same issue,,

    I needed to fire Animation Events manually too.

    Code (CSharp):
    1. public class AnimatorTicker : MonoBehaviour
    2. {
    3.     public AudioSource AudioSource;
    4.  
    5.     Animator animator;
    6.     AnimatorStateInfo currentAnimatorStateInfo;
    7.  
    8.     double startTick;
    9.     double deltaDSP, oldDeltaDSP;
    10.  
    11.     void Start()
    12.     {
    13.         animator = GetComponent<Animator>();
    14.         animator.enabled = false;
    15.  
    16.         currentAnimatorStateInfo = animator.GetCurrentAnimatorStateInfo (0);
    17.         startTick = AudioSettings.dspTime;
    18.  
    19.         AudioSource.PlayScheduled (startTick);
    20.     }
    21.  
    22.     void Update()
    23.     {
    24.         if (currentAnimatorStateInfo.fullPathHash != animator.GetCurrentAnimatorStateInfo (0).fullPathHash)
    25.         {
    26.             currentAnimatorStateInfo = animator.GetCurrentAnimatorStateInfo (0);
    27.             startTick = AudioSettings.dspTime;
    28.             deltaDSP = 0;
    29.         }
    30.         oldDeltaDSP = deltaDSP;
    31.         deltaDSP = AudioSettings.dspTime - startTick;
    32.  
    33.         // Change time for currently played state
    34.         animator.PlayInFixedTime (0, 0, (float)(deltaDSP));
    35.         animator.Update (0.0f);
    36.  
    37.         foreach (var clip in animator.GetCurrentAnimatorClipInfo(0))
    38.         {
    39.             foreach (var animEvent in clip.clip.events)
    40.             {
    41.                 if(animEvent.time >= oldDeltaDSP && animEvent.time < deltaDSP)
    42.                 {
    43.                     foreach(var j in gameObject.GetComponents(typeof(MonoBehaviour))) {
    44.                         try {
    45.                             var mi = j.GetType().GetMethod(animEvent.functionName);
    46.                             if(mi != null) {
    47.                                 var pers = mi.GetParameters();
    48.                                 if(pers != null && pers.Length > 0) {
    49.                                     if(pers.Length == 1) {
    50.                                         if(pers[0].ParameterType == typeof(string)) {
    51.                                             mi.Invoke(j, new object[] { animEvent.stringParameter });
    52.                                             break;
    53.                                         }
    54.                                         else if(pers[0].ParameterType.IsByRef) {
    55.                                             mi.Invoke(j, new object[] { animEvent.objectReferenceParameter });
    56.                                             break;
    57.                                         }
    58.                                         else if(pers[0].ParameterType == typeof(float)) {
    59.                                             mi.Invoke(j, new object[] { animEvent.floatParameter });
    60.                                             break;
    61.                                         }
    62.                                         else if(pers[0].ParameterType == typeof(int)) {
    63.                                             mi.Invoke(j, new object[] { animEvent.intParameter });
    64.                                             break;
    65.                                         }
    66.                                     }
    67.                                 }
    68.                                 else {
    69.                                     mi.Invoke(j, null);
    70.                                     break;
    71.                                 }
    72.                             }
    73.                         }
    74.                         catch(System.Reflection.AmbiguousMatchException e) {
    75.                             Debug.LogError("Cannot find animation event method : " + animEvent.functionName);
    76.                         }
    77.                     }
    78.                 }
    79.             }
    80.         }
    81.     }
    82. }
    credits : http://answers.unity3d.com/questions/578235/animation-events-wont-trigger-if-animation-time-se.html
     
    Last edited: Feb 14, 2017
  7. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    yes in your case this is expected, as you are not increasing the time in the animator, but rather sample the clip at a precise time.
    If you change your logic a little bit and manage to call
    animator.Update(deltaDSP)
    like this
    Code (CSharp):
    1.  void Update()
    2.     {
    3.         if (currentAnimatorStateInfo.fullPathHash != animator.GetCurrentAnimatorStateInfo (0).fullPathHash)
    4.         {
    5.             currentAnimatorStateInfo = animator.GetCurrentAnimatorStateInfo (0);
    6.             startTick = AudioSettings.dspTime;
    7.             deltaDSP = 0;
    8.            animator.PlayInFixedTime (0, 0, (float)(deltaDSP));
    9.         }
    10.         oldDeltaDSP = deltaDSP;
    11.         deltaDSP = AudioSettings.dspTime - startTick;
    12.         // Change time for currently played state
    13.        
    14.         animator.Update (deltaDSP );
    the animation events should start to be fired again by the system.
    The reason why it doesn't work in your case is because we do fire all events that are in the range ]previousTime, currentTime] and since you are always reseting the time of the clip without providing a deltaTime your play range is always ]deltaDSP, deltaDSP]
     
    mohamedheaderccc likes this.
  8. mohamedheaderccc

    mohamedheaderccc

    Joined:
    Oct 11, 2016
    Posts:
    3
    I tried the attached code,
    Now the animation is playing very fast.
     
  9. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    yes this is probably because you animator is enabled so the system also tick the animator.
    When you take control of the animator timing you must also disable the animator component otherwise you will get exactly what you are getting.
     
  10. mohamedheaderccc

    mohamedheaderccc

    Joined:
    Oct 11, 2016
    Posts:
    3
    Hmmm, strange because Animator is already disabled.
    I will continue use the other script posted before, If you think it could be a bug, then let me know to test it on another scene and if so would report it.

    Thanks,
     
  11. KLWN

    KLWN

    Joined:
    Apr 11, 2019
    Posts:
    35
  12. Dahku

    Dahku

    Joined:
    Jan 30, 2013
    Posts:
    15
    Very old thread, but wanted to chip in for anyone who finds their way here and is having issues with Animation Events.

    Mecanim-Dev's code doesn't work, and I don't even want to think about the performance implications of the other solution to fire Events manually. My alternative: create empty objects containing a MonoBehaviour with "OnDisable()", and disable them from the animation in time with where you'd have put your Events. You can also just re-enable them if you want the same event fired multiple times.

    It's such a cheesy workaround that I actually laughed out loud when I thought of it, but I had a cutscene synced to music that needed events to fire off in sync and it worked perfectly.