extends Control ## 角色选择:左边一个 3D 预览(慢慢自转),右边挑角色和脸。 ## ## 预览里这具模型和玩家场景里的模型是**两套独立的 FaceRig**,但共用同一份存档: ## 这里点「确定」写进 user://,玩家出生时自己的 FaceRig.apply_saved() 读回来。 ## ## 目前只有一个角色模型(mouse.glb),所以角色那一栏只有一个按钮 —— ## 以后加了新模型,往 CharRow 里再放一个按钮就行。 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 var _selected_preset: Texture2D 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() func _process(delta: float) -> void: # 转的是外层 Model 而不是模型自己,免得和朝向/动画打架 if _model != null: _model.rotate_y(SPIN_SPEED * delta) # ---------------- 选择 ---------------- 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() 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_draw() -> void: _selected_preset = null _sync_preset_highlight() _paint_panel.open(_rig, BLANK_FACE) # ---------------- 进出 ---------------- func _on_confirm() -> void: if _rig != null: _rig.save() confirmed.emit() func _on_back() -> void: back_pressed.emit()