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 # (4,0) = reset_plate — amber (#502) const TILE_SIZE: int = Constants.TILE_SIZE const GROUND_FLOOR: int = 0 # Server floor level for ground — filter target in update_tiles() enum TileType { FLOOR = 0, WALL = 1, DOOR = 2, OBJECT = 3, RESET_PLATE = 4 } # Wire-format string to TileType mapping const TILE_TYPE_MAP: Dictionary = { "floor": TileType.FLOOR, "wall": TileType.WALL, "door": TileType.DOOR, "object": TileType.OBJECT, "reset_plate": TileType.RESET_PLATE, } 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 * 5, 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)) # Reset plate (4,0) — amber (#502) _fill_tile_with_border(img, 4, Color(0.91, 0.77, 0.28), Color(0.65, 0.55, 0.2)) 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(5): 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} # z here is the server-side FLOOR LEVEL (0 = ground, 1 = first floor, etc.), # NOT the Godot scene z_index (which controls render order within a floor). # This node only renders floor-level 0. Higher floor levels will be handled # by separate TileMapLayer nodes when multi-floor rendering is implemented. 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 # Floor-level filter: only render tiles at ground floor. # Upper floor tiles (level 1+) are for future multi-floor nodes. var tile_z: int = tile_data.get("z", 0) if tile_z != GROUND_FLOOR: 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)