feat: add double-barrel shotgun weapon, player movement state machine, and procedural sound assets

This commit is contained in:
DottsGit
2026-06-04 16:40:15 -04:00
parent 13ca2f46e9
commit 67bd5b4302
18 changed files with 282 additions and 3 deletions
+89 -2
View File
@@ -10,6 +10,19 @@ var head_pivot: Node3D = null # FPSCameraRig node
var camera: Camera3D = null
var _damage_layer: CanvasLayer = null
# Audio
var footstep_player: AudioStreamPlayer
var dash_player: AudioStreamPlayer
var hit_player: AudioStreamPlayer
var slide_player: AudioStreamPlayer
var wallrun_player: AudioStreamPlayer
var wind_player: AudioStreamPlayer
var jump_player: AudioStreamPlayer
var double_jump_player: AudioStreamPlayer
# UI
var hit_marker: Control
func _ready() -> void:
var sm := _ensure_machine()
@@ -25,10 +38,72 @@ func _ready() -> void:
_damage_layer = CanvasLayer.new()
add_child(_damage_layer)
_setup_audio()
_setup_hit_marker()
set_process(true)
set_physics_process(true)
func _setup_audio() -> void:
footstep_player = AudioStreamPlayer.new()
footstep_player.stream = load("res://assets/sounds/footstep.wav")
add_child(footstep_player)
jump_player = AudioStreamPlayer.new()
jump_player.stream = load("res://assets/sounds/jump.wav")
add_child(jump_player)
double_jump_player = AudioStreamPlayer.new()
double_jump_player.stream = load("res://assets/sounds/double_jump.wav")
add_child(double_jump_player)
dash_player = AudioStreamPlayer.new()
dash_player.stream = load("res://assets/sounds/dash.wav")
dash_player.volume_db = -5.0
add_child(dash_player)
hit_player = AudioStreamPlayer.new()
hit_player.stream = load("res://assets/sounds/hit_confirm.wav")
add_child(hit_player)
slide_player = AudioStreamPlayer.new()
slide_player.stream = load("res://assets/sounds/slide.wav")
slide_player.volume_db = -10.0
add_child(slide_player)
wallrun_player = AudioStreamPlayer.new()
wallrun_player.stream = load("res://assets/sounds/wallrun.wav")
wallrun_player.volume_db = -10.0
add_child(wallrun_player)
wind_player = AudioStreamPlayer.new()
wind_player.stream = load("res://assets/sounds/wind.wav")
wind_player.volume_db = -80.0 # Start silent
add_child(wind_player)
wind_player.play()
func _setup_hit_marker() -> void:
hit_marker = Control.new()
hit_marker.set_anchors_preset(Control.PRESET_CENTER)
hit_marker.modulate.a = 0.0 # Hidden by default
_damage_layer.add_child(hit_marker)
# Draw an X perfectly centered
var length = 12
var thickness = 2
for angle in [PI/4, 3*PI/4, 5*PI/4, 7*PI/4]:
var rect = ColorRect.new()
rect.color = Color.WHITE
rect.size = Vector2(length, thickness)
rect.pivot_offset = rect.size / 2.0
# Center the rect itself at (0,0) before offset
var center_pos = -rect.size / 2.0
# Move it outward along the angle so it doesn't cover the exact center dot
rect.position = center_pos + Vector2(cos(angle), sin(angle)) * (length / 2.0)
rect.rotation = angle
hit_marker.add_child(rect)
func spawn_damage_number(amount: float, hit_pos: Vector3) -> void:
if not camera or not _damage_layer: return
@@ -45,7 +120,12 @@ func spawn_damage_number(amount: float, hit_pos: Vector3) -> void:
label.camera = camera
_damage_layer.add_child(label)
# Play sound and flash hit marker
hit_player.play()
hit_marker.modulate.a = 1.0
var tween = create_tween()
tween.tween_property(hit_marker, "modulate:a", 0.0, 0.4)
func _ensure_machine() -> MovementStateMachine:
if is_instance_valid(_machine):
@@ -58,8 +138,15 @@ func _ensure_machine() -> MovementStateMachine:
_machine = null
return null
func _physics_process(_delta: float) -> void:
var speed = velocity.length()
var wind_factor = clampf((speed - 10.0) / 25.0, 0.0, 1.0)
# Decibel scale is non-linear, so lerping from -80 means it stays inaudible for a long time.
# -40 db is practically silent for a background loop, -15 db is our max quiet wind.
wind_player.volume_db = lerpf(-40.0, -15.0, wind_factor)
if not wind_player.playing and wind_factor > 0.0:
wind_player.play()
var sm := _ensure_machine()
if not sm:
return
+4
View File
@@ -81,6 +81,8 @@ func update(delta: float) -> void:
machine.on_ground = false
machine.jump_buffer_time = 0.0
machine.register_chain_mechanic("bunny_hop")
if player.jump_player:
player.jump_player.play()
# Stay in air state
return
@@ -92,6 +94,8 @@ func update(delta: float) -> void:
player.velocity.y = params.double_jump_velocity
machine.current_jump_count += 1
machine.register_chain_mechanic("double_jump")
if player.double_jump_player:
player.double_jump_player.play()
return
# ── Wall run transition ───────────────────────────────────────────────
+4
View File
@@ -24,6 +24,10 @@ func enter(_data: Dictionary = {}) -> void:
elapsed = 0.0
_last_dash_time = now
var player := machine.player
if player.dash_player:
player.dash_player.play()
# Direction: use wish direction if available, else player forward
var wish := machine.wish_dir_world
if wish.length_squared() > 0.01:
+15
View File
@@ -5,6 +5,7 @@ var machine: MovementStateMachine
var params: MovementParams:
get: return machine.params
var _footstep_timer: float = 0.0
func enter(_data: Dictionary = {}) -> void:
machine.on_ground = true
@@ -33,6 +34,8 @@ func update(delta: float) -> void:
machine.on_ground = false
machine.coyote_timer = 0.0
machine.register_chain_mechanic("jump")
if player.jump_player:
player.jump_player.play()
machine.switch_to("air")
return
@@ -43,6 +46,8 @@ func update(delta: float) -> void:
machine.on_ground = false
machine.jump_buffer_time = 0.0
machine.register_chain_mechanic("jump")
if player.jump_player:
player.jump_player.play()
machine.switch_to("air")
return
@@ -86,6 +91,16 @@ func update(delta: float) -> void:
_update_capsule_height()
var current_hspeed = Vector2(player.velocity.x, player.velocity.z).length()
if current_hspeed > 1.0:
_footstep_timer -= delta
if _footstep_timer <= 0.0:
if player.footstep_player:
player.footstep_player.play()
_footstep_timer = max(0.2, 3.0 / current_hspeed)
else:
_footstep_timer = 0.0
# ── Update ground status ──────────────────────────────────────────────
if player.is_on_floor():
machine.on_ground = true
+11
View File
@@ -14,6 +14,9 @@ func enter(_data: Dictionary = {}) -> void:
elapsed = 0.0
machine.register_chain_mechanic("slide")
if machine.player.slide_player and not machine.player.slide_player.playing:
machine.player.slide_player.play()
# Save original capsule height and halve it
var shape: CapsuleShape3D = _get_capsule()
if shape:
@@ -38,6 +41,10 @@ func enter(_data: Dictionary = {}) -> void:
func exit() -> void:
machine.slide_cooldown_timer = params.slide_cooldown
if machine.player.slide_player:
machine.player.slide_player.stop()
# Restore original capsule height
var shape: CapsuleShape3D = _get_capsule()
if shape and _saved_capsule_height > 0.0:
@@ -47,6 +54,10 @@ func exit() -> void:
func update(delta: float) -> void:
elapsed += delta
var player := machine.player
if player.slide_player and not player.slide_player.playing:
player.slide_player.play()
var vel: Vector3 = player.velocity
# ── Apply friction to horizontal velocity ─────────────────────────────
+9
View File
@@ -22,6 +22,9 @@ func enter(_data: Dictionary = {}) -> void:
machine.on_ground = false
_last_vel = machine.player.velocity
if machine.player.wallrun_player and not machine.player.wallrun_player.playing:
machine.player.wallrun_player.play()
var hvel := Vector3(machine.player.velocity.x, 0.0, machine.player.velocity.z)
_current_tangent = machine.wall_normal.cross(Vector3.UP).normalized()
if hvel.dot(_current_tangent) < 0.0:
@@ -43,6 +46,9 @@ func exit() -> void:
var rig = _get_camera_rig()
if rig:
rig.clear_wall_tilt()
if machine.player.wallrun_player:
machine.player.wallrun_player.stop()
func update(delta: float) -> void:
@@ -57,6 +63,9 @@ func update(delta: float) -> void:
var player := machine.player
var vel: Vector3 = player.velocity
if player.wallrun_player and not player.wallrun_player.playing:
player.wallrun_player.play()
if vel.distance_squared_to(_last_vel) > 100.0:
machine.wall_normal = Vector3.ZERO
machine.wall_side = 0.0