Synchronizing multiple AnimationNodeStateMachine

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By cyanotic

Hello,
I am creating a platform shooter game.
I would like to do something similar to what Abuse has done quite a few decades ago.
The lower body and the upper body of my character need to be fully separated, but still, I would like to use sprites.

So, the idea is that I would have two state machines: one for the lower body (legs) and one for the upper body (torso, head, arms) that are animated independently but, somehow, synchronized.

Right now I’m using two separate AnimationNodeStateMachine and a single State Machine to handle the two animations.

This is working flawlessly

  • Upper body: IDLE → WALK → IDLE
  • Lower body: IDLE → WALK → IDLE

Since the two animations are switching at the same time, they contain the same frames, so the position of legs/arms is always correct.

If, instead, I do introduce something in between like:

  • Upper body: IDLE → WALK → FIRE → WALK
  • Lower body: IDLE → WALK

The second WALK animation of the upper-body is now off-sync with the lower-body, since the two AnimationNodeStateMachines are playing different frames (e.g. upper body is playing the frame 6 and the lower body is playing the frame 1 of the same animation).

Should I use something different than AnimationNodeStateMachine? Is there a way to implement something like:
“If the upper body is playing ‘IDLE’ and lower body is playing ‘IDLE’, make sure they’re playing the same frame.”?

Of course, this is a simple scenario: I already have more states (JUMP, RELOAD, …) and I would like to be able to perform JUMP-FIRE, WALK-FIRE, RUN-FIRE, JUMP-RELOAD, WALK-RELOAD, RUN-RELOAD, …

I’ve already watched this video as well.

Thanks :slight_smile:

:bust_in_silhouette: Reply From: cyanotic

After much fiddling I’ve ended up discarding completely the AnimationNodeStateMachine.

Reason for that is that it has an internal State Machine that cannot be manipulated from code.

So, basically, I’ve created two independent state machines, with two AnimationPlayers: one for the whole body, the second one for the arms.

The change and synchronization of the animations happens in the root script (e.g. Player.gd, but this can be contained in a separate class, if you want).

These are the states for the scripted state machine:

  • Body: Idle, Walk, Run, Jump
  • Arms: None, Shoot, Reload

These are the states for the AnimationPlayers:

  • Body: Idle, Walk, Run, Jump
  • Arms: Idle, Walk, Run, Jump, Shoot, Reload

As you can see the two players share the same animations plus the two extra “shoot” and “reload”.

Scenario 1 - top and bottom of the body needs to be in sync
The scripted state machines will be in the following state(s)

  • Body: Idle | Walk | Run | Jump
  • Arms: None

This means that the two AnimationPlayers should execute exactly the same animations:

  • Body: Idle | Walk | Run | Jump
  • Arms: Idle | Walk | Run | Jump

Scenario 2 - top and bottom of the body will diverge due to an action

The scripted state machines will be in the following state(s)

  • Body: Idle | Walk | Run | Jump
  • Arms: Shoot | Reload

Same goes for the AnimationPlayers:

  • Body: Idle | Walk | Run | Jump
  • Arms: Shoot | Reload

Once we’re done with the “Shoot” or “Reload” animation, we would like to go back to Scenario 1, however, we need to synchronise the frames on the two players.

** Code **
Here are the utility functions I’ve written to handle all the above:


# These are two AnimationPlayers
onready var animation_body = $animation_body
onready var animation_arms = $animation_arms

# Sets the whole body animation (called from the state machine)
func set_body_animation(animation):
    # Get the old animations
    var arms_animation = animation_arms.current_animation
    var body_animation = animation_body.current_animation
    # Update body animation with the new one
    animation_body.current_animation = animation

    # If the arms animation is not set OR needs to be the same as the the body
    # we synchronize them.
    if arms_animation == null or arms_animation == body_animation:
        _sync_animations()

# Sets the arms animation (called from the state machine)
func set_arms_animation(animation):
    if animation == null:
        # If the arms animation is null we synchronize them.
        _sync_animations()
    else:
        # Otherwise body and arms animations are independent
        animation_arms.current_animation = animation

# Utility function to synchronize animations
func _sync_animations():
    # Get the current body animation
    var animation = animation_body.current_animation
    # Get the current position in playback of the body animation
    var position = animation_body.current_animation_position
    # Update the arms animation to match the one of the body
    animation_arms.current_animation = animation
    # Seek to the same position of the body animation
    animation_arms.seek(position, false)