Animation synchronization with soundtrack in Unity

Sometimes we might need to play the animations together with a separate soundtrack in Unity. However, animations and the soundtrack may be out of sync due to frame drop or other performance problems even we start playing them in the same frame. A simple but not efficient solution is to force the animation to play the exact frame as the soundtrack ever frame. Below is a code example.

 

public class AnimationSync : MonoBehaviour{
    public Animator animator;
    public string animationClip; // *NOTE(1): Should be set manually according to the clip

    [SerializeField]
    private int length; // *Note(2): Should be set manually according to the clip
    float normTimer = 0.0f; // Start time of the current destination state. Value is in normalized time.

    private bool isLoop = false; // *Note(2): Should be set manually according to the clip

    public override void Start() {
        if(animation != null) {
            animator.gameObject.SetActive(true);
            //animator.SetTrigger(animationClip); // If not Play on Awake
        }
        //// *Note(2): Uncomment this to get the length when the clip changes
        //float lengthf = 0.0f;
        //RuntimeAnimatorController ac = animator.runtimeAnimatorController;    //Get Animator controller
        //for (int i = 0; i < ac.animationClips.Length; i++)                    //For all animationClips
        //{
        //    if (ac.animationClips[i].name == this.animationClip)              //If it has the same name as your clip
        //    {
        //        lengthf = ac.animationClips[i].length;
        //        this.isLoop = ac.animationClips[i].isLooping;
        //        break;
        //    }
        //}

        //this.length = (int)(lengthf * 1000.0f);

        StartCoroutine(this.CoPlayAnimation());
    }

    private IEnumerator CoPlayAnimation()
    {
        this.startTime = this.playheadPosition; // *NOTE(3)   
        while(true)
        {
            if(!this.isLoop)
                normTimer = (float)((float)(this.playheadPosition - this.startTime) / this.length);
            else
                normTimer = (float)((float)((this.playheadPosition - this.startTime) % this.length ) / this.length);

            animator.Play(this.animationClip, -1, normTimer); // *Note(4): Force the animation to play the exact frame
            yield return null;
        }
    }
}

Several notes that need to be noticed:

  1. this.animationClip is the name of the clip you need in an animator
  2. this.length is the EXACT length of the animator. For some reason, any version below Unity 5.4 cannot call animator.runtimeAnimatorController correctly in the BUILD mode, but in EDITOR mode.  Therefore, one way to make it works is to call the commented block in editor mode above to get the value of the length and set it manually in the inspector, or hard code it. Remember to remove it when trying to make a build.
    float lengthf = 0.0f;
    RuntimeAnimatorController ac = animator.runtimeAnimatorController;    //Get Animator controller
        for (int i = 0; i < ac.animationClips.Length; i++)                //For all animationClips
        {
            if (ac.animationClips[i].name == this.animationClip)          //If it has the same name as your clip
            {
                lengthf = ac.animationClips[i].length;
                this.isLoop = ac.animationClips[i].isLooping;
                break;
            }
        }
    
    this.length = (int)(lengthf * 1000.0f);
  3. this.playheadPosition is updated by the timer of the soundtrack every frame. (Wasn’t shown here)
  4. Details about this function: https://docs.unity3d.com/ScriptReference/Animator.Play.html

This method has a very poor performance. But it does its work.

 

 

Leave a Reply