extends Control ## 角色选择:预览模型、内置表情 atlas 与玩家本地表情库。 signal confirmed signal back_pressed @onready var _model: Node3D = %Model @onready var _camera: Camera3D = %PreviewCamera @onready var _rig: FaceRig = %FaceRig @onready var _paint_panel: Control = $PaintPanel @onready var _expression_row: HFlowContainer = %PresetRow var _expression_buttons: Array[TextureButton] = [] func _ready() -> void: Input.mouse_mode = Input.MOUSE_MODE_VISIBLE _camera.look_at(Vector3(0.0, 0.45, 0.0), Vector3.UP) %ConfirmButton.pressed.connect(_on_confirm) %BackButton.pressed.connect(_on_back) %DrawButton.pressed.connect(_on_draw) _paint_panel.finished.connect(_refresh_expression_list) _paint_panel.cancelled.connect(_refresh_expression_list) _refresh_expression_list() # 将模型选择180度 if (_model != null): _model.rotate_y(PI) ## 内置表情用 AtlasTexture 显示对应 UV 小格;本地表情直接显示各自 PNG。 func _refresh_expression_list() -> void: for child in _expression_row.get_children(): child.free() _expression_buttons.clear() for entry in _rig.get_expression_catalog(): var card := VBoxContainer.new() card.custom_minimum_size = Vector2(96, 116) card.add_theme_constant_override("separation", 4) var skin := ColorRect.new() skin.color = _rig.skin_color skin.mouse_filter = Control.MOUSE_FILTER_IGNORE skin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) var button := TextureButton.new() button.custom_minimum_size = Vector2(96, 96) button.texture_normal = entry["thumbnail"] as Texture2D button.ignore_texture_size = true button.stretch_mode = TextureButton.STRETCH_KEEP_ASPECT_CENTERED button.modulate = Color.WHITE if str(entry["id"]) == _rig.selected_expression_id else Color(0.62, 0.62, 0.62) button.set_meta("expression_id", str(entry["id"])) button.pressed.connect(_on_expression_pressed.bind(str(entry["id"]))) _expression_buttons.append(button) var preview := Control.new() preview.custom_minimum_size = Vector2(96, 96) preview.add_child(skin) preview.add_child(button) var label := Label.new() label.text = str(entry["name"]) label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS label.custom_minimum_size = Vector2(96, 16) card.add_child(preview) card.add_child(label) _expression_row.add_child(card) func _on_expression_pressed(expression_id: String) -> void: if _rig.select_expression(expression_id): _refresh_expression_highlight(expression_id) func _refresh_expression_highlight(expression_id: String) -> void: for button in _expression_buttons: button.modulate = Color.WHITE if str(button.get_meta("expression_id", "")) == expression_id else Color(0.62, 0.62, 0.62) ## 「自己画」从无底图起手。面板会先快照,取消时还原表情 ID 和笔画。 func _on_draw() -> void: _paint_panel.open(_rig, true) _refresh_expression_list() func _on_confirm() -> void: _rig.save() confirmed.emit() func _on_back() -> void: back_pressed.emit()