using System; using System.Collections.Generic; using UnityEngine; [CreateAssetMenu(fileName = "NewGalSO", menuName = "Galgame/galSO")] public class ingame_galgame_so : ScriptableObject { [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 int textNumber; [Tooltip("发言人索引,来自 speakersDataSO 的 speakers 列表。-1 表示系统或旁白。")] public int senderIndex = -1; [Tooltip("角色图片显示在左侧还是右侧。")] public ImagePosition imagePos = ImagePosition.Left; [Tooltip("角色图片是否水平翻转。")] public bool flipX; [Tooltip("该条文本在所选时间轴上的出现时间。")] public float appearTime; [Tooltip("自动播放时的停留时间,仅在 WaitForDuration 下生效。")] public float duration = 1f; [Tooltip("当前文本如何推进到下一条。")] public StepMethod stepMethod = StepMethod.ClickToNext; [Tooltip("文本播放期间对游戏时间流做的动作。")] public TimeAction timeAction = TimeAction.None; [Tooltip("当 timeAction 为 SlowMotion 时使用的 Time.timeScale。")] [Range(0f, 1f)] public float slowMotionScale = 0.5f; [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 }; } } [Serializable] public class DialogueElement { public enum PositionTag { RoleHost, RoleHint, Narration, HintOrLyrics } [Header("显示模式")] [Tooltip("决定本段文本使用哪种对话框样式。")] public PositionTag positionTag = PositionTag.RoleHost; [HideInInspector] public List customPositions = new List(); [Header("文本列表")] [Tooltip("该段落下的实际文本播放列表。")] public List textList = new List(); public DialogueElement Clone() { var clone = new DialogueElement { positionTag = positionTag, customPositions = customPositions != null ? new List(customPositions) : new List(), textList = new List() }; 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 } [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 dialogueList = new List(); public JsonDefinition Clone() { var clone = new JsonDefinition { version = version, dialogueType = dialogueType, triggerMethod = triggerMethod, timelineStandard = timelineStandard, dialogueList = new List() }; 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 = DialogueType.Galgame; [Tooltip("运行时由代码触发、按时间轴触发,还是两者混合。")] public TriggerMethod triggerMethod = TriggerMethod.TimerTrigger; [Tooltip("appearTime 使用哪一种时间基准。")] public TimelineBase timelineStandard = TimelineBase.SceneStartTime; [Header("角色数据")] [Tooltip("发言人配置来源。")] public galgame_speakers_so speakersDataSO; [Header("JSON 覆盖")] [Tooltip("如果拖入 JSON 文本资源,运行时会优先使用 JSON 内容,而不是当前 Inspector 里的对话配置。")] public TextAsset jsonDefinitionAsset; [Header("对话内容")] public List dialogueList = new List(); 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() }; 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(jsonDefinitionAsset.text); if (definition == null) { return false; } if (definition.dialogueList == null) { definition.dialogueList = new List(); } 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(); 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; } }