开始菜单、玩家选择、脸部绘制
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
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()
|
||||
@@ -0,0 +1 @@
|
||||
uid://cwd4rsma685f
|
||||
@@ -0,0 +1,187 @@
|
||||
[gd_scene load_steps=11 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://ui/character_select.gd" id="1_select"]
|
||||
[ext_resource type="Script" path="res://character/player/face/face_rig.gd" id="7_face_rig"]
|
||||
[ext_resource type="PackedScene" path="res://ui/paint_panel.tscn" id="2_paint"]
|
||||
[ext_resource type="PackedScene" uid="uid://css1vk3u8gt7o" path="res://character/player/assets/mouse.glb" id="3_mouse"]
|
||||
[ext_resource type="Sky" uid="uid://bi632suyifms5" path="res://world/sky/cartoon_sky.tres" id="4_sky"]
|
||||
[ext_resource type="Texture2D" path="res://character/player/face/presets/face_01.png" id="5_face01"]
|
||||
[ext_resource type="Texture2D" path="res://character/player/face/presets/face_02.png" id="6_face02"]
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_bg"]
|
||||
offsets = PackedFloat32Array(0, 0.55, 1)
|
||||
colors = PackedColorArray(0.09, 0.3, 0.78, 1, 0.28, 0.6, 0.95, 1, 0.78, 0.88, 1, 1)
|
||||
|
||||
[sub_resource type="GradientTexture2D" id="GradientTexture2D_bg"]
|
||||
gradient = SubResource("Gradient_bg")
|
||||
width = 4
|
||||
height = 512
|
||||
fill_from = Vector2(0.5, 0)
|
||||
fill_to = Vector2(0.5, 1)
|
||||
|
||||
[sub_resource type="Environment" id="Environment_preview"]
|
||||
background_mode = 2
|
||||
sky = ExtResource("4_sky")
|
||||
ambient_light_source = 3
|
||||
reflected_light_source = 2
|
||||
|
||||
[node name="CharacterSelect" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_select")
|
||||
|
||||
[node name="Background" type="TextureRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
texture = SubResource("GradientTexture2D_bg")
|
||||
expand_mode = 1
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
theme_override_constants/margin_left = 48
|
||||
theme_override_constants/margin_top = 40
|
||||
theme_override_constants/margin_right = 48
|
||||
theme_override_constants/margin_bottom = 40
|
||||
|
||||
[node name="Layout" type="HBoxContainer" parent="Margin"]
|
||||
layout_mode = 2
|
||||
mouse_filter = 2
|
||||
theme_override_constants/separation = 36
|
||||
|
||||
[node name="Preview" type="SubViewportContainer" parent="Margin/Layout"]
|
||||
custom_minimum_size = Vector2(420, 520)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
size_flags_vertical = 4
|
||||
stretch = true
|
||||
|
||||
[node name="PreviewViewport" type="SubViewport" parent="Margin/Layout/Preview"]
|
||||
own_world_3d = true
|
||||
handle_input_locally = false
|
||||
size = Vector2i(420, 520)
|
||||
render_target_update_mode = 4
|
||||
|
||||
[node name="Environment" type="WorldEnvironment" parent="Margin/Layout/Preview/PreviewViewport"]
|
||||
environment = SubResource("Environment_preview")
|
||||
|
||||
[node name="Sun" type="DirectionalLight3D" parent="Margin/Layout/Preview/PreviewViewport"]
|
||||
transform = Transform3D(0.866025, -0.353553, 0.353553, 0, 0.707107, 0.707107, -0.5, -0.612372, 0.612372, 0, 3, 0)
|
||||
light_energy = 1.1
|
||||
|
||||
[node name="PreviewCamera" type="Camera3D" parent="Margin/Layout/Preview/PreviewViewport"]
|
||||
unique_name_in_owner = true
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.62, 1.5)
|
||||
|
||||
[node name="Model" type="Node3D" parent="Margin/Layout/Preview/PreviewViewport"]
|
||||
unique_name_in_owner = true
|
||||
transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 0)
|
||||
|
||||
[node name="mouse" parent="Margin/Layout/Preview/PreviewViewport/Model" instance=ExtResource("3_mouse")]
|
||||
transform = Transform3D(0.18240133, 0, 0, 0, 0.18240133, 0, 0, 0, 0.18240133, 0, 0, 0)
|
||||
|
||||
[node name="FaceRig" type="Node" parent="Margin/Layout/Preview/PreviewViewport"]
|
||||
unique_name_in_owner = true
|
||||
script = ExtResource("7_face_rig")
|
||||
mesh_path = NodePath("../Model/mouse/MouseRig/Skeleton3D/body_mesh")
|
||||
default_preset = ExtResource("5_face01")
|
||||
|
||||
[node name="Right" type="VBoxContainer" parent="Margin/Layout"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 14
|
||||
|
||||
[node name="CharTitle" type="Label" parent="Margin/Layout/Right"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(1, 1, 1, 1)
|
||||
theme_override_colors/font_outline_color = Color(0.05, 0.1, 0.25, 0.85)
|
||||
theme_override_constants/outline_size = 10
|
||||
theme_override_font_sizes/font_size = 40
|
||||
text = "角色"
|
||||
|
||||
[node name="CharRow" type="HBoxContainer" parent="Margin/Layout/Right"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="CharButton" type="Button" parent="Margin/Layout/Right/CharRow"]
|
||||
custom_minimum_size = Vector2(160, 46)
|
||||
layout_mode = 2
|
||||
text = "小老鼠"
|
||||
|
||||
[node name="Spacer" type="Control" parent="Margin/Layout/Right"]
|
||||
custom_minimum_size = Vector2(0, 12)
|
||||
layout_mode = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="FaceTitle" type="Label" parent="Margin/Layout/Right"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(1, 1, 1, 1)
|
||||
theme_override_colors/font_outline_color = Color(0.05, 0.1, 0.25, 0.85)
|
||||
theme_override_constants/outline_size = 8
|
||||
theme_override_font_sizes/font_size = 28
|
||||
text = "脸部"
|
||||
|
||||
[node name="PresetRow" type="HBoxContainer" parent="Margin/Layout/Right"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 12
|
||||
|
||||
[node name="Preset0" type="TextureButton" parent="Margin/Layout/Right/PresetRow"]
|
||||
custom_minimum_size = Vector2(96, 96)
|
||||
layout_mode = 2
|
||||
texture_normal = ExtResource("5_face01")
|
||||
ignore_texture_size = true
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="Preset1" type="TextureButton" parent="Margin/Layout/Right/PresetRow"]
|
||||
custom_minimum_size = Vector2(96, 96)
|
||||
layout_mode = 2
|
||||
texture_normal = ExtResource("6_face02")
|
||||
ignore_texture_size = true
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="DrawButton" type="Button" parent="Margin/Layout/Right"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(160, 46)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
text = "自己画"
|
||||
|
||||
[node name="Grow" type="Control" parent="Margin/Layout/Right"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="Bottom" type="HBoxContainer" parent="Margin/Layout/Right"]
|
||||
layout_mode = 2
|
||||
alignment = 2
|
||||
theme_override_constants/separation = 12
|
||||
|
||||
[node name="BackButton" type="Button" parent="Margin/Layout/Right/Bottom"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(130, 46)
|
||||
layout_mode = 2
|
||||
text = "返回"
|
||||
|
||||
[node name="ConfirmButton" type="Button" parent="Margin/Layout/Right/Bottom"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(130, 46)
|
||||
layout_mode = 2
|
||||
text = "确定"
|
||||
|
||||
[node name="PaintPanel" parent="." instance=ExtResource("2_paint")]
|
||||
visible = false
|
||||
+53
-13
@@ -1,30 +1,70 @@
|
||||
extends Control
|
||||
## 游戏开始菜单 —— 只有界面,没有逻辑。
|
||||
## 游戏开始菜单 —— 项目的入口场景(project.godot 的 run/main_scene)。
|
||||
##
|
||||
## 四个按钮按下去只把对应信号发出去:谁接、接了之后是加载世界、连服务器还是弹设置面板,
|
||||
## 都不在这个文件里。这样菜单版式可以单独改,主流程也能单独换。
|
||||
## 「开始游戏」→ 角色选择 → 确定 = 开主机并进世界。这个项目是客户端-服务器模型,
|
||||
## 单人玩也是本机开服(没有单独的「离线模式」)。
|
||||
## 「联机」把 Net 的创建/加入面板放出来,那边选完同样会切进世界。
|
||||
##
|
||||
## 接线示例(放在外层,比如以后的主场景或一个菜单流程单例):
|
||||
## menu.start_pressed.connect(_on_start)
|
||||
## menu.multiplayer_pressed.connect(_on_multiplayer)
|
||||
## 进世界、回菜单这两件事都归 Net 管(见 net.gd 的 _enter_world / leave_game),
|
||||
## 菜单只负责「按了哪个按钮」。
|
||||
##
|
||||
## 接线示例(放在外层):
|
||||
## menu.settings_pressed.connect(_on_settings)
|
||||
## menu.quit_pressed.connect(get_tree().quit)
|
||||
##
|
||||
## 用法:把本场景设成 project.godot 的 application/run/main_scene 即可(这一步也没做)。
|
||||
|
||||
const CHARACTER_SELECT := preload("res://ui/character_select.tscn")
|
||||
## 用节点路径而不是全局标识符访问单例:autoload 的全局名只在引擎启动时注册,
|
||||
## 这样写不依赖注册时机(和 world/main.gd 一致)
|
||||
const NET_PATH := "/root/Net"
|
||||
|
||||
signal start_pressed
|
||||
signal multiplayer_pressed
|
||||
signal settings_pressed
|
||||
signal quit_pressed
|
||||
|
||||
var _net: Node
|
||||
var _character_select: Control
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
# 从游戏内退回菜单时鼠标可能还是被捕获的,先放出来 —— 不然菜单点不动。
|
||||
# 这不算逻辑,是菜单能不能用的前提。
|
||||
# 从游戏内退回菜单时鼠标可能还是被捕获的,先放出来 —— 不然菜单点不动
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
_net = get_node_or_null(NET_PATH)
|
||||
# Net 那个「联机测试」面板默认可见(CanvasLayer layer 20),会正好盖在菜单中间
|
||||
if _net != null:
|
||||
_net.set_menu_visible(false)
|
||||
|
||||
# 按钮只负责把 pressed 转发成信号,不在这里做任何决定。
|
||||
%StartButton.pressed.connect(start_pressed.emit)
|
||||
%MultiplayerButton.pressed.connect(multiplayer_pressed.emit)
|
||||
%StartButton.pressed.connect(_on_start_pressed)
|
||||
%MultiplayerButton.pressed.connect(_on_multiplayer_pressed)
|
||||
%SettingsButton.pressed.connect(settings_pressed.emit)
|
||||
%QuitButton.pressed.connect(quit_pressed.emit)
|
||||
|
||||
|
||||
## 「开始游戏」:先把信号发出去(外部想接就接),再把角色选择盖上来
|
||||
func _on_start_pressed() -> void:
|
||||
start_pressed.emit()
|
||||
if _character_select == null:
|
||||
_character_select = CHARACTER_SELECT.instantiate()
|
||||
add_child(_character_select)
|
||||
_character_select.confirmed.connect(_on_character_confirmed)
|
||||
_character_select.back_pressed.connect(_close_character_select)
|
||||
_character_select.visible = true
|
||||
|
||||
|
||||
## 角色选择点「确定」:脸这时已经写进 user:// 了,开主机,Net 负责切进世界
|
||||
func _on_character_confirmed() -> void:
|
||||
_close_character_select()
|
||||
if _net != null:
|
||||
_net.host_game()
|
||||
|
||||
|
||||
func _on_multiplayer_pressed() -> void:
|
||||
multiplayer_pressed.emit()
|
||||
# 暂时直接把 Net 那个创建/加入面板放出来顶着 —— 换成正经的联机子页后删掉这行
|
||||
if _net != null:
|
||||
_net.set_menu_visible(true)
|
||||
|
||||
|
||||
func _close_character_select() -> void:
|
||||
if _character_select != null:
|
||||
_character_select.visible = false
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
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()
|
||||
@@ -0,0 +1 @@
|
||||
uid://cjxxd2m1w00y5
|
||||
@@ -0,0 +1,148 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://ui/paint_panel.gd" id="1_paint"]
|
||||
|
||||
[node name="PaintPanel" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_paint")
|
||||
|
||||
[node name="Dim" type="ColorRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
color = Color(0.03, 0.06, 0.14, 0.72)
|
||||
|
||||
[node name="Center" type="CenterContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="Panel" type="PanelContainer" parent="Center"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="Center/Panel"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 18
|
||||
theme_override_constants/margin_top = 14
|
||||
theme_override_constants/margin_right = 18
|
||||
theme_override_constants/margin_bottom = 14
|
||||
|
||||
[node name="Box" type="VBoxContainer" parent="Center/Panel/Margin"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 12
|
||||
|
||||
[node name="Title" type="Label" parent="Center/Panel/Margin/Box"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 22
|
||||
text = "画脸"
|
||||
|
||||
[node name="Row" type="HBoxContainer" parent="Center/Panel/Margin/Box"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 16
|
||||
|
||||
[node name="CanvasStack" type="Control" parent="Center/Panel/Margin/Box/Row"]
|
||||
custom_minimum_size = Vector2(420, 420)
|
||||
layout_mode = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="PresetRect" type="TextureRect" parent="Center/Panel/Margin/Box/Row/CanvasStack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
expand_mode = 1
|
||||
|
||||
[node name="StrokeRect" type="TextureRect" parent="Center/Panel/Margin/Box/Row/CanvasStack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 0
|
||||
expand_mode = 1
|
||||
|
||||
[node name="Tools" type="VBoxContainer" parent="Center/Panel/Margin/Box/Row"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="ColorButton" type="ColorPickerButton" parent="Center/Panel/Margin/Box/Row/Tools"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(160, 36)
|
||||
layout_mode = 2
|
||||
color = Color(0, 0, 0, 1)
|
||||
|
||||
[node name="WidthRow" type="HBoxContainer" parent="Center/Panel/Margin/Box/Row/Tools"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="Label" type="Label" parent="Center/Panel/Margin/Box/Row/Tools/WidthRow"]
|
||||
layout_mode = 2
|
||||
text = "粗细"
|
||||
|
||||
[node name="WidthSlider" type="HSlider" parent="Center/Panel/Margin/Box/Row/Tools/WidthRow"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(110, 0)
|
||||
layout_mode = 2
|
||||
min_value = 2.0
|
||||
max_value = 48.0
|
||||
value = 12.0
|
||||
|
||||
[node name="WidthLabel" type="Label" parent="Center/Panel/Margin/Box/Row/Tools/WidthRow"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(52, 0)
|
||||
layout_mode = 2
|
||||
text = "12 px"
|
||||
|
||||
[node name="EraserToggle" type="CheckButton" parent="Center/Panel/Margin/Box/Row/Tools"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "橡皮"
|
||||
|
||||
[node name="ClearButton" type="Button" parent="Center/Panel/Margin/Box/Row/Tools"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "清空"
|
||||
|
||||
[node name="Hint" type="Label" parent="Center/Panel/Margin/Box/Row/Tools"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(160, 0)
|
||||
modulate = Color(1, 1, 1, 0.65)
|
||||
theme_override_font_sizes/font_size = 13
|
||||
text = "在左边画布上直接画。
|
||||
橡皮擦掉笔画,露出来的是预设脸。"
|
||||
autowrap_mode = 3
|
||||
|
||||
[node name="Bottom" type="HBoxContainer" parent="Center/Panel/Margin/Box"]
|
||||
layout_mode = 2
|
||||
alignment = 2
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="CancelButton" type="Button" parent="Center/Panel/Margin/Box/Bottom"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(120, 40)
|
||||
layout_mode = 2
|
||||
text = "取消"
|
||||
|
||||
[node name="DoneButton" type="Button" parent="Center/Panel/Margin/Box/Bottom"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(120, 40)
|
||||
layout_mode = 2
|
||||
text = "完成"
|
||||
Reference in New Issue
Block a user