1
This commit is contained in:
+57
-50
@@ -1,90 +1,97 @@
|
||||
extends Control
|
||||
## 角色选择:左边一个 3D 预览(慢慢自转),右边挑角色和脸。
|
||||
##
|
||||
## 预览里这具模型和玩家场景里的模型是**两套独立的 FaceRig**,但共用同一份存档:
|
||||
## 这里点「确定」写进 user://,玩家出生时自己的 FaceRig.apply_saved() 读回来。
|
||||
##
|
||||
## 目前只有一个角色模型(mouse.glb),所以角色那一栏只有一个按钮 ——
|
||||
## 以后加了新模型,往 CharRow 里再放一个按钮就行。
|
||||
## 角色选择:预览模型、内置表情 atlas 与玩家本地表情库。
|
||||
|
||||
signal confirmed
|
||||
## 「返回」按下 —— 这一趟的改动不写存档
|
||||
signal back_pressed
|
||||
|
||||
## 预览里模型的自转速度(弧度/秒)
|
||||
const SPIN_SPEED := 0.5
|
||||
## 「自己画」用的空白脸 —— 纯色底,等于「这张脸上什么都没画」
|
||||
const BLANK_FACE := preload("res://character/player/face/presets/face_00_blank.png")
|
||||
|
||||
@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 _selected_preset: Texture2D
|
||||
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)
|
||||
|
||||
# 预设缩略图就是 PresetRow 里的子节点 —— 加预设 = 往场景里多放一个 TextureButton
|
||||
for child in %PresetRow.get_children():
|
||||
var btn := child as TextureButton
|
||||
if btn == null or btn.texture_normal == null:
|
||||
continue
|
||||
btn.pressed.connect(_on_preset_pressed.bind(btn))
|
||||
# 存档里选的那张,就是当前高亮的
|
||||
if btn.texture_normal.resource_path == _rig.preset_path:
|
||||
_selected_preset = btn.texture_normal
|
||||
_sync_preset_highlight()
|
||||
_paint_panel.finished.connect(_refresh_expression_list)
|
||||
_paint_panel.cancelled.connect(_refresh_expression_list)
|
||||
_refresh_expression_list()
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
# 转的是外层 Model 而不是模型自己,免得和朝向/动画打架
|
||||
if _model != null:
|
||||
_model.rotate_y(SPIN_SPEED * delta)
|
||||
|
||||
|
||||
# ---------------- 选择 ----------------
|
||||
## 内置表情用 AtlasTexture 显示对应 UV 小格;本地表情直接显示各自 PNG。
|
||||
func _refresh_expression_list() -> void:
|
||||
for child in _expression_row.get_children():
|
||||
child.free()
|
||||
_expression_buttons.clear()
|
||||
|
||||
func _on_preset_pressed(btn: TextureButton) -> void:
|
||||
if _rig == null:
|
||||
return
|
||||
# 只换底图,玩家画的笔画保留 —— 所以可以先挑脸型再补几笔
|
||||
_rig.set_preset(btn.texture_normal, btn.texture_normal.resource_path)
|
||||
_selected_preset = btn.texture_normal
|
||||
_sync_preset_highlight()
|
||||
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 _sync_preset_highlight() -> void:
|
||||
for child in %PresetRow.get_children():
|
||||
var btn := child as TextureButton
|
||||
if btn == null:
|
||||
continue
|
||||
var picked := _selected_preset != null and btn.texture_normal == _selected_preset
|
||||
btn.modulate = Color.WHITE if picked else Color(0.62, 0.62, 0.62)
|
||||
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:
|
||||
_selected_preset = null
|
||||
_sync_preset_highlight()
|
||||
_paint_panel.open(_rig, BLANK_FACE)
|
||||
_paint_panel.open(_rig, true)
|
||||
_refresh_expression_list()
|
||||
|
||||
|
||||
# ---------------- 进出 ----------------
|
||||
|
||||
func _on_confirm() -> void:
|
||||
if _rig != null:
|
||||
_rig.save()
|
||||
_rig.save()
|
||||
confirmed.emit()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user