diff --git a/README.md b/README.md index 95714c2..9699124 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,20 @@ Designer-friendly Finite State Machine implemented in "Godotic" way > It is not recommended to be used in production yet, as api might be changed before v1.0. > Testing & reporting bugs are greatly appreciated. +## Content + +- [Feature](#feature) +- [Installation](#installation) +- [Getting Started](#getting-started) + - [Editor](#editor) + - [Code](#code) +- [Nested FSM](#nested-fsm) + - [State](#state) + - [Parameter](#parameter) +- [Debug](#debug) +- [Demo](#demo) +- [Documentation](addons/imjp94.yafsm/README.md) + ## Feature - Designer-friendly @@ -18,6 +32,8 @@ Designer-friendly Finite State Machine implemented in "Godotic" way > Visualize game/UI state from flowchart - Zero learning curve > Similar workflow as using `AnimationTree`, and not required to inherit any custom class, just plug and play +- Nested FSM + > Nested Finite State Machine workflow supported to create complex state machine with ease - Reusability > As a `Resource`, `StateMachine` can be used repeatedly in different scenarios(`StateMachinePlayer`) and provide different outcome based on the input. - Minimal @@ -27,13 +43,13 @@ For more detail, see [CHANGELOG.md](CHANGELOG.md) ## Installation -- Install directly from Godot Asset Library +1. Install directly from Godot Asset Library or -- Download this respository, - 1. Move `addons/imjp94.yafsm` to your `{project_dir}` - 2. Enable it from Project -> Settings -> Plugins +1. Download this respository, move `addons/imjp94.yafsm` to your `{project_dir}` + +2. Enable it from Project -> Settings -> Plugins ## Getting Started @@ -47,7 +63,7 @@ or 3. Click on "Create StateMachine" button to get started. -Finally, right-click on graph to add state node. +Finally, `Right-Click` on graph to add state node and `Shift + Drag` on node to start connect(`Shift + Drag` again on line to reconnect) Special states: @@ -64,12 +80,91 @@ After setup `StateMachine` with editor, you can connect to the following signals ![Signal Example](screenshots/yafsm_state_machine_player_signal_example.png) *Example code snippet of KinematicBody connect "updated" signal* +And control `StateMachinePlayer` by accessing parameter: + +```gdscript +var smp = get_node(StateMachinePlayer) +smp.set_trigger("jump") +smp.set_param("jump_count", 1) +smp.get_param("on_floor", false) +smp.has_param("velocity") +``` + That's it! For most of the case, you don't have to inherit from any custom class by this plugin, simply just connect signals to your existing node and you're good to go. > See documentation below for more details +## Nested FSM + +The only different between nested/normal FSM is how state/parameters are accessed. + +### State + +- normal state - "State" +- nested state - "BaseState/AnotherState/EndState" + +```gdscript +var normal_state = "Idle" +var nested_state = "App/Game/Play" # EndState can be Entry/Exit +``` + +`StateDirectory` class is provided to traverse state path like file directory: + +```gdscript +const StateDirectory = preload("addons/imjp94.yafsm/src/StateDirectory.gd) + +# Handle "transited" signal +func _on_normal_state_transited(from, to): + match to: + "Entry": + print("Enter") + "Game": + print("Game") + "Exit": + print("Exit) + +# Handle "transited" signal +func _on_nested_state_transited(from, to): + var to_dir = StateDirectory.new(to) + + match to_dir.next(): # Initial next() required to move to base state + "Entry": + print("Enter") + "Game": + match to_dir.next(): # It can be called recursively, until return null + "Entry": + print("Game Enter") # Game/Entry + "Exit": + print("Exit) +``` + +### Parameter + +Behind the scene, `StateMachinePlayer` always differentiate parameters into 2 types: global & local + +- global parameter + - Just a normal parameter - "param_name" + - Never erased automatically, unless `erase_param`/`clear_param` called by user +- local parameter + - Parameter that local to nested state - "BaseState/TargetState/param_name" + - Erased upon Exit, for example, "App/Game/Exit" will cause all local parameter("App/Game/{param_name}") to be erased + +```gdscript +var smp = get_node(StateMachinePlayer) +var global_param = smp.get_param("state") +var local_param = smp.get_param("App/Game/playing") +``` + +Besides of controlling `StateMachinePlayer`, it's useful to set arbitrary value with `set_param` + +```gdscript +var smp = get_node(StateMachinePlayer) +smp.set_param("game", preload("game.scn")) +var game_scn = smp.get_param("game") +``` + ### Debug - Stack @@ -77,7 +172,7 @@ For most of the case, you don't have to inherit from any custom class by this pl ## Demo -Check out [gd-YAFSM-demo](https://github.com/imjp94/gd-yafsm-demo) for how you can integrate gd-YAFSM into you project. +Check out [gd-YAFSM-demo](https://github.com/imjp94/gd-yafsm-demo) for how you can integrate gd-YAFSM into you project and manage app state with `StateMachine` ## Documentation diff --git a/addons/imjp94.yafsm/README.md b/addons/imjp94.yafsm/README.md index eace719..f1b6db0 100644 --- a/addons/imjp94.yafsm/README.md +++ b/addons/imjp94.yafsm/README.md @@ -2,7 +2,7 @@ ## Classes -All of the classes are located in `res://addons/imjp94.yafsm/src` but you can just preload `res://addons/imjp94.yafsm/YAFSM.gd` to import all classes available: +All of the class are located in `res://addons/imjp94.yafsm/src` but you can just preload `res://addons/imjp94.yafsm/YAFSM.gd` to import all class available: ```gdscript const YAFSM = preload("res://addons/imjp94.yafsm/YAFSM.gd") @@ -29,8 +29,8 @@ const State = YAFSM.State - `process_mode # ProcessMode of player` - signals: - `transited(from, to) # Transition of state` - - `entered() # Entry of state machine` - - `exited() # Exit of state machine` + - `entered(to) # Entry of state machine(including nested), empty string equals to root` + - `exited(from) # Exit of state machine(including nested, empty string equals to root` - `updated(state, delta) # Time to update(based on process_mode), up to user to handle any logic, for example, update movement of KinematicBody` ### Control @@ -38,6 +38,11 @@ const State = YAFSM.State - [StackPlayerDebugger](src/debugger/StackPlayerDebugger.gd) > Visualize stack of parent StackPlayer on screen +### Reference + +- [StateDirectory](src/StateDirectory.gd) + > Convert state path to directory object for traversal, mainly used for nested state + ### Resource Relationship between all `Resource`s can be best represented as below: @@ -73,3 +78,4 @@ var condition = transition.conditions[condition_name] # keyed by condition name - [BooleanCondition](src/conditions/BooleanCondition.gd)(`extends ValueCondition`) - [IntegerCondition](src/conditions/IntegerCondition.gd)(`extends ValueCondition`) - [FloatCondition](src/conditions/FloatCondition.gd)(`extends ValueCondition`) +- [StringCondition](src/conditions/StringCondition.gd)(`extends ValueCondition`) diff --git a/screenshots/yafsm_editor_showcase.gif b/screenshots/yafsm_editor_showcase.gif index b23411a..7bfbb60 100644 Binary files a/screenshots/yafsm_editor_showcase.gif and b/screenshots/yafsm_editor_showcase.gif differ