feat(client): D-033 entity colors, peripheral dimming, facing indicator (#130)
Replace hardcoded entity colors with D-033 relationship palette: Player=#e0e8ff, Npc=unknown teal #4a9ebb, Object=grey #8b8ba0. Phase 1 defaults by entity kind; Phase 2 (#361) will derive color from RelationshipState via knowledge graph. Entities in peripheral vision dimmed to 50% alpha (D-015). Player entity gets a Polygon2D triangle indicator showing facing direction, rotated from GameState.player_facing. 82 tests total, 0 failures. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,10 @@ 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}}
|
||||
# Entity format (from Protocol v2): {entity_id, x, y, z, kind: {variant, data}, visibility}
|
||||
#
|
||||
# D-033 colors: Phase 1 defaults by entity kind. Phase 2 (#361) will derive
|
||||
# color from RelationshipState via the knowledge graph.
|
||||
|
||||
const TILE_SIZE: int = Constants.TILE_SIZE
|
||||
const ENTITY_SIZE: int = 24
|
||||
@@ -41,30 +44,27 @@ func update_entities(entities: Array) -> void:
|
||||
for entity_id in ids_to_remove:
|
||||
_remove_entity_node(entity_id)
|
||||
|
||||
# Create a new entity node (placeholder visual)
|
||||
# Create a new entity node with D-033 color and optional facing indicator
|
||||
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
|
||||
# D-033 color by entity kind (Phase 1 default)
|
||||
# TODO(#361): derive from RelationshipState via knowledge graph
|
||||
entity_node.color = _color_for_kind(entity_data)
|
||||
|
||||
add_child(entity_node)
|
||||
entity_nodes[entity_id] = entity_node
|
||||
|
||||
# Add facing indicator for the player entity
|
||||
if entity_id == GameState.player_entity_id:
|
||||
_add_facing_indicator(entity_node)
|
||||
|
||||
_update_entity_node(entity_id, entity_data)
|
||||
|
||||
# Update an existing entity node
|
||||
# Update an existing entity node (position, visibility dimming, facing)
|
||||
func _update_entity_node(entity_id: int, entity_data: Dictionary) -> void:
|
||||
if not entity_nodes.has(entity_id):
|
||||
return
|
||||
@@ -78,6 +78,19 @@ func _update_entity_node(entity_id: int, entity_data: Dictionary) -> void:
|
||||
entity_data.y * TILE_SIZE + ENTITY_OFFSET
|
||||
)
|
||||
|
||||
# v2: Peripheral vision dimming (D-015)
|
||||
var visibility: Variant = entity_data.get("visibility")
|
||||
if visibility == "Peripheral":
|
||||
entity_node.modulate.a = Constants.PERIPHERAL_ALPHA
|
||||
else:
|
||||
entity_node.modulate.a = 1.0
|
||||
|
||||
# v2: Update facing indicator rotation (player entity only)
|
||||
if entity_id == GameState.player_entity_id:
|
||||
var indicator = entity_node.get_node_or_null("FacingIndicator")
|
||||
if indicator != null:
|
||||
indicator.rotation = _facing_to_rotation(GameState.player_facing)
|
||||
|
||||
# Remove an entity node
|
||||
func _remove_entity_node(entity_id: int) -> void:
|
||||
if not entity_nodes.has(entity_id):
|
||||
@@ -86,3 +99,46 @@ func _remove_entity_node(entity_id: int) -> void:
|
||||
var entity_node = entity_nodes[entity_id]
|
||||
entity_node.queue_free()
|
||||
entity_nodes.erase(entity_id)
|
||||
|
||||
# D-033 color by entity kind (Phase 1: defaults by kind, not relationship)
|
||||
static func _color_for_kind(entity_data: Dictionary) -> Color:
|
||||
var kind_variant: String = entity_data.get("kind", {}).get("variant", "")
|
||||
match kind_variant:
|
||||
"Player":
|
||||
return Constants.ENTITY_COLOR_PLAYER
|
||||
"Npc":
|
||||
return Constants.ENTITY_COLOR_UNKNOWN
|
||||
"Object", "Terrain":
|
||||
return Constants.ENTITY_COLOR_OBJECT
|
||||
_:
|
||||
return Constants.ENTITY_COLOR_OBJECT
|
||||
|
||||
# Add a facing direction indicator triangle to the player entity
|
||||
func _add_facing_indicator(parent_node: Control) -> void:
|
||||
var indicator := Polygon2D.new()
|
||||
indicator.name = "FacingIndicator"
|
||||
var s := Constants.FACING_INDICATOR_SIZE
|
||||
var offset := Constants.FACING_INDICATOR_OFFSET
|
||||
# Triangle pointing up (North), offset from center. Rotates around (0,0).
|
||||
indicator.polygon = PackedVector2Array([
|
||||
Vector2(0, -offset - s),
|
||||
Vector2(-s * 0.6, -offset + s * 0.4),
|
||||
Vector2(s * 0.6, -offset + s * 0.4),
|
||||
])
|
||||
indicator.color = Constants.ENTITY_COLOR_PLAYER
|
||||
# Position at center of parent ColorRect — rotation around this point
|
||||
indicator.position = Vector2(ENTITY_SIZE / 2.0, ENTITY_SIZE / 2.0)
|
||||
parent_node.add_child(indicator)
|
||||
|
||||
# Convert facing direction string to rotation in radians (0 = North/up)
|
||||
static func _facing_to_rotation(facing: String) -> float:
|
||||
match facing:
|
||||
"North": return 0.0
|
||||
"Northeast": return PI / 4.0
|
||||
"East": return PI / 2.0
|
||||
"Southeast": return 3.0 * PI / 4.0
|
||||
"South": return PI
|
||||
"Southwest": return 5.0 * PI / 4.0
|
||||
"West": return 3.0 * PI / 2.0
|
||||
"Northwest": return 7.0 * PI / 4.0
|
||||
_: return 0.0
|
||||
|
||||
Reference in New Issue
Block a user