Add industrial metal soundtrack with seamless loop
- assets/audio/music/zombie_industrial_loop.ogg: 57.60 s Ogg Vorbis loop generated locally with YuE2-3B on the Tesla V100 (score written for this track; vocals removed with HTDemucs). The loop is spliced at the track's structural repeat with a 0.6 s crossfade: measured seam dip <= 1.6 dB. - scripts/main.gd: "Music" AudioStreamPlayer at -11 dB, started at boot on desktop and on the start click on Web (browser autoplay policy). It forces stream.loop at runtime because the Web staging step re-imports the OGG with loop=false. - tools/test_music.gd: soundtrack test (loop, length, volume, playback, still playing after player death). - docs/MUSIC.md: source, loop construction, verification, licence. - docs/WEB-DEPLOY.md: release ab6054111f5919a8 notes. Initial commit of the project.
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
extends CanvasLayer
|
||||
## Local-only trauma pivot: never modifies the player's aim angles.
|
||||
const SHAKE_SECONDS := 0.42
|
||||
const BLOOD_SECONDS := 1.15
|
||||
const SPLAT_COUNT := 7
|
||||
var pivot: Node3D
|
||||
var shake_left := 0.0
|
||||
var blood_left := 0.0
|
||||
var intensity := 0.0
|
||||
var phase := 0.0
|
||||
var active := true
|
||||
var overlay: Control
|
||||
var vignette: ColorRect
|
||||
var shader_material: ShaderMaterial
|
||||
var splats: Array[TextureRect] = []
|
||||
|
||||
func _ready() -> void:
|
||||
layer = 2
|
||||
overlay = Control.new()
|
||||
overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(overlay)
|
||||
overlay.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
vignette = ColorRect.new()
|
||||
vignette.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
shader_material = ShaderMaterial.new()
|
||||
shader_material.shader = load("res://assets/effects/damage_vignette.gdshader")
|
||||
vignette.material = shader_material
|
||||
overlay.add_child(vignette)
|
||||
vignette.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
for i in SPLAT_COUNT:
|
||||
var splat := TextureRect.new()
|
||||
splat.texture = load("res://assets/effects/blood.png")
|
||||
splat.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
splat.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
splat.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
splat.modulate.a = 0
|
||||
overlay.add_child(splat)
|
||||
splats.append(splat)
|
||||
|
||||
func hit(amount: float) -> void:
|
||||
if not active or amount <= 0:
|
||||
return
|
||||
# Refresh instead of stacking tweens/particles: bounded cost and shake.
|
||||
intensity = minf(1.0, maxf(intensity, 0.55 + amount / 100.0) + 0.12)
|
||||
shake_left = SHAKE_SECONDS
|
||||
blood_left = BLOOD_SECONDS
|
||||
phase = randf_range(0, TAU)
|
||||
var viewport_size := get_viewport().get_visible_rect().size
|
||||
for i in splats.size():
|
||||
var splat := splats[i]
|
||||
var size := minf(viewport_size.x, viewport_size.y) * randf_range(0.22, 0.38)
|
||||
splat.size = Vector2.ONE * size
|
||||
splat.pivot_offset = splat.size * 0.5
|
||||
# Keep the central reticle and HUD readable; splats lie near the edges.
|
||||
var center := Vector2.ZERO
|
||||
match i % 4:
|
||||
0: center = viewport_size * Vector2(randf_range(0.06, 0.17), randf_range(0.25, 0.90))
|
||||
1: center = viewport_size * Vector2(randf_range(0.83, 0.95), randf_range(0.25, 0.90))
|
||||
2: center = viewport_size * Vector2(randf_range(0.25, 0.75), randf_range(0.82, 0.98))
|
||||
3: center = viewport_size * Vector2(randf_range(0.3, 0.8), randf_range(0.04, 0.13))
|
||||
splat.position = center - splat.size * 0.5
|
||||
splat.rotation = randf_range(-PI, PI)
|
||||
splat.modulate = Color(0.85, 0.1, 0.12, 0.85 * intensity)
|
||||
shader_material.set_shader_parameter("strength", intensity)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if not active:
|
||||
return
|
||||
phase += delta * 42.0
|
||||
shake_left = maxf(0, shake_left - delta)
|
||||
blood_left = maxf(0, blood_left - delta)
|
||||
var shake := pow(shake_left / SHAKE_SECONDS, 2.0) * intensity
|
||||
if is_instance_valid(pivot):
|
||||
pivot.position = Vector3(sin(phase) * 0.025, cos(phase * 1.3) * 0.018, sin(phase * 0.8) * 0.012) * shake
|
||||
pivot.rotation = Vector3(sin(phase * 0.9) * 0.024, cos(phase) * 0.013, sin(phase * 1.1) * 0.035) * shake
|
||||
var alpha := blood_left / BLOOD_SECONDS
|
||||
for splat in splats:
|
||||
splat.modulate.a = alpha * 0.85 * intensity
|
||||
shader_material.set_shader_parameter("strength", minf(1, alpha * 1.4) * intensity)
|
||||
if blood_left <= 0:
|
||||
intensity = 0
|
||||
|
||||
func stop() -> void:
|
||||
active = false
|
||||
shake_left = 0
|
||||
blood_left = 0
|
||||
intensity = 0
|
||||
if is_instance_valid(pivot):
|
||||
pivot.transform = Transform3D.IDENTITY
|
||||
overlay.hide()
|
||||
Reference in New Issue
Block a user