XSM 4

An asset by atn
The page banner background of a mountain and forest
XSM 4 hero image

Quick Information

0 ratings
XSM 4 icon image
atn
XSM 4

A freely inspired implementation of StateCharts for Godot 4. This plugin provides States composition (ie sub-States), regions (ie parallel States), Loops, Random States, and helper functions for animations, sound and timers. It is licensed MIT and written by atn.This version 4.1 sees the arrival of StateSound, a derivative of StateAnimation that can play your sounds for you when the state enters!

Supported Engine Version
4.0
Version String
4.1.0
License Version
MIT
Support Level
community
Modified Date
1 year ago
Git URL
Issue URL

XSM4 Extended State Machine (godot4)

Latest version : 4.0.2

A freely inspired implementation of StateCharts for Godot 4. This plugin provides States composition (ie sub-States), regions (ie parallel States) and helper functions for animations and timers. It is licensed MIT and written by ATN.

For the very first state machine with XSM, try our HelloWorld example down this page. You can also look at the simple examples provided with this plugin to have a glimpse of what is possible with it.

The plugin provides templates for your States. If you want to add a script to a State, use "inherit from script" to easily add from a State template.

Understanding XSM

A Finite State Machine (FSM) is a way for game creators to separate their code's logic into different parts. In Godot, it would be in different Nodes. XSM allows to have substates of a State so that you don't have to create a complex inheritage pattern. You can simply add State Nodes as children of a State Node. If you do so, when a child is active, all his parents are active too and they are going to proceed (update).

If a State is inside a StateRegion, all its children are active or inactive at the same time, as soon as it is active or inactive.

It allows schemas such as : statechart statechart

more on : StateCharts

How to use XSM

You can add a State node to your scene. This State will be the root of your XSM. Then you can add different States to this root and separate the logic of your scene into those different sub-States. You can also add a State as a child of another State and create complex trees of States by doing so.

There are many different types of States:

  • State -> The basic type of State. All the other inherit from this one
  • StateRegion -> All its child States will be active at the same time
  • StateAnimation -> An easy to use State that will automatically play animations from an AnimationPlayer. The AnimationPlayer has to be either defined in the inspector or simply be a sibling of your XSM's root
  • StateLoop -> This State can loop (forward, backwards or ping-pong) through its children States
  • StateRand -> A way to randomly chose one of the children States. A powerful way to switch between idle animations

By default, your XSM is enabled, you can disable it (or any branch of your XSM's tree) in the inspector.

You can use the same names for states in different branches of your StateMachine but if so THEIR PARENT NAMES MUST BE DIFFERENT. In the state_map and active_states_list, they will be referenced as "ParentName/ChildName" to differentiate them.

The state_root owns a history of the active_states_list, the size of which can be changed in the StateRoot's inspector. You can call state_root.was_state_active("StateName") to know if the StateName was active last frame. Careful: if two states have the same name, they are referenced as "ParentName/StateName" in the state's history.

Each State can have its own target (any Node of the scene, including another State). If you don't, XSM will get the root's ones. If the root does not have a target, it will use its parent as target.

If needed, you can print some debug infos. You can enable debug either in the root of in any State branch. This will print texts from the state that calls change_state(). If you call change_state inside _on_enter(), it will add nested debug texts. Then it also will notify when the change has been done (and reduce the indentation)

An empty State template is provided in res://script_template/empty_state.gd. You just need to add a script to your State and specify this one as a model.

Abstract functions to inherit in you states

When you enter a State (with change_state("State")), XSM will first exit the old branch. Starting from the common root of the new State and the old one, it will call _before_exit(), exit the children, then call _on_exit(). Then it will enter the new branch. Starting from the common root of the new State and the old State, it will call _on_enter(), enter the proper child, then call _after_enter() for the child and eventually _after_enter() for the root. If the specified State is not the last of the branch, XSM is going to enter each following first chid. During your scene's _process(), XSM will update the active root and call _on_update(), then _on_update() for its active child (or children if there are regions), _after_update() for the child and eventually _after_update() for the root. If you add any timer to a State (with add_timer("name",time)) as soon as the timer is done, it calls _on_timeout("name") and destroys itself. If it had this timer's name already as a child, the old timer is destroyed and a new one is created with the specified time.

So, in each State's script, you can implement the following abstract public functions:

#  func _on_enter(args) -> void:
#  func _after_enter(args) -> void:
#  func _on_update(_delta) -> void:
#  func _after_update(_delta) -> void:
#  func _before_exit(args) -> void:
#  func _on_exit(args) -> void:
#  func _state_timeout() -> void:
#  func _on_timeout(_name) -> void:

Utility functions to call in your States

In any State node, you can call the following public functions:

  • change_state("MyState") -> State where "MyState" is the name of an existing Node State. If two states have the same name, you MUST add the parent's name before change_state("Parent/Child")

  • change_state("MyState", args_on_enter = null, args_after_enter = null, args_before_exit = null, args_on_exit = null) The "change_state" method accepts arguments, to be able to pass variables to some inherited enter or exit functions in your states' logic. If "MyState" == "", then it will be considered as self.

  • change_state_node(my_state) -> State or change_state_to(my_state) -> State Where my_state is an existing Node State. This function accepts the same arguments as change_state(). If no argument is entered, it will try to change state to self.

  • change_to_next( args_on_enter = null, args_after_enter = null, args_before_exit = null, args_on_exit = null) -> State: Helper functions to change to the State defined in next_state.

  • change_to_next_substate() -> State: Helper functions to ask to the parent state which one is the next_state (very useful for StateRand or StateLoop).

  • change_state_if(new_state: String, if_state: String) -> State This one changes state only if the second state specified is active

  • change_state_node_force(new_state_node: State = null, args_on_enter = null, args_after_enter = null,args_before_exit = null, args_on_exit = null) -> State: Very important ! This function forces the change even if the new_state_node is already active

  • is_active("MyState") -> bool returns true if a state "MyState" is active in this xsm

  • get_active_substate() if active, returns the active substate (or all the children if has_regions)

  • get_state("MyState") -> State an alias for find_state_node()

  • get_previous_active_states(history_id) -> Dictionary returns a dictionary with all the active States from history_id + 1 frames ago

  • was_state_active(state_name: String, history_id: int = 0) -> bool returns true if a state "MyState" was active in this xsm last frame (_physics_process) You can specify an history_id to get the result for older frames

  • find_state_node_or_null("MyState") -> State returns the State Node "MyState", You have to specify "Parent/MyState" if "MyState" is not a unique name.

  • add_timer("Name", time) adds a timer named "Name" and returns this timer when the time is out, the function _on_timeout(_name) is called

  • del_timer("Name") deletes the timer "Name"

  • del_timers() deletes all the timers of this State

  • has_timer("Name") returns true if there is a Timer "Name" running in this State

For StateAnimation only:

  • play("Anim") plays the animation "Anim" of the State's AnimationPlayer

  • play_backwards("Anim") plays the animation "Anim" of the State's AnimationPlayer, starting from the end

  • play_blend("Anim", custom_blend = 0.0, custom_speed = 1.0, from_end = false) blends the animation "Anim" with the current one of the State's AnimationPlayer (plays both animation during the custom_blend time)

  • play_sync("Anim", custom_speed = 1.0, from_end = false) synchronizes the animation "Anim" with the current animation of the State's AnimationPlayer

  • pause() pauses the current animation

  • queue("Anim) queues the animation "Anim" at the end of the State's AnimationPlayer list. If the current animation is looping, the queue will NOT play.

  • stop() stops the current animation

  • is_playing("Anim) returns true if "Anim" is playing

For StateLoop only:

  • next_in_loop(args_on_enter = null, args_after_enter = null, args_before_exit = null, args_on_exit = null) Change state to the next in loop, depending on the loop_mode

  • prev_in_loop(args_on_enter = null, args_after_enter = null, args_before_exit = null, args_on_exit = null) Change state to the previous in loop, depending on the loop_mode

  • exit_loop(args_on_enter = null, args_after_enter = null, args_before_exit = null, args_on_exit = null) Change state to the exit state defined in the loop's inspector

Signals

The States are calling different signals during their life :

  • signal state_entered(sender)
  • signal state_exited(sender)
  • signal state_updated(sender)
  • signal state_changed(sender, new_state)
  • signal substate_entered(sender)
  • signal substate_exited(sender)
  • signal substate_changed(sender, new_state)
  • signal disabled()
  • signal enabled()

The root state can emit

  • signal some_state_changed(sender, new_state_node)
  • signal pending_state_changed(added_state_node)
  • signal pending_state_added(new_state_name)
  • signal active_state_list_changed(active_states_list)

A StateLoop can emit

  • signal looped()

Hello World

Your first try with XSM could be to :

  • Install the module (using godot's AssetLib on the top of your editor)
  • Activate the module (project / parameters / Extensions and check activate for XSM)
  • Select the node that needs a StateMachine (for example a character)
  • Add a new child node (or Ctrl-A) of type State
  • You can now add two State nodes as children to your State (same as above : Ctrl-A)
  • Add a script to the first State and chose the Empty State template.
  • Add a script to the second State and chose the Empty State template.
  • in the first script, in the _on_enter(_args) function, add a line with: print("Hello world of state 1")
  • then in the _on_update(_delta) function, add:
  if Input.is_action_just_pressed("ui_accept"):
     change_state("State2")
  • in the second script, in the _on_enter(_args) function, add a line with: print("Hello world of state 2")
  • then in the _on_update(_delta) function, add:
  if Input.is_action_just_pressed("ui_accept"):
     change_state("State")

And here you are, you made your first state machine with XSM! You can launch it with F5 and press the spacebar to switch states. The output should be explicit ;)

To go further, you can look at the examples provided with the addon.

Special Thanks

To TealOrbiter and Frontrider1 for such kind and useful suggestions that make xsm getting better each version.

To flaticon.com and whoever made the icons used :

https://www.flaticon.com/free-icon-font/chart-network_5528059
https://www.flaticon.com/free-icon-font/chart-tree_5528048
https://www.flaticon.com/free-icon-font/chart-connected_5528111
https://www.flaticon.com/free-icon-font/chart-set-theory_5528101
https://www.flaticon.com/free-icon/question_2669746

To piratesephiroth and MadFlyFish for a C# port of XSMv1 : https://github.com/MadFlyFish/XSM-Csharp

To DrPetter's sfxr allowing a quick add of sound effects : https://www.drpetter.se/project_sfxr.html

To André for the platformer's music : [link to add someday, the guy is shy]

What's next ?

Well now you can create open source games with Godot and share, right ? Please report any bug and suggest enhancements to the plugin.

For any question, issue or request, [email protected] or gitlab. See you

A freely inspired implementation of StateCharts for Godot 4. This plugin provides States composition (ie sub-States), regions (ie parallel States), Loops, Random States, and helper functions for animations, sound and timers. It is licensed MIT and written by atn.

This version 4.1 sees the arrival of StateSound, a derivative of StateAnimation that can play your sounds for you when the state enters!

Reviews

0 ratings

Your Rating

Headline must be at least 3 characters but not more than 50
Review must be at least 5 characters but not more than 500
Please sign in to add a review

Quick Information

0 ratings
XSM 4 icon image
atn
XSM 4

A freely inspired implementation of StateCharts for Godot 4. This plugin provides States composition (ie sub-States), regions (ie parallel States), Loops, Random States, and helper functions for animations, sound and timers. It is licensed MIT and written by atn.This version 4.1 sees the arrival of StateSound, a derivative of StateAnimation that can play your sounds for you when the state enters!

Supported Engine Version
4.0
Version String
4.1.0
License Version
MIT
Support Level
community
Modified Date
1 year ago
Git URL
Issue URL

Open Source

Released under the AGPLv3 license

Plug and Play

Browse assets directly from Godot

Community Driven

Created by developers for developers