Lucas pixel art
Published on

Synchronize Animations in Unity

Ever wonder how to keep multiple animations synchronized in Unity? I had the same problem!

Advent is a Pixel Tactical RPG, which means it's very important to us that the game map evokes the classic feel of old Game Boy Advance Fire Emblem games. There was something magical about playing a strategy game on a handheld for the first time. Fire Emblem wasn't as sophisticated as Starcraft, but the characters were entertaining and the pixel art storyline was top-notch.

I had implemented a rudimentary map combat system and animated my character sprites. Though the character idle animations started out synchronized, over time they'd slowly drift apart, every character marching to their own drum. Especially as a big fan of rhythm games, this insubordination could not stand!

For reference, watch this video of Fire Emblem: The Sacred Stones and notice how the animations "tick" in lock-step with each other -

The problem was that the characters walked to new positions, then we re-played their idle animations. The characters walked at a different and variable cadence depending on the length of their journey (and, sometimes I did vary the speed). With some details left out, the move code looks like this:

foreach (var board in boardCellPath.Skip(1))
{
    string moveAnimation;
    (lastDirection, moveAnimation) = MoveAnimation(board, lastCell);

    // this plays the walking animation
    animator.Play(moveAnimation);

    // here we move the board piece to their next cell in their journey
    var (worldX, worldY) = board.ToWorld();
    GameObject.transform.DOMove(new Vector3(worldX, worldY, 0), speed).SetEase(Ease.Linear).Play();

    yield return new WaitForSeconds(speed);
}

// now we reset the idle animation (with a slight detail whether they face left or right)
animator.Play(lastDirection == Direction.West ? "IdleLeft" : "IdleRight");

This resulted in the chaos I mentioned earlier, with every character clearly disrespecting the rules of the TRPG. What absolute insolence. Although, it looked like they were having fun. Who could really blame them.

Fortunately, the fix for this is relatively easy!

animator.Play() takes a few optional parameters. The last optional parameter, normalizedTime, gives you the ability to specify the time offset. This is a float number between 0 and 1. Meaning, essentially, that this is supposed to be the % of the way through the animation when you ask the animator to Play()!

Perfect. Now we just need a metronome. The steps boil down to -

  1. Make sure all your idle animations are the same length and transition at the same time
  2. Create one "synchronizing" Animation GameObject, the metronome, and pass its normalizedTime into your Play() idle animation calls.
animator metronome

Here is my metronome, with one of the animations (Idle is the first to run automatically in the state machine). It just slowly ticks through the animation steps, no Sprite component is attached to display. As mentioned earlier, it's important that all idle animations are the same length - all of mine are on one second loops. Also note that it is easy to forget the "Always Animate" flag.

We're nearly there! All that's left is to make sure every time we play the idle animation, we pass in the metronome % progress. Make sure you grab the normalizedTime from the object right before you need it - I struggled for a minute because I was passing the value of the normalizedTime then waiting around for coroutines, which lead to stale values of normalizedTime - the fix is to pass around the reference so that the normalizedTime can be plucked out after coroutines run.

var metronome = GameObject.Find("Metronome").GetComponent<Animator>(); // or even better, make this a field in your MonoBehavior
animator.Play(lastDirection == Direction.West ? "IdleLeft" : "IdleRight", -1, metronome.GetCurrentAnimatorStateInfo(0).normalizedTime);

Better than any marching band!

Credit to this unity forum thread specifically bennichols23 for providing the solution.