(sprite from Terraria)
Wisp is a very minimal script (<100 lines) for all your state machine logic:
- Easy to use: You can create a state machine in a few lines of code
- Concurrent: You can have multiple state machines running at the same time
- Minimal: as little overhead as possible and easy to install
- Full Control: your classes, your code, your game
In your project folder, run:
git submodule add https://github.com/wyvernbw/wisp
If you are using godot 4, run these commands after cloning the repo:
cd wisp # or wherever you cloned the repo
git switch @godot4
done. You can now use the script in your project. Otherwise, you can just copy the script into a new file in your project.
States are just classes that inherit from Wisp.State
.
# Player.gd
class_name Player
extends KinematicBody2D
class IdleState extends Wisp.State:
func _enter():
print("Entering Idle State")
or
# IdleState.gd
class_name IdleState
extends Wisp.State
func _enter():
print("Entering Idle State")
The enter and exit functions are called when transitioning states.
Note that the wisp_process, wisp_physics_process, and wisp_input functions
all return a state to transition to. If you want to stay in the same state,
return self
.
class ExampleState extends Wisp.State:
func wisp_input(owner: Node, event: InputEvent) -> Wisp.State:
if event.is_action_pressed("jump"):
return JumpState.new()
return self
enter also supports returning a new state. You can also use yield
to wait before transitioning. This is useful for states that will always transition to a new state after a certain amount of time.
WARNING: returning a new instance of the same class will cause an infinite loop and crash your game!
NOTE: before, you could use the 'transition' signal to transition to a new state. While this is still supported, it is recommended to use the return value instead.
class ExampleState extends Wisp.State:
func enter(owner: Node) -> void:
# do stuff
yield(get_tree().create_timer(1), "timeout")
return JumpState.new()
For this, use Wisp.use_state_machine()
. This will return a StateMachine
object.
func use_state_machine(owner: Node, initial_state: State) -> StateMachine
Example:
onready var state_machine = Wisp.use_state_machine(self, IdleState)
then, in your _process()
, _physics_process()
, or _input()
functions,
simply call state_machine.process()
, state_machine.physics_process()
, or
state_machine.input()
, respectively.
func _process(delta):
state_machine.process(delta)
func _physics_process(delta):
state_machine.physics_process(delta)
func _input(event):
state_machine.input(event)
Feel free to opt out of calling any of these functions if you don't need to.
called when entering the state, returns a state to transition to, supports yield
func enter(owner: Node) -> State
called when exiting the state
func exit(owner: Node) -> void
called every frame, returns a state to transition to
func wisp_process(owner: Node, delta: float) -> State
called every physics frame, returns a state to transition to
func wisp_physics_process(owner: Node, delta: float) -> State
called every input event, returns a state to transition to
func wisp_input(owner: Node, event: InputEvent) -> State
- Basic state logic
- Concurrency
- pushdown automata