feat(client): character creation screen with live 3D preview (#705)
Full character creation UI with 5-tab customisation panel (Body, Head, Hair, Clothing, Accessories), live 3D compositor preview via SubViewport, D-165 color picker modal, keyboard navigation, and game flow integration. - CharacterCreation scene + script (~1440 lines) implementing wireframe spec - SubViewport with Camera3D (frontal/dramatic/overhead presets per D-158) - Cardinal rotation (Q/E) per D-155, randomise (R), Tab/Enter/Esc navigation - Body type grid (11 types, D-159), skin tone dock (9 tones, shared state) - Head template, hair, facial hair, eyebrow grids with DirAccess asset scanning - Clothing slot selector with per-item Primary/Secondary/Accent tints - Accessory slot selector with Primary/Secondary tints (Array[Color] descriptor) - 54-swatch color picker modal (D-165) with hex input and recent colors - Auto-derive flags for highlight, eyebrow tint, facial hair tint from hair primary - Game flow: main_menu → archetype select → character creation → game start - GameState.character_visual_descriptor field for gameplay consumption - Updated accessory_tints from single Color to Array[Color] in descriptor + compositor - 40+ GdUnit tests covering scene structure, signals, keyboard nav, color derivation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -518,7 +518,9 @@ func _load_accessories(desc: CharacterVisualDescriptor) -> void:
|
||||
push_warning("CharacterVisual: unknown accessory slot '%s'" % slot)
|
||||
continue
|
||||
var path := BASE_PATH + "accessories/%s.glb" % item_id
|
||||
var tint: Color = desc.accessory_tints.get(item_id, Color.WHITE)
|
||||
# accessory_tints is now Array[Color] (Primary + Secondary). Pass Primary ([0]).
|
||||
var tints: Array = desc.accessory_tints.get(item_id, [])
|
||||
var tint: Color = tints[0] if not tints.is_empty() else Color.WHITE
|
||||
var att := _attach_to_bone(path, bone_name, tint)
|
||||
if att != null:
|
||||
# Track accessory attachments separately for get_accessory_node_count()
|
||||
|
||||
@@ -97,7 +97,8 @@ var clothing_tints: Dictionary = {}
|
||||
## Accessory slot → item_id ("hat" -> "hat_hardhat")
|
||||
var accessory_slots: Dictionary = {}
|
||||
|
||||
## item_id → Color for accessories
|
||||
## item_id → Array[Color] for accessories (Primary + Secondary). Matches clothing_tints shape.
|
||||
## Changed from single Color per Tyre architecture review (Task #6).
|
||||
var accessory_tints: Dictionary = {}
|
||||
|
||||
|
||||
@@ -133,7 +134,7 @@ static func from_dict(data: Dictionary) -> CharacterVisualDescriptor:
|
||||
desc.clothing_slots = data.get("clothing_slots", {})
|
||||
desc.clothing_tints = _decode_tint_map(data.get("clothing_tints", {}))
|
||||
desc.accessory_slots = data.get("accessory_slots", {})
|
||||
desc.accessory_tints = _decode_single_tint_map(data.get("accessory_tints", {}))
|
||||
desc.accessory_tints = _decode_tint_map(data.get("accessory_tints", {}))
|
||||
|
||||
return desc
|
||||
|
||||
@@ -153,7 +154,7 @@ func to_dict() -> Dictionary:
|
||||
"clothing_slots": clothing_slots,
|
||||
"clothing_tints": _encode_tint_map(clothing_tints),
|
||||
"accessory_slots": accessory_slots,
|
||||
"accessory_tints": _encode_single_tint_map(accessory_tints),
|
||||
"accessory_tints": _encode_tint_map(accessory_tints),
|
||||
}
|
||||
|
||||
|
||||
@@ -194,17 +195,3 @@ static func _encode_tint_map(data: Dictionary) -> Dictionary:
|
||||
return result
|
||||
|
||||
|
||||
## Decode accessory_tints: item_id → single Color.
|
||||
static func _decode_single_tint_map(data: Dictionary) -> Dictionary:
|
||||
var result := {}
|
||||
for key: String in data:
|
||||
result[key] = _decode_color(data[key], Color.WHITE)
|
||||
return result
|
||||
|
||||
|
||||
## Encode accessory_tints to wire format.
|
||||
static func _encode_single_tint_map(data: Dictionary) -> Dictionary:
|
||||
var result := {}
|
||||
for key: String in data:
|
||||
result[key] = _encode_color(data[key])
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user