Created
June 17, 2026 19:46
-
-
Save glinesbdev/71ba4f139c0e86463f0a3eac09a5ecf3 to your computer and use it in GitHub Desktop.
Godot Player State Machine
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class_name PlayerAttackState | |
| extends PlayerState | |
| func enter(_opts := {}) -> void: | |
| var player: Player = entity as Player | |
| if player.equipped_weapon == null or player.weapon_hitbox_scene == null: | |
| return | |
| var hitbox := player.weapon_hitbox_scene.instantiate() as WeaponHitbox | |
| get_tree().current_scene.add_child(hitbox) | |
| hitbox.global_position = player.weapon_socket.global_position | |
| hitbox.configure(player.equipped_weapon, player.combatant) | |
| # TODO: Transition back to previous state when process is done | |
| # i.e. animation, hit connect, etc. | |
| machine.transition_to_previous() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class_name PlayerDashState | |
| extends PlayerState | |
| @export var dash_speed: float = 16.0 | |
| @export var dash_duration: float = 0.14 | |
| @export var turn_speed: float = 30.0 | |
| var dash_time_left: float = 0.0 | |
| var dash_direction: Vector3 = Vector3.ZERO | |
| func enter(opts := {}) -> void: | |
| dash_time_left = dash_duration | |
| dash_direction = opts.get("direction", Vector3.ZERO) | |
| if dash_direction == Vector3.ZERO: | |
| dash_direction = -(entity as Player).global_transform.basis.z | |
| dash_direction.y = 0.0 | |
| dash_direction = dash_direction.normalized() | |
| _turn_visual(dash_direction, turn_speed, 0.0) | |
| func physics_process(delta: float, _input: Dictionary) -> void: | |
| _set_velocity(Vector3(dash_direction.x * dash_speed, dash_direction.y, dash_direction.z * dash_speed)) | |
| dash_time_left -= delta | |
| if dash_time_left <= 0.0: | |
| transitioned.emit(IDLE_STATE, {}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class_name PlayerDiedState | |
| extends PlayerState | |
| func enter(_opts := {}) -> void: | |
| machine = null | |
| (entity as Player).queue_free() | |
| func physics_process(_delta: float, _input: Dictionary) -> void: | |
| _set_velocity(Vector3.ZERO) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class_name PlayerIdleState | |
| extends PlayerState | |
| func enter(_opts := {}) -> void: | |
| var player := (entity as Player) | |
| _set_velocity(Vector3(0.0, player.velocity.y, 0.0)) | |
| func physics_process(_delta: float, input: Dictionary) -> void: | |
| var dir := _get_movement_direction(input) | |
| if dir != Vector3.ZERO: | |
| transitioned.emit(WALK_STATE, {}) | |
| return | |
| if input["run_pressed"]: | |
| transitioned.emit(RUN_STATE, {}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class_name Player | |
| extends CharacterBody3D | |
| @export_group("References") | |
| @export var equipped_weapon: WeaponData | |
| @export var weapon_hitbox_scene: PackedScene | |
| @export var camera_path: NodePath | |
| @export_group("Movement") | |
| @export var speed: float = 5.0 | |
| @export var turn_speed: float = 30.0 | |
| @export var acceleration_rate: float = 18.0 | |
| @export var deceleration_rate: float = 50.0 | |
| @onready var combatant: Combatant = $Combatant | |
| @onready var weapon_socket: Node3D = $WeaponSocket | |
| @onready var camera: Camera3D = get_node(camera_path) as Camera3D | |
| @onready var max_speed: float = speed * 2 | |
| @onready var visual_root: Node3D = $VisualRoot | |
| @onready var state_machine: PlayerStateMachine = $StateMachine | |
| func _ready() -> void: | |
| combatant.health.died.connect(_on_died) | |
| func _input(event: InputEvent) -> void: | |
| if event.is_action_pressed(PlayerInputs.ATTACK): | |
| state_machine.transition_to("AttackState", {}) | |
| func _on_died() -> void: | |
| state_machine.transition_to("DiedState", {}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class_name PlayerState | |
| extends State | |
| const IDLE_STATE = "IdleState" | |
| const WALK_STATE = "WalkState" | |
| const RUN_STATE = "RunState" | |
| const DASH_STATE = "DashState" | |
| const ATTACK_STATE = "AttackState" | |
| const DIED_STATE = "DiedState" | |
| func _set_velocity(vel: Vector3) -> void: | |
| (entity as Player).velocity = vel | |
| func _get_movement_direction(input: Dictionary) -> Vector3: | |
| var input_vector: Vector2 = input["move"] | |
| var cam: Camera3D = (entity as Player).get_viewport().get_camera_3d() as Camera3D | |
| if cam == null: | |
| return Vector3.ZERO | |
| var basis := cam.global_transform.basis | |
| var right := basis.x | |
| var forward := basis.z | |
| right.y = 0.0 | |
| forward.y = 0.0 | |
| right = right.normalized() | |
| forward = forward.normalized() | |
| var dir := right * input_vector.x + forward * input_vector.y | |
| if dir.length_squared() > 0.0: | |
| dir = dir.normalized() | |
| return dir | |
| func _turn_visual(direction: Vector3, turn_speed: float, delta: float) -> void: | |
| var visual_root := (entity as Player).get_node_or_null("VisualRoot") as Node3D | |
| if visual_root == null: return | |
| var target_y := atan2(direction.x, direction.z) | |
| visual_root.rotation.y = lerp_angle(visual_root.rotation.y, target_y, 1.0 - exp(-turn_speed * delta)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class_name PlayerStateMachine | |
| extends StateMachine | |
| func _physics_process(_delta: float) -> void: | |
| super._physics_process(_delta) | |
| (entity as Player).move_and_slide() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class_name PlayerRunState | |
| extends PlayerState | |
| @export var run_speed: float = 10.0 | |
| @export var turn_speed: float = 30.0 | |
| func physics_process(delta: float, input: Dictionary) -> void: | |
| var dir := _get_movement_direction(input) | |
| if dir == Vector3.ZERO: | |
| transitioned.emit(IDLE_STATE, {}) | |
| return | |
| _set_velocity(Vector3(dir.x * run_speed, dir.y, dir.z * run_speed)) | |
| _turn_visual(dir, turn_speed, delta) | |
| if not input["run_pressed"]: | |
| transitioned.emit(WALK_STATE, {}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class_name State | |
| extends Node | |
| signal transitioned(new_state: String, opts: Dictionary) | |
| var entity: Object | |
| var machine: StateMachine | |
| func enter(_opts := {}) -> void: | |
| pass | |
| func exit() -> void: | |
| pass | |
| func process(_delta: float) -> void: | |
| pass | |
| func physics_process(_delta: float, _input: Dictionary) -> void: | |
| pass | |
| func handle_input(_event: InputEvent) -> void: | |
| pass |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class_name StateMachine | |
| extends Node | |
| @export var initial_state: NodePath | |
| var current_state: State | |
| var previous_state: State | |
| var states: Dictionary = {} | |
| var player_input_context: Dictionary = {} | |
| var entity: Object | |
| func _ready() -> void: | |
| entity = get_parent() | |
| if not entity: return | |
| for child in get_children(): | |
| if child is State: | |
| var state := child as State | |
| state.entity = entity | |
| state.machine = self | |
| state.transitioned.connect(_on_state_transitioned) | |
| states[state.name] = state | |
| current_state = get_node(initial_state) as State | |
| current_state.enter() | |
| func _process(delta: float) -> void: | |
| if current_state: current_state.process(delta) | |
| func _physics_process(delta: float) -> void: | |
| player_input_context = PlayerInputs.build_player_input_context() | |
| if current_state: current_state.physics_process(delta, player_input_context) | |
| func _unhandled_input(event: InputEvent) -> void: | |
| if current_state: current_state.handle_input(event) | |
| func transition_to(state_name: String, opts := {}) -> void: | |
| if not states.has(state_name): return | |
| if current_state: current_state.exit() | |
| previous_state = current_state | |
| current_state = states[state_name] | |
| current_state.enter(opts) | |
| func transition_to_previous() -> void: | |
| current_state = previous_state | |
| previous_state = null | |
| func _on_state_transitioned(new_state: String, opts: Dictionary) -> void: | |
| transition_to(new_state, opts) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class_name PlayerWalkState | |
| extends PlayerState | |
| @export var walk_speed: float = 5.0 | |
| @export var turn_speed: float = 30.0 | |
| func physics_process(delta: float, input: Dictionary) -> void: | |
| var dir := _get_movement_direction(input) | |
| if dir == Vector3.ZERO: | |
| transitioned.emit(IDLE_STATE, {}) | |
| return | |
| _set_velocity(Vector3(dir.x * walk_speed, dir.y, dir.z * walk_speed)) | |
| _turn_visual(dir, turn_speed, delta) | |
| if input["run_pressed"]: | |
| transitioned.emit(RUN_STATE, {}) | |
| if input["run_just_released"]: | |
| transitioned.emit(DASH_STATE, { "direction": dir }) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment