feat: implement movement state machine architecture and add various sound assets

This commit is contained in:
DottsGit
2026-06-07 16:13:01 -04:00
parent 064bc90f53
commit 22ea918e6d
30 changed files with 372 additions and 13 deletions
+68
View File
@@ -55,6 +55,12 @@ func _ready() -> void:
grapple_state.set_script(load("res://movement/states/state_grapple.gd"))
add_child(grapple_state)
# Ensure wall climb state is injected
var wall_climb_state = Node.new()
wall_climb_state.name = "state_wall_climb"
wall_climb_state.set_script(load("res://movement/states/state_wall_climb.gd"))
add_child(wall_climb_state)
for child in get_children():
if child is Node and child.name.begins_with("state_"):
states[child.name.replace("state_", "")] = child
@@ -214,6 +220,9 @@ func detect_wall_horizontal() -> Vector3:
ray.exclude = [player.get_rid()]
var hit := space_state.intersect_ray(ray)
if not hit.is_empty():
var collider = hit.get("collider")
if collider is PlayerMovementController:
continue
var n: Vector3 = hit.get("normal", Vector3.ZERO)
# Wall must be roughly vertical (normal mostly horizontal)
if absf(n.y) < 0.3 and n.length_squared() > 0.0:
@@ -232,6 +241,9 @@ func detect_wall_horizontal() -> Vector3:
ray.exclude = [player.get_rid()]
var hit := space_state.intersect_ray(ray)
if not hit.is_empty():
var collider = hit.get("collider")
if collider is PlayerMovementController:
continue
var n: Vector3 = hit.get("normal", Vector3.ZERO)
if absf(n.y) < 0.3 and n.length_squared() > 0.0:
wall_normal = n.normalized()
@@ -242,6 +254,62 @@ func detect_wall_horizontal() -> Vector3:
wall_side = 0.0
return Vector3.ZERO
## Utility: detect wall directly in front of the player.
## Useful for wall climbing and vaulting over short walls.
func detect_wall_forward() -> Dictionary:
if not player:
return {"hit": false, "normal": Vector3.ZERO, "is_short": false}
var hvel := Vector3(player.velocity.x, 0.0, player.velocity.z)
var move_dir := Vector3.ZERO
if hvel.length_squared() > 0.1:
move_dir = hvel.normalized()
else:
move_dir = -player.global_transform.basis.z
move_dir.y = 0.0
if move_dir.length_squared() > 0.01:
move_dir = move_dir.normalized()
if move_dir.length_squared() < 0.1:
return {"hit": false, "normal": Vector3.ZERO, "is_short": false}
var space_state := player.get_world_3d().direct_space_state
var dist := params.wall_detect_distance
# Lower ray (feet/knees)
var origin_low := player.global_position + Vector3.UP * params.wall_ray_down_height
var ray_low := PhysicsRayQueryParameters3D.create(origin_low, origin_low + move_dir * dist)
ray_low.exclude = [player.get_rid()]
var hit_low := space_state.intersect_ray(ray_low)
# Mid ray (chest/head)
var origin_mid := player.global_position + Vector3.UP * params.wall_ray_up_height
var ray_mid := PhysicsRayQueryParameters3D.create(origin_mid, origin_mid + move_dir * dist)
ray_mid.exclude = [player.get_rid()]
var hit_mid := space_state.intersect_ray(ray_mid)
# If both lower rays miss, there is no wall directly in front
if hit_low.is_empty() and hit_mid.is_empty():
return {"hit": false, "normal": Vector3.ZERO, "is_short": false}
# Get the normal from the highest point we hit, or the lowest if we only hit low
var best_hit = hit_mid if not hit_mid.is_empty() else hit_low
if best_hit.get("collider") is PlayerMovementController:
return {"hit": false, "normal": Vector3.ZERO, "is_short": false}
var normal: Vector3 = best_hit.get("normal", Vector3.ZERO)
if absf(normal.y) >= 0.3:
return {"hit": false, "normal": Vector3.ZERO, "is_short": false} # Not a vertical wall
# Check if wall is short (ledge check) using a top ray
var origin_high := player.global_position + Vector3.UP * (params.wall_ray_up_height + params.wall_climb_vault_height_check)
var ray_high := PhysicsRayQueryParameters3D.create(origin_high, origin_high + move_dir * dist)
ray_high.exclude = [player.get_rid()]
var hit_high := space_state.intersect_ray(ray_high)
return {"hit": true, "normal": normal.normalized(), "is_short": hit_high.is_empty()}
func _apply_crouch(crouched: bool) -> void:
var shape_node = null