Visual pass 2: tree + rock + stone wall sprite swaps

Replaces three procedural _draw() entities with bundle sprites:

- Tree: was draw_rect trunk + draw_circle canopy. Now Sprite2D using
  FG_Tree_Spring.png (64x80, 4 variants picked deterministically from
  tile coord). Bottom-anchored so trunk base sits at tile bottom, canopy
  rises into the cell above; y_sort_enabled so canopies tuck behind
  pawns south of the trunk. Chop-progress notch overlay retained.
- Rock: was draw_colored_polygon hex. Now Sprite2D reading from the
  existing FG_Grasslands_Spring.png decoration atlas at three eyeballed
  coords (2 gray boulders, 1 brown rock pile). Variant deterministic
  per tile. Mine-progress crack overlay retained.
- Stone wall: was procedural top-band + front-band + mortar lines. Now
  Sprite2D from FG_Fortress.png at (1,1) — clean tan-stone brick fill.
  Bottom-anchored (offset.y=-8) so the 16x16 sprite spans y=-16..0,
  matching the procedural draw box exactly. Ghost state via modulate.a.
  Wood walls still use procedural _draw_wood_wall — no clean 16x16 wood
  tile found in the bundle yet (Pixel Crawler Walls.png is 32x32, would
  need crop+rescale).

Asset additions:
- art/sprites/FG_Tree_Spring.png (Tier 1, Grasslands pack)
- FG_Fortress.png and FG_Grasslands_Spring.png were already in art/tiles
  from earlier passes; this commit just consumes them from new sites.

Headless boots clean, runtime verified: trees look like chunky pixel-art
trees with root flare, rocks read as real boulders, cabin walls show
proper brick texture.

License: all ElvGames Humble bundle — commercial OK with credit.
Credit-string compilation still open.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-05-12 14:19:06 +01:00
parent 314c7a70b4
commit 2de5130ae0
5 changed files with 145 additions and 51 deletions

View file

@ -23,6 +23,13 @@ const TILE_SIZE_PX: int = 16
## Sim ticks to complete construction at 1× speed (100 ticks = 5 sim seconds).
const BUILD_TICKS: int = 100
## ElvGames Fortress tileset — coord (1, 1) is a plain tan-stone fill tile.
## Eyeballed from /tmp/walls/probe.png in the 2026-05-12 visual pass.
## We use a single sprite per material (Phase 5 lock: no autotile yet).
const _STONE_TEX: Texture2D = preload("res://art/tiles/FG_Fortress.png")
const _STONE_FILL_COORD: Vector2i = Vector2i(1, 1)
## Supported materials. Phase 5 uses MATERIAL_STONE; MATERIAL_WOOD is reserved
## for the Phase 6+ art-authoring pass.
const MATERIAL_STONE: StringName = &"stone"
@ -61,10 +68,35 @@ func setup(p_tile: Vector2i, p_material: StringName) -> void:
tile.x * TILE_SIZE_PX + TILE_SIZE_PX / 2.0,
tile.y * TILE_SIZE_PX + TILE_SIZE_PX
)
# Stone uses a sprite from FG_Fortress; wood still draws procedurally below
# until we find a 16×16 wood-wall tile that fits the perspective.
if wall_material == MATERIAL_STONE:
_build_stone_sprite()
queue_redraw()
Audit.log("wall", "%s wall ghost placed at %s" % [wall_material, tile])
## Builds the stone-fill Sprite2D child. Bottom-anchored so it sits flush with
## the tile's bottom edge (matching the procedural draw box y=-16..0).
func _build_stone_sprite() -> void:
var sprite := Sprite2D.new()
sprite.name = "Sprite"
sprite.texture = _STONE_TEX
sprite.region_enabled = true
sprite.region_rect = Rect2(
_STONE_FILL_COORD.x * TILE_SIZE_PX,
_STONE_FILL_COORD.y * TILE_SIZE_PX,
TILE_SIZE_PX,
TILE_SIZE_PX,
)
sprite.centered = true
# Sprite center at y=-8 so 16×16 sprite spans y=-16..0 (matches procedural).
sprite.offset = Vector2(0, -8)
# Ghost state — translucent until built.
sprite.modulate.a = 1.0 if _completed else 0.4
add_child(sprite)
## True while the wall still needs construction work.
## JobRunner's _tick_build checks this to decide when the toil is done.
func is_buildable() -> bool:
@ -126,18 +158,11 @@ static func from_dict(d: Dictionary) -> Dictionary:
# ── render ─────────────────────────────────────────────────────────────────────
func _draw() -> void:
# 3/4-perspective wall rendering — fits WITHIN the wall's own tile so it
# never encroaches on adjacent floor/interior tiles. Two-band look:
# Top band (lit) = the wall's "top surface" (looking down at it)
# Bottom band (dark) = the wall's "front face" (looking at the side)
#
# Origin (0, 0) is at the tile's bottom-centre. Tile spans local Y: -16 to 0.
# We draw entirely within that 16×16 box.
# Stone walls render via the Sprite2D child (see _build_stone_sprite).
# Wood walls still draw procedurally until a wood-wall sprite lands.
var alpha: float = 1.0 if _completed else 0.4
if wall_material == MATERIAL_STONE:
_draw_stone_wall(alpha)
else:
if wall_material == MATERIAL_WOOD:
_draw_wood_wall(alpha)
@ -187,5 +212,11 @@ func _complete() -> void:
# Stamp the data-layer TileMap so room / roof / save logic sees the wall.
World.mark_wall_tile(tile, wall_material)
# Solidify the ghost: sprite (if any) → full opacity; wood _draw rereads alpha.
var sprite: Sprite2D = get_node_or_null("Sprite")
if sprite != null:
sprite.modulate.a = 1.0
queue_redraw()
queue_redraw()
Audit.log("wall", "%s wall completed at %s" % [wall_material, tile])