feat: add double-barrel shotgun weapon, player movement state machine, and procedural sound assets
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 ───────────────────────────────────────────────
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ─────────────────────────────
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user