Files
settled-reach/client/scripts/rendering/tile_renderer.gd
T
jpmschweitzerandClaude Opus 4.6 aa3a3fea7d fix(client): address PR review — invalidation, constants, tests
Review feedback from Hoshe + Tyre on PR #8:

- Extract shared TILE_SIZE to Constants class_name (Tyre #5, Hoshe #7)
- Fix tile invalidation: detect tile/visibility count changes instead
  of one-shot dirty flag, supports chunk loading (Tyre #3, #4)
- Fog dirty tracking: only re-render when visible_positions changes
- Add bounds warning for unknown tile types (Hoshe #1)
- Add player-not-found warning in GameState (Hoshe #5)
- Use Image.fill_rect() instead of pixel loops (Tyre #10)
- Document _player_pos as reserved for fog decay #113 (Tyre #6)
- Add TODO(#130) for D-033 relationship colors (Hoshe #3, Tyre #8)
- Add class_name to EntityRenderer, FogRenderer, TileRenderer
- 20 new rendering tests (D-030 Layer 1): entity lifecycle, fog
  registration, tile type mapping, snapshot completeness, constants
- 65 total tests passing, 0 failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 00:15:42 +01:00

95 lines
3.0 KiB
GDScript

class_name TileRenderer
extends TileMapLayer
# Tile renderer — draws map tiles from ObserverSnapshot tile data
# Uses a programmatic TileSet with placeholder colored rectangles (D-014)
#
# Tile types (atlas coords in the programmatic source):
# (0,0) = floor — dark gray
# (1,0) = wall — lighter gray
# (2,0) = door — brown
# (3,0) = object — teal
const TILE_SIZE: int = Constants.TILE_SIZE
enum TileType { FLOOR = 0, WALL = 1, DOOR = 2, OBJECT = 3 }
# Wire-format string to TileType mapping
const TILE_TYPE_MAP: Dictionary = {
"floor": TileType.FLOOR,
"wall": TileType.WALL,
"door": TileType.DOOR,
"object": TileType.OBJECT,
}
var _initialized: bool = false
func _ready() -> void:
_setup_tileset()
_initialized = true
print("TileRenderer: Initialized")
# Build a programmatic TileSet with colored placeholder tiles
func _setup_tileset() -> void:
var ts := TileSet.new()
ts.tile_size = Vector2i(TILE_SIZE, TILE_SIZE)
# Create an atlas source backed by a programmatic image
var source := TileSetAtlasSource.new()
var img := Image.create(TILE_SIZE * 4, TILE_SIZE, false, Image.FORMAT_RGBA8)
# Floor (0,0) — dark gray
_fill_tile(img, 0, Color(0.18, 0.18, 0.22))
# Wall (1,0) — lighter gray with subtle border
_fill_tile_with_border(img, 1, Color(0.4, 0.4, 0.45), Color(0.25, 0.25, 0.3))
# Door (2,0) — brown
_fill_tile_with_border(img, 2, Color(0.5, 0.35, 0.2), Color(0.35, 0.25, 0.15))
# Object (3,0) — teal
_fill_tile(img, 3, Color(0.2, 0.45, 0.45))
var tex := ImageTexture.create_from_image(img)
source.texture = tex
source.texture_region_size = Vector2i(TILE_SIZE, TILE_SIZE)
# Create tile entries in the atlas
for i in range(4):
source.create_tile(Vector2i(i, 0))
var source_id := ts.add_source(source)
tile_set = ts
# Update tiles from snapshot data
# tiles: Array of {x: int, y: int, z: int, type: String}
func update_tiles(tiles: Array) -> void:
if not _initialized:
return
clear()
for tile_data in tiles:
if not tile_data.has("x") or not tile_data.has("y") or not tile_data.has("type"):
continue
var tile_type_str: String = tile_data.type
if not TILE_TYPE_MAP.has(tile_type_str):
push_warning("TileRenderer: unknown tile type '%s' at (%d, %d)" % [
tile_type_str, tile_data.x, tile_data.y])
continue
var atlas_x: int = TILE_TYPE_MAP[tile_type_str]
var coords := Vector2i(tile_data.x, tile_data.y)
set_cell(coords, 0, Vector2i(atlas_x, 0))
# Fill a tile region with a solid color
func _fill_tile(img: Image, tile_index: int, color: Color) -> void:
var rect := Rect2i(tile_index * TILE_SIZE, 0, TILE_SIZE, TILE_SIZE)
img.fill_rect(rect, color)
# Fill a tile region with a color and a 1px border
func _fill_tile_with_border(img: Image, tile_index: int, fill: Color, border: Color) -> void:
var x_offset := tile_index * TILE_SIZE
# Border (full tile)
img.fill_rect(Rect2i(x_offset, 0, TILE_SIZE, TILE_SIZE), border)
# Fill (inset by 1px)
img.fill_rect(Rect2i(x_offset + 1, 1, TILE_SIZE - 2, TILE_SIZE - 2), fill)