Add office furniture and dim fire-lit horror atmosphere
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bvxhc5epqpwms"
|
||||||
|
path="res://.godot/imported/flame.png-5c6c1591c52492c60f7d263f90723222.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/effects/flame.png"
|
||||||
|
dest_files=["res://.godot/imported/flame.png-5c6c1591c52492c60f7d263f90723222.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Office horror atmosphere
|
||||||
|
|
||||||
|
`scripts/office_atmosphere.gd` adds two desks, three chairs and a metal fire barrel.
|
||||||
|
Props use low-poly primitives with simple static collisions on world layer 1.
|
||||||
|
Positions avoid the four cardinal entry corridors and the central combat space.
|
||||||
|
The zombies still use their existing direct pursuit, not a new pathfinding system;
|
||||||
|
props can obstruct arbitrary paths when the player hides behind them.
|
||||||
|
|
||||||
|
The central ceiling OmniLight was removed. Ambient energy is 0.12 with a cool blue
|
||||||
|
color, preserving silhouette readability rather than total darkness. Two warm desk
|
||||||
|
lamps use short-range unshadowed OmniLights. The barrel is the only persistent
|
||||||
|
shadow-casting light (range10, energy1.9–2.8), with smooth bounded sine-based flicker.
|
||||||
|
The existing muzzle flash is unchanged: four omni lights total.
|
||||||
|
|
||||||
|
Fire uses 28 CPUParticles3D and small unshaded additive billboard quads. A generated
|
||||||
|
64x96 alpha flame mask is in assets/effects/flame.png. No glow, volumetric fog, GI,
|
||||||
|
GPU particles or Forward+-only effects are required. No fire damage was added.
|
||||||
|
The flame mesh does not cast shadows. The barrel and furniture do.
|
||||||
|
|
||||||
|
References consulted (Perplexity unavailable; official docs via Exa):
|
||||||
|
- https://docs.godotengine.org/en/stable/tutorials/3d/lights_and_shadows.html
|
||||||
|
- https://docs.godotengine.org/en/stable/classes/class_omnilight3d.html
|
||||||
|
- https://docs.godotengine.org/en/stable/classes/class_cpuparticles3d.html
|
||||||
|
|
||||||
|
Compatibility defaults to eight omni lights per mesh. Shadows are expensive and
|
||||||
|
can change light brightness because Compatibility uses a different shadowed-light
|
||||||
|
rendering path; visual checks were therefore done with gl_compatibility.
|
||||||
|
|
||||||
|
Verification (installed Godot4.7.1):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
"$G" --headless --path . --script res://tools/test_office_atmosphere.gd
|
||||||
|
"$G" --display-driver x11 --rendering-method gl_compatibility --path . \
|
||||||
|
--script res://tools/test_office_atmosphere.gd -- --screenshot
|
||||||
|
```
|
||||||
|
|
||||||
|
Checks: prop counts/collisions, bounded flicker, CPU emitter, light count, absent
|
||||||
|
ceiling light, low ambient, free cardinal ray corridors and desk collision.
|
||||||
|
Three screenshots `/tmp/zombie-office-{0,1,2}.png` inspected, including the barrel.
|
||||||
|
All seven prior suites pass: health/sky, music, grenade, waves, death, damage, touch.
|
||||||
|
Existing resource-in-use warnings at test shutdown remain. Actual smartphone
|
||||||
|
performance has not been measured; this is a small light/particle budget, not an
|
||||||
|
FPS guarantee. Keep testing arbitrary zombie routes around the new obstacles.
|
||||||
@@ -1,5 +1,13 @@
|
|||||||
# Web build and compressed deployment
|
# Web build and compressed deployment
|
||||||
|
|
||||||
|
## Office atmosphere release: fd65770c62e811fe
|
||||||
|
|
||||||
|
See `docs/OFFICE-ATMOSPHERE.md`: two desks, three chairs, two desk lamps and
|
||||||
|
CPU-particle fire barrel; ceiling light removed, low cool ambient.
|
||||||
|
PCK 3,946,516 bytes; gzip payload total 12,402,612 bytes. Previous releases retained.
|
||||||
|
Root backup: `index-before-office.html`. Serial import workaround applied to staging.
|
||||||
|
|
||||||
|
|
||||||
## Health + starry sky release: 31564f79b9f5e299
|
## Health + starry sky release: 31564f79b9f5e299
|
||||||
|
|
||||||
See `docs/HEALTH-SKY.md`: +1 HP/second while alive, capped at 100; Half-Life
|
See `docs/HEALTH-SKY.md`: +1 HP/second while alive, capped at 100; Half-Life
|
||||||
|
|||||||
+5
-8
@@ -39,16 +39,13 @@ func _ready() -> void:
|
|||||||
# Solo sfondo: illuminazione e riflessi non dipendono dal cielo scuro.
|
# Solo sfondo: illuminazione e riflessi non dipendono dal cielo scuro.
|
||||||
env.reflected_light_source = Environment.REFLECTION_SOURCE_DISABLED
|
env.reflected_light_source = Environment.REFLECTION_SOURCE_DISABLED
|
||||||
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||||
env.ambient_light_color = Color(0.65, 0.73, 0.85)
|
env.ambient_light_color = Color(0.28, 0.36, 0.48)
|
||||||
env.ambient_light_energy = 0.65
|
env.ambient_light_energy = 0.12
|
||||||
envnode.environment = env
|
envnode.environment = env
|
||||||
add_child(envnode)
|
add_child(envnode)
|
||||||
var light := OmniLight3D.new()
|
var office := Node3D.new()
|
||||||
light.position = Vector3(0, 4.5, 0)
|
office.set_script(preload("res://scripts/office_atmosphere.gd"))
|
||||||
light.omni_range = 24
|
add_child(office)
|
||||||
light.light_energy = 3
|
|
||||||
light.shadow_enabled = true
|
|
||||||
add_child(light)
|
|
||||||
var canvas := CanvasLayer.new()
|
var canvas := CanvasLayer.new()
|
||||||
add_child(canvas)
|
add_child(canvas)
|
||||||
hud = Label.new()
|
hud = Label.new()
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
extends Node3D
|
||||||
|
## Sparse office props and bounded fire animation; Web/Compatibility safe.
|
||||||
|
const FIRE_MIN := 1.9
|
||||||
|
const FIRE_MAX := 2.8
|
||||||
|
var fire_light: OmniLight3D
|
||||||
|
var _time := 0.0
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
name = "OfficeAtmosphere"
|
||||||
|
_desk(Vector3(-5, 0, -4))
|
||||||
|
_desk(Vector3(5, 0, 4))
|
||||||
|
_chair(Vector3(-5, 0, -2.6), 0)
|
||||||
|
_chair(Vector3(5, 0, 5.4), PI)
|
||||||
|
_chair(Vector3(6.8, 0, -4), -0.35)
|
||||||
|
_barrel(Vector3(-4, 0, 4))
|
||||||
|
|
||||||
|
func _material(color: Color, metallic: float = 0.0) -> StandardMaterial3D:
|
||||||
|
var mat := StandardMaterial3D.new()
|
||||||
|
mat.albedo_color = color
|
||||||
|
mat.roughness = 0.85
|
||||||
|
mat.metallic = metallic
|
||||||
|
return mat
|
||||||
|
|
||||||
|
func _box(parent: Node3D, pos: Vector3, size: Vector3, mat: Material) -> MeshInstance3D:
|
||||||
|
var mesh := BoxMesh.new()
|
||||||
|
mesh.size = size
|
||||||
|
mesh.material = mat
|
||||||
|
var node := MeshInstance3D.new()
|
||||||
|
node.mesh = mesh
|
||||||
|
node.position = pos
|
||||||
|
parent.add_child(node)
|
||||||
|
return node
|
||||||
|
|
||||||
|
func _body(pos: Vector3, label: String) -> StaticBody3D:
|
||||||
|
var body := StaticBody3D.new()
|
||||||
|
body.name = label
|
||||||
|
body.position = pos
|
||||||
|
body.collision_layer = 1
|
||||||
|
body.collision_mask = 0
|
||||||
|
body.add_to_group("office_props")
|
||||||
|
add_child(body, true)
|
||||||
|
return body
|
||||||
|
|
||||||
|
func _collision(body: StaticBody3D, center: Vector3, size: Vector3) -> void:
|
||||||
|
var shape := BoxShape3D.new()
|
||||||
|
shape.size = size
|
||||||
|
var collider := CollisionShape3D.new()
|
||||||
|
collider.shape = shape
|
||||||
|
collider.position = center
|
||||||
|
body.add_child(collider)
|
||||||
|
|
||||||
|
func _desk(pos: Vector3) -> void:
|
||||||
|
var desk := _body(pos, "OfficeDesk")
|
||||||
|
var wood := _material(Color(0.19, 0.12, 0.07))
|
||||||
|
var metal := _material(Color(0.12, 0.15, 0.14), 0.5)
|
||||||
|
_box(desk, Vector3(0, 0.84, 0), Vector3(2.5, 0.12, 1.15), wood)
|
||||||
|
for x in [-1.05, 1.05]:
|
||||||
|
for z in [-0.43, 0.43]:
|
||||||
|
_box(desk, Vector3(x, 0.4, z), Vector3(0.08, 0.8, 0.08), metal)
|
||||||
|
_box(desk, Vector3(-0.82, 0.53, 0), Vector3(0.48, 0.5, 0.94), metal)
|
||||||
|
for y in [0.4, 0.62]:
|
||||||
|
_box(desk, Vector3(-0.82, y, 0.49), Vector3(0.23, 0.025, 0.035), wood)
|
||||||
|
_box(desk, Vector3(0.3, 0.91, 0.05), Vector3(0.48, 0.015, 0.35), _material(Color(0.45, 0.43, 0.32)))
|
||||||
|
_collision(desk, Vector3(0, 0.45, 0), Vector3(2.5, 0.9, 1.15))
|
||||||
|
_lamp(desk, Vector3(0.84, 0.91, -0.23))
|
||||||
|
|
||||||
|
func _chair(pos: Vector3, yaw: float) -> void:
|
||||||
|
var chair := _body(pos, "OfficeChair")
|
||||||
|
chair.rotation.y = yaw
|
||||||
|
var metal := _material(Color(0.12, 0.12, 0.13), 0.6)
|
||||||
|
var fabric := _material(Color(0.1, 0.14, 0.12))
|
||||||
|
_box(chair, Vector3(0, 0.48, 0), Vector3(0.58, 0.12, 0.56), fabric)
|
||||||
|
_box(chair, Vector3(0, 0.84, 0.25), Vector3(0.58, 0.64, 0.09), fabric)
|
||||||
|
for x in [-0.23, 0.23]:
|
||||||
|
for z in [-0.22, 0.22]:
|
||||||
|
_box(chair, Vector3(x, 0.21, z), Vector3(0.045, 0.42, 0.045), metal)
|
||||||
|
_collision(chair, Vector3(0, 0.53, 0), Vector3(0.6, 1.06, 0.6))
|
||||||
|
|
||||||
|
func _cylinder(parent: Node3D, pos: Vector3, radius: float, height: float, mat: Material) -> void:
|
||||||
|
var mesh := CylinderMesh.new()
|
||||||
|
mesh.top_radius = radius
|
||||||
|
mesh.bottom_radius = radius
|
||||||
|
mesh.height = height
|
||||||
|
mesh.radial_segments = 24
|
||||||
|
mesh.material = mat
|
||||||
|
var node := MeshInstance3D.new()
|
||||||
|
node.mesh = mesh
|
||||||
|
node.position = pos
|
||||||
|
parent.add_child(node)
|
||||||
|
|
||||||
|
func _lamp(parent: Node3D, pos: Vector3) -> void:
|
||||||
|
var lamp := Node3D.new()
|
||||||
|
lamp.name = "DeskLamp"
|
||||||
|
lamp.position = pos
|
||||||
|
parent.add_child(lamp)
|
||||||
|
var metal := _material(Color(0.13, 0.19, 0.15), 0.5)
|
||||||
|
_cylinder(lamp, Vector3(0, 0.035, 0), 0.16, 0.07, metal)
|
||||||
|
_box(lamp, Vector3(0, 0.24, 0), Vector3(0.04, 0.4, 0.04), metal)
|
||||||
|
_box(lamp, Vector3(0, 0.44, 0), Vector3(0.4, 0.12, 0.24), metal)
|
||||||
|
var bulb := _material(Color(0.8, 0.63, 0.3))
|
||||||
|
bulb.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||||
|
_box(lamp, Vector3(0, 0.375, 0), Vector3(0.32, 0.012, 0.18), bulb)
|
||||||
|
var light := OmniLight3D.new()
|
||||||
|
light.name = "DeskLampLight"
|
||||||
|
light.position = Vector3(0, 0.31, 0)
|
||||||
|
light.light_color = Color(1.0, 0.7, 0.35)
|
||||||
|
light.light_energy = 1.2
|
||||||
|
light.omni_range = 5.5
|
||||||
|
light.shadow_enabled = false
|
||||||
|
lamp.add_child(light)
|
||||||
|
|
||||||
|
func _barrel(pos: Vector3) -> void:
|
||||||
|
var barrel := _body(pos, "FireBarrel")
|
||||||
|
var metal := _material(Color(0.15, 0.1, 0.075), 0.65)
|
||||||
|
_cylinder(barrel, Vector3(0, 0.5, 0), 0.43, 1.0, metal)
|
||||||
|
for y in [0.12, 0.78, 1.01]:
|
||||||
|
_cylinder(barrel, Vector3(0, y, 0), 0.46, 0.055, metal)
|
||||||
|
_cylinder(barrel, Vector3(0, 1.045, 0), 0.39, 0.015, _material(Color(0.025, 0.012, 0.005)))
|
||||||
|
_collision(barrel, Vector3(0, 0.52, 0), Vector3(0.92, 1.04, 0.92))
|
||||||
|
var fire := CPUParticles3D.new()
|
||||||
|
fire.name = "BarrelFlames"
|
||||||
|
fire.position = Vector3(0, 1.08, 0)
|
||||||
|
fire.amount = 28
|
||||||
|
fire.lifetime = 0.85
|
||||||
|
fire.preprocess = 1.0
|
||||||
|
fire.emission_shape = CPUParticles3D.EMISSION_SHAPE_SPHERE
|
||||||
|
fire.emission_sphere_radius = 0.23
|
||||||
|
fire.direction = Vector3.UP
|
||||||
|
fire.spread = 12
|
||||||
|
fire.gravity = Vector3(0, 0.6, 0)
|
||||||
|
fire.initial_velocity_min = 0.5
|
||||||
|
fire.initial_velocity_max = 1.15
|
||||||
|
fire.scale_amount_min = 0.5
|
||||||
|
fire.scale_amount_max = 1.0
|
||||||
|
var gradient := Gradient.new()
|
||||||
|
gradient.set_color(0, Color(1, 0.65, 0.12, 0.8))
|
||||||
|
gradient.set_color(1, Color(0.6, 0.04, 0.005, 0))
|
||||||
|
fire.color_ramp = gradient
|
||||||
|
var quad := QuadMesh.new()
|
||||||
|
quad.size = Vector2(0.48, 0.8)
|
||||||
|
var flame := StandardMaterial3D.new()
|
||||||
|
flame.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||||
|
flame.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||||
|
flame.blend_mode = BaseMaterial3D.BLEND_MODE_ADD
|
||||||
|
flame.billboard_mode = BaseMaterial3D.BILLBOARD_PARTICLES
|
||||||
|
flame.albedo_texture = load("res://assets/effects/flame.png")
|
||||||
|
flame.vertex_color_use_as_albedo = true
|
||||||
|
flame.cull_mode = BaseMaterial3D.CULL_DISABLED
|
||||||
|
quad.material = flame
|
||||||
|
fire.mesh = quad
|
||||||
|
fire.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||||
|
barrel.add_child(fire)
|
||||||
|
fire_light = OmniLight3D.new()
|
||||||
|
fire_light.name = "BarrelFireLight"
|
||||||
|
fire_light.position = Vector3(0, 1.5, 0)
|
||||||
|
fire_light.light_color = Color(1, 0.36, 0.07)
|
||||||
|
fire_light.light_energy = (FIRE_MIN + FIRE_MAX) * 0.5
|
||||||
|
fire_light.omni_range = 10
|
||||||
|
fire_light.shadow_enabled = true
|
||||||
|
barrel.add_child(fire_light)
|
||||||
|
|
||||||
|
func _process(dt: float) -> void:
|
||||||
|
_time += dt
|
||||||
|
# Smooth, deterministic, bounded flicker: no per-frame random flashes.
|
||||||
|
var wave := 0.5 + 0.3 * sin(_time * 7.1) + 0.2 * sin(_time * 11.7 + 1.2)
|
||||||
|
fire_light.light_energy = lerpf(FIRE_MIN, FIRE_MAX, wave)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cx02g4bsbwanh
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dvv3ey14k862x
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
call_deferred("run_test")
|
||||||
|
|
||||||
|
func run_test() -> void:
|
||||||
|
var world = load("res://scenes/main.tscn").instantiate()
|
||||||
|
root.add_child(world)
|
||||||
|
var office = world.get_node("OfficeAtmosphere")
|
||||||
|
assert(get_nodes_in_group("office_props").size() == 6)
|
||||||
|
var desks := 0
|
||||||
|
var chairs := 0
|
||||||
|
for prop in get_nodes_in_group("office_props"):
|
||||||
|
if prop.name.begins_with("OfficeDesk"):
|
||||||
|
desks += 1
|
||||||
|
if prop.name.begins_with("OfficeChair"):
|
||||||
|
chairs += 1
|
||||||
|
assert(prop.collision_layer == 1)
|
||||||
|
assert(desks == 2 and chairs == 3)
|
||||||
|
print("PASS: due scrivanie, tre sedie, bidone con collisioni")
|
||||||
|
var flames = office.get_node("FireBarrel/BarrelFlames")
|
||||||
|
assert(flames is CPUParticles3D and flames.amount == 28 and flames.emitting)
|
||||||
|
assert(office.fire_light.shadow_enabled)
|
||||||
|
for i in 100:
|
||||||
|
office._process(0.016)
|
||||||
|
assert(office.fire_light.light_energy >= office.FIRE_MIN and office.fire_light.light_energy <= office.FIRE_MAX)
|
||||||
|
print("PASS: fiamme CPU leggere e flicker continuo entro limiti")
|
||||||
|
var lights: Array[Node] = world.find_children("*", "OmniLight3D", true, false)
|
||||||
|
assert(lights.size() == 4) # due lampade, bidone, muzzle flash
|
||||||
|
assert(world.find_children("DeskLampLight", "OmniLight3D", true, false).size() == 2)
|
||||||
|
for node in world.get_children():
|
||||||
|
assert(not node is OmniLight3D) # nessuna plafoniera
|
||||||
|
if node is WorldEnvironment:
|
||||||
|
assert(node.environment.ambient_light_energy <= 0.15)
|
||||||
|
print("PASS: plafoniera rimossa, ambient basso e quattro luci totali")
|
||||||
|
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)]:
|
||||||
|
var q := PhysicsRayQueryParameters3D.create(edge, Vector3(0, 0.8, 0), 1)
|
||||||
|
q.exclude = [world.player.get_rid()]
|
||||||
|
assert(space.intersect_ray(q).is_empty())
|
||||||
|
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")
|
||||||
|
if "--screenshot" in OS.get_cmdline_user_args():
|
||||||
|
world.player.set_physics_process(false)
|
||||||
|
world.player.position = Vector3(0, 0.1, 0)
|
||||||
|
world.player.camera.rotation.x = -0.08
|
||||||
|
for i in 3:
|
||||||
|
if i == 2:
|
||||||
|
world.player.look_at(Vector3(-4, 0.1, 4))
|
||||||
|
else:
|
||||||
|
world.player.rotation.y = -PI * 0.25 if i == 0 else PI * 0.25 + PI
|
||||||
|
await create_timer(0.4).timeout
|
||||||
|
await RenderingServer.frame_post_draw
|
||||||
|
root.get_texture().get_image().save_png("/tmp/zombie-office-%d.png" % i)
|
||||||
|
world.queue_free()
|
||||||
|
await process_frame
|
||||||
|
quit()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dbyackx6d67ns
|
||||||
Reference in New Issue
Block a user