diff --git a/docs/review/2026-04-07-architecture-review.md b/docs/review/2026-04-07-architecture-review.md index 3d061f1..62a2726 100644 --- a/docs/review/2026-04-07-architecture-review.md +++ b/docs/review/2026-04-07-architecture-review.md @@ -129,7 +129,9 @@ Both systems independently iterate `get_nodes_in_group("units")`, hook `node_add --- -### J. Sprite spam in `FogRenderer` and `WallRenderer` +### J. Sprite spam in `FogRenderer` and `WallRenderer` — **Resolved 2026-04-07** + +Both renderers now override `_draw()` and use `draw_texture_rect_region()` directly. Zero child sprites; layout changes call `queue_redraw()` instead of thrashing the scene tree. Findings **O** (redundant `match` arms) and **P** (undocumented `FOG_RECT`) were folded into the same change. **Files:** `nodes/fog_renderer.gd:13-43`, `nodes/wall_renderer.gd:44-176` @@ -254,7 +256,7 @@ Findings 2 and 9 from the prior review are the largest gameplay gap. Until that | G | `remove_unit` leaks signal connections | Medium | Small | | H | Dead `process_combat` | Medium | Trivial | | I | O(n) unit lookup | Medium | Small | -| J | Sprite-per-tile renderers | Medium | Medium | +| J | Sprite-per-tile renderers | ~~Medium~~ | **Done** | | K | `MapLayout.initialize` not guarded | Medium | Small | | L | Highlight updates while modal open | Medium | Trivial | | M | Inline scripts in `unit.tscn` | Medium | Small | diff --git a/nodes/fog_renderer.gd b/nodes/fog_renderer.gd index c69be5d..213698b 100644 --- a/nodes/fog_renderer.gd +++ b/nodes/fog_renderer.gd @@ -5,39 +5,35 @@ extends Node2D ## that is not part of any room. Future: drive visibility from map state. const TILE_SIZE := 100.0 +## Fog tile region in aux_terrain.BMP const FOG_RECT := Rect2(53, 53, 100, 100) @export var atlas_texture: Texture2D +var _fog_tiles: PackedVector2Array = [] + func draw_fog_for_layout(map_layout: MapLayout) -> void: - _clear() + _fog_tiles.clear() if not map_layout or not atlas_texture: + queue_redraw() return for y in map_layout.size.y: for x in map_layout.size.x: var tile := Vector2i(x, y) if map_layout.is_tile_valid(tile): continue - _create_fog_sprite(Vector2(tile) * TILE_SIZE) + _fog_tiles.append(Vector2(tile) * TILE_SIZE) + queue_redraw() -func _clear() -> void: - for child in get_children(): - child.queue_free() - - -func _create_fog_sprite(pos: Vector2) -> void: - var atlas := AtlasTexture.new() - atlas.atlas = atlas_texture - atlas.region = FOG_RECT - - var sprite := Sprite2D.new() - sprite.texture = atlas - sprite.centered = false - sprite.position = pos - sprite.scale = Vector2( - TILE_SIZE / FOG_RECT.size.x, - TILE_SIZE / FOG_RECT.size.y, - ) - add_child(sprite) +func _draw() -> void: + if not atlas_texture: + return + var dest_size := Vector2(TILE_SIZE, TILE_SIZE) + for pos in _fog_tiles: + draw_texture_rect_region( + atlas_texture, + Rect2(pos, dest_size), + FOG_RECT, + ) diff --git a/nodes/wall_renderer.gd b/nodes/wall_renderer.gd index 6fdb6e3..03f860a 100644 --- a/nodes/wall_renderer.gd +++ b/nodes/wall_renderer.gd @@ -40,21 +40,28 @@ const HALF_EDGE := TILE_SIZE / 2.0 @export var atlas_texture: Texture2D +# Each entry is [dest_rect: Rect2, source_rect: Rect2] +var _segments: Array = [] + func draw_walls_for_layout(map_layout: MapLayout) -> void: - _clear_walls() + _segments.clear() if not map_layout or not atlas_texture: + queue_redraw() return var tile_edges := _collect_tile_edges(map_layout) for tile in tile_edges: var edges: Array = tile_edges[tile] - _draw_tile_walls(tile, edges) + _build_tile_walls(tile, edges) + queue_redraw() -func _clear_walls() -> void: - for child in get_children(): - child.queue_free() +func _draw() -> void: + if not atlas_texture: + return + for seg in _segments: + draw_texture_rect_region(atlas_texture, seg[0], seg[1]) func _collect_tile_edges(map_layout: MapLayout) -> Dictionary: @@ -97,28 +104,20 @@ func _direction_to_edge(dir: Vector2i) -> StringName: return &"" -func _draw_tile_walls(tile: Vector2i, edges: Array) -> void: +func _build_tile_walls(tile: Vector2i, edges: Array) -> void: var tile_origin := Vector2(tile) * TILE_SIZE for edge in edges: - match edge: - &"left": - _draw_edge_segments(tile_origin, edge) - &"right": - _draw_edge_segments(tile_origin, edge) - &"top": - _draw_edge_segments(tile_origin, edge) - &"bottom": - _draw_edge_segments(tile_origin, edge) + _build_edge_segments(tile_origin, edge) # TODO: Outer corner segments - _draw_outer_corners(tile, tile_origin, edges) + _build_outer_corners(tile, tile_origin, edges) - # TODO: Inner corner segments (for non-rectangular room support) - _draw_inner_corners(tile, tile_origin, edges) + # Inner corner segments (for non-rectangular room support) + _build_inner_corners(tile, tile_origin, edges) -func _draw_edge_segments(tile_origin: Vector2, edge: StringName) -> void: +func _build_edge_segments(tile_origin: Vector2, edge: StringName) -> void: var seg_a_rect: Rect2 var seg_b_rect: Rect2 var seg_a_offset: Vector2 @@ -156,34 +155,22 @@ func _draw_edge_segments(tile_origin: Vector2, edge: StringName) -> void: seg_a_offset = Vector2(0, TILE_SIZE - WALL_THICKNESS) seg_b_offset = Vector2(HALF_EDGE, TILE_SIZE - WALL_THICKNESS) - _create_segment_sprite(tile_origin + seg_a_offset, seg_a_size, seg_a_rect) - _create_segment_sprite(tile_origin + seg_b_offset, seg_b_size, seg_b_rect) + _queue_segment(tile_origin + seg_a_offset, seg_a_size, seg_a_rect) + _queue_segment(tile_origin + seg_b_offset, seg_b_size, seg_b_rect) -func _create_segment_sprite(pos: Vector2, target_size: Vector2, source_rect: Rect2) -> void: - var atlas := AtlasTexture.new() - atlas.atlas = atlas_texture - atlas.region = source_rect - - var sprite := Sprite2D.new() - sprite.texture = atlas - sprite.centered = false - sprite.position = pos - sprite.scale = Vector2( - target_size.x / source_rect.size.x, - target_size.y / source_rect.size.y - ) - add_child(sprite) +func _queue_segment(pos: Vector2, target_size: Vector2, source_rect: Rect2) -> void: + _segments.append([Rect2(pos, target_size), source_rect]) -func _draw_outer_corners(_tile: Vector2i, _tile_origin: Vector2, _edges: Array) -> void: +func _build_outer_corners(_tile: Vector2i, _tile_origin: Vector2, _edges: Array) -> void: # TODO: Implement outer corner segments # Check pairs of adjacent edges (e.g., "top" + "left" → top-left outer corner) # and draw the corner piece from the atlas. pass -func _draw_inner_corners(_tile: Vector2i, tile_origin: Vector2, edges: Array) -> void: +func _build_inner_corners(_tile: Vector2i, tile_origin: Vector2, edges: Array) -> void: ## Draws decorative corner pieces wherever two perpendicular wall edges meet ## on the same tile (e.g., a top wall + left wall produces an upper-left corner). var has_top := edges.has(&"top") @@ -193,25 +180,25 @@ func _draw_inner_corners(_tile: Vector2i, tile_origin: Vector2, edges: Array) -> var corner_size := Vector2(CORNER_SIZE, CORNER_SIZE) if has_top and has_left: - _create_segment_sprite( + _queue_segment( tile_origin + Vector2(0, 0), corner_size, INNER_CORNER_UPPER_LEFT_RECT ) if has_top and has_right: - _create_segment_sprite( + _queue_segment( tile_origin + Vector2(TILE_SIZE - CORNER_SIZE, 0), corner_size, INNER_CORNER_UPPER_RIGHT_RECT ) if has_bottom and has_left: - _create_segment_sprite( + _queue_segment( tile_origin + Vector2(0, TILE_SIZE - CORNER_SIZE), corner_size, INNER_CORNER_LOWER_LEFT_RECT ) if has_bottom and has_right: - _create_segment_sprite( + _queue_segment( tile_origin + Vector2(TILE_SIZE - CORNER_SIZE, TILE_SIZE - CORNER_SIZE), corner_size, INNER_CORNER_LOWER_RIGHT_RECT