- 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.
199 lines
7.4 KiB
GDScript
199 lines
7.4 KiB
GDScript
## Stanza 18x18x5 con 4 finestre (una per muro) e nessuna porta.
|
|
## Geometria costruita in codice per determinismo; vetro = Area3D rompibile.
|
|
class_name Room
|
|
extends Node3D
|
|
|
|
const W: float = 18.0 # larghezza (x)
|
|
const D: float = 18.0 # profondita (z)
|
|
const H: float = 5.0 # altezza (y)
|
|
const WALL_T: float = 0.3
|
|
|
|
# Finestra: 4 m x 2 m, bordo basso a 1.5 m
|
|
const WIN_W: float = 4.0
|
|
const WIN_H: float = 2.0
|
|
const WIN_Y0: float = 1.5
|
|
|
|
var glass_panes: Array[Area3D] = []
|
|
## Punti di spawn esterni, uno per finestra (4 finestre x 4 slot)
|
|
var window_spawn_points: Array[Vector3] = []
|
|
var window_normals: Array[Vector3] = [] # direzione "dentro" la stanza per ogni finestra
|
|
|
|
func _ready() -> void:
|
|
var wall_mat := _surface_material("wall", 0.9)
|
|
var floor_mat := _surface_material("floor", 0.95)
|
|
var ceil_mat := _surface_material("ceiling", 0.85)
|
|
|
|
# Pavimento
|
|
_box(Vector3(W, 0.2, D), Vector3(0, -0.1, 0), floor_mat, true)
|
|
# Soffitto
|
|
_box(Vector3(W, 0.2, D), Vector3(0, H + 0.1, 0), ceil_mat, false)
|
|
|
|
# 4 muri con apertura finestra al centro
|
|
# nord (z = -D/2), sud (z = +D/2), ovest (x = -W/2), est (x = +W/2)
|
|
var walls := [
|
|
{"center": Vector3(0, H / 2, -D / 2 - WALL_T / 2), "size": Vector3(W, H, WALL_T), "axis": "x"},
|
|
{"center": Vector3(0, H / 2, D / 2 + WALL_T / 2), "size": Vector3(W, H, WALL_T), "axis": "x"},
|
|
{"center": Vector3(-W / 2 - WALL_T / 2, H / 2, 0), "size": Vector3(WALL_T, H, D), "axis": "z"},
|
|
{"center": Vector3(W / 2 + WALL_T / 2, H / 2, 0), "size": Vector3(WALL_T, H, D), "axis": "z"},
|
|
]
|
|
for w in walls:
|
|
_build_wall_with_window(w.center, w.size, w.axis, wall_mat)
|
|
|
|
# Vetrata per ogni finestra
|
|
_add_glass(Vector3(0, WIN_Y0 + WIN_H / 2, -D / 2), Vector3(WIN_W, WIN_H, 0.05), Vector3(1, 0, 0))
|
|
_add_glass(Vector3(0, WIN_Y0 + WIN_H / 2, D / 2), Vector3(WIN_W, WIN_H, 0.05), Vector3(1, 0, 0))
|
|
_add_glass(Vector3(-W / 2, WIN_Y0 + WIN_H / 2, 0), Vector3(0.05, WIN_H, WIN_W), Vector3(0, 0, 1))
|
|
_add_glass(Vector3(W / 2, WIN_Y0 + WIN_H / 2, 0), Vector3(0.05, WIN_H, WIN_W), Vector3(0, 0, 1))
|
|
|
|
# Punti di spawn: 4 slot per finestra, fuori dal muro
|
|
var win_centers := [
|
|
{"c": Vector3(0, 0, -D / 2), "n": Vector3(0, 0, 1)},
|
|
{"c": Vector3(0, 0, D / 2), "n": Vector3(0, 0, -1)},
|
|
{"c": Vector3(-W / 2, 0, 0), "n": Vector3(1, 0, 0)},
|
|
{"c": Vector3(W / 2, 0, 0), "n": Vector3(-1, 0, 0)},
|
|
]
|
|
for wc in win_centers:
|
|
var side: Vector3 = wc.n.cross(Vector3(0, 1, 0)) # asse laterale della finestra
|
|
for i in 4:
|
|
var off := (i - 1.5) * (WIN_W / 3.0)
|
|
var p: Vector3 = wc.c + side * off + wc.n * (-1.2) # 1.2 m fuori dalla finestra
|
|
window_spawn_points.append(p)
|
|
window_normals.append(wc.n)
|
|
|
|
func _build_wall_with_window(center: Vector3, size: Vector3, axis: String, mat: StandardMaterial3D) -> void:
|
|
# Segments around the window opening (window is centered on the wall face)
|
|
var wall := StaticBody3D.new()
|
|
wall.name = "Wall"
|
|
add_child(wall)
|
|
var half_win_x := WIN_W / 2.0
|
|
var y0 := WIN_Y0
|
|
var y1 := WIN_Y0 + WIN_H
|
|
|
|
if axis == "x": # muro lungo x
|
|
var L := size.x
|
|
var segs := [
|
|
{"s": Vector3(L / 2 - half_win_x, size.y, size.z), "c": Vector3(-half_win_x - (L / 2 - half_win_x) / 2.0, 0, 0)},
|
|
{"s": Vector3(L / 2 - half_win_x, size.y, size.z), "c": Vector3(half_win_x + (L / 2 - half_win_x) / 2.0, 0, 0)},
|
|
{"s": Vector3(WIN_W, y0, size.z), "c": Vector3(0, -size.y / 2 + y0 / 2.0, 0)},
|
|
{"s": Vector3(WIN_W, size.y - y1, size.z), "c": Vector3(0, size.y / 2 - (size.y - y1) / 2.0, 0)},
|
|
]
|
|
for seg in segs:
|
|
_add_box_to(wall, seg.s, center + seg.c, mat)
|
|
else: # muro lungo z
|
|
var L := size.z
|
|
var segs := [
|
|
{"s": Vector3(size.x, size.y, L / 2 - half_win_x), "c": Vector3(0, 0, -half_win_x - (L / 2 - half_win_x) / 2.0)},
|
|
{"s": Vector3(size.x, size.y, L / 2 - half_win_x), "c": Vector3(0, 0, half_win_x + (L / 2 - half_win_x) / 2.0)},
|
|
{"s": Vector3(size.x, y0, WIN_W), "c": Vector3(0, -size.y / 2 + y0 / 2.0, 0)},
|
|
{"s": Vector3(size.x, size.y - y1, WIN_W), "c": Vector3(0, size.y / 2 - (size.y - y1) / 2.0, 0)},
|
|
]
|
|
for seg in segs:
|
|
_add_box_to(wall, seg.s, center + seg.c, mat)
|
|
|
|
func _add_glass(center: Vector3, size: Vector3, rot_axis: Vector3) -> void:
|
|
var glass := Area3D.new()
|
|
glass.name = "Glass"
|
|
glass.collision_layer = 8
|
|
glass.collision_mask = 0
|
|
add_child(glass)
|
|
var shape := CollisionShape3D.new()
|
|
var box := BoxShape3D.new()
|
|
box.size = size
|
|
shape.shape = box
|
|
glass.add_child(shape)
|
|
glass.global_position = center
|
|
var mesh := MeshInstance3D.new()
|
|
var bm := BoxMesh.new()
|
|
bm.size = size
|
|
var gm := StandardMaterial3D.new()
|
|
gm.albedo_color = Color(0.7, 0.85, 0.9, 0.16)
|
|
gm.cull_mode = BaseMaterial3D.CULL_DISABLED
|
|
gm.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
|
gm.shading_mode = BaseMaterial3D.SHADING_MODE_PER_PIXEL
|
|
gm.roughness = 0.05
|
|
gm.metallic = 0.1
|
|
bm.material = gm
|
|
mesh.mesh = bm
|
|
glass.add_child(mesh)
|
|
glass_panes.append(glass)
|
|
|
|
func shatter_glass(glass: Area3D) -> void:
|
|
var sound_origin := Node3D.new()
|
|
add_child(sound_origin)
|
|
sound_origin.global_position = glass.global_position
|
|
preload("res://scripts/hl_audio.gd").play(sound_origin, "debris/bustglass%d.wav" % randi_range(1, 3), true, -12)
|
|
get_tree().create_timer(4).timeout.connect(sound_origin.queue_free)
|
|
for i in glass_panes.size():
|
|
if glass_panes[i] == glass:
|
|
glass_panes.remove_at(i)
|
|
break
|
|
# Schegge di vetro (particelle semplici)
|
|
var sh := CPUParticles3D.new()
|
|
sh.direction = Vector3(0, -0.3, 0)
|
|
sh.spread = 180
|
|
sh.initial_velocity_min = 1.5
|
|
sh.initial_velocity_max = 4
|
|
sh.gravity = Vector3(0, -9.8, 0)
|
|
sh.scale_amount_min = 0.4
|
|
sh.scale_amount_max = 1.2
|
|
sh.color = Color(0.7, 0.85, 1.0, 0.8)
|
|
sh.emission_shape = CPUParticles3D.EMISSION_SHAPE_SPHERE
|
|
sh.emission_sphere_radius = 1.2
|
|
var shard := BoxMesh.new()
|
|
shard.size = Vector3(0.05, 0.02, 0.08)
|
|
var shard_mat := StandardMaterial3D.new()
|
|
shard_mat.albedo_color = Color(0.6, 0.8, 0.9)
|
|
shard.material = shard_mat
|
|
sh.mesh = shard
|
|
sh.amount = 40
|
|
sh.lifetime = 0.9
|
|
sh.one_shot = true
|
|
sh.explosiveness = 1.0
|
|
sh.position = glass.global_position
|
|
add_child(sh)
|
|
sh.emitting = true
|
|
get_tree().create_timer(2).timeout.connect(sh.queue_free)
|
|
glass.queue_free()
|
|
|
|
func _box(s: Vector3, c: Vector3, m: StandardMaterial3D, collide: bool) -> void:
|
|
var body := StaticBody3D.new()
|
|
add_child(body)
|
|
_add_box_to(body, s, c, m)
|
|
if not collide:
|
|
body.get_node("shape").set_deferred("disabled", true)
|
|
|
|
func _add_box_to(parent: Node3D, s: Vector3, c: Vector3, m: StandardMaterial3D) -> void:
|
|
var mi := MeshInstance3D.new()
|
|
var bm := BoxMesh.new()
|
|
bm.size = s
|
|
bm.material = m
|
|
mi.mesh = bm
|
|
parent.add_child(mi)
|
|
var cs := CollisionShape3D.new()
|
|
var bs := BoxShape3D.new()
|
|
bs.size = s
|
|
cs.shape = bs
|
|
cs.position = c - parent.position if parent is StaticBody3D else c
|
|
cs.name = "shape"
|
|
parent.add_child(cs)
|
|
# il mesh va centrato sullo stesso punto della collision
|
|
mi.position = c - parent.position if parent is StaticBody3D else c
|
|
|
|
func _surface_material(texture_name: String, rough: float) -> StandardMaterial3D:
|
|
var material := StandardMaterial3D.new()
|
|
material.albedo_texture = load("res://assets/textures/" + texture_name + ".png")
|
|
material.albedo_color = Color.WHITE
|
|
material.roughness = rough
|
|
material.texture_filter = BaseMaterial3D.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC
|
|
material.texture_repeat = true
|
|
material.uv1_triplanar = true
|
|
material.uv1_world_triplanar = true
|
|
material.uv1_scale = Vector3.ONE * 0.5
|
|
return material
|
|
|
|
func _mat(color: Color, rough: float) -> StandardMaterial3D:
|
|
var m := StandardMaterial3D.new()
|
|
m.albedo_color = color
|
|
m.roughness = rough
|
|
return m
|