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:
File diff suppressed because it is too large
Load Diff
+44
-8
@@ -7,6 +7,7 @@ extends Control
|
||||
|
||||
const GAME_SCENE := "res://scenes/main.tscn"
|
||||
const CHARACTER_SELECT_SCENE := "res://scenes/character_select.tscn"
|
||||
const CHARACTER_CREATION_SCENE := "res://scenes/character_creation.tscn"
|
||||
|
||||
const BG_COLOR := Color(0.05, 0.05, 0.08, 1.0)
|
||||
const TITLE_COLOR := Color("#c8d0e0")
|
||||
@@ -25,7 +26,8 @@ const FONT_SIZE_BTN := 15
|
||||
@onready var _saves_list: VBoxContainer = $LoadGamePanel/VBox/SavesScroll/SavesList
|
||||
@onready var _load_back_btn: Button = $LoadGamePanel/VBox/BackBtn
|
||||
|
||||
var _char_select: Control = null # Instantiated on demand
|
||||
var _char_select: Control = null # Instantiated on demand
|
||||
var _char_creation: Control = null # Instantiated after archetype confirmed
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -68,14 +70,9 @@ func _on_archetype_confirmed(archetype: String) -> void:
|
||||
if _char_select != null and is_instance_valid(_char_select):
|
||||
_char_select.queue_free()
|
||||
_char_select = null
|
||||
# Set archetype before new_game() so SessionManager can persist it.
|
||||
# Set archetype first; save dir created after character creation confirms.
|
||||
GameState.character_archetype = archetype
|
||||
var game_id := SessionManager.new_game()
|
||||
if game_id.is_empty():
|
||||
push_error("MainMenu: new_game() failed to create save directory — cannot start")
|
||||
return
|
||||
SessionManager.save_character_archetype(game_id, archetype)
|
||||
get_tree().change_scene_to_file(GAME_SCENE)
|
||||
_show_character_creation()
|
||||
|
||||
|
||||
func _on_archetype_cancelled() -> void:
|
||||
@@ -84,6 +81,45 @@ func _on_archetype_cancelled() -> void:
|
||||
_char_select = null
|
||||
|
||||
|
||||
func _show_character_creation() -> void:
|
||||
if _char_creation != null and is_instance_valid(_char_creation):
|
||||
_char_creation.queue_free()
|
||||
var scene := load(CHARACTER_CREATION_SCENE) as PackedScene
|
||||
if scene == null:
|
||||
push_error("MainMenu: failed to load character_creation.tscn — skipping to game")
|
||||
_start_game_with_defaults()
|
||||
return
|
||||
_char_creation = scene.instantiate()
|
||||
add_child(_char_creation)
|
||||
_char_creation.creation_confirmed.connect(_on_creation_confirmed)
|
||||
_char_creation.creation_cancelled.connect(_on_creation_cancelled)
|
||||
|
||||
|
||||
func _on_creation_confirmed(descriptor: CharacterVisualDescriptor) -> void:
|
||||
if _char_creation != null and is_instance_valid(_char_creation):
|
||||
_char_creation.queue_free()
|
||||
_char_creation = null
|
||||
GameState.character_visual_descriptor = descriptor
|
||||
_start_game_with_defaults()
|
||||
|
||||
|
||||
func _on_creation_cancelled() -> void:
|
||||
if _char_creation != null and is_instance_valid(_char_creation):
|
||||
_char_creation.queue_free()
|
||||
_char_creation = null
|
||||
# Return to archetype select
|
||||
_show_character_select()
|
||||
|
||||
|
||||
func _start_game_with_defaults() -> void:
|
||||
var game_id := SessionManager.new_game()
|
||||
if game_id.is_empty():
|
||||
push_error("MainMenu: new_game() failed to create save directory — cannot start")
|
||||
return
|
||||
SessionManager.save_character_archetype(game_id, GameState.character_archetype)
|
||||
get_tree().change_scene_to_file(GAME_SCENE)
|
||||
|
||||
|
||||
func _on_continue() -> void:
|
||||
GameState.pending_load_path = "" # clear stale load path from previous Load selection
|
||||
var saves := SessionManager.list_game_dirs()
|
||||
|
||||
Reference in New Issue
Block a user