131 lines
4.5 KiB
GDScript
131 lines
4.5 KiB
GDScript
extends Control
|
|
## 画脸面板:把 FaceRig 的笔画层显示出来,并把鼠标坐标换算成画布坐标喂给画笔。
|
|
##
|
|
## 面板自己不画任何东西 —— 笔画直接落在 FaceRig 的画笔上,所以关掉面板后角色脸上立刻就是刚画的。
|
|
## 打开时拍一张快照,点「取消」还原;点「完成」才写进存档。
|
|
|
|
signal finished
|
|
signal cancelled
|
|
|
|
## 画布在屏幕上的显示边长。真实画布是 rig.canvas_size 见方,坐标按比例换算
|
|
const VIEW_SIZE := 420
|
|
|
|
@onready var _canvas_rect: TextureRect = %StrokeRect
|
|
@onready var _preset_rect: TextureRect = %PresetRect
|
|
@onready var _color_button: ColorPickerButton = %ColorButton
|
|
@onready var _width_slider: HSlider = %WidthSlider
|
|
@onready var _width_label: Label = %WidthLabel
|
|
@onready var _eraser_toggle: CheckButton = %EraserToggle
|
|
|
|
var _rig: FaceRig
|
|
## 打开面板时的笔画层快照,取消时还原
|
|
var _snapshot: PackedByteArray
|
|
## 打开面板时用的是哪张预设,取消时一并还原(「自己画」会把底图换成空白)
|
|
var _preset_before: String = ""
|
|
## 左键是不是按着 —— 拖拽判定用这个,不看 motion 事件里的 button_mask
|
|
var _drawing := false
|
|
|
|
|
|
func _ready() -> void:
|
|
visible = false
|
|
_canvas_rect.custom_minimum_size = Vector2(VIEW_SIZE, VIEW_SIZE)
|
|
_canvas_rect.gui_input.connect(_on_canvas_input)
|
|
_color_button.color_changed.connect(_on_color_changed)
|
|
_width_slider.value_changed.connect(_on_width_changed)
|
|
_eraser_toggle.toggled.connect(_on_eraser_toggled)
|
|
%ClearButton.pressed.connect(_on_clear)
|
|
%DoneButton.pressed.connect(_on_done)
|
|
%CancelButton.pressed.connect(_on_cancel)
|
|
_on_width_changed(_width_slider.value)
|
|
|
|
|
|
# ---------------- 对外接口 ----------------
|
|
|
|
## 打开面板并绑定到某个 FaceRig。
|
|
## blank_face 传了就从这张空白脸起手(「自己画」用)—— 快照仍然记的是改动之前的状态,
|
|
## 所以点「取消」能把底图和笔画一起还原。
|
|
func open(rig: FaceRig, blank_face: Texture2D = null) -> void:
|
|
if rig == null or rig.painter == null:
|
|
push_warning("画脸面板没拿到可用的 FaceRig")
|
|
return
|
|
_rig = rig
|
|
_snapshot = rig.painter.snapshot()
|
|
_preset_before = rig.preset_path
|
|
if blank_face != null:
|
|
rig.set_preset(blank_face, blank_face.resource_path)
|
|
rig.clear_paint()
|
|
_canvas_rect.texture = rig.painter.texture
|
|
# 画布只显示笔画层,底下得把底图一起摆出来 —— 不然玩家是在对着空气画
|
|
_preset_rect.texture = rig.material.get_shader_parameter("base_tex") as Texture2D
|
|
rig.painter.brush_color = _color_button.color
|
|
rig.painter.brush_width = _width_slider.value
|
|
rig.painter.erasing = _eraser_toggle.button_pressed
|
|
visible = true
|
|
|
|
|
|
# ---------------- 画布输入 ----------------
|
|
|
|
## 屏幕坐标 → 画布坐标。显示尺寸和画布尺寸不一致时按比例缩
|
|
func _canvas_pos(event: InputEventMouse) -> Vector2:
|
|
var scale := float(_rig.painter.canvas_size) / maxf(1.0, _canvas_rect.size.x)
|
|
return event.position * scale
|
|
|
|
|
|
func _on_canvas_input(event: InputEvent) -> void:
|
|
if _rig == null:
|
|
return
|
|
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
|
|
# 自己记「按没按下」,不靠 motion 事件里的 button_mask —— 那个字段不总是可靠
|
|
_drawing = event.pressed
|
|
if event.pressed:
|
|
_rig.painter.begin_stroke(_canvas_pos(event))
|
|
else:
|
|
_rig.painter.end_stroke()
|
|
elif event is InputEventMouseMotion and _drawing:
|
|
_rig.painter.extend_stroke(_canvas_pos(event))
|
|
|
|
|
|
# ---------------- 工具条 ----------------
|
|
|
|
func _on_color_changed(c: Color) -> void:
|
|
if _rig != null:
|
|
_rig.painter.brush_color = c
|
|
# 选了颜色就顺手退出橡皮 —— 不然「我选了红色怎么还是擦」
|
|
_eraser_toggle.set_pressed_no_signal(false)
|
|
if _rig != null:
|
|
_rig.painter.erasing = false
|
|
|
|
|
|
func _on_width_changed(w: float) -> void:
|
|
if _rig != null:
|
|
_rig.painter.brush_width = w
|
|
_width_label.text = "%d px" % int(w)
|
|
|
|
|
|
func _on_eraser_toggled(on: bool) -> void:
|
|
if _rig != null:
|
|
_rig.painter.erasing = on
|
|
|
|
|
|
func _on_clear() -> void:
|
|
if _rig != null:
|
|
_rig.painter.clear()
|
|
|
|
|
|
func _on_done() -> void:
|
|
if _rig != null:
|
|
_rig.save()
|
|
visible = false
|
|
finished.emit()
|
|
|
|
|
|
func _on_cancel() -> void:
|
|
if _rig != null:
|
|
if _snapshot.size() > 0:
|
|
_rig.painter.restore(_snapshot)
|
|
# 底图也要还原 —— 「自己画」把它换成空白了,只还原笔画会留下一张白脸
|
|
if not _preset_before.is_empty() and ResourceLoader.exists(_preset_before):
|
|
_rig.set_preset(load(_preset_before), _preset_before)
|
|
visible = false
|
|
cancelled.emit()
|