功能批量实现 优化 服务端 新浮动小游戏框架 新界面UI
This commit is contained in:
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
using System;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
public class galPrefab : MonoBehaviour
|
||||
{
|
||||
[Header("NowUsing")]
|
||||
[SerializeField] private int nowUsing = 0; // 0: none, 1: RoleHost, 2: RoleHint, 3: Narration, 4: HintOrLyrics
|
||||
[Header("publics")]
|
||||
public Color defaultTextColor;
|
||||
[Header("默认文本颜色")]
|
||||
public Color defaultTextColor = Color.white;
|
||||
|
||||
[Header("mode1: RoleHost")]
|
||||
[Header("模式1: 角色正式对话")]
|
||||
public Image roleHostBGbar;
|
||||
public Image L_roleHostImage;
|
||||
public Image R_roleHosetImage;
|
||||
@@ -17,30 +16,167 @@ public class galPrefab : MonoBehaviour
|
||||
public TextMeshProUGUI roleHostMessage;
|
||||
public Button host_nextButton;
|
||||
|
||||
[Header("mode2: RoleHint")]
|
||||
[Header("模式2: 角色提示对话")]
|
||||
public Image roleHintBGbar;
|
||||
public Image roleHintImage;
|
||||
public TextMeshProUGUI roleHintName;
|
||||
public TextMeshProUGUI roleHintMessage;
|
||||
public Button hint_nextButton;
|
||||
|
||||
[Header("mode3: Narration")]
|
||||
[Header("模式3: 旁白")]
|
||||
public Image narrationBGbar;
|
||||
public TextMeshProUGUI narrationMessage;
|
||||
|
||||
[Header("mode4: HintOrLyrics")]
|
||||
[Header("模式4: 提示或歌词")]
|
||||
public Image hintOrLyricsBGbar;
|
||||
public TextMeshProUGUI hintOrLyricsMessage;
|
||||
|
||||
[Header("fathers of 4modes")]
|
||||
[Header("四种模式容器")]
|
||||
public GameObject roleHostFather;
|
||||
public GameObject roleHintFather;
|
||||
public GameObject narrationFather;
|
||||
public GameObject hintOrLyricsFather;
|
||||
|
||||
private Action advanceCallback;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
BindButtons();
|
||||
HideAllModes();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// Try to find cameras from Camera.allCameras first
|
||||
TryAssignCanvasCamera();
|
||||
}
|
||||
|
||||
public void Initialize(Action onAdvance)
|
||||
{
|
||||
advanceCallback = onAdvance;
|
||||
BindButtons();
|
||||
HideAllModes();
|
||||
}
|
||||
|
||||
public void Present(
|
||||
ingame_galgame_so.DialogueElement.PositionTag positionTag,
|
||||
ingame_galgame_so.DialogueText dialogue,
|
||||
Speaker speaker)
|
||||
{
|
||||
if (dialogue == null)
|
||||
{
|
||||
HideAllModes();
|
||||
return;
|
||||
}
|
||||
|
||||
HideAllModes();
|
||||
|
||||
switch (positionTag)
|
||||
{
|
||||
case ingame_galgame_so.DialogueElement.PositionTag.RoleHost:
|
||||
PresentRoleHost(dialogue, speaker);
|
||||
break;
|
||||
case ingame_galgame_so.DialogueElement.PositionTag.RoleHint:
|
||||
PresentRoleHint(dialogue, speaker);
|
||||
break;
|
||||
case ingame_galgame_so.DialogueElement.PositionTag.Narration:
|
||||
PresentNarration(dialogue);
|
||||
break;
|
||||
case ingame_galgame_so.DialogueElement.PositionTag.HintOrLyrics:
|
||||
PresentHintOrLyrics(dialogue);
|
||||
break;
|
||||
default:
|
||||
PresentNarration(dialogue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void HideAllModes()
|
||||
{
|
||||
SetActiveSafe(roleHostFather, false);
|
||||
SetActiveSafe(roleHintFather, false);
|
||||
SetActiveSafe(narrationFather, false);
|
||||
SetActiveSafe(hintOrLyricsFather, false);
|
||||
}
|
||||
|
||||
public void SetAdvanceEnabled(bool enabled)
|
||||
{
|
||||
SetButtonEnabled(host_nextButton, enabled);
|
||||
SetButtonEnabled(hint_nextButton, enabled);
|
||||
}
|
||||
|
||||
private void PresentRoleHost(ingame_galgame_so.DialogueText dialogue, Speaker speaker)
|
||||
{
|
||||
SetActiveSafe(roleHostFather, true);
|
||||
|
||||
Color accent = speaker != null ? speaker.spkr_themeColor : defaultTextColor;
|
||||
string speakerName = speaker != null ? speaker.spkrName : string.Empty;
|
||||
Sprite portrait = speaker != null ? speaker.spkrHD_Sprite : null;
|
||||
|
||||
ApplyImage(roleHostBGbar, roleHostBGbar != null ? roleHostBGbar.sprite : null, accent, true);
|
||||
ApplyText(roleHostName, speakerName, accent);
|
||||
ApplyText(roleHostMessage, dialogue.content, defaultTextColor);
|
||||
|
||||
bool leftActive = dialogue.imagePos == ingame_galgame_so.DialogueText.ImagePosition.Left;
|
||||
bool rightActive = !leftActive;
|
||||
|
||||
ApplyPortrait(L_roleHostImage, leftActive ? portrait : null, leftActive, dialogue.flipX);
|
||||
ApplyPortrait(R_roleHosetImage, rightActive ? portrait : null, rightActive, dialogue.flipX);
|
||||
}
|
||||
|
||||
private void PresentRoleHint(ingame_galgame_so.DialogueText dialogue, Speaker speaker)
|
||||
{
|
||||
SetActiveSafe(roleHintFather, true);
|
||||
|
||||
Color accent = speaker != null ? speaker.spkr_themeColor : defaultTextColor;
|
||||
string speakerName = speaker != null ? speaker.spkrName : string.Empty;
|
||||
Sprite portrait = null;
|
||||
if (speaker != null)
|
||||
{
|
||||
portrait = speaker.spkrProfile_Sprite != null ? speaker.spkrProfile_Sprite : speaker.spkrHD_Sprite;
|
||||
}
|
||||
|
||||
ApplyImage(roleHintBGbar, roleHintBGbar != null ? roleHintBGbar.sprite : null, accent, true);
|
||||
ApplyPortrait(roleHintImage, portrait, portrait != null, dialogue.flipX);
|
||||
ApplyText(roleHintName, speakerName, accent);
|
||||
ApplyText(roleHintMessage, dialogue.content, defaultTextColor);
|
||||
}
|
||||
|
||||
private void PresentNarration(ingame_galgame_so.DialogueText dialogue)
|
||||
{
|
||||
SetActiveSafe(narrationFather, true);
|
||||
ApplyText(narrationMessage, dialogue.content, defaultTextColor);
|
||||
}
|
||||
|
||||
private void PresentHintOrLyrics(ingame_galgame_so.DialogueText dialogue)
|
||||
{
|
||||
SetActiveSafe(hintOrLyricsFather, true);
|
||||
ApplyText(hintOrLyricsMessage, dialogue.content, defaultTextColor);
|
||||
}
|
||||
|
||||
private void BindButtons()
|
||||
{
|
||||
BindButton(host_nextButton);
|
||||
BindButton(hint_nextButton);
|
||||
}
|
||||
|
||||
private void BindButton(Button button)
|
||||
{
|
||||
if (button == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
button.onClick.RemoveListener(HandleAdvanceClicked);
|
||||
button.onClick.AddListener(HandleAdvanceClicked);
|
||||
}
|
||||
|
||||
private void HandleAdvanceClicked()
|
||||
{
|
||||
advanceCallback?.Invoke();
|
||||
}
|
||||
|
||||
private void TryAssignCanvasCamera()
|
||||
{
|
||||
Camera foundCamera = null;
|
||||
Camera[] cameras = Camera.allCameras;
|
||||
|
||||
@@ -55,22 +191,87 @@ public class galPrefab : MonoBehaviour
|
||||
{
|
||||
foundCamera = camObjs[0];
|
||||
}
|
||||
camObjs = null; // release temporary reference
|
||||
}
|
||||
|
||||
if (foundCamera == null)
|
||||
foundCamera = Camera.main;
|
||||
|
||||
if (foundCamera != null)
|
||||
{
|
||||
Canvas parentCanvas = GetComponentInParent<Canvas>();
|
||||
if (parentCanvas != null)
|
||||
{
|
||||
parentCanvas.worldCamera = foundCamera;
|
||||
}
|
||||
foundCamera = Camera.main;
|
||||
}
|
||||
|
||||
// release temporary reference
|
||||
cameras = null;
|
||||
if (foundCamera == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Canvas parentCanvas = GetComponentInParent<Canvas>();
|
||||
if (parentCanvas != null)
|
||||
{
|
||||
parentCanvas.worldCamera = foundCamera;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetActiveSafe(GameObject target, bool active)
|
||||
{
|
||||
if (target != null)
|
||||
{
|
||||
target.SetActive(active);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetButtonEnabled(Button button, bool enabled)
|
||||
{
|
||||
if (button == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
button.interactable = enabled;
|
||||
if (button.gameObject.activeSelf != enabled)
|
||||
{
|
||||
button.gameObject.SetActive(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyText(TextMeshProUGUI target, string text, Color color)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
target.text = text ?? string.Empty;
|
||||
target.color = color;
|
||||
}
|
||||
|
||||
private static void ApplyImage(Image target, Sprite sprite, Color color, bool enabled)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
target.enabled = enabled;
|
||||
target.sprite = sprite;
|
||||
target.color = color;
|
||||
}
|
||||
|
||||
private static void ApplyPortrait(Image target, Sprite sprite, bool visible, bool flipX)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
target.enabled = visible && sprite != null;
|
||||
target.sprite = sprite;
|
||||
|
||||
RectTransform rect = target.rectTransform;
|
||||
if (rect != null)
|
||||
{
|
||||
Vector3 scale = rect.localScale;
|
||||
float absX = Mathf.Abs(scale.x);
|
||||
scale.x = flipX ? -absX : absX;
|
||||
rect.localScale = scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,533 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class gameplayGalgame : MonoBehaviour
|
||||
{
|
||||
[Header("enable galgame so")]
|
||||
private ingame_galgame_so.JsonDefinition resolvedDefinition;
|
||||
|
||||
private sealed class RuntimeDialogueEntry
|
||||
{
|
||||
public int sequence;
|
||||
public ingame_galgame_so.DialogueElement.PositionTag positionTag;
|
||||
public ingame_galgame_so.DialogueText dialogue;
|
||||
public Speaker speaker;
|
||||
}
|
||||
|
||||
[Header("启用开关")]
|
||||
public bool enableGalgame = false;
|
||||
public bool enableLyricsSO = true;
|
||||
[Header("gal so")]
|
||||
|
||||
[Header("对话数据")]
|
||||
public ingame_galgame_so golSO;
|
||||
[Header("prefabs")]
|
||||
|
||||
[Header("预制体")]
|
||||
public GameObject galgamePrefab;
|
||||
public GameObject where_to_put;
|
||||
|
||||
private string timeType;
|
||||
private string triggerType;
|
||||
private string timelineStandard;
|
||||
[Header("时间轴")]
|
||||
public AudioSource musicTimelineSource;
|
||||
|
||||
[Header("输入")]
|
||||
public bool allowGlobalClickToAdvance = true;
|
||||
public KeyCode keyboardAdvanceKey = KeyCode.Space;
|
||||
public KeyCode keyboardAdvanceKeyAlt = KeyCode.Return;
|
||||
|
||||
private readonly List<RuntimeDialogueEntry> runtimeEntries = new List<RuntimeDialogueEntry>();
|
||||
|
||||
private galPrefab galController;
|
||||
private GameObject galInstance;
|
||||
private int currentEntryIndex = -1;
|
||||
private bool isRunning;
|
||||
private bool waitingForClick;
|
||||
private bool pauseQueueRequested;
|
||||
private bool entryVisible;
|
||||
private bool advanceRequested;
|
||||
private float sceneStartRealtime;
|
||||
private float entryShownRealtime;
|
||||
private float customTimelineTime;
|
||||
private float cachedTimeScale = 1f;
|
||||
private bool timeActionApplied;
|
||||
|
||||
public bool IsRunning => isRunning;
|
||||
public bool IsPausedQueue => pauseQueueRequested;
|
||||
public bool HasActiveEntry => isRunning && currentEntryIndex >= 0 && currentEntryIndex < runtimeEntries.Count;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
sceneStartRealtime = Time.unscaledTime;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
TryPrepareRuntime();
|
||||
|
||||
if (ShouldAutoStart())
|
||||
{
|
||||
StartSequence();
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!isRunning || golSO == null || runtimeEntries.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (golSO.timelineStandard == ingame_galgame_so.TimelineBase.CustomTimer)
|
||||
{
|
||||
customTimelineTime += Time.unscaledDeltaTime;
|
||||
}
|
||||
|
||||
if (pauseQueueRequested)
|
||||
{
|
||||
if (advanceRequested || DetectAdvanceInput())
|
||||
{
|
||||
advanceRequested = false;
|
||||
ResumeSequence();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
RuntimeDialogueEntry entry = GetCurrentEntry();
|
||||
if (entry == null)
|
||||
{
|
||||
FinishSequence();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entryVisible)
|
||||
{
|
||||
if (GetTimelineTime() >= Mathf.Max(0f, entry.dialogue.appearTime))
|
||||
{
|
||||
ShowCurrentEntry(entry);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (waitingForClick)
|
||||
{
|
||||
if (advanceRequested || DetectAdvanceInput())
|
||||
{
|
||||
advanceRequested = false;
|
||||
ResolveCurrentEntry(entry);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
float requiredDuration = Mathf.Max(0f, entry.dialogue.duration);
|
||||
if (Time.unscaledTime - entryShownRealtime >= requiredDuration)
|
||||
{
|
||||
ResolveCurrentEntry(entry);
|
||||
}
|
||||
}
|
||||
|
||||
public void read_gol_so()
|
||||
{
|
||||
if (golSO != null)
|
||||
if (golSO == null)
|
||||
{
|
||||
timeType = golSO.dialogueType.ToString();
|
||||
triggerType = golSO.triggerMethod.ToString();
|
||||
timelineStandard = golSO.timelineStandard.ToString();
|
||||
Debug.LogWarning("[gameplayGalgame] 没有配置 galgame SO。");
|
||||
return;
|
||||
}
|
||||
else Debug.LogWarning("no gol so");
|
||||
|
||||
ResolveDefinition();
|
||||
if (resolvedDefinition == null)
|
||||
{
|
||||
Debug.LogWarning("[gameplayGalgame] 无法解析 galgame 配置。");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"[gameplayGalgame] dialogueType={resolvedDefinition.dialogueType}, triggerMethod={resolvedDefinition.triggerMethod}, timeline={resolvedDefinition.timelineStandard}");
|
||||
}
|
||||
|
||||
public void StartSequence()
|
||||
{
|
||||
if (!ShouldPlayConfiguredSo())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TryPrepareRuntime();
|
||||
if (golSO == null || runtimeEntries.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StopSequenceInternal(false);
|
||||
|
||||
sceneStartRealtime = Time.unscaledTime;
|
||||
customTimelineTime = 0f;
|
||||
currentEntryIndex = 0;
|
||||
isRunning = true;
|
||||
waitingForClick = false;
|
||||
pauseQueueRequested = false;
|
||||
entryVisible = false;
|
||||
advanceRequested = false;
|
||||
|
||||
EnsureGalInstance();
|
||||
if (galController != null)
|
||||
{
|
||||
galController.HideAllModes();
|
||||
}
|
||||
}
|
||||
|
||||
public void RestartSequence()
|
||||
{
|
||||
StartSequence();
|
||||
}
|
||||
|
||||
public void ResumeSequence()
|
||||
{
|
||||
if (!pauseQueueRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pauseQueueRequested = false;
|
||||
AdvanceToNextEntry();
|
||||
}
|
||||
|
||||
public void StopSequence()
|
||||
{
|
||||
StopSequenceInternal(true);
|
||||
}
|
||||
|
||||
public void RequestAdvance()
|
||||
{
|
||||
advanceRequested = true;
|
||||
}
|
||||
|
||||
public void SetCustomTimelineTime(float value)
|
||||
{
|
||||
customTimelineTime = Mathf.Max(0f, value);
|
||||
}
|
||||
|
||||
public void AdvanceCustomTimeline(float delta)
|
||||
{
|
||||
customTimelineTime = Mathf.Max(0f, customTimelineTime + delta);
|
||||
}
|
||||
|
||||
private bool ShouldAutoStart()
|
||||
{
|
||||
if (!ShouldPlayConfiguredSo() || golSO == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ResolveDefinition();
|
||||
if (resolvedDefinition == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return resolvedDefinition.triggerMethod == ingame_galgame_so.TriggerMethod.TimerTrigger
|
||||
|| resolvedDefinition.triggerMethod == ingame_galgame_so.TriggerMethod.MixedTrigger;
|
||||
}
|
||||
|
||||
private bool ShouldPlayConfiguredSo()
|
||||
{
|
||||
if (golSO == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ResolveDefinition();
|
||||
if (resolvedDefinition == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (resolvedDefinition.dialogueType == ingame_galgame_so.DialogueType.Galgame)
|
||||
{
|
||||
return enableGalgame;
|
||||
}
|
||||
|
||||
if (resolvedDefinition.dialogueType == ingame_galgame_so.DialogueType.Lyrics)
|
||||
{
|
||||
return enableLyricsSO;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void TryPrepareRuntime()
|
||||
{
|
||||
ResolveDefinition();
|
||||
BuildRuntimeEntries();
|
||||
EnsureGalInstance();
|
||||
}
|
||||
|
||||
private void BuildRuntimeEntries()
|
||||
{
|
||||
runtimeEntries.Clear();
|
||||
|
||||
if (golSO == null || resolvedDefinition == null || resolvedDefinition.dialogueList == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int sequence = 0;
|
||||
for (int i = 0; i < resolvedDefinition.dialogueList.Count; i++)
|
||||
{
|
||||
ingame_galgame_so.DialogueElement element = resolvedDefinition.dialogueList[i];
|
||||
if (element == null || element.textList == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < element.textList.Count; j++)
|
||||
{
|
||||
ingame_galgame_so.DialogueText text = element.textList[j];
|
||||
if (text == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
runtimeEntries.Add(new RuntimeDialogueEntry
|
||||
{
|
||||
sequence = sequence++,
|
||||
positionTag = element.positionTag,
|
||||
dialogue = text,
|
||||
speaker = ResolveSpeaker(text.senderIndex)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Speaker ResolveSpeaker(int senderIndex)
|
||||
{
|
||||
if (golSO == null || golSO.speakersDataSO == null || golSO.speakersDataSO.speakers == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (senderIndex < 0 || senderIndex >= golSO.speakersDataSO.speakers.Count)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return golSO.speakersDataSO.speakers[senderIndex];
|
||||
}
|
||||
|
||||
private void EnsureGalInstance()
|
||||
{
|
||||
if (galController != null)
|
||||
{
|
||||
galController.Initialize(RequestAdvance);
|
||||
return;
|
||||
}
|
||||
|
||||
if (galgamePrefab == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Transform parent = where_to_put != null ? where_to_put.transform : transform;
|
||||
|
||||
if (galInstance == null)
|
||||
{
|
||||
galInstance = Instantiate(galgamePrefab, parent, false);
|
||||
}
|
||||
|
||||
galController = galInstance != null ? galInstance.GetComponent<galPrefab>() : null;
|
||||
if (galController != null)
|
||||
{
|
||||
galController.Initialize(RequestAdvance);
|
||||
}
|
||||
}
|
||||
|
||||
private RuntimeDialogueEntry GetCurrentEntry()
|
||||
{
|
||||
if (currentEntryIndex < 0 || currentEntryIndex >= runtimeEntries.Count)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return runtimeEntries[currentEntryIndex];
|
||||
}
|
||||
|
||||
private void ShowCurrentEntry(RuntimeDialogueEntry entry)
|
||||
{
|
||||
if (entry == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureGalInstance();
|
||||
RestoreTimeAction();
|
||||
|
||||
if (galController != null)
|
||||
{
|
||||
galController.Present(entry.positionTag, entry.dialogue, entry.speaker);
|
||||
bool clickToNext = entry.dialogue.stepMethod == ingame_galgame_so.DialogueText.StepMethod.ClickToNext;
|
||||
galController.SetAdvanceEnabled(clickToNext);
|
||||
}
|
||||
|
||||
ApplyTimeAction(entry.dialogue);
|
||||
|
||||
entryVisible = true;
|
||||
entryShownRealtime = Time.unscaledTime;
|
||||
waitingForClick = entry.dialogue.stepMethod == ingame_galgame_so.DialogueText.StepMethod.ClickToNext;
|
||||
}
|
||||
|
||||
private void ResolveCurrentEntry(RuntimeDialogueEntry entry)
|
||||
{
|
||||
if (entry == null)
|
||||
{
|
||||
FinishSequence();
|
||||
return;
|
||||
}
|
||||
|
||||
RestoreTimeAction();
|
||||
waitingForClick = false;
|
||||
entryVisible = false;
|
||||
|
||||
switch (entry.dialogue.endAction)
|
||||
{
|
||||
case ingame_galgame_so.DialogueText.EndAction.NextText:
|
||||
AdvanceToNextEntry();
|
||||
break;
|
||||
case ingame_galgame_so.DialogueText.EndAction.PauseQueue:
|
||||
pauseQueueRequested = true;
|
||||
if (galController != null)
|
||||
{
|
||||
galController.SetAdvanceEnabled(true);
|
||||
}
|
||||
break;
|
||||
case ingame_galgame_so.DialogueText.EndAction.EndPerformance:
|
||||
FinishSequence();
|
||||
break;
|
||||
default:
|
||||
AdvanceToNextEntry();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void AdvanceToNextEntry()
|
||||
{
|
||||
currentEntryIndex++;
|
||||
if (currentEntryIndex >= runtimeEntries.Count)
|
||||
{
|
||||
FinishSequence();
|
||||
return;
|
||||
}
|
||||
|
||||
advanceRequested = false;
|
||||
waitingForClick = false;
|
||||
entryVisible = false;
|
||||
}
|
||||
|
||||
private void FinishSequence()
|
||||
{
|
||||
StopSequenceInternal(true);
|
||||
}
|
||||
|
||||
private void StopSequenceInternal(bool hideUi)
|
||||
{
|
||||
RestoreTimeAction();
|
||||
isRunning = false;
|
||||
waitingForClick = false;
|
||||
pauseQueueRequested = false;
|
||||
entryVisible = false;
|
||||
advanceRequested = false;
|
||||
currentEntryIndex = -1;
|
||||
|
||||
if (hideUi && galController != null)
|
||||
{
|
||||
galController.HideAllModes();
|
||||
galController.SetAdvanceEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyTimeAction(ingame_galgame_so.DialogueText dialogue)
|
||||
{
|
||||
if (dialogue == null || dialogue.timeAction == ingame_galgame_so.DialogueText.TimeAction.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cachedTimeScale = Time.timeScale;
|
||||
timeActionApplied = true;
|
||||
|
||||
switch (dialogue.timeAction)
|
||||
{
|
||||
case ingame_galgame_so.DialogueText.TimeAction.SlowMotion:
|
||||
Time.timeScale = Mathf.Clamp(dialogue.slowMotionScale, 0f, 1f);
|
||||
break;
|
||||
case ingame_galgame_so.DialogueText.TimeAction.Pause:
|
||||
Time.timeScale = 0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void RestoreTimeAction()
|
||||
{
|
||||
if (!timeActionApplied)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Time.timeScale = cachedTimeScale;
|
||||
timeActionApplied = false;
|
||||
}
|
||||
|
||||
private float GetTimelineTime()
|
||||
{
|
||||
if (golSO == null)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
ResolveDefinition();
|
||||
if (resolvedDefinition == null)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
switch (resolvedDefinition.timelineStandard)
|
||||
{
|
||||
case ingame_galgame_so.TimelineBase.SceneStartTime:
|
||||
return Mathf.Max(0f, Time.unscaledTime - sceneStartRealtime);
|
||||
case ingame_galgame_so.TimelineBase.CustomTimer:
|
||||
return customTimelineTime;
|
||||
case ingame_galgame_so.TimelineBase.MusicTime:
|
||||
if (musicTimelineSource != null)
|
||||
{
|
||||
return Mathf.Max(0f, musicTimelineSource.time);
|
||||
}
|
||||
return Mathf.Max(0f, Time.unscaledTime - sceneStartRealtime);
|
||||
default:
|
||||
return Mathf.Max(0f, Time.unscaledTime - sceneStartRealtime);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResolveDefinition()
|
||||
{
|
||||
resolvedDefinition = golSO != null ? golSO.GetResolvedDefinition() : null;
|
||||
}
|
||||
|
||||
private bool DetectAdvanceInput()
|
||||
{
|
||||
if (!allowGlobalClickToAdvance)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Input.GetMouseButtonDown(0))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Input.touchCount > 0)
|
||||
{
|
||||
Touch touch = Input.GetTouch(0);
|
||||
if (touch.phase == TouchPhase.Began)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return Input.GetKeyDown(keyboardAdvanceKey) || Input.GetKeyDown(keyboardAdvanceKeyAlt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 179.685, y: -24.015}
|
||||
m_AnchoredPosition: {x: 129.685, y: -24.015}
|
||||
m_SizeDelta: {x: 159.37, y: 24.01}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &1904443238537391023
|
||||
@@ -327,7 +327,7 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 179.685, y: -6.005}
|
||||
m_AnchoredPosition: {x: 129.685, y: -6.005}
|
||||
m_SizeDelta: {x: 5.48, y: 12.01}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &7988485425920675964
|
||||
@@ -1393,7 +1393,7 @@ RectTransform:
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 359.37, y: 48.03}
|
||||
m_SizeDelta: {x: 259.37, y: 48.03}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &4601708949971673813
|
||||
CanvasRenderer:
|
||||
@@ -1446,8 +1446,8 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Padding:
|
||||
m_Left: 100
|
||||
m_Right: 100
|
||||
m_Left: 50
|
||||
m_Right: 50
|
||||
m_Top: 0
|
||||
m_Bottom: 0
|
||||
m_ChildAlignment: 4
|
||||
@@ -2228,7 +2228,7 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 179.685, y: -42.025}
|
||||
m_AnchoredPosition: {x: 129.685, y: -42.025}
|
||||
m_SizeDelta: {x: 5.48, y: 12.01}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &8766812421281384592
|
||||
@@ -2743,6 +2743,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 6c868a66b282da94faaf38c62d26fcbe, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
prefabCanvas: {fileID: 8228997081257688641}
|
||||
nowUsing: 0
|
||||
defaultTextColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
roleHostBGbar: {fileID: 662360290209903647}
|
||||
|
||||
@@ -1,79 +1,326 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
[CreateAssetMenu(fileName = "NewGalSO", menuName = "Galgame/galSO")]
|
||||
public class ingame_galgame_so : ScriptableObject
|
||||
{
|
||||
[System.Serializable]
|
||||
[Serializable]
|
||||
public class DialogueText
|
||||
{
|
||||
public enum ImagePosition
|
||||
{
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
|
||||
public enum StepMethod
|
||||
{
|
||||
ClickToNext,
|
||||
WaitForDuration
|
||||
}
|
||||
|
||||
public enum TimeAction
|
||||
{
|
||||
None,
|
||||
SlowMotion,
|
||||
Pause
|
||||
}
|
||||
|
||||
public enum EndAction
|
||||
{
|
||||
NextText,
|
||||
PauseQueue,
|
||||
EndPerformance
|
||||
}
|
||||
|
||||
[TextArea(3, 10)]
|
||||
[Tooltip("文本内容")]
|
||||
public string content; // 文本内容
|
||||
[Tooltip("当前对话实际显示的文本内容。")]
|
||||
public string content;
|
||||
|
||||
[Tooltip("队列中第几个文本")]
|
||||
public int textNumber; // 队列中第几个文本
|
||||
[Tooltip("文本编号,仅用于人工整理,不作为运行时唯一依据。")]
|
||||
public int textNumber;
|
||||
|
||||
[Tooltip("从 speakersDataSO 中选择发言者")]
|
||||
public int senderIndex = -1; // 改为索引,通过下拉选择
|
||||
[Tooltip("发言人索引,来自 speakersDataSO 的 speakers 列表。-1 表示系统或旁白。")]
|
||||
public int senderIndex = -1;
|
||||
|
||||
public enum ImagePosition { Left, Right }
|
||||
[Tooltip("对象图片左右位置")]
|
||||
public ImagePosition imagePos; // 对象图片左右位置
|
||||
[Tooltip("对象图片是否水平翻转")]
|
||||
[Tooltip("角色图片显示在左侧还是右侧。")]
|
||||
public ImagePosition imagePos = ImagePosition.Left;
|
||||
|
||||
[Tooltip("角色图片是否水平翻转。")]
|
||||
public bool flipX;
|
||||
|
||||
[Tooltip("文本出现时间")]
|
||||
public float appearTime; // 文本出现时间
|
||||
[Tooltip("文本持续时间")]
|
||||
public float duration; // 文本持续时间
|
||||
[Tooltip("该条文本在所选时间轴上的出现时间。")]
|
||||
public float appearTime;
|
||||
|
||||
public enum StepMethod { ClickToNext, WaitForDuration }
|
||||
[Tooltip("文本步进方法:点击后下一步 / 等待时长结束")]
|
||||
public StepMethod stepMethod; // 文本步进方法
|
||||
[Tooltip("自动播放时的停留时间,仅在 WaitForDuration 下生效。")]
|
||||
public float duration = 1f;
|
||||
|
||||
public enum TimeAction { None, SlowMotion, Pause }
|
||||
[Tooltip("文本出现时动作:无/慢动作/暂停")]
|
||||
public TimeAction timeAction; // 文本出现时动作
|
||||
[Tooltip("当前文本如何推进到下一条。")]
|
||||
public StepMethod stepMethod = StepMethod.ClickToNext;
|
||||
|
||||
[Tooltip("文本播放期间时间流速,0为暂停")]
|
||||
[Tooltip("文本播放期间对游戏时间流做的动作。")]
|
||||
public TimeAction timeAction = TimeAction.None;
|
||||
|
||||
[Tooltip("当 timeAction 为 SlowMotion 时使用的 Time.timeScale。")]
|
||||
[Range(0f, 1f)]
|
||||
public float slowMotionScale = 0.5f; // 慢动作允许TimeScale设置值
|
||||
public float slowMotionScale = 0.5f;
|
||||
|
||||
public enum EndAction { NextText, PauseQueue, EndPerformance }
|
||||
[Tooltip("文本结束动作")]
|
||||
public EndAction endAction; // 文本结束动作
|
||||
[Tooltip("当前文本结束后的队列行为。")]
|
||||
public EndAction endAction = EndAction.NextText;
|
||||
|
||||
public DialogueText Clone()
|
||||
{
|
||||
return new DialogueText
|
||||
{
|
||||
content = content,
|
||||
textNumber = textNumber,
|
||||
senderIndex = senderIndex,
|
||||
imagePos = imagePos,
|
||||
flipX = flipX,
|
||||
appearTime = appearTime,
|
||||
duration = duration,
|
||||
stepMethod = stepMethod,
|
||||
timeAction = timeAction,
|
||||
slowMotionScale = slowMotionScale,
|
||||
endAction = endAction
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
[Serializable]
|
||||
public class DialogueElement
|
||||
{
|
||||
public enum PositionTag { RoleHost, RoleHint, Narration, HintOrLyrics }
|
||||
public enum PositionTag
|
||||
{
|
||||
RoleHost,
|
||||
RoleHint,
|
||||
Narration,
|
||||
HintOrLyrics
|
||||
}
|
||||
|
||||
[Header("显示模式")]
|
||||
[Tooltip("决定本段文本使用哪种对话框样式。")]
|
||||
public PositionTag positionTag = PositionTag.RoleHost;
|
||||
|
||||
[Header("位置配置")]
|
||||
public PositionTag positionTag;
|
||||
[HideInInspector]
|
||||
public List<Vector2> customPositions;
|
||||
public List<Vector2> customPositions = new List<Vector2>();
|
||||
|
||||
[Header("文本内容配置")]
|
||||
public List<DialogueText> textList; // 文字列表
|
||||
[Header("文本列表")]
|
||||
[Tooltip("该段落下的实际文本播放列表。")]
|
||||
public List<DialogueText> textList = new List<DialogueText>();
|
||||
|
||||
public DialogueElement Clone()
|
||||
{
|
||||
var clone = new DialogueElement
|
||||
{
|
||||
positionTag = positionTag,
|
||||
customPositions = customPositions != null ? new List<Vector2>(customPositions) : new List<Vector2>(),
|
||||
textList = new List<DialogueText>()
|
||||
};
|
||||
|
||||
if (textList != null)
|
||||
{
|
||||
for (int i = 0; i < textList.Count; i++)
|
||||
{
|
||||
if (textList[i] != null)
|
||||
{
|
||||
clone.textList.Add(textList[i].Clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
|
||||
public enum TimelineBase { MusicTime, SceneStartTime, CustomTimer }
|
||||
public enum TriggerMethod { CodeTrigger, TimerTrigger, MixedTrigger }
|
||||
public enum DialogueType { Galgame, Lyrics }
|
||||
public enum TimelineBase
|
||||
{
|
||||
MusicTime,
|
||||
SceneStartTime,
|
||||
CustomTimer
|
||||
}
|
||||
|
||||
public enum TriggerMethod
|
||||
{
|
||||
CodeTrigger,
|
||||
TimerTrigger,
|
||||
MixedTrigger
|
||||
}
|
||||
|
||||
public enum DialogueType
|
||||
{
|
||||
Galgame,
|
||||
Lyrics
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class JsonDefinition
|
||||
{
|
||||
public int version = 1;
|
||||
public DialogueType dialogueType = DialogueType.Galgame;
|
||||
public TriggerMethod triggerMethod = TriggerMethod.TimerTrigger;
|
||||
public TimelineBase timelineStandard = TimelineBase.SceneStartTime;
|
||||
public List<DialogueElement> dialogueList = new List<DialogueElement>();
|
||||
|
||||
public JsonDefinition Clone()
|
||||
{
|
||||
var clone = new JsonDefinition
|
||||
{
|
||||
version = version,
|
||||
dialogueType = dialogueType,
|
||||
triggerMethod = triggerMethod,
|
||||
timelineStandard = timelineStandard,
|
||||
dialogueList = new List<DialogueElement>()
|
||||
};
|
||||
|
||||
if (dialogueList != null)
|
||||
{
|
||||
for (int i = 0; i < dialogueList.Count; i++)
|
||||
{
|
||||
if (dialogueList[i] != null)
|
||||
{
|
||||
clone.dialogueList.Add(dialogueList[i].Clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
|
||||
[Header("全局配置")]
|
||||
[Tooltip("对话类型")]
|
||||
public DialogueType dialogueType;
|
||||
[Tooltip("触发方式")]
|
||||
public TriggerMethod triggerMethod;
|
||||
[Tooltip("时间线基准")]
|
||||
public TimelineBase timelineStandard;
|
||||
[Tooltip("当前脚本化演出是普通对话还是歌词模式。")]
|
||||
public DialogueType dialogueType = DialogueType.Galgame;
|
||||
|
||||
[Tooltip("运行时由代码触发、按时间轴触发,还是两者混合。")]
|
||||
public TriggerMethod triggerMethod = TriggerMethod.TimerTrigger;
|
||||
|
||||
[Tooltip("appearTime 使用哪一种时间基准。")]
|
||||
public TimelineBase timelineStandard = TimelineBase.SceneStartTime;
|
||||
|
||||
[Header("角色数据")]
|
||||
[Tooltip("引用 Speaker 配置 ScriptableObject")]
|
||||
[Tooltip("发言人配置来源。")]
|
||||
public galgame_speakers_so speakersDataSO;
|
||||
|
||||
[Header("JSON 覆盖")]
|
||||
[Tooltip("如果拖入 JSON 文本资源,运行时会优先使用 JSON 内容,而不是当前 Inspector 里的对话配置。")]
|
||||
public TextAsset jsonDefinitionAsset;
|
||||
|
||||
[Header("对话内容")]
|
||||
public List<DialogueElement> dialogueList = new List<DialogueElement>();
|
||||
|
||||
public bool HasJsonOverride => jsonDefinitionAsset != null && !string.IsNullOrWhiteSpace(jsonDefinitionAsset.text);
|
||||
|
||||
public JsonDefinition BuildDefinitionFromInspector()
|
||||
{
|
||||
var definition = new JsonDefinition
|
||||
{
|
||||
dialogueType = dialogueType,
|
||||
triggerMethod = triggerMethod,
|
||||
timelineStandard = timelineStandard,
|
||||
dialogueList = new List<DialogueElement>()
|
||||
};
|
||||
|
||||
if (dialogueList != null)
|
||||
{
|
||||
for (int i = 0; i < dialogueList.Count; i++)
|
||||
{
|
||||
if (dialogueList[i] != null)
|
||||
{
|
||||
definition.dialogueList.Add(dialogueList[i].Clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return definition;
|
||||
}
|
||||
|
||||
public bool TryParseJsonOverride(out JsonDefinition definition)
|
||||
{
|
||||
definition = null;
|
||||
if (!HasJsonOverride)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
definition = JsonUtility.FromJson<JsonDefinition>(jsonDefinitionAsset.text);
|
||||
if (definition == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (definition.dialogueList == null)
|
||||
{
|
||||
definition.dialogueList = new List<DialogueElement>();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[ingame_galgame_so] JSON 解析失败: {ex.Message}", this);
|
||||
definition = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public JsonDefinition GetResolvedDefinition()
|
||||
{
|
||||
if (TryParseJsonOverride(out JsonDefinition fromJson))
|
||||
{
|
||||
return fromJson;
|
||||
}
|
||||
|
||||
return BuildDefinitionFromInspector();
|
||||
}
|
||||
|
||||
public string ExportCurrentDefinitionToJson(bool prettyPrint = true)
|
||||
{
|
||||
return JsonUtility.ToJson(BuildDefinitionFromInspector(), prettyPrint);
|
||||
}
|
||||
|
||||
public void ApplyDefinitionToInspector(JsonDefinition definition)
|
||||
{
|
||||
if (definition == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dialogueType = definition.dialogueType;
|
||||
triggerMethod = definition.triggerMethod;
|
||||
timelineStandard = definition.timelineStandard;
|
||||
|
||||
dialogueList ??= new List<DialogueElement>();
|
||||
dialogueList.Clear();
|
||||
|
||||
if (definition.dialogueList == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < definition.dialogueList.Count; i++)
|
||||
{
|
||||
DialogueElement element = definition.dialogueList[i];
|
||||
if (element != null)
|
||||
{
|
||||
dialogueList.Add(element.Clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryApplyJsonOverrideToInspector(out string errorMessage)
|
||||
{
|
||||
errorMessage = string.Empty;
|
||||
if (!TryParseJsonOverride(out JsonDefinition definition))
|
||||
{
|
||||
errorMessage = HasJsonOverride ? "JSON 解析失败" : "未配置 JSON 覆盖源";
|
||||
return false;
|
||||
}
|
||||
|
||||
ApplyDefinitionToInspector(definition);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,21 +16,22 @@ MonoBehaviour:
|
||||
triggerMethod: 1
|
||||
timelineStandard: 0
|
||||
speakersDataSO: {fileID: 11400000, guid: 8e500b367b7861f44897e5396b1989ce, type: 2}
|
||||
jsonDefinitionAsset: {fileID: 4900000, guid: 59e1b7a6ebe2596419c72f27a80cec9a, type: 3}
|
||||
dialogueList:
|
||||
- positionTag: 0
|
||||
customPositions: []
|
||||
textList:
|
||||
- content: 1
|
||||
- content: "\u4F60\u597D"
|
||||
textNumber: 1
|
||||
senderIndex: 0
|
||||
senderIndex: 2
|
||||
imagePos: 0
|
||||
flipX: 0
|
||||
appearTime: 0
|
||||
duration: 0
|
||||
duration: 5
|
||||
stepMethod: 1
|
||||
timeAction: 0
|
||||
slowMotionScale: 0
|
||||
endAction: 0
|
||||
endAction: 2
|
||||
- positionTag: 0
|
||||
customPositions: []
|
||||
textList:
|
||||
@@ -73,3 +74,17 @@ MonoBehaviour:
|
||||
timeAction: 0
|
||||
slowMotionScale: 0
|
||||
endAction: 0
|
||||
- positionTag: 0
|
||||
customPositions: []
|
||||
textList:
|
||||
- content: 4
|
||||
textNumber: 1
|
||||
senderIndex: -1
|
||||
imagePos: 0
|
||||
flipX: 0
|
||||
appearTime: 0
|
||||
duration: 0
|
||||
stepMethod: 0
|
||||
timeAction: 0
|
||||
slowMotionScale: 0
|
||||
endAction: 0
|
||||
|
||||
@@ -18,16 +18,19 @@ MonoBehaviour:
|
||||
spkrName: SystemBG
|
||||
spkr_themeColor: {r: 0, g: 0, b: 0, a: 0}
|
||||
spkrDescription: "\u7CFB\u7EDF\u65C1\u767D"
|
||||
spkrSprite: {fileID: 0}
|
||||
spkrHD_Sprite: {fileID: 0}
|
||||
spkrProfile_Sprite: {fileID: 0}
|
||||
- spkrType: 1
|
||||
spkrID: 30001
|
||||
spkrName: SystemLyrics
|
||||
spkr_themeColor: {r: 0, g: 0, b: 0, a: 0}
|
||||
spkrDescription: "\u7CFB\u7EDF\u6B4C\u8BCD"
|
||||
spkrSprite: {fileID: 0}
|
||||
spkrHD_Sprite: {fileID: 0}
|
||||
spkrProfile_Sprite: {fileID: 0}
|
||||
- spkrType: 2
|
||||
spkrID: 30101
|
||||
spkrName: Sonia
|
||||
spkr_themeColor: {r: 0, g: 0, b: 0, a: 0}
|
||||
spkrDescription: "\u89D2\u8272\u7D22\u5C3C\u5A05"
|
||||
spkrSprite: {fileID: 0}
|
||||
spkrHD_Sprite: {fileID: 21300000, guid: 1a757eef01620424883e7d20aa232463, type: 3}
|
||||
spkrProfile_Sprite: {fileID: 21300000, guid: 1a62adf36834dd04181a6dd61344f8b8, type: 3}
|
||||
|
||||
Reference in New Issue
Block a user