功能批量实现 优化 服务端 新浮动小游戏框架 新界面UI

This commit is contained in:
2026-04-24 13:20:16 +08:00
parent e0bc0bbf08
commit 5eec879582
257 changed files with 38481 additions and 1288 deletions
@@ -1,10 +1,10 @@
#if UNITY_EDITOR
#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using System.Collections.Generic;
public class IngameGalgameGraphWindow : EditorWindow
{
@@ -46,15 +46,18 @@ public class IngameGalgameGraphWindow : EditorWindow
private void CreateToolbar()
{
var toolbar = new Toolbar();
var addBtn = new ToolbarButton(() => graphView?.CreateNodeAt(graphView.GetDefaultNodePosition())) { text = "+" };
var delBtn = new ToolbarButton(() => graphView?.DeleteSelected()) { text = "-" };
toolbar.Add(addBtn);
toolbar.Add(delBtn);
soField = new ObjectField("数据") { objectType = typeof(ingame_galgame_so), allowSceneObjects = false };
toolbar.Add(new ToolbarButton(() => graphView?.CreateNodeAt(graphView.GetDefaultNodePosition())) { text = "+" });
toolbar.Add(new ToolbarButton(() => graphView?.DeleteSelected()) { text = "-" });
soField = new ObjectField("数据")
{
objectType = typeof(ingame_galgame_so),
allowSceneObjects = false
};
soField.RegisterValueChangedCallback(evt => SetSo(evt.newValue as ingame_galgame_so));
toolbar.Add(soField);
var refreshBtn = new ToolbarButton(RefreshGraph) { text = "刷新" };
toolbar.Add(refreshBtn);
toolbar.Add(new ToolbarButton(RefreshGraph) { text = "刷新" });
rootVisualElement.Add(toolbar);
}
@@ -70,12 +73,16 @@ public class IngameGalgameGraphWindow : EditorWindow
private void RefreshGraph()
{
if (graphView == null) return;
if (graphView == null)
{
return;
}
graphView.Build(currentSo);
}
}
class IngameGalgameGraphView : GraphView
internal class IngameGalgameGraphView : GraphView
{
private ingame_galgame_so currentSo;
@@ -85,9 +92,11 @@ class IngameGalgameGraphView : GraphView
this.AddManipulator(new ContentDragger());
this.AddManipulator(new SelectionDragger());
this.AddManipulator(new RectangleSelector());
var grid = new GridBackground();
Insert(0, grid);
grid.StretchToParentSize();
SetupZoom(ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale);
RegisterCallback<KeyDownEvent>(OnKeyDown);
}
@@ -96,18 +105,30 @@ class IngameGalgameGraphView : GraphView
{
ClearGraph();
currentSo = so;
if (so == null) return;
if (so == null)
{
return;
}
var serialized = new SerializedObject(so);
var dialogueList = serialized.FindProperty("dialogueList");
if (dialogueList == null || !dialogueList.isArray) return;
var speakerNames = BuildSpeakerNames(serialized);
if (dialogueList == null || !dialogueList.isArray)
{
return;
}
string[] speakerNames = BuildSpeakerNames(serialized);
DialogueTextNode prev = null;
for (int i = 0; i < dialogueList.arraySize; i++)
{
var elemProp = dialogueList.GetArrayElementAtIndex(i);
var positionTagProp = elemProp.FindPropertyRelative("positionTag");
var textListProp = elemProp.FindPropertyRelative("textList");
if (textListProp == null || !textListProp.isArray) continue;
if (textListProp == null || !textListProp.isArray)
{
continue;
}
for (int j = 0; j < textListProp.arraySize; j++)
{
var textProp = textListProp.GetArrayElementAtIndex(j);
@@ -116,8 +137,7 @@ class IngameGalgameGraphView : GraphView
AddElement(node);
if (prev != null)
{
var edge = prev.output.ConnectTo(node.input);
AddElement(edge);
AddElement(prev.output.ConnectTo(node.input));
}
prev = node;
}
@@ -136,10 +156,12 @@ class IngameGalgameGraphView : GraphView
private DialogueTextNode CreateTextNode(SerializedObject so, SerializedProperty positionTagProp, SerializedProperty textProp, string[] speakerNames, int elemIndex, int textIndex)
{
var node = new DialogueTextNode();
node.title = $"段落 {elemIndex + 1} / 文本 {textIndex + 1}";
node.elementIndex = elemIndex;
node.textIndex = textIndex;
var node = new DialogueTextNode
{
title = $"段落 {elemIndex + 1} / 文本 {textIndex + 1}",
elementIndex = elemIndex,
textIndex = textIndex
};
node.input = node.InstantiatePort(Orientation.Horizontal, Direction.Input, Port.Capacity.Multi, typeof(float));
node.input.portName = "输入";
node.output = node.InstantiatePort(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, typeof(float));
@@ -149,18 +171,18 @@ class IngameGalgameGraphView : GraphView
if (positionTagProp != null)
{
var field = new PropertyField(positionTagProp, "位置标签");
var field = new PropertyField(positionTagProp, "显示模式");
field.Bind(so);
node.extensionContainer.Add(field);
}
AddTextField(node, so, textProp.FindPropertyRelative("content"), "内容");
AddIntPopup(node, so, textProp.FindPropertyRelative("senderIndex"), speakerNames);
AddField(node, so, textProp.FindPropertyRelative("content"), "文本内容");
AddSpeakerPopup(node, so, textProp.FindPropertyRelative("senderIndex"), speakerNames);
AddField(node, so, textProp.FindPropertyRelative("imagePos"), "图片位置");
AddField(node, so, textProp.FindPropertyRelative("flipX"), "水平翻转");
AddField(node, so, textProp.FindPropertyRelative("appearTime"), "出现时间");
AddField(node, so, textProp.FindPropertyRelative("duration"), "持续时间");
AddField(node, so, textProp.FindPropertyRelative("stepMethod"), "进方式");
AddField(node, so, textProp.FindPropertyRelative("stepMethod"), "进方式");
AddField(node, so, textProp.FindPropertyRelative("timeAction"), "时间动作");
AddField(node, so, textProp.FindPropertyRelative("slowMotionScale"), "慢动作倍率");
AddField(node, so, textProp.FindPropertyRelative("endAction"), "结束动作");
@@ -170,35 +192,36 @@ class IngameGalgameGraphView : GraphView
return node;
}
private void AddTextField(Node node, SerializedObject so, SerializedProperty prop, string label)
private static void AddField(Node node, SerializedObject so, SerializedProperty prop, string label)
{
if (prop == null) return;
var field = new PropertyField(prop, label);
field.Bind(so);
node.extensionContainer.Add(field);
}
private void AddField(Node node, SerializedObject so, SerializedProperty prop, string label)
{
if (prop == null) return;
var field = new PropertyField(prop, label);
field.Bind(so);
node.extensionContainer.Add(field);
}
private void AddIntPopup(Node node, SerializedObject so, SerializedProperty senderIndexProp, string[] speakerNames)
{
if (senderIndexProp == null) return;
if (speakerNames == null || speakerNames.Length == 0)
if (prop == null)
{
AddField(node, so, senderIndexProp, "发言者索引");
return;
}
var field = new PropertyField(prop, label);
field.Bind(so);
node.extensionContainer.Add(field);
}
private static void AddSpeakerPopup(Node node, SerializedObject so, SerializedProperty senderIndexProp, string[] speakerNames)
{
if (senderIndexProp == null)
{
return;
}
if (speakerNames == null || speakerNames.Length == 0)
{
AddField(node, so, senderIndexProp, "发言人");
return;
}
var imgui = new IMGUIContainer(() =>
{
so.Update();
int current = Mathf.Clamp(senderIndexProp.intValue + 1, 0, speakerNames.Length - 1);
int chosen = EditorGUILayout.Popup("发言", current, speakerNames);
int chosen = EditorGUILayout.Popup("发言", current, speakerNames);
senderIndexProp.intValue = Mathf.Max(-1, chosen - 1);
so.ApplyModifiedProperties();
});
@@ -208,17 +231,25 @@ class IngameGalgameGraphView : GraphView
private string[] BuildSpeakerNames(SerializedObject so)
{
var speakersDataSOProp = so.FindProperty("speakersDataSO");
if (speakersDataSOProp == null || speakersDataSOProp.objectReferenceValue == null) return null;
if (speakersDataSOProp == null || speakersDataSOProp.objectReferenceValue == null)
{
return null;
}
var speakersSO = new SerializedObject(speakersDataSOProp.objectReferenceValue);
var speakersList = speakersSO.FindProperty("speakers");
if (speakersList == null || !speakersList.isArray) return null;
int c = speakersList.arraySize;
var names = new string[c + 1];
names[0] = "(无)";
for (int i = 0; i < c; i++)
if (speakersList == null || !speakersList.isArray)
{
var e = speakersList.GetArrayElementAtIndex(i);
var nameProp = e.FindPropertyRelative("spkrName");
return null;
}
int count = speakersList.arraySize;
string[] names = new string[count + 1];
names[0] = "(无)";
for (int i = 0; i < count; i++)
{
var element = speakersList.GetArrayElementAtIndex(i);
var nameProp = element.FindPropertyRelative("spkrName");
names[i + 1] = nameProp != null ? nameProp.stringValue : $"Speaker {i}";
}
return names;
@@ -232,11 +263,19 @@ class IngameGalgameGraphView : GraphView
public void CreateNodeAt(Vector2 position)
{
if (currentSo == null) return;
if (currentSo == null)
{
return;
}
Undo.RecordObject(currentSo, "Create Dialogue Node");
var so = new SerializedObject(currentSo);
var dialogueList = so.FindProperty("dialogueList");
if (dialogueList == null) return;
if (dialogueList == null)
{
return;
}
int elemIndex = dialogueList.arraySize;
dialogueList.arraySize++;
var elemProp = dialogueList.GetArrayElementAtIndex(elemIndex);
@@ -252,6 +291,7 @@ class IngameGalgameGraphView : GraphView
ApplyTextDefaults(textProp);
}
}
so.ApplyModifiedProperties();
EditorUtility.SetDirty(currentSo);
Build(currentSo);
@@ -259,38 +299,69 @@ class IngameGalgameGraphView : GraphView
public void DeleteSelected()
{
if (currentSo == null) return;
var selectedNodes = new List<DialogueTextNode>();
foreach (var e in selection)
if (currentSo == null)
{
if (e is DialogueTextNode n) selectedNodes.Add(n);
return;
}
if (selectedNodes.Count == 0) return;
var selectedNodes = new List<DialogueTextNode>();
foreach (var element in selection)
{
if (element is DialogueTextNode node)
{
selectedNodes.Add(node);
}
}
if (selectedNodes.Count == 0)
{
return;
}
selectedNodes.Sort((a, b) =>
{
int c = b.elementIndex.CompareTo(a.elementIndex);
return c != 0 ? c : b.textIndex.CompareTo(a.textIndex);
int compare = b.elementIndex.CompareTo(a.elementIndex);
return compare != 0 ? compare : b.textIndex.CompareTo(a.textIndex);
});
Undo.RecordObject(currentSo, "Delete Dialogue Node");
var so = new SerializedObject(currentSo);
var dialogueList = so.FindProperty("dialogueList");
if (dialogueList == null) return;
if (dialogueList == null)
{
return;
}
foreach (var node in selectedNodes)
{
if (node.elementIndex < 0 || node.elementIndex >= dialogueList.arraySize) continue;
if (node.elementIndex < 0 || node.elementIndex >= dialogueList.arraySize)
{
continue;
}
var elemProp = dialogueList.GetArrayElementAtIndex(node.elementIndex);
if (elemProp == null) continue;
if (elemProp == null)
{
continue;
}
var textListProp = elemProp.FindPropertyRelative("textList");
if (textListProp == null) continue;
if (textListProp == null)
{
continue;
}
if (node.textIndex >= 0 && node.textIndex < textListProp.arraySize)
{
textListProp.DeleteArrayElementAtIndex(node.textIndex);
}
if (textListProp.arraySize <= 0)
{
dialogueList.DeleteArrayElementAtIndex(node.elementIndex);
}
}
so.ApplyModifiedProperties();
EditorUtility.SetDirty(currentSo);
Build(currentSo);
@@ -299,12 +370,18 @@ class IngameGalgameGraphView : GraphView
public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
{
evt.menu.AppendAction("新建节点", _ => CreateNodeAt(evt.localMousePosition));
bool hasSelection = false;
foreach (var e in selection)
foreach (var element in selection)
{
if (e is DialogueTextNode) { hasSelection = true; break; }
if (element is DialogueTextNode)
{
hasSelection = true;
break;
}
}
evt.menu.AppendAction("删除所选", _ => DeleteSelected(), hasSelection ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled);
evt.menu.AppendAction("删除选中", _ => DeleteSelected(), hasSelection ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled);
}
private void OnKeyDown(KeyDownEvent evt)
@@ -328,65 +405,108 @@ class IngameGalgameGraphView : GraphView
private string SerializeSelection()
{
if (currentSo == null) return string.Empty;
if (currentSo == null)
{
return string.Empty;
}
var payload = new DialogueCopyPayload();
var so = new SerializedObject(currentSo);
var dialogueList = so.FindProperty("dialogueList");
foreach (var e in selection)
foreach (var element in selection)
{
if (e is not DialogueTextNode node) continue;
if (dialogueList == null) continue;
if (node.elementIndex < 0 || node.elementIndex >= dialogueList.arraySize) continue;
if (element is not DialogueTextNode node || dialogueList == null)
{
continue;
}
if (node.elementIndex < 0 || node.elementIndex >= dialogueList.arraySize)
{
continue;
}
var elemProp = dialogueList.GetArrayElementAtIndex(node.elementIndex);
if (elemProp == null) continue;
var positionTagProp = elemProp.FindPropertyRelative("positionTag");
var textListProp = elemProp.FindPropertyRelative("textList");
if (textListProp == null) continue;
if (node.textIndex < 0 || node.textIndex >= textListProp.arraySize) continue;
var positionTagProp = elemProp?.FindPropertyRelative("positionTag");
var textListProp = elemProp?.FindPropertyRelative("textList");
if (textListProp == null || node.textIndex < 0 || node.textIndex >= textListProp.arraySize)
{
continue;
}
var textProp = textListProp.GetArrayElementAtIndex(node.textIndex);
if (textProp == null) continue;
var data = new DialogueTextCopyData();
data.positionTag = positionTagProp != null ? positionTagProp.enumValueIndex : 0;
data.content = textProp.FindPropertyRelative("content")?.stringValue;
data.textNumber = textProp.FindPropertyRelative("textNumber")?.intValue ?? 0;
data.senderIndex = textProp.FindPropertyRelative("senderIndex")?.intValue ?? -1;
data.imagePos = textProp.FindPropertyRelative("imagePos")?.enumValueIndex ?? 0;
data.flipX = textProp.FindPropertyRelative("flipX")?.boolValue ?? false;
data.appearTime = textProp.FindPropertyRelative("appearTime")?.floatValue ?? 0f;
data.duration = textProp.FindPropertyRelative("duration")?.floatValue ?? 0f;
data.stepMethod = textProp.FindPropertyRelative("stepMethod")?.enumValueIndex ?? 0;
data.timeAction = textProp.FindPropertyRelative("timeAction")?.enumValueIndex ?? 0;
data.slowMotionScale = textProp.FindPropertyRelative("slowMotionScale")?.floatValue ?? 0f;
data.endAction = textProp.FindPropertyRelative("endAction")?.enumValueIndex ?? 0;
payload.items.Add(data);
if (textProp == null)
{
continue;
}
payload.items.Add(new DialogueTextCopyData
{
positionTag = positionTagProp != null ? positionTagProp.enumValueIndex : 0,
content = textProp.FindPropertyRelative("content")?.stringValue,
textNumber = textProp.FindPropertyRelative("textNumber")?.intValue ?? 0,
senderIndex = textProp.FindPropertyRelative("senderIndex")?.intValue ?? -1,
imagePos = textProp.FindPropertyRelative("imagePos")?.enumValueIndex ?? 0,
flipX = textProp.FindPropertyRelative("flipX")?.boolValue ?? false,
appearTime = textProp.FindPropertyRelative("appearTime")?.floatValue ?? 0f,
duration = textProp.FindPropertyRelative("duration")?.floatValue ?? 0f,
stepMethod = textProp.FindPropertyRelative("stepMethod")?.enumValueIndex ?? 0,
timeAction = textProp.FindPropertyRelative("timeAction")?.enumValueIndex ?? 0,
slowMotionScale = textProp.FindPropertyRelative("slowMotionScale")?.floatValue ?? 0f,
endAction = textProp.FindPropertyRelative("endAction")?.enumValueIndex ?? 0
});
}
return payload.items.Count == 0 ? string.Empty : JsonUtility.ToJson(payload);
}
private bool CanPasteDataInternal(string serializedData)
private static bool CanPasteDataInternal(string serializedData)
{
if (string.IsNullOrEmpty(serializedData)) return false;
if (string.IsNullOrEmpty(serializedData))
{
return false;
}
var payload = JsonUtility.FromJson<DialogueCopyPayload>(serializedData);
return payload != null && payload.items != null && payload.items.Count > 0;
}
private void PasteFromClipboard()
{
if (currentSo == null) return;
if (currentSo == null)
{
return;
}
var serializedData = EditorGUIUtility.systemCopyBuffer;
if (!CanPasteDataInternal(serializedData)) return;
if (!CanPasteDataInternal(serializedData))
{
return;
}
var payload = JsonUtility.FromJson<DialogueCopyPayload>(serializedData);
if (payload == null || payload.items == null || payload.items.Count == 0) return;
if (payload == null || payload.items == null || payload.items.Count == 0)
{
return;
}
Undo.RecordObject(currentSo, "Paste Dialogue Node");
var so = new SerializedObject(currentSo);
var dialogueList = so.FindProperty("dialogueList");
if (dialogueList == null) return;
if (dialogueList == null)
{
return;
}
foreach (var item in payload.items)
{
int elemIndex = dialogueList.arraySize;
dialogueList.arraySize++;
var elemProp = dialogueList.GetArrayElementAtIndex(elemIndex);
if (elemProp == null) continue;
if (elemProp == null)
{
continue;
}
var positionTagProp = elemProp.FindPropertyRelative("positionTag");
if (positionTagProp != null) positionTagProp.enumValueIndex = item.positionTag;
var textListProp = elemProp.FindPropertyRelative("textList");
@@ -397,14 +517,19 @@ class IngameGalgameGraphView : GraphView
ApplyTextData(textProp, item);
}
}
so.ApplyModifiedProperties();
EditorUtility.SetDirty(currentSo);
Build(currentSo);
}
private void ApplyTextDefaults(SerializedProperty textProp)
private static void ApplyTextDefaults(SerializedProperty textProp)
{
if (textProp == null) return;
if (textProp == null)
{
return;
}
textProp.FindPropertyRelative("content").stringValue = string.Empty;
textProp.FindPropertyRelative("textNumber").intValue = 1;
textProp.FindPropertyRelative("senderIndex").intValue = -1;
@@ -412,16 +537,20 @@ class IngameGalgameGraphView : GraphView
var flipXProp = textProp.FindPropertyRelative("flipX");
if (flipXProp != null) flipXProp.boolValue = false;
textProp.FindPropertyRelative("appearTime").floatValue = 0f;
textProp.FindPropertyRelative("duration").floatValue = 0f;
textProp.FindPropertyRelative("duration").floatValue = 1f;
textProp.FindPropertyRelative("stepMethod").enumValueIndex = 0;
textProp.FindPropertyRelative("timeAction").enumValueIndex = 0;
textProp.FindPropertyRelative("slowMotionScale").floatValue = 0f;
textProp.FindPropertyRelative("slowMotionScale").floatValue = 0.5f;
textProp.FindPropertyRelative("endAction").enumValueIndex = 0;
}
private void ApplyTextData(SerializedProperty textProp, DialogueTextCopyData data)
private static void ApplyTextData(SerializedProperty textProp, DialogueTextCopyData data)
{
if (textProp == null || data == null) return;
if (textProp == null || data == null)
{
return;
}
textProp.FindPropertyRelative("content").stringValue = data.content ?? string.Empty;
textProp.FindPropertyRelative("textNumber").intValue = data.textNumber;
textProp.FindPropertyRelative("senderIndex").intValue = data.senderIndex;
@@ -460,7 +589,7 @@ class IngameGalgameGraphView : GraphView
}
}
class DialogueTextNode : Node
internal class DialogueTextNode : Node
{
public Port input;
public Port output;
@@ -1,9 +1,10 @@
#if UNITY_EDITOR
#if UNITY_EDITOR
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
using System.Collections.Generic;
using System.Reflection;
[CustomEditor(typeof(ScriptableObject), true)]
public class ingame_galgame_so_Editor : Editor
@@ -12,6 +13,7 @@ public class ingame_galgame_so_Editor : Editor
private SerializedProperty timelineStandardProp;
private SerializedProperty dialogueTypeProp;
private SerializedProperty speakersDataSOProp;
private SerializedProperty jsonDefinitionAssetProp;
private SerializedProperty dialogueListProp;
private ReorderableList dialogueListRl;
@@ -20,18 +22,18 @@ public class ingame_galgame_so_Editor : Editor
private readonly GUIContent labelDialogueType = new GUIContent("对话类型");
private readonly GUIContent labelTriggerMethod = new GUIContent("触发方式");
private readonly GUIContent labelTimelineStandard = new GUIContent("时间线基准");
private readonly GUIContent labelTimelineStandard = new GUIContent("时间基准");
private readonly GUIContent labelSpeakers = new GUIContent("角色数据");
private readonly GUIContent labelPositionTag = new GUIContent("位置标签");
private readonly GUIContent labelContent = new GUIContent("内容");
private readonly GUIContent labelTextNumber = new GUIContent("文本序号");
private readonly GUIContent labelSender = new GUIContent("发言者");
private readonly GUIContent labelSenderIndex = new GUIContent("发言者索引");
private readonly GUIContent labelJsonDefinition = new GUIContent("JSON 覆盖源");
private readonly GUIContent labelPositionTag = new GUIContent("显示模式");
private readonly GUIContent labelContent = new GUIContent("文本内容");
private readonly GUIContent labelTextNumber = new GUIContent("文本编号");
private readonly GUIContent labelSenderIndex = new GUIContent("发言");
private readonly GUIContent labelImagePos = new GUIContent("图片位置");
private readonly GUIContent labelFlipX = new GUIContent("水平翻转");
private readonly GUIContent labelAppearTime = new GUIContent("出现时间");
private readonly GUIContent labelDuration = new GUIContent("持续时间");
private readonly GUIContent labelStepMethod = new GUIContent("进方式");
private readonly GUIContent labelStepMethod = new GUIContent("进方式");
private readonly GUIContent labelTimeAction = new GUIContent("时间动作");
private readonly GUIContent labelSlowMotionScale = new GUIContent("慢动作倍率");
private readonly GUIContent labelEndAction = new GUIContent("结束动作");
@@ -44,8 +46,10 @@ public class ingame_galgame_so_Editor : Editor
timelineStandardProp = serializedObject.FindProperty("timelineStandard");
dialogueTypeProp = serializedObject.FindProperty("dialogueType");
speakersDataSOProp = serializedObject.FindProperty("speakersDataSO");
jsonDefinitionAssetProp = serializedObject.FindProperty("jsonDefinitionAsset");
dialogueListProp = serializedObject.FindProperty("dialogueList");
}
dialogueListRl = null;
textListRlMap.Clear();
}
@@ -76,15 +80,101 @@ public class ingame_galgame_so_Editor : Editor
if (triggerMethodProp != null) EditorGUILayout.PropertyField(triggerMethodProp, labelTriggerMethod);
if (timelineStandardProp != null) EditorGUILayout.PropertyField(timelineStandardProp, labelTimelineStandard);
if (speakersDataSOProp != null) EditorGUILayout.PropertyField(speakersDataSOProp, labelSpeakers);
if (jsonDefinitionAssetProp != null) EditorGUILayout.PropertyField(jsonDefinitionAssetProp, labelJsonDefinition);
var so = target as ingame_galgame_so;
if (so != null)
{
EditorGUILayout.HelpBox(
so.HasJsonOverride
? "当前已配置 JSON 覆盖源,运行时将优先使用 JSON 内容。"
: "当前未配置 JSON 覆盖源,运行时将使用 Inspector 中的配置。",
MessageType.Info);
}
if (GUILayout.Button("打开蓝图视图"))
{
var t = typeof(ingame_galgame_so_Editor).Assembly.GetType("IngameGalgameGraphWindow");
var m = t != null ? t.GetMethod("Open", BindingFlags.Public | BindingFlags.Static) : null;
if (m != null) m.Invoke(null, new object[] { target as ingame_galgame_so });
var type = typeof(ingame_galgame_so_Editor).Assembly.GetType("IngameGalgameGraphWindow");
var method = type != null ? type.GetMethod("Open", BindingFlags.Public | BindingFlags.Static) : null;
if (method != null)
{
method.Invoke(null, new object[] { target as ingame_galgame_so });
}
}
if (GUILayout.Button("导出当前 SO 为 JSON"))
{
ExportCurrentSoAsJson();
}
using (new EditorGUI.DisabledScope(so == null || !so.HasJsonOverride))
{
if (GUILayout.Button("从 JSON 一键填入当前 SO"))
{
ImportJsonIntoCurrentSo();
}
}
EditorGUILayout.EndVertical();
}
private void ExportCurrentSoAsJson()
{
if (target is not ingame_galgame_so so)
{
return;
}
string defaultName = $"{so.name}.json";
string path = EditorUtility.SaveFilePanelInProject(
"导出 Galgame JSON",
defaultName,
"json",
"选择 JSON 导出位置");
if (string.IsNullOrWhiteSpace(path))
{
return;
}
File.WriteAllText(path, so.ExportCurrentDefinitionToJson(true), System.Text.Encoding.UTF8);
AssetDatabase.Refresh();
TextAsset exportedJson = AssetDatabase.LoadAssetAtPath<TextAsset>(path);
if (jsonDefinitionAssetProp != null && exportedJson != null)
{
serializedObject.Update();
jsonDefinitionAssetProp.objectReferenceValue = exportedJson;
serializedObject.ApplyModifiedProperties();
}
EditorGUIUtility.PingObject(exportedJson);
}
private void ImportJsonIntoCurrentSo()
{
if (target is not ingame_galgame_so so)
{
return;
}
serializedObject.ApplyModifiedProperties();
if (!so.TryApplyJsonOverrideToInspector(out string errorMessage))
{
EditorUtility.DisplayDialog("导入 JSON 失败", string.IsNullOrWhiteSpace(errorMessage) ? "无法从 JSON 回填到当前 SO。" : errorMessage, "确定");
return;
}
EditorUtility.SetDirty(so);
serializedObject.Update();
dialogueListRl = null;
textListRlMap.Clear();
Repaint();
AssetDatabase.SaveAssets();
EditorUtility.DisplayDialog("导入成功", "已将当前 JSON 内容回填到 SO Inspector 数据中。", "确定");
}
private void DrawDialogueSection()
{
if (dialogueListProp == null)
@@ -100,112 +190,133 @@ public class ingame_galgame_so_Editor : Editor
private void EnsureDialogueList()
{
if (dialogueListRl != null && dialogueListRl.serializedProperty == dialogueListProp) return;
if (dialogueListRl != null && dialogueListRl.serializedProperty == dialogueListProp)
{
return;
}
dialogueListRl = new ReorderableList(serializedObject, dialogueListProp, true, true, true, true);
dialogueListRl.drawHeaderCallback = rect => EditorGUI.LabelField(rect, "对话列表");
dialogueListRl.drawHeaderCallback = rect => EditorGUI.LabelField(rect, "对话段落列表");
dialogueListRl.elementHeightCallback = index =>
{
var elemProp = dialogueListProp.GetArrayElementAtIndex(index);
float line = EditorGUIUtility.singleLineHeight;
float space = EditorGUIUtility.standardVerticalSpacing;
float h = line + space;
float height = line + space;
if (elemProp != null && elemProp.isExpanded)
{
var positionTagProp = elemProp.FindPropertyRelative("positionTag");
if (positionTagProp != null) h += EditorGUI.GetPropertyHeight(positionTagProp, true) + space;
if (positionTagProp != null) height += EditorGUI.GetPropertyHeight(positionTagProp, true) + space;
var textListProp = elemProp.FindPropertyRelative("textList");
if (textListProp != null)
{
var rl = GetTextList(textListProp);
if (rl != null) h += rl.GetHeight() + space;
if (rl != null) height += rl.GetHeight() + space;
}
}
return h;
return height;
};
dialogueListRl.drawElementCallback = (rect, index, isActive, isFocused) =>
{
var elemProp = dialogueListProp.GetArrayElementAtIndex(index);
if (elemProp == null) return;
if (elemProp == null)
{
return;
}
float line = EditorGUIUtility.singleLineHeight;
float space = EditorGUIUtility.standardVerticalSpacing;
Rect r = rect;
r.height = line;
elemProp.isExpanded = EditorGUI.Foldout(r, elemProp.isExpanded, $"段落 {index + 1}", true);
if (!elemProp.isExpanded) return;
r.y += line + space;
Rect rowRect = rect;
rowRect.height = line;
elemProp.isExpanded = EditorGUI.Foldout(rowRect, elemProp.isExpanded, $"段落 {index + 1}", true);
if (!elemProp.isExpanded)
{
return;
}
rowRect.y += line + space;
EditorGUI.indentLevel++;
var positionTagProp = elemProp.FindPropertyRelative("positionTag");
if (positionTagProp != null)
{
EditorGUI.PropertyField(r, positionTagProp, labelPositionTag);
r.y += line + space;
EditorGUI.PropertyField(rowRect, positionTagProp, labelPositionTag);
rowRect.y += line + space;
}
var textListProp = elemProp.FindPropertyRelative("textList");
if (textListProp != null)
{
var rl = GetTextList(textListProp);
if (rl != null)
{
r.height = rl.GetHeight();
rl.DoList(r);
rowRect.height = rl.GetHeight();
rl.DoList(rowRect);
}
}
EditorGUI.indentLevel--;
};
}
private ReorderableList GetTextList(SerializedProperty textListProp)
{
if (textListProp == null) return null;
if (textListProp == null)
{
return null;
}
string key = textListProp.propertyPath;
if (!textListRlMap.TryGetValue(key, out var rl) || rl.serializedProperty != textListProp)
{
rl = new ReorderableList(serializedObject, textListProp, true, true, true, true);
rl.drawHeaderCallback = rect => EditorGUI.LabelField(rect, "文本列表");
rl.elementHeightCallback = index =>
{
var textProp = textListProp.GetArrayElementAtIndex(index);
return GetTextElementHeight(textProp);
};
rl.drawElementCallback = (rect, index, isActive, isFocused) =>
{
var textProp = textListProp.GetArrayElementAtIndex(index);
DrawTextElement(rect, textProp);
};
rl.elementHeightCallback = index => GetTextElementHeight(textListProp.GetArrayElementAtIndex(index));
rl.drawElementCallback = (rect, index, isActive, isFocused) => DrawTextElement(rect, textListProp.GetArrayElementAtIndex(index));
textListRlMap[key] = rl;
}
return rl;
}
private float GetTextElementHeight(SerializedProperty textProp)
{
if (textProp == null) return EditorGUIUtility.singleLineHeight;
if (textProp == null)
{
return EditorGUIUtility.singleLineHeight;
}
float space = EditorGUIUtility.standardVerticalSpacing;
float h = 0f;
float padding = 4f;
float height = 0f;
SerializedProperty prop;
prop = textProp.FindPropertyRelative("content"); if (prop != null) h += EditorGUI.GetPropertyHeight(prop, true) + space;
prop = textProp.FindPropertyRelative("textNumber"); if (prop != null) h += EditorGUI.GetPropertyHeight(prop, true) + space;
prop = textProp.FindPropertyRelative("senderIndex"); if (prop != null) h += EditorGUI.GetPropertyHeight(prop, true) + space;
prop = textProp.FindPropertyRelative("imagePos"); if (prop != null) h += EditorGUI.GetPropertyHeight(prop, true) + space;
prop = textProp.FindPropertyRelative("flipX"); if (prop != null) h += EditorGUI.GetPropertyHeight(prop, true) + space;
prop = textProp.FindPropertyRelative("appearTime"); if (prop != null) h += EditorGUI.GetPropertyHeight(prop, true) + space;
prop = textProp.FindPropertyRelative("duration"); if (prop != null) h += EditorGUI.GetPropertyHeight(prop, true) + space;
prop = textProp.FindPropertyRelative("stepMethod"); if (prop != null) h += EditorGUI.GetPropertyHeight(prop, true) + space;
prop = textProp.FindPropertyRelative("timeAction"); if (prop != null) h += EditorGUI.GetPropertyHeight(prop, true) + space;
prop = textProp.FindPropertyRelative("slowMotionScale"); if (prop != null) h += EditorGUI.GetPropertyHeight(prop, true) + space;
prop = textProp.FindPropertyRelative("endAction"); if (prop != null) h += EditorGUI.GetPropertyHeight(prop, true) + space;
return h + padding * 2f;
prop = textProp.FindPropertyRelative("content"); if (prop != null) height += EditorGUI.GetPropertyHeight(prop, true) + space;
prop = textProp.FindPropertyRelative("textNumber"); if (prop != null) height += EditorGUI.GetPropertyHeight(prop, true) + space;
prop = textProp.FindPropertyRelative("senderIndex"); if (prop != null) height += EditorGUI.GetPropertyHeight(prop, true) + space;
prop = textProp.FindPropertyRelative("imagePos"); if (prop != null) height += EditorGUI.GetPropertyHeight(prop, true) + space;
prop = textProp.FindPropertyRelative("flipX"); if (prop != null) height += EditorGUI.GetPropertyHeight(prop, true) + space;
prop = textProp.FindPropertyRelative("appearTime"); if (prop != null) height += EditorGUI.GetPropertyHeight(prop, true) + space;
prop = textProp.FindPropertyRelative("duration"); if (prop != null) height += EditorGUI.GetPropertyHeight(prop, true) + space;
prop = textProp.FindPropertyRelative("stepMethod"); if (prop != null) height += EditorGUI.GetPropertyHeight(prop, true) + space;
prop = textProp.FindPropertyRelative("timeAction"); if (prop != null) height += EditorGUI.GetPropertyHeight(prop, true) + space;
prop = textProp.FindPropertyRelative("slowMotionScale"); if (prop != null) height += EditorGUI.GetPropertyHeight(prop, true) + space;
prop = textProp.FindPropertyRelative("endAction"); if (prop != null) height += EditorGUI.GetPropertyHeight(prop, true) + space;
return height + padding * 2f;
}
private void DrawTextElement(Rect rect, SerializedProperty textProp)
{
if (textProp == null) return;
if (textProp == null)
{
return;
}
float line = EditorGUIUtility.singleLineHeight;
float space = EditorGUIUtility.standardVerticalSpacing;
float padding = 4f;
Rect r = rect;
r.y += padding;
Rect rowRect = rect;
rowRect.y += padding;
var contentProp = textProp.FindPropertyRelative("content");
var textNumberProp = textProp.FindPropertyRelative("textNumber");
@@ -221,69 +332,77 @@ public class ingame_galgame_so_Editor : Editor
if (contentProp != null)
{
r.height = EditorGUI.GetPropertyHeight(contentProp, true);
EditorGUI.PropertyField(r, contentProp, labelContent, true);
r.y += r.height + space;
rowRect.height = EditorGUI.GetPropertyHeight(contentProp, true);
EditorGUI.PropertyField(rowRect, contentProp, labelContent, true);
rowRect.y += rowRect.height + space;
}
r.height = line;
if (textNumberProp != null) EditorGUI.PropertyField(r, textNumberProp, labelTextNumber);
r.y += line + space;
rowRect.height = line;
if (textNumberProp != null) EditorGUI.PropertyField(rowRect, textNumberProp, labelTextNumber);
rowRect.y += line + space;
if (senderIndexProp != null)
{
if (speakerNames != null && speakerNames.Length > 0)
{
int current = Mathf.Clamp(senderIndexProp.intValue + 1, 0, speakerNames.Length - 1);
int chosen = EditorGUI.Popup(r, "发言", current, speakerNames);
int chosen = EditorGUI.Popup(rowRect, "发言", current, speakerNames);
senderIndexProp.intValue = Mathf.Max(-1, chosen - 1);
}
else
{
EditorGUI.PropertyField(r, senderIndexProp, labelSenderIndex);
EditorGUI.PropertyField(rowRect, senderIndexProp, labelSenderIndex);
}
r.y += line + space;
rowRect.y += line + space;
}
if (imagePosProp != null) EditorGUI.PropertyField(r, imagePosProp, labelImagePos);
r.y += line + space;
if (imagePosProp != null) EditorGUI.PropertyField(rowRect, imagePosProp, labelImagePos);
rowRect.y += line + space;
if (flipXProp != null) EditorGUI.PropertyField(r, flipXProp, labelFlipX);
r.y += line + space;
if (flipXProp != null) EditorGUI.PropertyField(rowRect, flipXProp, labelFlipX);
rowRect.y += line + space;
if (appearTimeProp != null) EditorGUI.PropertyField(r, appearTimeProp, labelAppearTime);
r.y += line + space;
if (appearTimeProp != null) EditorGUI.PropertyField(rowRect, appearTimeProp, labelAppearTime);
rowRect.y += line + space;
if (durationProp != null) EditorGUI.PropertyField(r, durationProp, labelDuration);
r.y += line + space;
if (durationProp != null) EditorGUI.PropertyField(rowRect, durationProp, labelDuration);
rowRect.y += line + space;
if (stepMethodProp != null) EditorGUI.PropertyField(r, stepMethodProp, labelStepMethod);
r.y += line + space;
if (stepMethodProp != null) EditorGUI.PropertyField(rowRect, stepMethodProp, labelStepMethod);
rowRect.y += line + space;
if (timeActionProp != null) EditorGUI.PropertyField(r, timeActionProp, labelTimeAction);
r.y += line + space;
if (timeActionProp != null) EditorGUI.PropertyField(rowRect, timeActionProp, labelTimeAction);
rowRect.y += line + space;
if (slowMotionProp != null) EditorGUI.PropertyField(r, slowMotionProp, labelSlowMotionScale);
r.y += line + space;
if (slowMotionProp != null) EditorGUI.PropertyField(rowRect, slowMotionProp, labelSlowMotionScale);
rowRect.y += line + space;
if (endActionProp != null) EditorGUI.PropertyField(r, endActionProp, labelEndAction);
if (endActionProp != null) EditorGUI.PropertyField(rowRect, endActionProp, labelEndAction);
}
private void BuildSpeakerNames()
{
speakerNames = null;
Object speakersObj = speakersDataSOProp != null ? speakersDataSOProp.objectReferenceValue : null;
if (speakersObj == null) return;
if (speakersObj == null)
{
return;
}
var speakersSO = new SerializedObject(speakersObj);
var speakersList = speakersSO.FindProperty("speakers");
if (speakersList == null || !speakersList.isArray) return;
int c = speakersList.arraySize;
speakerNames = new string[c + 1];
speakerNames[0] = "(无)";
for (int i = 0; i < c; i++)
if (speakersList == null || !speakersList.isArray)
{
var e = speakersList.GetArrayElementAtIndex(i);
var nameProp = e.FindPropertyRelative("spkrName");
return;
}
int count = speakersList.arraySize;
speakerNames = new string[count + 1];
speakerNames[0] = "(无)";
for (int i = 0; i < count; i++)
{
var element = speakersList.GetArrayElementAtIndex(i);
var nameProp = element.FindPropertyRelative("spkrName");
speakerNames[i + 1] = nameProp != null ? nameProp.stringValue : $"Speaker {i}";
}
}
@@ -294,6 +413,7 @@ public class ingame_galgame_so_Editor : Editor
if (timelineStandardProp == null) timelineStandardProp = serializedObject.FindProperty("timelineStandard");
if (dialogueTypeProp == null) dialogueTypeProp = serializedObject.FindProperty("dialogueType");
if (speakersDataSOProp == null) speakersDataSOProp = serializedObject.FindProperty("speakersDataSO");
if (jsonDefinitionAssetProp == null) jsonDefinitionAssetProp = serializedObject.FindProperty("jsonDefinitionAsset");
if (dialogueListProp == null) dialogueListProp = serializedObject.FindProperty("dialogueList");
}
}