Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
125 lines
4.3 KiB
GDScript
125 lines
4.3 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
|
|
# (4,0) = reset_plate — amber (#502)
|
|
|
|
enum TileType { FLOOR = 0, WALL = 1, DOOR = 2, OBJECT = 3, RESET_PLATE = 4 }
|
|
|
|
const TILE_SIZE: int = Constants.TILE_SIZE
|
|
const GROUND_FLOOR: int = 0 # Server floor level for ground — filter target in update_tiles()
|
|
|
|
# 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, visibility: String (optional)}
|
|
# 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.
|
|
#
|
|
# BoundaryWall tiles (#585): visibility="BoundaryWall" tiles (wall tiles 1 step beyond
|
|
# LOS boundary) are rendered normally here — they have a "type" field from protocol.gd
|
|
# so they composite correctly under the fog shader. The fog/exploration exemption is
|
|
# handled in fog_state.gd (VIS_FORWARD without EXP_VISIBLE) and game_state.gd
|
|
# (boundary_positions not visible_positions). No special handling needed in this method.
|
|
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)
|