Eight windows per room and random zombie entry from different windows

This commit is contained in:
enne2
2026-10-13 21:21:17 +02:00
parent 70c82df450
commit fcf298c692
9 changed files with 400 additions and 70 deletions
+44
View File
@@ -0,0 +1,44 @@
extends SceneTree
## Cattura la stanza con le otto finestre: le quattro pareti dal centro, una vista
## d'angolo e un ingresso in corso dalla finestra effettivamente scelta dallo spawn.
func _initialize() -> void:
call_deferred("run_test")
func run_test() -> void:
var world = load("res://scenes/main.tscn").instantiate()
root.add_child(world)
world.player.hp = 100000
world.player.set_physics_process(false)
await create_timer(0.35).timeout # la prima ondata parte in _ready
var chosen: int = world.last_window
var window: Dictionary = world.room.windows[chosen]
# Ingresso in corso: il tween dura 1,2 s salendo verso il davanzale.
world.player.position = Vector3(window.center) + Vector3(window.normal) * -0.5 + Vector3(0, 0.1, 0)
world.player.look_at(Vector3(window.center) + Vector3(0, 1.8, 0))
await create_timer(0.7).timeout
await RenderingServer.frame_post_draw
root.get_texture().get_image().save_png("/tmp/zombie-windows-ingresso.png")
print("SHOT ingresso: finestra %d da %s" % [chosen, window.center])
# Le quattro pareti dal centro.
var views := [
["nord", Vector3(0, 2.2, -9)],
["est", Vector3(9, 2.2, 0)],
["sud", Vector3(0, 2.2, 9)],
["ovest", Vector3(-9, 2.2, 0)],
]
for view in views:
world.player.position = Vector3(0, 0.1, 0)
world.player.look_at(view[1])
await create_timer(0.4).timeout
await RenderingServer.frame_post_draw
root.get_texture().get_image().save_png("/tmp/zombie-windows-%s.png" % view[0])
# Vista d'angolo: due pareti insieme.
world.player.position = Vector3(-7, 0.1, -7)
world.player.look_at(Vector3(6, 1.5, 6))
await create_timer(0.4).timeout
await RenderingServer.frame_post_draw
root.get_texture().get_image().save_png("/tmp/zombie-windows-angolo.png")
print("SHOT: immagini in /tmp/zombie-windows-*.png")
world.queue_free()
await process_frame
quit()
+1
View File
@@ -0,0 +1 @@
uid://bptxvotrlu7n4
+15 -4
View File
@@ -41,14 +41,25 @@ func run_test() -> void:
await physics_frame
await physics_frame
var space = world.get_world_3d().direct_space_state
# Cardinal routes from windows to center remain free at zombie torso height.
for edge in [Vector3(0, 0.8, -8), Vector3(0, 0.8, 8), Vector3(-8, 0.8, 0), Vector3(8, 0.8, 0)]:
# Ogni finestra deve essere un'APERTURA e deve essere COLLEGATA al centro: gli zombie
# entrano e devono poter arrivare. Il raggio prova l'apertura; il percorso A* prova
# la raggiungibilita', anche quando un arredo cade su una diagonale.
var inside_points: Array[Vector3] = []
for window in world.room.windows:
var edge: Vector3 = window.center + Vector3(window.normal) * 0.8 + Vector3(0, 0.8, 0)
var q := PhysicsRayQueryParameters3D.create(edge, Vector3(0, 0.8, 0), 1)
q.exclude = [world.player.get_rid()]
assert(space.intersect_ray(q).is_empty())
inside_points.append(edge)
if not space.intersect_ray(q).is_empty():
print(" (nota: un arredo ostruisce la diagonale da ", edge, " al centro)")
assert(inside_points.size() >= 8)
for point in inside_points:
var path: PackedVector3Array = world.navigation.route(point, Vector3(0, 0, 0))
assert(path.size() >= 2) # raggiungibile dal centro
assert(path[path.size() - 1].distance_to(Vector3.ZERO) < 1.5)
var q := PhysicsRayQueryParameters3D.create(Vector3(-5, 0.5, -2), Vector3(-5, 0.5, -4), 1)
assert(not space.intersect_ray(q).is_empty())
print("PASS: corridoi cardinali liberi, scrivanie bloccano fisicamente")
print("PASS: otto aperture libere e tutte collegate al centro dalla navigazione, scrivanie solide")
if "--screenshot" in OS.get_cmdline_user_args():
world.player.set_physics_process(false)
world.player.position = Vector3(0, 0.1, 0)
+5 -2
View File
@@ -8,7 +8,10 @@ func run_test() -> void:
var world = load("res://scenes/main.tscn").instantiate()
root.add_child(world)
var surfaces = get_nodes_in_group("office_surfaces")
assert(surfaces.size() == 17) # sixteen wall segments + ceiling
# Muri: per ogni muro (aperture+1) pezzi pieni, piu' davanzale e architrave per
# ogni apertura; poi il soffitto. Il numero si ricava dalle costanti della stanza.
var per_wall: int = (RoomScript.WINDOWS_PER_WALL + 1) + 2 * RoomScript.WINDOWS_PER_WALL
assert(surfaces.size() == 4 * per_wall + 1) # quattro muri + soffitto
for surface in surfaces:
assert(surface.mesh is ArrayMesh)
var mat = surface.mesh.surface_get_material(0)
@@ -16,7 +19,7 @@ func run_test() -> void:
assert(mat.albedo_texture.resource_path.contains("/office/"))
var arrays = surface.mesh.surface_get_arrays(0)
assert(arrays[Mesh.ARRAY_TEX_UV].size() == arrays[Mesh.ARRAY_VERTEX].size())
print("PASS: 17 superfici con UV esplicite e texture office originali")
print("PASS: %d superfici con UV esplicite e texture office originali (%d per muro)" % [surfaces.size(), per_wall])
var north := Vector3(0, 2.5, -9.15)
var low := Vector3(0, 0.75, -9.15)
var high := Vector3(0, 4.25, -9.15)
+128
View File
@@ -0,0 +1,128 @@
extends SceneTree
## Verifica finestre multiple e spawn casuale da finestre diverse.
##
## Ordine voluto: prima i controlli sui DATI della stanza (immediati e indipendenti
## dal gioco), poi la parte con gli spawn, che richiede di aspettare la prima ondata
## automatica e i suoi tween.
func _initialize() -> void:
call_deferred("run_test")
func run_test() -> void:
var world = load("res://scenes/main.tscn").instantiate()
root.add_child(world)
var room = world.room
var walls := 4
# --- finestre: almeno otto, due per muro, distribuite in modo simmetrico ---
assert(room.WINDOWS_PER_WALL == 2)
var expected: int = room.WINDOWS_PER_WALL * walls
assert(room.windows.size() == expected and expected >= 8)
assert(room.window_spawn_points.size() == expected * room.SPAWN_SLOTS)
assert(room.window_normals.size() == room.window_spawn_points.size())
assert(room.glass_panes.size() == expected) # nessun vetro rotto prima delle ondate
var sides := {}
for window in room.windows:
var center: Vector3 = window.center
var on_x := absf(center.z) > absf(center.x) # muro nord/sud
sides[("x" if on_x else "z")] = int(sides.get("x" if on_x else "z", 0)) + 1
var slots: Array = window.slots
assert(slots.size() == room.SPAWN_SLOTS)
assert(is_instance_valid(window.glass))
for slot in slots:
assert(not (absf(slot.x) <= 9.0 and absf(slot.z) <= 9.0)) # fuori dalla stanza
assert(is_equal_approx(window.normal.dot(slot - center), -room.SPAWN_OUTSIDE))
assert(absf((slot - center).dot(Vector3.UP)) < 0.001)
assert(sides.size() == 2 and sides["x"] == walls and sides["z"] == walls) # 4 finestre per asse
var north_centers: Array[float] = []
for window in room.windows:
if absf(window.center.z + 9.0) < 0.01:
north_centers.append(window.center.x)
north_centers.sort()
# Geometria, non formula: simmetria, margine agli angoli e pilastro fra le aperture.
var limit: float = room.W * 0.5 - room.WIN_W * 0.5
for i in north_centers.size():
assert(is_equal_approx(north_centers[i], -north_centers[north_centers.size() - 1 - i]))
assert(limit - absf(north_centers[i]) >= 1.0) # almeno 1 m di muro all'angolo
var pillar: float = room.W - 2.0 * room.WIN_EDGE_GAP - 2.0 * room.WIN_W
assert(pillar >= 1.0)
assert(is_equal_approx(north_centers[1] - north_centers[0] - room.WIN_W, pillar))
print("PASS: %d finestre, due per muro a %.1f m dal centro, pilastro %.1f m fra le aperture" % [room.windows.size(), north_centers[1], pillar])
# --- le finestre sono aperture vere: si vede il cielo attraverso ---
await physics_frame # lo spazio fisico va aggiornato, altrimenti le query non vedono nulla
await physics_frame
var space = world.get_world_3d().direct_space_state
for window in room.windows:
var center: Vector3 = window.center + Vector3(0, room.WIN_Y0 + room.WIN_H * 0.5, 0) # centro apertura
var query := PhysicsRayQueryParameters3D.create(center, center - Vector3(window.normal) * 3.0, 1)
assert(space.intersect_ray(query).is_empty()) # nessun muro davanti all'apertura
var pillar_ray := PhysicsRayQueryParameters3D.create(Vector3(0, 2.5, -6.0), Vector3(0, 2.5, -9.6), 1) # dall'interno verso il muro
assert(not space.intersect_ray(pillar_ray).is_empty()) # il pilastro fra le due finestre c'e'
print("PASS: le otto aperture sono libere e i pilastri fra le finestre sono solidi")
# --- selezione casuale, mai la stessa finestra due volte di fila ---
var hits := {}
var repeats := 0
world.last_window = -1
for i in 400:
var picked: int = world._pick_window()
assert(picked >= 0 and picked < room.windows.size())
if picked == world.last_window:
repeats += 1
hits[picked] = int(hits.get(picked, 0)) + 1
world.last_window = picked
assert(repeats == 0)
assert(hits.size() == expected) # tutte le finestre vengono usate
var minimum := 400
for count in hits.values():
minimum = mini(minimum, count)
assert(minimum > 10) # non sempre le stesse due
print("PASS: 400 estrazioni su %d finestre, nessuna ripetizione immediata, minimo %d per finestra" % [hits.size(), minimum])
# --- da qui servono gli spawn: prima si aspetta la prima ondata automatica ---
world.player.hp = 100000
world.spawning = true
world.wave_pending = true # nessuna nuova ondata
var guard := 0
while world.spawning and guard < 200:
await create_timer(0.1).timeout
guard += 1
await create_timer(2.4).timeout # fine dei tween d'ingresso dell'ondata
for zombie in get_nodes_in_group("live_zombies"):
zombie.set_physics_process(false)
# --- spawn reale: ogni zombie esce e rientra dalla propria finestra ---
var before: int = room.glass_panes.size()
var intact := {}
for i in room.windows.size():
intact[i] = is_instance_valid(room.windows[i].glass)
var spawned: Array = []
var windows_used := {}
for i in 6:
world._spawn()
spawned.append(world.get_child(world.get_child_count() - 1))
windows_used[world.last_window] = true # _pick_window registra la scelta
assert(windows_used.size() >= 3) # non tutti dalla stessa apertura
var used := {}
for zombie in spawned:
assert(not (absf(zombie.position.x) <= 9.0 and absf(zombie.position.z) <= 9.0))
used[Vector2i(roundi(zombie.position.x * 0.5), roundi(zombie.position.z * 0.5))] = true
assert(used.size() >= 3)
await create_timer(2.6).timeout # tween d'ingresso: 1,2 + 0,8 s
for zombie in spawned:
if is_instance_valid(zombie):
assert(absf(zombie.position.x) <= 9.0 and absf(zombie.position.z) <= 9.0) # dentro
assert(not zombie.entering)
# Identita', non solo conteggio: si rompe il vetro della finestra usata, e una volta
# sola anche se due zombie entrano dalla stessa apertura.
var broken_by_me := 0
for index in windows_used.keys():
assert(intact.has(index))
if intact[index]:
broken_by_me += 1
assert(not is_instance_valid(room.windows[index].glass))
assert(room.glass_panes.size() == before - broken_by_me)
print("PASS: 6 zombie entrati dall'esterno usando %d finestre diverse, %d vetri rotti, tutti dentro" % [windows_used.size(), broken_by_me])
world.queue_free()
await process_frame
quit()
+1
View File
@@ -0,0 +1 @@
uid://bre5y2exwoc1l