Feat/1 movement foundation #5

Merged
Dotts merged 40 commits from feat/1-movement-foundation into main 2026-06-03 21:34:25 -07:00
Showing only changes of commit 60091932bd - Show all commits
+32 -54
View File
@@ -1,78 +1,56 @@
extends CharacterBody3D extends CharacterBody3D
class_name PlayerMovementController class_name PlayerMovementController
## PlayerMovementController — root node for all player 3D locomotion.
## Attach to a CharacterBody3D with a CollisionShape3D.
## States are expected to be children: state_ground, state_air,
## state_wall_run, state_wall_cling, state_slide, state_dash.
signal chain_updated(count: int, bonus: float) signal chain_updated(count: int, bonus: float)
@export var params: MovementParams = preload("res://movement/movement_params.gd").new() @export var params: MovementParams = preload("res://movement/movement_params.gd").new()
var machine: MovementStateMachine
var jump_buffered: bool = false
var _ready_ran: bool = false
var _machine: MovementStateMachine = null var _machine: MovementStateMachine = null
# ── Helpers ───────────────────────────────────────────────────────────────────
func _ensure_machine() -> MovementStateMachine: func _ensure_machine() -> MovementStateMachine:
if typeof(_machine) != TYPE_NIL and is_instance_valid(_machine): if is_instance_valid(_machine):
return _machine as MovementStateMachine
var candidates := [
get_node("MovementStateMachine") if has_node("MovementStateMachine") else null,
_machine if is_instance_valid(_machine) and _machine.has_method("register_chain_mechanic") else null,
]
for c in candidates:
if c and (c is MovementStateMachine or (c.has_method("switch_to") and c.has_method("register_chain_mechanic"))):
_machine = c
return _machine return _machine
if has_node("MovementStateMachine"):
_machine = $MovementStateMachine
if _machine is MovementStateMachine:
return _machine
_machine = null
return null return null
# ── Input references ─────────────────────────────────────────────────────────
func _unhandled_input(event: InputEvent) -> void: func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("ui_accept"): var sm := _ensure_machine()
# Space / jump if not sm:
pass
var node := _ensure_machine()
if not node:
return return
node.input_jump_just_pressed = Input.is_action_just_pressed("jump") sm.input_jump_just_pressed = Input.is_action_just_pressed("jump")
node.input_jump_pressed = Input.is_action_pressed("jump") sm.input_jump_pressed = Input.is_action_pressed("jump")
node.input_sprint = Input.is_action_pressed("sprint") sm.input_sprint = Input.is_action_pressed("sprint")
node.input_crouch = Input.is_action_pressed("crouch") sm.input_crouch = Input.is_action_pressed("crouch")
node.input_dash = Input.is_action_just_pressed("dash") sm.input_dash = Input.is_action_just_pressed("dash")
var input_v := Input.get_vector("move_left", "move_right", "move_forward", "move_back") sm.input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_back")
node.input_dir = input_v
@onready var _machine = get_node("MovementStateMachine")
func _ready() -> void: func _ready() -> void:
if _ready_ran: var sm := _ensure_machine()
return if sm:
_ready_ran = true
var node := _ensure_machine()
if node:
var sm := node
sm.player = self sm.player = self
sm.params = params sm.params = params
# Mirror signal up sm.movement_event.connect(_on_movement_event)
sm.movement_event.connect(func(ev, data):
if ev == "chain_updated":
chain_updated.emit(data.count, data.bonus)
)
func _process(_delta: float) -> void: func _process(_delta: float) -> void:
var node := _ensure_machine() var sm := _ensure_machine()
if not node: if not sm:
return return
# Keep one consistent source of truth for inputs sm.input_jump_just_pressed = Input.is_action_just_pressed("jump") and not sm.input_jump_pressed
node.input_jump_just_pressed = Input.is_action_just_pressed("jump") and not node.input_jump_pressed sm.input_jump_pressed = Input.is_action_pressed("jump")
node.input_jump_pressed = Input.is_action_pressed("jump") sm.input_sprint = Input.is_action_pressed("sprint")
node.input_sprint = Input.is_action_pressed("sprint") sm.input_crouch = Input.is_action_pressed("crouch")
node.input_crouch = Input.is_action_pressed("crouch") sm.input_dash = Input.is_action_just_pressed("dash") and not sm.input_dash
node.input_dash = Input.is_action_just_pressed("dash") and not node.input_dash sm.input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_back")
node.input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_back")
func _on_movement_event(ev: String, data: Dictionary) -> void:
if ev == "chain_updated":
chain_updated.emit(data.count, data.bonus)