Shared States - Grisgram/gml-raptor GitHub Wiki

Shared states are more of a concept than a physically existing class.
As States are just struct classes, they are references. And references can be linked to multiple locations. The problem with this is the data member of the StateMachine is shared with all other states that the StateMachine knows.

If we assigned the same State reference to multiple StateMachines, we would create a chaotic mix of data pointers, and you would never be sure whose data you are currently working with.

StateMachine is capable of managing this. It assigns its own data member to a State immediately before the on_enter, on_leave or step callbacks are invoked. So, you may use the same State in multiple StateMachines!

With that said, let's look at the State class.

State constructor

/// @function		State(_name, _on_enter = undefined, _on_step, _on_leave = undefined)
/// @description	Defines a state for a StateMachine.
/// @param {string} _name	The name of the state
/// @param {func} _on_enter	Callback to be invoked when this state becomes the active state
/// @param {func} _on_step	Callback to be invoked on every frame
/// @param {func} _on_leave	Callback to be invoked when this state is no longer the active state

You see, the signature is quite similar to the add_state method of the StateMachine. Behind the scenes, add_state creates an instance of this class and assigns it internally to the StateMachines' collection of known states.

Now, to create a shared state, you simply create one of these instances by yourself, store it in any variable, and then you can assign this one State to multiple StateMachines by invoking add_state_shared on the StateMachine.

Examples

There are many situations where this might become handy.
Of course, you can create base objects and children of those base objects, and you can declare the common states in the base and add more custom states in each of the children, but this way of working has limitations: What if one standard state will be used by multiple base objects? GML does not support multiple inheritance (and this is good!), so you would have to declare the same base-state in multiple places.

An example would be a state like ev:mouse_enter that causes objects to make some sound effect whenever the mouse touches them. This state might be applied to all npc's, all monsters, and interactive furniture.

It doesn't matter how many "parents" share this state. You can declare it in the Create event of the RoomController or even globally in some script. The key point is:

You only need to write the code for this state once in your game!

No copy/paste sessions and no horrible "omg, no, I can't change this, I would need to replace that in 100 places" anymore.

// Somewhere in a script
global_mouse_enter = new State("ev:mouse_enter", 
    function(sdata, prev_state) {
        // Play a sound effect
        return prev_state; // And return to the state, the object had before
    }
}

With such a simple construct you can declare a game-wide state for an event and then assign it to any StateMachine you want.

Remember: To use events as states, your object must be a child of StatefulObject.

Redefining event states (like dynamic key bindings)

Most games offer at least a little configuration UI and it is quite common, to allow the player to redefine key bindings. While I have seen some games, where "WASD" seemed to be hardcoded and I could not redefine them, most other keys, like Inventory, Map, and several other actions could be rebound. Some players prefer I for inventory, others prefer B for Bag. We as developers should respect that.

So, now that we have shared states being used in some arbitrary number of StateMachines and the player can redefine Inventory to be opened with B instead of I, how can we find all of these states in order to make changes?

The answer is simple: if you use a shared state, you only have one single instance! So, just set

// assuming, your globally shared state is held in a variable named global_mouse_enter
global_mouse_enter.name = "ev:key_press_B"

and you have renamed it everywhere. That's the biggest benefit of shared states!

Of course, there might be other situations where a state is not shared or where an unknown number of objects in your game owns a state that listens for a specific key (imagine a game like Diablo -- when you press the Alt key, many many loot objects on the ground start rendering their name so you can see what has dropped).

To solve this, recall the ListPool class and the fact that all StateMachine instances are globally managed by the RoomController. We have a global macro available called STATEMACHINES, and the ListPool offers a process_all method.
Also, remember that the StateMachine offers a rename_state method.

Putting these ideas together, we have everything we need to create dynamically changed keyboard shortcuts!

function rebind_key(old_key, new_key) {
    STATEMACHINES.process_all("rename_state", "ev:key_press_" + old_key, "ev:key_press_" + new_key);
}

Call this as rebind_key("I", "B"); and all states in all state machines that listened for the I key will now listen for B!


That's it for the StateMachine.

Again, I highly recommend you look at the source code of the Example Project to see how the StateMachine is used!

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