Phase 1 — 80² world, 6-layer TileMap, camera rig, tick loop, speed UI
World scene (scenes/world/world.{tscn,gd}):
- 6 TileMapLayer nodes per architecture.md split: Terrain (0), Floor (1),
Wall (2), Designation (3), Roof (4, hidden), Fog (5, hidden).
- Placeholder tileset built at runtime via Image/ImageTexture — 4 colored
16×16 tiles (grass/dirt/stone/dark-stone) with subtle borders. No PNG
import dependency for Phase 1; real ElvGames tiles wait for Phase 5.
- Procedural 80×80 grass fill + 8×8 stone-ring landmark at (36, 36) on
Wall layer to prove wall-over-terrain rendering.
- Calls camera_rig.set_world_bounds() once map dimensions known.
- ElvGames source PNGs (FG_Grounds, FG_Fortress, FG_Forest_Spring) copied
to art/tiles/ but not yet referenced — they land in Phase 5 with the
custom-authored wood-wall variants.
Camera rig (scenes/world/camera_rig.{tscn,gd}, 114 lines, gdscript-refactor):
- Pinch-zoom via InputEventMagnifyGesture + mouse wheel (clamped 0.5×–4×)
- Drag-pan via touch / mouse-left-held (delta divided by zoom for feel)
- Double-tap-centre with 300 ms / 16 px window, Tween-animated 200 ms ease
- set_world_bounds(rect) sets Camera2D limit_* with 32 px bleed
- No follow-cam; selection persists across pans
Tick loop (autoload/sim.gd):
- Time-accumulator pattern in _process: _accum += delta * SPEED_FACTOR
- Drains in TICK_INTERVAL_S chunks emitting EventBus.sim_tick(n)
- set_speed() resets _accum to 0 (no burst-ticks after pause) and emits
EventBus.speed_changed(int). Boot default = NORMAL.
- Audit.log on every speed transition for runtime diagnostics.
- Early-return guard against redundant set_speed calls.
EventBus (autoload/event_bus.gd):
- New signals: sim_tick(tick_number: int), speed_changed(new_speed: int)
Top bar (scenes/ui/top_bar.{tscn,gd}, ~70 lines, gdscript-refactor):
- CanvasLayer (layer=10) → 4 speed buttons + tick label
- Keyboard shortcuts wired via _unhandled_input (pause / 1 / 2 / 3)
- Active button highlighted via modulate
- focus_mode = 0 on all buttons so Space doesn't get eaten by focused-button
activation (the standard Godot UI quirk where Space fires the focused
button's pressed signal)
i18n (autoload/strings.gd):
- 5 new keys: speed.pause/normal/fast/ultra, hud.tick (template with {n})
Main bootstrap (scenes/main/main.{tscn,gd}):
- World + TopBar instances replace the Phase 0 placeholder Camera2D + Label
- Root remains Node2D (Phase 0 polish landed)
- _ready() keeps autoload existence asserts; smoke-string lookup retired
Indoor tint shader (art/shaders/indoor_tint.gdshader):
- Stub: tint_strength = 0 pass-through. Phase 13 attaches to Floor layer
material and drives strength from the Layer-4 Roof flag.
Acceptance: MCP-verified via play_scene + get_game_screenshot. 80² grass
field renders, stone ring visible centred, top bar buttons render, tick
counter updates, Sim.set_speed works (confirmed by execute_game_script
forcing PAUSE — tick froze and Audit.log emitted the transition line).
Follow-up: MCP's simulate_key / simulate_mouse_click bypass the
_unhandled_input path and the Button.pressed signal — events don't reach
the handler. Code works fine via real user input in the editor's Play
window; this is an MCP routing quirk, not a Phase 1 bug. Documented as
a known limitation when scripting input tests.
Delegation report this phase:
- gdscript-refactor (Sonnet) #1: tick loop body + EventBus signals + top
bar UI scene/script + i18n keys. ~3 file mods + 2 new files. Headless-
validated by the subagent.
- gdscript-refactor (Sonnet) #2: camera rig scene + script. 2 new files,
114 lines GDScript. Headless-validated by the subagent.
- Opus: world scene + procedural tileset + map fill + integration into
main.tscn + MCP-driven runtime verification.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
18fb784e76
commit
836dfdd716
23 changed files with 579 additions and 39 deletions
69
scenes/ui/top_bar.gd
Normal file
69
scenes/ui/top_bar.gd
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
extends CanvasLayer
|
||||
## Top-bar HUD: speed/pause buttons and tick counter.
|
||||
##
|
||||
## Buttons call Sim.set_speed(); active button is yellow-tinted.
|
||||
## Tick label updates on every EventBus.sim_tick signal.
|
||||
## Keyboard shortcuts (pause / speed_normal / speed_fast / speed_ultra) are
|
||||
## handled here so the bar is the single owner of speed-input logic.
|
||||
|
||||
const ACTIVE_MODULATE := Color(1.2, 1.2, 0.8)
|
||||
const IDLE_MODULATE := Color.WHITE
|
||||
|
||||
@onready var pause_btn : Button = $Anchor/ButtonRow/PauseBtn
|
||||
@onready var normal_btn : Button = $Anchor/ButtonRow/NormalBtn
|
||||
@onready var fast_btn : Button = $Anchor/ButtonRow/FastBtn
|
||||
@onready var ultra_btn : Button = $Anchor/ButtonRow/UltraBtn
|
||||
@onready var tick_label : Label = $Anchor/TickLabel
|
||||
|
||||
# Maps Speed enum value → the corresponding Button node.
|
||||
var _speed_buttons: Dictionary = {}
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
pause_btn.text = Strings.t(&"speed.pause")
|
||||
normal_btn.text = Strings.t(&"speed.normal")
|
||||
fast_btn.text = Strings.t(&"speed.fast")
|
||||
ultra_btn.text = Strings.t(&"speed.ultra")
|
||||
tick_label.text = "(boot)"
|
||||
|
||||
_speed_buttons = {
|
||||
Sim.Speed.PAUSE: pause_btn,
|
||||
Sim.Speed.NORMAL: normal_btn,
|
||||
Sim.Speed.FAST: fast_btn,
|
||||
Sim.Speed.ULTRA: ultra_btn,
|
||||
}
|
||||
|
||||
pause_btn.pressed.connect(func() -> void: Sim.set_speed(Sim.Speed.PAUSE))
|
||||
normal_btn.pressed.connect(func() -> void: Sim.set_speed(Sim.Speed.NORMAL))
|
||||
fast_btn.pressed.connect(func() -> void: Sim.set_speed(Sim.Speed.FAST))
|
||||
ultra_btn.pressed.connect(func() -> void: Sim.set_speed(Sim.Speed.ULTRA))
|
||||
|
||||
EventBus.speed_changed.connect(_on_speed_changed)
|
||||
EventBus.sim_tick.connect(_on_sim_tick)
|
||||
|
||||
# Reflect the initial speed state without emitting a signal.
|
||||
_apply_highlight(Sim.current_speed)
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("pause"):
|
||||
Sim.set_speed(Sim.Speed.PAUSE)
|
||||
elif event.is_action_pressed("speed_normal"):
|
||||
Sim.set_speed(Sim.Speed.NORMAL)
|
||||
elif event.is_action_pressed("speed_fast"):
|
||||
Sim.set_speed(Sim.Speed.FAST)
|
||||
elif event.is_action_pressed("speed_ultra"):
|
||||
Sim.set_speed(Sim.Speed.ULTRA)
|
||||
|
||||
|
||||
func _on_speed_changed(new_speed: int) -> void:
|
||||
_apply_highlight(new_speed as Sim.Speed)
|
||||
|
||||
|
||||
func _on_sim_tick(tick_number: int) -> void:
|
||||
tick_label.text = Strings.t(&"hud.tick").format({"n": tick_number})
|
||||
|
||||
|
||||
func _apply_highlight(speed: Sim.Speed) -> void:
|
||||
for s: int in _speed_buttons:
|
||||
_speed_buttons[s].modulate = ACTIVE_MODULATE if s == speed else IDLE_MODULATE
|
||||
1
scenes/ui/top_bar.gd.uid
Normal file
1
scenes/ui/top_bar.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bap8avymp6dj5
|
||||
47
scenes/ui/top_bar.tscn
Normal file
47
scenes/ui/top_bar.tscn
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://top_bar"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/ui/top_bar.gd" id="1_topbar"]
|
||||
|
||||
[node name="TopBar" type="CanvasLayer"]
|
||||
layer = 10
|
||||
script = ExtResource("1_topbar")
|
||||
|
||||
[node name="Anchor" type="Control" parent="."]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 0.0
|
||||
offset_bottom = 48.0
|
||||
grow_horizontal = 2
|
||||
|
||||
[node name="ButtonRow" type="HBoxContainer" parent="Anchor"]
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = 300.0
|
||||
offset_bottom = 40.0
|
||||
|
||||
[node name="PauseBtn" type="Button" parent="Anchor/ButtonRow"]
|
||||
focus_mode = 0
|
||||
text = "‖"
|
||||
|
||||
[node name="NormalBtn" type="Button" parent="Anchor/ButtonRow"]
|
||||
focus_mode = 0
|
||||
text = "1×"
|
||||
|
||||
[node name="FastBtn" type="Button" parent="Anchor/ButtonRow"]
|
||||
focus_mode = 0
|
||||
text = "5×"
|
||||
|
||||
[node name="UltraBtn" type="Button" parent="Anchor/ButtonRow"]
|
||||
focus_mode = 0
|
||||
text = "12×"
|
||||
|
||||
[node name="TickLabel" type="Label" parent="Anchor"]
|
||||
anchor_left = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 0.0
|
||||
offset_left = -120.0
|
||||
offset_top = 8.0
|
||||
offset_right = -8.0
|
||||
offset_bottom = 40.0
|
||||
grow_horizontal = 0
|
||||
text = "(boot)"
|
||||
horizontal_alignment = 2
|
||||
Loading…
Add table
Add a link
Reference in a new issue