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)