University
Game
Date
Duration
Team Size
The State Machine Generator is a small Godot editor addon I used to remove repetitive setup work from character logic prototyping. In several smaller projects, I kept creating the same node hierarchy and the same set of scripts again and again: a state machine node, a base state, and a couple of concrete states with the same callback functions. After doing that manually often enough, turning it into a tool felt like the better solution.
The repository credits Alessa and me as authors. Since I only have the repository here and no separate production notes, I want to keep the page focused on the part that is clearly visible in the code: the editor workflow itself. The interesting piece for me is not a complicated runtime state machine. It is the tooling that creates one quickly and consistently inside the Godot editor.
I really like state machines for character behavior in smaller Godot projects because they keep logic easy to read. The annoying part is not the pattern itself. It is the boilerplate around it. Before I can even write one actual behavior, I have to create the node structure, wire the first scripts, define an initial state, and make sure every file follows the same naming pattern.
This addon was my way of moving that work into the editor. Later on, the same workflow also showed up in projects such as Crunch Time and Scary Dark Dungeon. So even though the tool itself is small, it became part of how I liked to prototype character logic in Godot.
Instead of building a separate external generator, I integrated the workflow directly into the Godot editor with an EditorPlugin. The addon adds its own dock, checks the currently edited scene, and enables or disables controls depending on whether a state machine already exists for the selected root object.
func _enter_tree():
dock = preload(
"res://addons/state_machine_generator/state_machine_generator_dock.tscn"
).instantiate()
state_machine_button = dock.get_node("AddStateMachineButton")
state_button = dock.get_node("HBoxContainer/AddStateButton")
state_line = dock.get_node("HBoxContainer/StateLine")
state_machine_button.connect("button_down", add_state_machine)
state_button.connect("button_down", add_state)
state_line.connect("text_changed", change_state_name)
add_control_to_dock(DOCK_SLOT_RIGHT_UL, dock)
func _process(delta):
if !root_object:
return
if root_object.has_node(root_object.name + "StateMachine"):
state_machine_button.disabled = true
state_button.disabled = false
state_line.set_editable(true)
else:
state_machine_button.disabled = false
state_button.disabled = true
state_line.set_editable(false)I like this approach because it keeps the tool very close to the moment where it is needed. I do not have to leave the editor, run a script somewhere else, or copy generated files back into the project. I select a scene root, open the dock, and generate the structure right where I am already working.
The second half of the tool is file generation. Once the button is pressed, the plugin creates the directory structure, writes the scripts from templates, inserts the corresponding nodes into the scene tree, and attaches the generated scripts immediately.
func add_state_machine():
root_object = get_tree().get_edited_scene_root()
create_directory()
var state_machine_node = Node.new()
state_machine_node.name = root_object.name + "StateMachine"
create_state_machine()
create_base_state()
state_machine_node.set_script(load(
"res://StateMachines/" +
root_object.name + "/" +
root_object.name + "_State_Machine.gd"
))
root_object.add_child(state_machine_node)
state_machine_node.set_owner(root_object)
func add_state():
var state_node = Node.new()
state_node.name = state_name
create_state()
state_node.set_script(load(
"res://StateMachines/" +
root_object.name + "/States/" +
root_object.name + "_" + state_name + ".gd"
))
root_object.get_node(root_object.name + "StateMachine").add_child(state_node)
state_node.set_owner(root_object)This is the part that saved the most time for me. The tool does not try to solve behavior design. It only gets me to the starting line faster and with fewer naming mistakes. That was exactly what I wanted from it.
The templates themselves stay intentionally small. They define a clear contract for states with methods like enter, exit, and handle_input, and they give every generated state machine the same transition interface.
class_name %STATEMACHINENAME%
extends Node
@onready var character := owner as %CHARACTERNAME%
@export var initial_state : NodePath
var state
func _ready() -> void:
await owner.ready
if has_node(initial_state):
state = get_node(initial_state)
else:
printerr("Wrong initial state on " + character.name + "StateMachine Node")
return
for child in get_children():
child.state_machine = self
child.character = character
state.enter()
func transition_to(target_state_name: String, msg: Dictionary = {}) -> void:
if not has_node(target_state_name):
return
state.exit()
state = get_node(target_state_name)
state.enter(msg)That consistency mattered more to me than making the tool clever. Once the boilerplate is there, I can still customize everything manually. The addon just ensures that every new setup starts from a clean and predictable base.
I think small tools like this are easy to underestimate because they do not look impressive in isolation. But in practice they change how willing I am to iterate. If creating a new enemy state machine takes a few clicks instead of a repetitive setup ritual, I am much more likely to try ideas quickly and throw them away again if they do not work.
That is why I still like this addon. It reflects a part of programming work that I care about a lot: not only building features, but also reducing friction for the next feature.