Skip to content

Instantly share code, notes, and snippets.

@Sundwell
Created September 19, 2024 00:00
Show Gist options
  • Save Sundwell/dadb1ec05e5a057f954231af6fd1f0fa to your computer and use it in GitHub Desktop.
Save Sundwell/dadb1ec05e5a057f954231af6fd1f0fa to your computer and use it in GitHub Desktop.
Godot nodeless state machine
class_name CallableStateMachine
var state_dictionary = {}
var current_state: String
func add_states(
normal_state_callable: Callable,
enter_state_callable: Callable = Callable(),
leave_state_callable: Callable = Callable()
):
state_dictionary[normal_state_callable.get_method()] = {
"normal": normal_state_callable,
"enter": enter_state_callable,
"leave": leave_state_callable
}
func set_initial_state(state_callable: Callable):
var state_name: String = state_callable.get_method()
if state_dictionary.has(state_name):
_set_state(state_name)
else:
push_warning("No state with name " + state_name)
func update(delta: float):
if not current_state == null:
(state_dictionary[current_state].normal as Callable).call(delta)
func change_state(state_callable: Callable):
var state_name = state_callable.get_method()
if state_dictionary.has(state_name):
_set_state.call_deferred(state_name)
else:
push_warning("No state with name " + state_name)
func _set_state(state_name: String):
if current_state:
var leave_callable = state_dictionary[current_state].leave as Callable
if not leave_callable.is_null():
leave_callable.call()
current_state = state_name
var enter_callable = state_dictionary[current_state].enter as Callable
if not enter_callable.is_null():
enter_callable.call()
extends CharacterBody2D
@onready var weapon = $Weapon
@onready var health_component: HealthComponent = $HealthComponent
@onready var sprite: AnimatedSprite2D = $AnimatedSprite2D
const RUN_SPEED := 125.0
const BASE_MOVE_SPEED := 50.0
const ACCELERATION_SMOOTHING := 15
var is_shooting := false
var damage_rate := 10.0
var health := 100.0
var mutations: Dictionary = {}
var move_speed := 50.0
var state_machine := CallableStateMachine.new()
func _ready():
GameEvents.mutation_upgrade_selected.connect(on_mutation_upgrade_selected)
state_machine.add_states(state_idle, enter_state_idle)
state_machine.add_states(state_moving)
state_machine.set_initial_state(state_idle)
func _physics_process(delta: float):
state_machine.update(delta)
move_and_slide()
func shoot():
if Input.is_action_pressed('fire'):
is_shooting = true
weapon.shoot()
else:
is_shooting = false
func flip():
if velocity.x < 0:
sprite.flip_h = true
elif velocity.x > 0:
sprite.flip_h = false
func get_movement_direction() -> Vector2:
return Input.get_vector('move_left', 'move_right', 'move_top', 'move_down')
func move(delta: float):
var direction = get_movement_direction()
var speed = move_speed if is_shooting else RUN_SPEED
var target_velocity = direction.normalized() * speed
velocity = velocity.lerp(target_velocity, 1.0 - exp(-delta * ACCELERATION_SMOOTHING))
func enter_state_idle():
sprite.play('idle')
velocity = Vector2.ZERO
weapon.visible = true
func state_idle(delta: float):
shoot()
if get_movement_direction().length() > 0:
state_machine.change_state(state_moving)
func state_moving(delta: float):
shoot()
move(delta)
flip()
if get_movement_direction().length() == 0:
state_machine.change_state(state_idle)
return
if mutations.has("run_while_shooting"):
weapon.visible = true
sprite.play("run")
else:
if is_shooting:
weapon.visible = true
sprite.play("walk")
else:
weapon.visible = false
sprite.play("run")
func on_mutation_upgrade_selected(upgrade: MutationUpgrade, current_upgrades: Dictionary):
if upgrade.id == "run_while_shooting":
mutations["run_while_shooting"] = true
move_speed = RUN_SPEED
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment