- Phase-based lighting: sun direction/energy/color, ProceduralSkyMaterial gradient, and ambient per shift phase (GameManager.PHASE_LIGHTING), applied on phase transitions and shift start - WorldBuilder: street lamp poles with emissive bulbs + OmniLights, set_night_mode(), glowing STEP LINE marquee (Label3D + emissive bars), crosswalk stripes, planters, low-poly street trees - NPC avatars: per-seed height/brightness variation (deterministic LCG) - Denser routine traffic probabilities - capture_screenshots.gd: fast-forward to LATE phase and capture night set; README gallery is now 2x3 day+night; docs/screenshots regenerated - Version bumped to 0.2.0; build/ artifacts rebuilt; Linux binary smoke-tested - New headless tests for phased lighting config and street dressing (56 total)
103 lines
2.8 KiB
GDScript
103 lines
2.8 KiB
GDScript
extends SceneTree
|
|
## Dev helper: run the game windowed and capture staged screenshots for the
|
|
## README (day set + LATE-night set with street lamps and flashlight).
|
|
## Saves 1280x720 PNGs to user://screenshots/.
|
|
## Usage: godot --path . --script res://src/test/capture_screenshots.gd
|
|
|
|
const OUT_DIR := "user://screenshots"
|
|
const MAX_FRAMES := 3600 # hard cap (~60s)
|
|
|
|
var frames := 0
|
|
var gm: Node = null
|
|
var state := 0
|
|
var state_frames := 0
|
|
var targeted := false
|
|
|
|
func _initialize() -> void:
|
|
root.size = Vector2i(1600, 900)
|
|
var packed: PackedScene = load("res://scenes/main.tscn")
|
|
var inst: Node = packed.instantiate()
|
|
root.add_child(inst)
|
|
|
|
func _process(_delta: float) -> bool:
|
|
frames += 1
|
|
# The main scene's _ready may not have run when _initialize added it;
|
|
# look for GameManager lazily each frame until it exists.
|
|
if gm == null:
|
|
gm = root.find_child("GameManager", true, false)
|
|
if gm == null and frames > 120:
|
|
push_error("GameManager not found")
|
|
quit(1)
|
|
return true
|
|
if frames > MAX_FRAMES:
|
|
print("timeout reached")
|
|
quit(0)
|
|
return true
|
|
_advance()
|
|
return false
|
|
|
|
func _advance() -> void:
|
|
state_frames += 1
|
|
match state:
|
|
0:
|
|
# 2s overview from the guard post (early morning light).
|
|
if state_frames >= 120:
|
|
_shoot("01-venue-overview.png")
|
|
state = 1
|
|
state_frames = 0
|
|
1:
|
|
# Wait for a nearby NPC so the interaction HUD has a target.
|
|
if gm != null and gm.npcs.size() > 0:
|
|
gm.call("_cycle_nearby_target")
|
|
targeted = true
|
|
state = 2
|
|
state_frames = 0
|
|
2:
|
|
if state_frames >= 30:
|
|
_shoot("02-targeting-npc.png")
|
|
if targeted and gm != null:
|
|
gm.call("_toggle_inspection")
|
|
state = 3
|
|
state_frames = 0
|
|
3:
|
|
if state_frames >= 30:
|
|
_shoot("03-access-inspection.png")
|
|
if gm != null:
|
|
gm.hud.hide_inspection()
|
|
if gm.npcs.size() > 0:
|
|
gm.call("_cycle_nearby_target")
|
|
gm.call("_open_radio", false)
|
|
state = 4
|
|
state_frames = 0
|
|
4:
|
|
if state_frames >= 30:
|
|
_shoot("04-radio-report.png")
|
|
# Close the radio form, then jump the shift to the LATE phase.
|
|
if gm != null:
|
|
gm.radio_panel.hide()
|
|
gm.coordinator.elapsed_s = 750.0
|
|
state = 5
|
|
state_frames = 0
|
|
5:
|
|
# Let the phase transition (and street lamps / marquee) settle, then
|
|
# take the night avenue shot.
|
|
if state_frames >= 90:
|
|
_shoot("05-late-night-avenue.png")
|
|
if gm != null:
|
|
gm.call("_toggle_flashlight")
|
|
state = 6
|
|
state_frames = 0
|
|
6:
|
|
if state_frames >= 40:
|
|
_shoot("06-night-flashlight.png")
|
|
state = 7
|
|
quit(0)
|
|
|
|
func _shoot(file_name: String) -> void:
|
|
var dir := DirAccess.open("user://")
|
|
if dir == null or not dir.dir_exists("screenshots"):
|
|
dir.make_dir_recursive("screenshots")
|
|
var img: Image = root.get_texture().get_image()
|
|
var err := img.save_png(OUT_DIR.path_join(file_name))
|
|
print("%s -> err=%d (%dx%d)" % [file_name, err, img.get_width(), img.get_height()])
|