/* * @Author: 白哉 * @Description: 回放编辑器 * @Date: 2025年04月22日 星期二 15:04:14 * @Modify: */ using System; using System.Collections.Generic; using System.IO; using System.Linq; using Logic.Multilingual; using RuntimeData; using TH1_Logic.Comic; using TH1_Logic.Core; using UnityEditor; using UnityEngine; namespace Logic.Editor { public class ComicEditorWindow : EditorWindow { // 滑条 private Vector2 _barPosition; // 背景 private GUIStyle _redBoxStyle; private GUIStyle _whiteBoxStyle; private List _bigImageList; private List _itemImageList; private List _dialogList; private List _comicList; private string _selectedName; private List _itemDescList; private bool _isPlaying = false; private List _storyNameList; private string _selectedStoryName; private List _storyDialogIdList; private List _storyDialogList; private GameObject _root; private ComicSubItem _selectedComic; private string _newName = "comic"; private MultilingualType _currentType; private StoryDataAssets _storyAsset; private Dictionary _dataFoldouts = new Dictionary(); private Dictionary _itemFoldouts = new Dictionary(); private Dictionary _actionFoldouts = new Dictionary(); [MenuItem("Tools/漫画编辑器")] private static void ShowWindow() { var window = CreateWindow(); window.titleContent = new GUIContent("漫画编辑器"); window.Show(); } protected virtual void OnEnable() { EditorApplication.update += OnEditorUpdate; _currentType = MultilingualType.ZH; MultilingualManager.Instance.SetMultilingualType(_currentType); RefreshStrList(); RefreshStoryDialogList(); } private void OnDisable() { EditorApplication.update -= OnEditorUpdate; if (!_root) _root = GameObject.Find($"UICanvas/Comic"); if(_root != null) // 从后往前删除,避免索引变化 for (int i = _root.transform.childCount - 1; i >= 0; i--) { DestroyImmediate(_root.transform.GetChild(i).gameObject); } } private void OnEditorUpdate() { // 每帧刷新窗口 Repaint(); } private void Refresh() { if (!_root) _root = GameObject.Find($"UICanvas/Comic"); if (!_storyAsset) { string actionAssetPath = "Assets/Resources/Export/StoryDataAssets.asset"; _storyAsset = AssetDatabase.LoadAssetAtPath(actionAssetPath); } RefreshStrList(); } private void OnGUI() { Refresh(); ComicManager.Instance.Refresh(); if (_isPlaying) { ComicManager.Instance.ComicControl.Update(); EditorUtility.SetDirty(_root.gameObject); SceneView.RepaintAll(); } if (_redBoxStyle == null) { _redBoxStyle = InspectorUtils.GetHelpBoxStyle(); InspectorUtils.AddBorder(_redBoxStyle, new Color(0.7f, 0.2f, 0.2f, 0.6f)); } if (_whiteBoxStyle == null) { _whiteBoxStyle = InspectorUtils.GetHelpBoxStyle(); InspectorUtils.AddBorder(_whiteBoxStyle, new Color(1f, 1f, 1f, 0.2f)); } GUI.skin.button.wordWrap = true; _barPosition = EditorGUILayout.BeginScrollView(_barPosition); EditorGUILayout.BeginHorizontal(); InspectorUtils.InspectorTextWidthRich($" 新漫画 : "); _newName = EditorGUILayout.TextField(_newName, GUILayout.Width(300)); if (InspectorUtils.InspectorButtonWithTextWidth("创建")) { ComicManager.Instance.AddComicSheet(_newName); } if (InspectorUtils.InspectorButtonWithTextWidth("刷新")) { RefreshStrList(); } if (InspectorUtils.InspectorButtonWithTextWidth("保存")) { // 批量设置所有Action时长为0.1 /*foreach (var sheet in ComicManager.Instance.Asset.ComicSheets) { foreach (var data in sheet.ComicDatas) { foreach (var action in data.Actions) { action.Duration = 0.1f; } } }*/ ComicManager.Instance.SaveComicData(); } if (InspectorUtils.InspectorButtonWithTextWidth("检索图片库并保存")) { ComicManager.Instance.RefreshImageData(); } EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(_redBoxStyle); if (_isPlaying) { var actionIndex = ComicManager.Instance.ComicControl.ActionIndex; var maxIndex = ComicManager.Instance.ComicControl.MaxIndex; var sheet = ComicManager.Instance.ComicControl.ComicSheet; var progress = ComicManager.Instance.ComicControl.Progress; var duration = ComicManager.Instance.ComicControl.Duration; InspectorUtils.InspectorTextWidthRich($" 播放中"); InspectorUtils.InspectorTextWidthRich($" 漫画:{sheet.Name} "); InspectorUtils.InspectorTextWidthRich($" Action : {actionIndex} / {maxIndex}"); InspectorUtils.InspectorTextWidthRich($" 进度 : {progress} / {duration}"); } EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); InspectorUtils.InspectorTextWidthRich($" 语言环境 : "); var newType = (MultilingualType)EditorGUILayout.EnumPopup(_currentType, GUILayout.Width(200)); if (_currentType != newType) { _currentType = newType; MultilingualManager.Instance.SetMultilingualType(_currentType); } EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); InspectorUtils.InspectorTextWidthRich($" 台词 Asset 选择 : "); var storyIndex = GetIndexInStrList(_selectedStoryName, _storyNameList); storyIndex = EditorGUILayout.Popup(storyIndex, _storyNameList.ToArray(), GUILayout.Width(300)); if (_storyNameList.Count != 0 && _storyNameList[storyIndex] != _selectedStoryName) { _selectedStoryName = _storyNameList[storyIndex]; RefreshStoryDialogList(); } EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); InspectorUtils.InspectorTextWidthRich($" 选择漫画 : "); var index = GetIndexInComicList(_selectedName); index = EditorGUILayout.Popup(index, _comicList.ToArray(), GUILayout.Width(300)); if (_comicList.Count != 0) _selectedName = _comicList[index]; var comicSheet = ComicManager.Instance.Asset.GetComicSheetByName(_selectedName); if (comicSheet != null && InspectorUtils.InspectorButtonWithTextWidth($"删除此漫画 {comicSheet.Name}")) ComicManager.Instance.Asset.ComicSheets.Remove(comicSheet); EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); OnGUIComicSheet(comicSheet); EditorGUILayout.EndScrollView(); } private void OnGUIComicSheet(ComicSheet comicSheet) { if (comicSheet == null) return; EditorGUILayout.BeginHorizontal(); if (InspectorUtils.InspectorButtonWithTextWidth($"添加一页")) comicSheet.AddComicData(); if (InspectorUtils.InspectorButtonWithTextWidth($"播放")) { // 从后往前删除,避免索引变化 for (int i = _root.transform.childCount - 1; i >= 0; i--) { DestroyImmediate(_root.transform.GetChild(i).gameObject); } ComicManager.Instance.InitComic(comicSheet.Name, _root); _isPlaying = true; } if (_isPlaying && InspectorUtils.InspectorButtonWithTextWidth($"模拟点击(ForceNext)")) { ComicManager.Instance.ForceNext(); } if (_isPlaying && InspectorUtils.InspectorButtonWithTextWidth($"停止播放")) { _isPlaying = false; ComicManager.Instance.ComicControl.Clear(); } EditorGUILayout.EndHorizontal(); if (!_isPlaying) { int deleteIndex = -1; int upIndex = -1; int downIndex = -1; for (int i = 0; i < comicSheet.ComicDatas.Count; i++) { OnGUIComicData(comicSheet.ComicDatas[i], i, out bool delete, out bool up, out bool down); if (up) upIndex = i; if (down) downIndex = i; if (delete) deleteIndex = i; EditorGUILayout.Space(); } if (deleteIndex >= 0) comicSheet.ComicDatas.RemoveAt(deleteIndex); else if (upIndex >= 0) comicSheet.MoveComicData(comicSheet.ComicDatas[upIndex], -1); else if (downIndex >= 0) comicSheet.MoveComicData(comicSheet.ComicDatas[downIndex], 1); } } private void OnGUIComicData(ComicData data, int id, out bool isDelete, out bool up, out bool down) { up = false; down = false; isDelete = false; EditorGUILayout.BeginVertical(_whiteBoxStyle, GUILayout.Width(500)); EditorGUILayout.BeginHorizontal(); _dataFoldouts.TryAdd(data, false); _dataFoldouts[data] = EditorGUILayout.Foldout(_dataFoldouts[data], $"第 {id + 1} 页"); if (InspectorUtils.InspectorButtonWithTextWidth($"↑")) up = true; if (InspectorUtils.InspectorButtonWithTextWidth($"↓")) down = true; if (InspectorUtils.InspectorButtonWithTextWidth($"x")) isDelete = true; EditorGUILayout.EndHorizontal(); if (!_dataFoldouts[data]) { EditorGUILayout.EndVertical(); return; } EditorGUILayout.BeginHorizontal(); if (InspectorUtils.InspectorButtonWithTextWidth($"预览并编辑此页")) { if(_root != null) // 从后往前删除,避免索引变化 for (int i = _root.transform.childCount - 1; i >= 0; i--) { DestroyImmediate(_root.transform.GetChild(i).gameObject); } ComicManager.Instance.ComicControl.PreviewComicData(data, _root); } if (data.Mono && InspectorUtils.InspectorButtonWithTextWidth($"保存此页")) data.SaveItemsMono(); EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); InspectorUtils.InspectorTextWidthRich("底图:"); var index = GetIndexInStrList(data.Background.ImageName, _bigImageList); index = EditorGUILayout.Popup(index, _bigImageList.ToArray(), GUILayout.Width(300)); if (_bigImageList.Count != 0) data.Background.ImageName = _bigImageList[index]; EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); EditorGUILayout.BeginHorizontal(); if (InspectorUtils.InspectorButtonWithTextWidth($"添加Item")) data.AddSubItem(); EditorGUILayout.EndHorizontal(); // Items var deleteIndex = -1; for (int i = 0; i < data.SubItems.Count; i++) { OnGUIComicItem(data.SubItems[i], out bool delete); if (delete) deleteIndex = i; EditorGUILayout.Space(); } if (deleteIndex >= 0) data.RemoveSubItem(data.SubItems[deleteIndex]); EditorGUILayout.Space(); // Actions EditorGUILayout.BeginHorizontal(); if (InspectorUtils.InspectorButtonWithTextWidth($"添加Action")) data.AddAction(); EditorGUILayout.EndHorizontal(); _itemDescList ??= new List(); _itemDescList.Clear(); foreach (var item in data.SubItems) { var str = string.IsNullOrEmpty(item.Desc) ? $"Item ID : {item.Id}" : item.Desc; _itemDescList.Add(str); } deleteIndex = -1; for (int i = 0; i < data.Actions.Count; i++) { OnGUIComicAction(data, data.Actions[i], i, out bool delete); if (delete) deleteIndex = i; EditorGUILayout.Space(); } if (deleteIndex >= 0) data.RemoveAction(data.Actions[deleteIndex]); EditorGUILayout.EndVertical(); EditorGUILayout.Space(); } private void OnGUIComicItem(ComicSubItem item, out bool isDelete) { if (_selectedComic == item) EditorGUILayout.BeginVertical(_redBoxStyle); else EditorGUILayout.BeginVertical(_whiteBoxStyle); isDelete = false; EditorGUILayout.BeginHorizontal(); _itemFoldouts.TryAdd(item, false); var str = string.IsNullOrEmpty(item.Desc) ? $"Item ID : {item.Id}" : item.Desc; _itemFoldouts[item] = EditorGUILayout.Foldout(_itemFoldouts[item], str); if (item.Mono && InspectorUtils.InspectorButtonWithTextWidth($"选中")) { _selectedComic = item; Selection.activeGameObject = item.Mono.gameObject; EditorGUIUtility.PingObject(item.Mono.gameObject); // 在 Hierarchy 中闪烁高亮 SceneView.FrameLastActiveSceneView(); // Scene 视图聚焦 } if (InspectorUtils.InspectorButtonWithTextWidth($"x")) isDelete = true; EditorGUILayout.EndHorizontal(); if (!_itemFoldouts[item]) { EditorGUILayout.EndVertical(); return; } EditorGUILayout.BeginHorizontal(); item.Desc = EditorGUILayout.TextField($"描述", item.Desc, GUILayout.Width(300)); EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); InspectorUtils.InspectorTextWidthRich("类型:"); item.ItemType = (ComicSubItemType)EditorGUILayout.EnumPopup(item.ItemType, GUILayout.Width(200)); EditorGUILayout.EndHorizontal(); if (item.ItemType == ComicSubItemType.Image) { EditorGUILayout.BeginHorizontal(); InspectorUtils.InspectorTextWidthRich("图片:"); var index = GetIndexInStrList(item.ImageName, _itemImageList); index = EditorGUILayout.Popup(index, _itemImageList.ToArray(), GUILayout.Width(300)); if (_itemImageList.Count != 0) item.ImageName = _itemImageList[index]; EditorGUILayout.EndHorizontal(); } if (item.ItemType == ComicSubItemType.Dialog) { EditorGUILayout.BeginHorizontal(); InspectorUtils.InspectorTextWidthRich("对话框: "); var index = GetIndexInStrList(item.ImageName, _dialogList); index = EditorGUILayout.Popup(index, _dialogList.ToArray(), GUILayout.Width(300)); if (_dialogList.Count != 0) item.ImageName = _dialogList[index]; EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); InspectorUtils.InspectorTextWidthRich($"对话: "); var strIndex = GetIndexInStrList(item.Content, _storyDialogIdList); var newIndex = EditorGUILayout.Popup(strIndex, _storyDialogList.ToArray(), GUILayout.Width(300)); if (_storyDialogIdList.Count != 0 && newIndex != strIndex) item.Content = _storyDialogIdList[newIndex]; EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); InspectorUtils.InspectorTextWidthRich("对话框宽度: "); item.DialogWidth = EditorGUILayout.FloatField(item.DialogWidth, GUILayout.Width(300)); EditorGUILayout.EndHorizontal(); } EditorGUILayout.EndVertical(); } private void OnGUIComicAction(ComicData data, ComicAction action, int id, out bool isDelete) { var item = data.GetSubItemById(action.TargetId); if (item != null && _selectedComic == item) EditorGUILayout.BeginVertical(_redBoxStyle); else EditorGUILayout.BeginVertical(_whiteBoxStyle); isDelete = false; EditorGUILayout.BeginHorizontal(); _actionFoldouts.TryAdd(action, false); var str = string.IsNullOrEmpty(action.Desc) ? $"Action ID {id + 1}" : action.Desc; _actionFoldouts[action] = EditorGUILayout.Foldout(_actionFoldouts[action], str); if (item != null && item.Mono && InspectorUtils.InspectorButtonWithTextWidth($"选中")) { _selectedComic = item; Selection.activeGameObject = item.Mono.gameObject; EditorGUIUtility.PingObject(item.Mono.gameObject); // 在 Hierarchy 中闪烁高亮 SceneView.FrameLastActiveSceneView(); // Scene 视图聚焦 } if (InspectorUtils.InspectorButtonWithTextWidth($"x")) isDelete = true; EditorGUILayout.EndHorizontal(); if (!_actionFoldouts[action]) { EditorGUILayout.EndVertical(); return; } EditorGUILayout.BeginHorizontal(); action.Desc = EditorGUILayout.TextField($"描述", action.Desc, GUILayout.Width(300)); EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); InspectorUtils.InspectorTextWidthRich($"目标 Item"); var itemDesc = string.IsNullOrEmpty(item?.Desc) ? $"Item ID : {item?.Id}" : item.Desc; var index = GetIndexInStrList(itemDesc, _itemDescList); index = EditorGUILayout.Popup(index, _itemDescList.ToArray(), GUILayout.Width(300)); if (index < data.SubItems.Count) action.TargetId = data.SubItems[index].Id; EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); InspectorUtils.InspectorTextWidthRich($"Action 类型:"); action.ActionType = (ComicActionType)EditorGUILayout.EnumPopup(action.ActionType, GUILayout.Width(200)); EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); InspectorUtils.InspectorTextWidthRich($"时长:"); action.Duration = EditorGUILayout.FloatField(action.Duration, GUILayout.Width(200)); EditorGUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); } private int GetIndexInStrList(string str, List list) { for (int i = 0; i < list.Count; i++) { if (list[i] == str) return i; } return 0; } private int GetIndexInComicList(string name) { if (_comicList == null) _comicList = new List(); if (_comicList.Count != ComicManager.Instance.Asset.ComicSheets.Count) { _comicList.Clear(); foreach (var data in ComicManager.Instance.Asset.ComicSheets) { _comicList.Add(data.Name); } } for (int i = 0; i < _comicList.Count; i++) { if (_comicList[i] == name) return i; } return 0; } private void RefreshStrList() { if (_bigImageList == null || _bigImageList.Count == 0) { _bigImageList ??= new List(); _bigImageList.Clear(); string folderPath = "Assets/Resources/ArtResources/Comic/BigImage"; if (!Directory.Exists(folderPath)) Directory.CreateDirectory(folderPath); string[] files = Directory.GetFiles(folderPath, "*.png"); foreach (string file in files) { string fileName = Path.GetFileNameWithoutExtension(file); if (!string.IsNullOrEmpty(fileName)) _bigImageList.Add(fileName); } } if (_itemImageList == null || _itemImageList.Count == 0) { _itemImageList ??= new List(); _itemImageList.Clear(); string folderPath = "Assets/Resources/ArtResources/Comic/ItemImage"; if (!Directory.Exists(folderPath)) Directory.CreateDirectory(folderPath); string[] files = Directory.GetFiles(folderPath, "*.png"); foreach (string file in files) { string fileName = Path.GetFileNameWithoutExtension(file); if (!string.IsNullOrEmpty(fileName)) _itemImageList.Add(fileName); } } if (_dialogList == null || _dialogList.Count == 0) { _dialogList ??= new List(); _dialogList.Clear(); string folderPath = "Assets/Resources/ArtResources/Comic/Dialog"; if (!Directory.Exists(folderPath)) Directory.CreateDirectory(folderPath); string[] files = Directory.GetFiles(folderPath, "*.png"); foreach (string file in files) { string fileName = Path.GetFileNameWithoutExtension(file); if (!string.IsNullOrEmpty(fileName)) _dialogList.Add(fileName); } } _storyNameList ??= new List(); if (_storyAsset && _storyNameList.Count == 0) { _storyNameList.Clear(); foreach (var sheet in _storyAsset.SheetData) { _storyNameList.Add(sheet.SheetName); } } } private void RefreshStoryDialogList() { _storyDialogList ??= new List(); _storyDialogIdList ??= new List(); if (_storyAsset == null) return; var sheet = _storyAsset.SheetData.FirstOrDefault(s => s.SheetName == _selectedStoryName); if (sheet == null) return; _storyDialogList.Clear(); _storyDialogIdList.Clear(); foreach (var diag in sheet.DiagList) { _storyDialogIdList.Add(diag.Diag); var str = MultilingualManager.Instance.GetMultilingualTextSafe(diag.Diag); _storyDialogList.Add(str); } } } }