Animation meets StateMachine - Grisgram/gml-raptor GitHub Wiki

When you organize the behavior of your game object in StateMachines (which you should do!), you will quickly notice that the on_enter function for a state is by far the most important one. This is where the magic happens.

  • A monster entering the "hunting" state will likely draw its weapon (an Animation!), make some groaning sound, and start running towards the player.
  • Any living object getting "hit" will also play a Hit-Animation.
  • Anything spawning or dying might also,
  • and a chest that is opened will likely play its Open-Animation.

So, you have a very good reason to design many animations which are sometimes simply other sprites. Other times, like the spawn event, these are simple modifications of the idle version of the game object and can be covered with an Animation and a good AnimCurve quite well.

Some real examples

I want to show you a bit more of the source code from One (K)night alone with some states and how they utilize the Animation class in their on_enter event.

Example: Unfolding a map field

In this game, when the player enters a field, the neighboring fields become revealed (the "fog" disappears).
This is the on_enter of the "unfold" state of a map field in the game.

.add_state("unfold",
    function(sdata) {
	GAME.player_data.gain_xp(1);
	animation_run(self, 0, 30, acFogDisappear,, "open")  // enter the "open" state, when animation is finished
	    .add_finished_trigger(function() {               // .add_finished_trigger gets invoked when animation finishes
		image_alpha = 0;
		if (contents != undefined)
		    with(contents) event_user(game_event.field_unfolded); // game-internal -- ignore for this example
	    });
)

Example: Spawn of the "Bat" enemy (fade-in)

When spawned, the Bat simply fades in from alpha 0 to 1 and switches into the "idle" state when spawning is complete.

.add_state("spawn",
    function(sdata) {
	sprite_index = sprVampireBat;
	image_index = 0;
	image_speed = 1;
	image_alpha = 0;
		
        animation_run_ex(self, 0, 90, acLinearAlpha,, "idle");
	// Yes, you have seen right! It is only ONE SINGLE LINE of code!
	// What happens here:
	// * animation_run_ex is used ("run EXclusive"), which stops ALL other animations on this object
	//   before starting the alpha animation
	// * It runs raptor's default Alpha curve
	// * It will enter "idle" state, when spawning is finished
)

With these two examples you can see the pattern for most game objects:

  • When entering a state, the matching Animation for that state is played
  • Normally you'd want to add a finished trigger (more on Triggers in Animation Triggers) to start the next step after the animation

Pausing and Resuming an Animation

Many events can happen while the game runs, and it may be necessary to pause some (or all) of your running Animations whether it is because a full-screen overlay has been opened or simply the Pause Key has been pressed in your game.

To allow you to pause and later resume an Animation in the exact frame where it was paused, Animation has functions available which cover this scenario:

  • pause()
  • resume()
  • set_paused(paused)
  • is_paused()

Call scoping

As mentioned in then StateMachine and Animation main pages, both of these rely on the ListPool and are globally managed by the RoomController. Both behave identically.

Call scoping means that the ListPool controls who is self when invoking a callback.
In both cases, inside an on_enter, step and on_leave event of a State, as well as in any trigger of an Animation, the self pointer always points to the owner of the object. The 'owner' is the game object you supplied as the first parameter to the constructor - the object that owns the State or Animation respectively. For example, in the code above, the owner is The Bat object.


NOTE: There is no limit on the amount of parallel Animations running for one object. The RoomController manages all active animations, and if you have 2 independent Animations running on the same object (like one for fading in/out and another that rotates the object or changes the blend_color), this is perfectly ok!


Basic Workflow shortcuts between Animation <-> StateMachine

In Version 1.3, some shortcuts have been added to improve communication and control-sync between these two major elements of raptor.

Animation can set a follow-up state on finish

First, the Animation class constructor (and the animation_run_... functions) have been extended by an optional _finished_state parameter.
This one works for every StatefulObject and for all objects that own a StateMachine named states (which is the name of the StateMachine in a StatefulObject).

If you supply this parameter, you can omit the ever recurring finished_trigger that just sets a new state when an Animation has finished.

So, with the new parameter, an Animation like this

animation_run(self, 0, 30, acLinearMove)
    .add_finished_trigger(function() {
        states.set_state("idle");
    });

can now be greatly simplified as

animation_run(self, 0, 30, acLinearMove,, "idle");

StateMachine can lock its state while animating

This (on first sight) very simple addition can simplify your code greatly and will ensure correct timing when transitioning from one state to another through an animation.

The new method lock_animation in the StateMachine class will prevent state changes to any other state while the specified Animation is running. In addition to that, while animation-locked, the StateMachine will buffer the first request to a state change while it is locked. When the Animation finishes, the buffered state will be entered.

While animation locking in general is not always what you want in games, there are many situations where you will want this sync. Spawning & Dying of objects are good examples here or a phase-change in a boss fight where the boss is invulnerable while it changes shape.

"Why is only the first state change request bufferd?"
Because in the logical workflow of the game, there is normally only one correct follow-up state to the current state (from a workflow view).

Locking on an Animation is simple:

states.lock_animation(animation_run(self, 0, 25, acLinearScale));

With the code above, the StateMachine will be locked in its current state until that animation is finished.

...and now imagine, you already know, that you want the state after the animation to be "idle". To ensure, nothing can come in between, you can simply write this:

states.lock_animation(animation_run(self, 0, 25, acLinearScale));
states.set_state("idle");

Due to the animation lock, nothing will happen by calling set_state, but this request will be the first request after the animation lock and it will be buffered. So, nothing can prevent this workflow from happening. The Animation will play, then the object will enter the "idle" state, no matter what other modules of your game might try to do with that object.


Caution

Using lock_animation on a LOOP_INFINITE animation will freeze the StateMachine forever, until the animation is stopped using .abort()!


Buffering the next state change request is optional (but enabled by default). To turn it off (all state change requests will be ignored then while animating), you may supply false as the optional second parameter to lock_animation:

states.lock_animation(animation_run(self, 0, 25, acLinearScale), false);

Next to read: Animation Triggers

⚠️ **GitHub.com Fallback** ⚠️