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:
+158
@@ -0,0 +1,158 @@
|
||||
extends Node3D
|
||||
const PlayerScript = preload("res://scripts/player.gd")
|
||||
const ZombieScript = preload("res://scripts/zombie.gd")
|
||||
const RoomScript = preload("res://scripts/room.gd")
|
||||
const MUSIC_PATH := "res://assets/audio/music/zombie_industrial_loop.ogg"
|
||||
const MUSIC_VOLUME_DB := -11.0
|
||||
var player: CharacterBody3D
|
||||
var room: Node3D
|
||||
var music: AudioStreamPlayer
|
||||
var music_started := false
|
||||
var hud: Label
|
||||
var score := 0
|
||||
var wave := 0
|
||||
var alive := 0
|
||||
var spawning := false
|
||||
var wave_pending := false
|
||||
var smoke := false
|
||||
var awaiting_web_start := false
|
||||
var start_label: Label
|
||||
|
||||
func _ready() -> void:
|
||||
smoke = "--smoke" in OS.get_cmdline_user_args()
|
||||
room = Node3D.new()
|
||||
room.set_script(RoomScript)
|
||||
add_child(room)
|
||||
player = CharacterBody3D.new()
|
||||
player.set_script(PlayerScript)
|
||||
player.position = Vector3(0, 0.1, 0)
|
||||
add_child(player)
|
||||
var envnode := WorldEnvironment.new()
|
||||
var env := Environment.new()
|
||||
env.background_mode = Environment.BG_COLOR
|
||||
env.background_color = Color(0.08, 0.12, 0.19)
|
||||
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
env.ambient_light_color = Color(0.65, 0.73, 0.85)
|
||||
env.ambient_light_energy = 0.65
|
||||
envnode.environment = env
|
||||
add_child(envnode)
|
||||
var light := OmniLight3D.new()
|
||||
light.position = Vector3(0, 4.5, 0)
|
||||
light.omni_range = 24
|
||||
light.light_energy = 3
|
||||
light.shadow_enabled = true
|
||||
add_child(light)
|
||||
var canvas := CanvasLayer.new()
|
||||
add_child(canvas)
|
||||
hud = Label.new()
|
||||
hud.position = Vector2(24, 20)
|
||||
hud.add_theme_font_size_override("font_size", 24)
|
||||
canvas.add_child(hud)
|
||||
var cross := Label.new()
|
||||
cross.text = "+"
|
||||
cross.add_theme_font_size_override("font_size", 30)
|
||||
canvas.add_child(cross)
|
||||
cross.set_anchors_and_offsets_preset(Control.PRESET_CENTER)
|
||||
_setup_music()
|
||||
if OS.has_feature("web"):
|
||||
awaiting_web_start = true
|
||||
start_label = Label.new()
|
||||
start_label.text = "CLICCA PER INIZIARE\nWASD: movimento · clic sinistro: spara · destro: granata · R: ricarica"
|
||||
start_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
start_label.add_theme_font_size_override("font_size", 24)
|
||||
start_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
canvas.add_child(start_label)
|
||||
start_label.set_anchors_and_offsets_preset(Control.PRESET_CENTER)
|
||||
else:
|
||||
_start_music()
|
||||
_next_wave()
|
||||
if smoke:
|
||||
get_tree().create_timer(12).timeout.connect(func():
|
||||
print("SMOKE: wave=", wave, " alive=", alive, " player_hp=", player.hp)
|
||||
get_tree().quit())
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if awaiting_web_start and event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
awaiting_web_start = false
|
||||
start_label.queue_free()
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
# Il browser concede l'audio solo dopo un gesto dell'utente: qui il click.
|
||||
_start_music()
|
||||
_next_wave()
|
||||
|
||||
## Colonna sonora: industrial metal strumentale su loop senza giunzione udibile.
|
||||
func _setup_music() -> void:
|
||||
music = AudioStreamPlayer.new()
|
||||
music.name = "Music"
|
||||
music.stream = load(MUSIC_PATH)
|
||||
music.volume_db = MUSIC_VOLUME_DB
|
||||
if music.stream is AudioStreamOggVorbis:
|
||||
music.stream.loop = true
|
||||
add_child(music)
|
||||
music.add_to_group("music")
|
||||
# Rete di sicurezza: se il flag di loop mancasse, il brano riparte a fine coda.
|
||||
music.finished.connect(_restart_music)
|
||||
|
||||
func _start_music() -> void:
|
||||
if music_started or music == null:
|
||||
return
|
||||
music_started = true
|
||||
music.play()
|
||||
|
||||
func _restart_music() -> void:
|
||||
if music_started and is_instance_valid(music):
|
||||
music.play()
|
||||
|
||||
func _process(_dt: float) -> void:
|
||||
if hud:
|
||||
hud.text = "VITA %d MUNIZIONI %d/30 ONDATA %d PUNTI %d\nWASD · mouse · sinistro: spara · destro: granata · R: ricarica · spazio: salto · Esc: mouse" % [player.hp, player.ammo, wave, score]
|
||||
hud.text += "\nGRANATA: " + ("PRONTA" if player.grenade_cooldown <= 0 else "%d s" % ceili(player.grenade_cooldown))
|
||||
if player.reloading:
|
||||
hud.text += "\nRICARICA..."
|
||||
if player.hp <= 0:
|
||||
hud.text += "\nSEI MORTO — premi R per ricominciare"
|
||||
|
||||
func zombies_for_wave(number: int) -> int:
|
||||
return 4 + number * 2
|
||||
|
||||
func _next_wave() -> void:
|
||||
wave_pending = false
|
||||
if player.hp <= 0 or spawning:
|
||||
return
|
||||
wave += 1
|
||||
spawning = true
|
||||
for i in zombies_for_wave(wave):
|
||||
if player.hp <= 0:
|
||||
break
|
||||
_spawn(i % 4)
|
||||
await get_tree().create_timer(0.8).timeout
|
||||
spawning = false
|
||||
_check_wave()
|
||||
|
||||
func _spawn(index: int) -> void:
|
||||
var centers := [Vector3(0, 0, -9), Vector3(0, 0, 9), Vector3(-9, 0, 0), Vector3(9, 0, 0)]
|
||||
var inward: Vector3 = -centers[index].normalized()
|
||||
var zombie := CharacterBody3D.new()
|
||||
zombie.set_script(ZombieScript)
|
||||
zombie.player = player
|
||||
zombie.position = centers[index] - inward * 1.2
|
||||
add_child(zombie)
|
||||
alive += 1
|
||||
zombie.killed.connect(func(headshot: bool):
|
||||
score += 150 if headshot else 100
|
||||
alive -= 1
|
||||
_check_wave())
|
||||
var tween := zombie.create_tween()
|
||||
zombie.entry_tween = tween
|
||||
tween.tween_property(zombie, "position", centers[index] + Vector3(0, 1.55, 0), 1.2)
|
||||
tween.tween_callback(func():
|
||||
for glass in room.glass_panes.duplicate():
|
||||
if is_instance_valid(glass) and Vector2(glass.position.x - centers[index].x, glass.position.z - centers[index].z).length() < 2:
|
||||
room.shatter_glass(glass))
|
||||
tween.tween_property(zombie, "position", centers[index] + inward * 1.4 + Vector3(0, 0.1, 0), 0.8)
|
||||
tween.tween_callback(func(): zombie.entering = false)
|
||||
|
||||
func _check_wave() -> void:
|
||||
if alive == 0 and not spawning and not wave_pending and player.hp > 0:
|
||||
wave_pending = true
|
||||
get_tree().create_timer(3).timeout.connect(_next_wave)
|
||||
Reference in New Issue
Block a user