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>
89 lines
2.8 KiB
GDScript
89 lines
2.8 KiB
GDScript
class_name EntityRenderer
|
|
extends Node2D
|
|
|
|
# Entity renderer — manages entity sprites under the Entities node
|
|
# Creates/updates/removes ColorRect children based on entity data
|
|
# Entity format (from Protocol): {entity_id, x, y, z, kind: {variant, data}}
|
|
|
|
const TILE_SIZE: int = Constants.TILE_SIZE
|
|
const ENTITY_SIZE: int = 24
|
|
const ENTITY_OFFSET: float = (TILE_SIZE - ENTITY_SIZE) / 2.0 # center within tile
|
|
|
|
var entity_nodes: Dictionary = {} # entity_id -> Node2D mapping
|
|
|
|
func _ready() -> void:
|
|
print("EntityRenderer: Initialized")
|
|
|
|
# Update entities from snapshot data
|
|
func update_entities(entities: Array) -> void:
|
|
var active_ids: Array = []
|
|
|
|
# Create or update entities
|
|
for entity_data in entities:
|
|
if not entity_data.has("entity_id"):
|
|
continue
|
|
|
|
var entity_id = entity_data.entity_id
|
|
active_ids.append(entity_id)
|
|
|
|
# Create entity node if it doesn't exist
|
|
if not entity_nodes.has(entity_id):
|
|
_create_entity_node(entity_id, entity_data)
|
|
else:
|
|
_update_entity_node(entity_id, entity_data)
|
|
|
|
# Remove entities that are no longer visible
|
|
var ids_to_remove: Array = []
|
|
for entity_id in entity_nodes.keys():
|
|
if entity_id not in active_ids:
|
|
ids_to_remove.append(entity_id)
|
|
|
|
for entity_id in ids_to_remove:
|
|
_remove_entity_node(entity_id)
|
|
|
|
# Create a new entity node (placeholder visual)
|
|
func _create_entity_node(entity_id: int, entity_data: Dictionary) -> void:
|
|
var entity_node = ColorRect.new()
|
|
entity_node.name = "Entity_" + str(entity_id)
|
|
entity_node.size = Vector2(ENTITY_SIZE, ENTITY_SIZE)
|
|
entity_node.pivot_offset = Vector2(ENTITY_SIZE / 2.0, ENTITY_SIZE / 2.0)
|
|
|
|
# Color based on entity kind (from Protocol decoded format).
|
|
# TODO(#130): replace with D-033 relationship colors (teal/green/amber/red).
|
|
var kind_variant: String = entity_data.get("kind", {}).get("variant", "")
|
|
match kind_variant:
|
|
"Npc":
|
|
entity_node.color = Color(0.3, 0.6, 0.9) # Placeholder blue
|
|
"Player":
|
|
entity_node.color = Color(0.88, 0.91, 1.0) # Placeholder light blue
|
|
_:
|
|
entity_node.color = Color(0.8, 0.8, 0.8) # Gray for unknown
|
|
|
|
add_child(entity_node)
|
|
entity_nodes[entity_id] = entity_node
|
|
|
|
_update_entity_node(entity_id, entity_data)
|
|
|
|
# Update an existing entity node
|
|
func _update_entity_node(entity_id: int, entity_data: Dictionary) -> void:
|
|
if not entity_nodes.has(entity_id):
|
|
return
|
|
|
|
var entity_node = entity_nodes[entity_id]
|
|
|
|
# Update position from x, y fields (Protocol format), centered within tile
|
|
if entity_data.has("x") and entity_data.has("y"):
|
|
entity_node.position = Vector2(
|
|
entity_data.x * TILE_SIZE + ENTITY_OFFSET,
|
|
entity_data.y * TILE_SIZE + ENTITY_OFFSET
|
|
)
|
|
|
|
# Remove an entity node
|
|
func _remove_entity_node(entity_id: int) -> void:
|
|
if not entity_nodes.has(entity_id):
|
|
return
|
|
|
|
var entity_node = entity_nodes[entity_id]
|
|
entity_node.queue_free()
|
|
entity_nodes.erase(entity_id)
|