Sprint 2 rendering pipeline: tiles (#129), fog (#131), camera (#116). - tile_renderer.gd: programmatic TileSet with floor/wall/door/object placeholder tiles, renders from snapshot tile data - fog_renderer.gd: TileMapLayer overlay with three visibility states (visible/fog-edge/hidden), computed from visible_positions data - Camera2D: smoothing enabled (speed 6.0), 2x zoom, locked to player - game_state.gd: stores visible_tiles and visible_positions from snapshots - sim_bridge.gd: test data with 8x8 room, corridor, and Manhattan distance visibility for development without server - Scene render order: Tiles -> FogOverlay -> Entities - Background clear color set to near-black for unexplored areas Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
90 lines
2.8 KiB
GDScript
90 lines
2.8 KiB
GDScript
extends TileMapLayer
|
|
|
|
# Fog renderer — draws fog overlay on non-visible tiles (D-011)
|
|
# Three visibility states per tile:
|
|
# visible = no fog tile (clear)
|
|
# fog-edge = semi-transparent dark overlay (adjacent to visible)
|
|
# hidden = opaque black overlay
|
|
#
|
|
# Atlas layout:
|
|
# (0,0) = full fog (opaque black)
|
|
# (1,0) = fog edge (semi-transparent)
|
|
|
|
const TILE_SIZE: int = 32
|
|
|
|
var _initialized: bool = false
|
|
var _all_tile_positions: Dictionary = {} # Vector2i -> true, all known map tiles
|
|
|
|
func _ready() -> void:
|
|
_setup_tileset()
|
|
_initialized = true
|
|
print("FogRenderer: Initialized")
|
|
|
|
func _setup_tileset() -> void:
|
|
var ts := TileSet.new()
|
|
ts.tile_size = Vector2i(TILE_SIZE, TILE_SIZE)
|
|
|
|
var source := TileSetAtlasSource.new()
|
|
var img := Image.create(TILE_SIZE * 2, TILE_SIZE, false, Image.FORMAT_RGBA8)
|
|
|
|
# Full fog (0,0) — opaque black
|
|
_fill_tile(img, 0, Color(0.02, 0.02, 0.05, 1.0))
|
|
# Fog edge (1,0) — semi-transparent dark
|
|
_fill_tile(img, 1, Color(0.02, 0.02, 0.05, 0.6))
|
|
|
|
var tex := ImageTexture.create_from_image(img)
|
|
source.texture = tex
|
|
source.texture_region_size = Vector2i(TILE_SIZE, TILE_SIZE)
|
|
|
|
source.create_tile(Vector2i(0, 0))
|
|
source.create_tile(Vector2i(1, 0))
|
|
|
|
ts.add_source(source)
|
|
tile_set = ts
|
|
|
|
# Register all known tile positions (called when tile data arrives)
|
|
func register_tile_positions(tiles: Array) -> void:
|
|
_all_tile_positions.clear()
|
|
for tile_data in tiles:
|
|
if tile_data.has("x") and tile_data.has("y"):
|
|
_all_tile_positions[Vector2i(tile_data.x, tile_data.y)] = true
|
|
|
|
# Update fog based on visible positions
|
|
# visible_positions: Dictionary of Vector2i -> true
|
|
func update_fog(visible_positions: Dictionary, _player_pos: Vector2) -> void:
|
|
if not _initialized:
|
|
return
|
|
|
|
clear()
|
|
|
|
if _all_tile_positions.is_empty() or visible_positions.is_empty():
|
|
return
|
|
|
|
# Build set of fog-edge positions (adjacent to visible but not visible themselves)
|
|
var fog_edge: Dictionary = {}
|
|
var neighbors := [
|
|
Vector2i(-1, 0), Vector2i(1, 0), Vector2i(0, -1), Vector2i(0, 1),
|
|
Vector2i(-1, -1), Vector2i(1, -1), Vector2i(-1, 1), Vector2i(1, 1),
|
|
]
|
|
|
|
for pos in visible_positions:
|
|
for offset in neighbors:
|
|
var neighbor_pos: Vector2i = pos + offset
|
|
if not visible_positions.has(neighbor_pos) and _all_tile_positions.has(neighbor_pos):
|
|
fog_edge[neighbor_pos] = true
|
|
|
|
# Place fog tiles on all known positions that aren't visible
|
|
for pos in _all_tile_positions:
|
|
if visible_positions.has(pos):
|
|
continue # Visible — no fog
|
|
elif fog_edge.has(pos):
|
|
set_cell(pos, 0, Vector2i(1, 0)) # Fog edge — semi-transparent
|
|
else:
|
|
set_cell(pos, 0, Vector2i(0, 0)) # Full fog — opaque
|
|
|
|
func _fill_tile(img: Image, tile_index: int, color: Color) -> void:
|
|
var x_offset := tile_index * TILE_SIZE
|
|
for x in range(TILE_SIZE):
|
|
for y in range(TILE_SIZE):
|
|
img.set_pixel(x_offset + x, y, color)
|