TH1/Unity/Assets/Scripts/TH1_Logic/Editor/MultilingualEditorWindow.cs
2026-01-20 16:48:34 +08:00

949 lines
38 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* @Author: 白哉
* @Description:
* @Date: 2025年05月26日 星期一 17:05:14
* @Modify:
*/
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using Logic.Multilingual;
using TMPro;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using Debug = UnityEngine.Debug;
using Object = UnityEngine.Object;
namespace Logic.Editor
{
public class MultilingualEditorWindow : EditorWindow
{
// 滑条
private Vector2 _barPosition;
// 背景
private GUIStyle _redBoxStyle;
private GUIStyle _whiteBoxStyle;
private MultilingualData _asset;
private Dictionary<string, uint> _zhStrDict = new Dictionary<string, uint>();
private Dictionary<uint, bool> _isProperNounDict = new Dictionary<uint, bool>();
private Dictionary<uint, bool> _isDialogueDict = new Dictionary<uint, bool>();
private Dictionary<uint, bool> _isDeprecatedDict = new Dictionary<uint, bool>();
private Dictionary<uint, string> _speakerDict = new Dictionary<uint, string>();
private HashSet<uint> _activeSet = new HashSet<uint>();
private Dictionary<uint, string> _descDict = new Dictionary<uint, string>();
private uint _idIndex;
private int _showIndex = 0;
private List<TMP_FontAsset> _assets = new List<TMP_FontAsset>();
private HashSet<char> characterSet = new HashSet<char>();
private bool _isActive = true;
private bool _isProperNoun = true;
private bool _isDialogue = true;
private bool _isDeprecated = false;
[MenuItem("Tools/多语言编辑器")]
private static void ShowWindow()
{
var window = CreateWindow<MultilingualEditorWindow>();
window.titleContent = new GUIContent("多语言编辑器");
window.Show();
window.minSize = new Vector2(500, 600);
}
protected virtual void OnEnable()
{
}
private void OnDisable()
{
}
private void OnGUI()
{
if (!_asset)
{
var path = $"Assets/Resources/Export/Multilingual.asset";
_asset = AssetDatabase.LoadAssetAtPath<MultilingualData>(path);
if (!_asset)
{
_asset = CreateInstance<MultilingualData>();
AssetDatabase.CreateAsset(_asset, path);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
}
if (_assets.Count == 0)
{
var pathList = new List<string>();
pathList.Add($"Assets/Fonts/SourceHanSansCN-Bold SDF.asset");
pathList.Add($"Assets/Fonts/SourceHanSansCN-ExtraLight SDF.asset");
pathList.Add($"Assets/Fonts/SourceHanSansCN-Regular SDF.asset");
foreach (var path in pathList)
{
var asset = AssetDatabase.LoadAssetAtPath<TMP_FontAsset>(path);
if (asset) _assets.Add(asset);
}
}
if (_redBoxStyle == null)
{
_redBoxStyle = InspectorUtils.GetHelpBoxStyle();
InspectorUtils.AddBorder(_redBoxStyle, new Color(0.5f, 0.4f, 0.4f, 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();
if (InspectorUtils.InspectorButtonWithTextWidth("保存"))
{
EditorUtility.SetDirty(_asset);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
if (InspectorUtils.InspectorButtonWithTextWidth("清空"))
{
_asset.Items.Clear();
_asset.RefreshDict();
}
if (InspectorUtils.InspectorButtonWithTextWidth("导出 Excel"))
{
AssetExportToExcel();
}
if (InspectorUtils.InspectorButtonWithTextWidth("Excel 导回"))
{
ExcelExportToAsset();
}
if (InspectorUtils.InspectorButtonWithTextWidth("生成基础配置(中文字体)"))
{
var uiObj = GameObject.Find("UICanvas");
var coms = uiObj.GetComponentsInChildren<MultilingualTextMono>(true).ToList();
foreach (var com in coms)
{
var textCom = com.gameObject.GetComponent<TextMeshProUGUI>();
if (!textCom) continue;
if (com.GetMultiTextConfig(MultilingualType.ZH) == null)
{
var cfg = new MultiTextConfig(textCom, MultilingualType.ZH);
com.TextCfg.Add(cfg);
}
if (com.FontID == 0) com.FontID = _asset.GetFontGroupID(textCom.font);
}
}
if (InspectorUtils.InspectorButtonWithTextWidth("清除预览"))
{
var uiObj = GameObject.Find("UICanvas");
var coms = uiObj.GetComponentsInChildren<MultilingualTextMono>(true).ToList();
foreach (var com in coms)
{
var text = _asset.GetMultilingualStr(com.ID, MultilingualType.ZH);
var font = _asset.GetMultilingualFont(com.FontID, MultilingualType.ZH);
com.SetMultilingualText(text, MultilingualType.ZH, font);
var textCom = com.GetComponent<TextMeshProUGUI>();
textCom?.ForceMeshUpdate();
}
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (InspectorUtils.InspectorButtonWithTextWidth("添加字体组"))
{
_asset.FontGroups.Add(new MultilingualFontGroup());
}
if (InspectorUtils.InspectorButtonWithTextWidth("检查字符集是否有新增"))
{
string path = "Assets/Fonts/ChineseCharSet.txt";
if (string.IsNullOrEmpty(path))
{
EditorUtility.DisplayDialog("字符集提示", "找不到字符集TXT", "确定");
}
else
{
characterSet.Clear();
string content = System.IO.File.ReadAllText(path);
foreach (var c in content) characterSet.Add(c);
bool isNeedUpdate = false;
foreach (var item in _asset.Items)
{
foreach (var c in item.ZH)
{
if (!IsChinese(c)) continue;
if (!characterSet.Contains(c)) isNeedUpdate = true;
}
}
if (isNeedUpdate)EditorUtility.DisplayDialog("字符集提示", "有新增请创建新的字符集TXT", "确定");
else EditorUtility.DisplayDialog("字符集提示", "无新增,无需理会", "确定");
}
}
if (InspectorUtils.InspectorButtonWithTextWidth("创建新的字符集TXT"))
{
OnBuildChineseTxt();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
EditorGUILayout.BeginVertical(_redBoxStyle);
_isActive = EditorGUILayout.Toggle("筛选活跃文本", _isActive);
_isProperNoun = EditorGUILayout.Toggle("筛选专有名词", _isProperNoun);
_isDialogue = EditorGUILayout.Toggle("筛选对话文本", _isDialogue);
_isDeprecated = EditorGUILayout.Toggle("筛选已废弃文本", _isDeprecated);
if (InspectorUtils.InspectorButtonWithTextWidth("导出 Excel 筛选类型文本"))
{
AssetExportToExcel(true);
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space();
EditorGUILayout.BeginVertical(_whiteBoxStyle);
if (_asset.TargetTypes.Count != 5)
{
_asset.TargetTypes.Clear();
for (int i = (int)MultilingualType.ZH; i <= (int)MultilingualType.KR; i++)
_asset.TargetTypes.Add((MultilingualType)i);
}
for (int i = 0; i < _asset.TargetTypes.Count; i++)
{
EditorGUILayout.BeginHorizontal();
var type = (MultilingualType)(i + 1);
InspectorUtils.InspectorTextWidthRich($"<b>系统语言{type} 对应游戏语言: </b>");
_asset.TargetTypes[i] = (MultilingualType)EditorGUILayout.EnumPopup(_asset.TargetTypes[i], GUILayout.Width(200));
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndVertical();
foreach (var asset in _assets)
{
EditorGUILayout.BeginHorizontal();
if (InspectorUtils.InspectorButtonWithTextWidth($"·")) Selection.activeObject = asset;
EditorGUI.BeginDisabledGroup(true); // 开始禁用组
EditorGUILayout.ObjectField(asset, typeof(TMP_FontAsset), GUILayout.Width(400));
EditorGUI.EndDisabledGroup(); // 结束禁用组
EditorGUILayout.EndHorizontal();
}
var deleteSet = new HashSet<MultilingualFontGroup>();
for (int i = 0; i < _asset.FontGroups.Count; i++)
{
_asset.FontGroups[i].FontID = (uint)i + 1;
if (!ShowFontGroup(_asset.FontGroups[i])) continue;
deleteSet.Add(_asset.FontGroups[i]);
}
foreach (var deleteGroup in deleteSet) _asset.FontGroups.Remove(deleteGroup);
ShowAllMultilingualItem();
EditorGUILayout.EndScrollView();
}
private void ShowAllMultilingualItem()
{
int maxIndex = (_asset.Items.Count - 1) / 10;
_showIndex = Mathf.Clamp(_showIndex, 0, maxIndex);
EditorGUILayout.BeginHorizontal();
if (_showIndex > 0 && InspectorUtils.InspectorButtonWithTextWidth("上一页")) _showIndex--;
if (_showIndex < maxIndex && InspectorUtils.InspectorButtonWithTextWidth("下一页")) _showIndex++;
EditorGUILayout.EndHorizontal();
for (int i = _showIndex * 10; i < (_showIndex + 1) * 10; i++)
{
if (i < 0 || i >= _asset.Items.Count) continue;
ShowMultilingualItem(_asset.Items[i]);
}
}
private bool ShowFontGroup(MultilingualFontGroup fontGroup)
{
var isDelete = false;
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.BeginHorizontal();
InspectorUtils.InspectorTextWidthRich($"<b>ID : {fontGroup.FontID}</b>");
if (InspectorUtils.InspectorButtonWithTextWidth("x")) isDelete = true;
EditorGUILayout.EndHorizontal();
fontGroup.ZHFont =
(TMP_FontAsset)EditorGUILayout.ObjectField(fontGroup.ZHFont, typeof(TMP_FontAsset), false, GUILayout.Width(400));
fontGroup.TDZHFont =
(TMP_FontAsset)EditorGUILayout.ObjectField(fontGroup.TDZHFont, typeof(TMP_FontAsset), false, GUILayout.Width(400));
fontGroup.ENFont =
(TMP_FontAsset)EditorGUILayout.ObjectField(fontGroup.ENFont, typeof(TMP_FontAsset), false, GUILayout.Width(400));
fontGroup.JPFont =
(TMP_FontAsset)EditorGUILayout.ObjectField(fontGroup.JPFont, typeof(TMP_FontAsset), false, GUILayout.Width(400));
fontGroup.KRFont =
(TMP_FontAsset)EditorGUILayout.ObjectField(fontGroup.KRFont, typeof(TMP_FontAsset), false, GUILayout.Width(400));
EditorGUILayout.EndVertical();
EditorGUILayout.Space();
return isDelete;
}
private void ShowMultilingualItem(MultilingualItem item)
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
InspectorUtils.InspectorTextWidthRich($"<b>{item.ID} : </b>");
InspectorUtils.InspectorTextWidthRich($" <b>中文:</b> {item.ZH}");
if (!string.IsNullOrEmpty(item.TDZH))
InspectorUtils.InspectorTextWidthRich($" <b>繁中:</b> {item.TDZH}");
if (!string.IsNullOrEmpty(item.EN))
InspectorUtils.InspectorTextWidthRich($" <b>英语:</b> {item.EN}");
if (!string.IsNullOrEmpty(item.JP))
InspectorUtils.InspectorTextWidthRich($" <b>日语:</b> {item.JP}");
if (!string.IsNullOrEmpty(item.KR))
InspectorUtils.InspectorTextWidthRich($" <b>韩语:</b> {item.KR}");
var unicode = "";
foreach (var c in item.ZH) unicode += $"{(int)c:X4} ";
InspectorUtils.InspectorTextWidthRich($" <b>Unicode</b> {unicode}");
EditorGUILayout.EndVertical();
EditorGUILayout.Space();
}
private void DuplicateRemoval()
{
// 排序 asset.items 保证id从小到大
_asset.Items = _asset.Items.OrderBy(i => i.ID).ToList();
_zhStrDict.Clear();
var deleteItem = new HashSet<MultilingualItem>();
foreach (var item in _asset.Items)
{
item.Refresh();
if (_zhStrDict.ContainsKey(item.ZH))
{
deleteItem.Add(item);
continue;
}
_zhStrDict[item.ZH] = item.ID;
}
foreach (var item in deleteItem) _asset.Items.Remove(item);
_asset.RefreshDict();
}
private string RemoveCsvQuotes(string field)
{
if (string.IsNullOrEmpty(field)) return field;
// 去除首尾空格和换行符
field = field.Trim();
// 若字段以引号开头和结尾,则去除引号并处理内部转义
if (field.Length >= 2 && field.StartsWith("\"") && field.EndsWith("\""))
{
field = field.Substring(1, field.Length - 2) // 去除首尾引号
.Replace("\"\"", "\""); // 将转义引号还原为单个引号
}
return field;
}
private void ExcelExportToAsset()
{
GetExcelData();
_asset.RefreshDict();
string context;
using (var reader = new StreamReader("../Tools/MultilingualTxt.txt", Encoding.Default, true))
{
context = reader.ReadToEnd();
}
var lines = context.Split("!@#$%");
foreach (string line in lines)
{
if (string.IsNullOrWhiteSpace(line)) continue; // 跳过空行
string[] cells = line.Split("%$#@!");
if (cells.Length == 0) continue;
var id = uint.Parse(cells[0]);
MultilingualItem item;
if (_asset.ItemDict.TryGetValue(id, out var value)) item = value;
else
{
item = new MultilingualItem();
_asset.Items.Add(item);
}
item.ID = id;
if (cells.Length >= 3)
{
var str = RemoveCsvQuotes(cells[2]);
if (!string.IsNullOrEmpty(str)) item.ZH = str;
}
if (cells.Length >= 4)
{
var str = RemoveCsvQuotes(cells[3]);
if (!string.IsNullOrEmpty(str)) item.TDZH = str;
}
if (cells.Length >= 5)
{
var str = RemoveCsvQuotes(cells[4]);
if (!string.IsNullOrEmpty(str)) item.EN = str;
}
if (cells.Length >= 6)
{
var str = RemoveCsvQuotes(cells[5]);
if (!string.IsNullOrEmpty(str)) item.JP = str;
}
if (cells.Length >= 7)
{
var str = RemoveCsvQuotes(cells[6]);
if (!string.IsNullOrEmpty(str)) item.KR = str;
}
if (cells.Length >= 8)
{
var str = RemoveCsvQuotes(cells[7]);
if (!string.IsNullOrEmpty(str)) item.IsProperNoun = MultilingualItem.ParseBoolStr(str);
}
if (cells.Length >= 9)
{
var str = RemoveCsvQuotes(cells[8]);
if (!string.IsNullOrEmpty(str)) item.IsDialogue = MultilingualItem.ParseBoolStr(str);
}
if (cells.Length >= 10)
{
var str = RemoveCsvQuotes(cells[9]);
if (!string.IsNullOrEmpty(str)) item.DialogueSpeaker = str;
}
if (cells.Length >= 11)
{
var str = RemoveCsvQuotes(cells[10]);
if (!string.IsNullOrEmpty(str)) item.IsDeprecated = MultilingualItem.ParseBoolStr(str);
}
if (cells.Length >= 12)
{
var str = RemoveCsvQuotes(cells[11]);
if (!string.IsNullOrEmpty(str)) item.IsCustom = MultilingualItem.ParseBoolStr(str);
}
}
EditorUtility.SetDirty(_asset);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
private void AssetExportToExcel(bool isFilter = false)
{
DuplicateRemoval();
_zhStrDict.Clear();
_activeSet.Clear();
_isProperNounDict.Clear();
_isDialogueDict.Clear();
_isDeprecatedDict.Clear();
_speakerDict.Clear();
foreach (var item in _asset.Items) _zhStrDict[item.ZH] = item.ID;
if (_asset.Items.Count != 0) _idIndex = _asset.Items[^1].ID + 1;
else _idIndex = 1;
var uiObj = GameObject.Find("UICanvas");
if (!uiObj)
{
Debug.LogError($"找不到UI根节点");
return;
}
var coms = uiObj.GetComponentsInChildren<TextMeshProUGUI>(true).ToList();
foreach (var com in coms)
{
if (!Regex.IsMatch(com.text, @"[\u4E00-\u9FFF\u3400-\u4DBFa-zA-Z]")) continue;
// 去除首尾空格和换行符
com.text = com.text.Trim().Replace("\r\n", "\n");
var textCom = com.gameObject.GetComponent<MultilingualTextMono>();
if (!textCom) textCom = com.gameObject.AddComponent<MultilingualTextMono>();
if (_zhStrDict.ContainsKey(com.text))
{
textCom.ID = _zhStrDict[com.text];
}
else
{
textCom.ID = _idIndex;
_zhStrDict[com.text] = _idIndex;
_idIndex++;
}
_activeSet.Add(textCom.ID);
_descDict[textCom.ID] = GetGameObjectPath(com.gameObject);
EditorUtility.SetDirty(textCom);
PrefabUtility.RecordPrefabInstancePropertyModifications(textCom);
}
EditorSceneManager.MarkSceneDirty(uiObj.scene);
// 先处理 Assets/Resources/Prefab 路径下的所有 prefab 并保存
var prefabList = new List<GameObject>();
var prefabPath = $"Assets/Resources/Prefab/";
if (Directory.Exists(prefabPath))
{
string[] prefabPaths = Directory.GetFiles(prefabPath, "*.prefab", SearchOption.AllDirectories);
foreach (var prefabAssetPath in prefabPaths)
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabAssetPath);
if (!prefab) continue;
var prefabComs = prefab.GetComponentsInChildren<TextMeshProUGUI>(true).ToList();
if (prefabComs.Count == 0) continue;
foreach (var com in prefabComs)
{
if (!Regex.IsMatch(com.text, @"[\u4E00-\u9FFF\u3400-\u4DBFa-zA-Z]")) continue;
// 去除首尾空格和换行符
com.text = com.text.Trim().Replace("\r\n", "\n");
var textCom = com.gameObject.GetComponent<MultilingualTextMono>();
if (!textCom) textCom = com.gameObject.AddComponent<MultilingualTextMono>();
if (_zhStrDict.ContainsKey(com.text))
{
textCom.ID = _zhStrDict[com.text];
}
else
{
textCom.ID = _idIndex;
_zhStrDict[com.text] = _idIndex;
_idIndex++;
}
_activeSet.Add(textCom.ID);
_descDict[textCom.ID] = prefab.name + " " + GetGameObjectPath(com.gameObject);
}
prefabList.Add(prefab);
}
}
// 最后处理 assets
var path = $"Assets/Resources/DataAssets/";
string[] assetPaths = Directory.GetFiles(path, "*.asset", SearchOption.AllDirectories);
foreach (var assetPath in assetPaths)
{
var asset = AssetDatabase.LoadAssetAtPath<ScriptableObject>(assetPath);
if (!asset) continue;
ScriptableObject newAsset = Object.Instantiate(asset);
TraverseObject(newAsset);
SaveExportAsset(asset.name, newAsset);
}
_asset.RefreshDict();
foreach (var kv in _zhStrDict)
{
if (_asset.ItemDict.ContainsKey(kv.Value)) continue;
var item = new MultilingualItem();
item.ID = kv.Value;
item.ZH = kv.Key;
_asset.Items.Add(item);
}
foreach (var item in _asset.Items)
{
if(!_descDict.ContainsKey(item.ID)) continue;
item.Desc = _descDict[item.ID];
}
// 排序 asset.items 保证id从小到大
_asset.Items = _asset.Items.OrderBy(i => i.ID).ToList();
string filePath = "../Tools/MultilingualTxt.txt";
if (!File.Exists(filePath))
{
using (File.Create(filePath))
{
} // 立即释放句柄
}
using (StreamWriter sw = new StreamWriter(filePath, false, Encoding.UTF8))
{
StringBuilder sb = new StringBuilder();
foreach (var item in _asset.Items)
{
if (!item.IsCustom)
{
if (_isProperNounDict.ContainsKey(item.ID)) item.IsProperNoun = _isProperNounDict[item.ID];
if (_isDialogueDict.ContainsKey(item.ID)) item.IsDialogue = _isDialogueDict[item.ID];
if (_isDeprecatedDict.ContainsKey(item.ID)) item.IsDeprecated = _isDeprecatedDict[item.ID];
if (_speakerDict.ContainsKey(item.ID)) item.DialogueSpeaker = _speakerDict[item.ID];
}
if (isFilter)
{
if (_isActive && !_activeSet.Contains(item.ID)) continue;
if (_isProperNoun && !item.IsProperNoun) continue;
if (_isDialogue && !item.IsDialogue) continue;
if (_isDeprecated && !item.IsDeprecated) continue;
}
var active = _activeSet.Contains(item.ID);
sb.Append($"{item.ID}%$#@!{active}%$#@!{item.ZH}%$#@!{item.TDZH}%$#@!{item.EN}%$#@!{item.JP}%$#@!{item.KR}" +
$"%$#@!{item.IsProperNoun}%$#@!{item.IsDialogue}%$#@!{item.DialogueSpeaker}" +
$"%$#@!{item.IsDeprecated}%$#@!{item.IsCustom}%$#@!{item.Desc}!@#$%");
}
sw.Write(sb.ToString());
}
WriteToExcel();
foreach (var prefab in prefabList) EditorUtility.SetDirty(prefab);
EditorUtility.SetDirty(_asset);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
private string GetGameObjectPath(GameObject go)
{
if (go == null) return string.Empty;
if (go.transform.parent == null)
return go.name;
return GetGameObjectPath(go.transform.parent.gameObject) + "/" + go.name;
}
private void AssetExportToExcelPrefab(bool isFilter = false)
{
DuplicateRemoval();
_zhStrDict.Clear();
_activeSet.Clear();
foreach (var item in _asset.Items) _zhStrDict[item.ZH] = item.ID;
if (_asset.Items.Count != 0) _idIndex = _asset.Items[^1].ID + 1;
else _idIndex = 1;
// 先处理 Assets/Resources/Prefab 路径下的所有 prefab 并保存
var coms = new List<TextMeshProUGUI>();
var prefabList = new List<GameObject>();
var prefabPath = $"Assets/Resources/Prefab/";
if (Directory.Exists(prefabPath))
{
string[] prefabPaths = Directory.GetFiles(prefabPath, "*.prefab", SearchOption.AllDirectories);
foreach (var prefabAssetPath in prefabPaths)
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabAssetPath);
if (!prefab) continue;
var prefabComs = prefab.GetComponentsInChildren<TextMeshProUGUI>(true).ToList();
if (prefabComs.Count == 0) continue;
foreach (var com in prefabComs)coms.Add(com);
prefabList.Add(prefab);
}
}
foreach (var com in coms)
{
if (!Regex.IsMatch(com.text, @"[\u4E00-\u9FFF\u3400-\u4DBFa-zA-Z]")) continue;
// 去除首尾空格和换行符
com.text = com.text.Trim().Replace("\r\n", "\n");
var textCom = com.gameObject.GetComponent<MultilingualTextMono>();
if (!textCom) textCom = com.gameObject.AddComponent<MultilingualTextMono>();
if (_zhStrDict.ContainsKey(com.text))
{
textCom.ID = _zhStrDict[com.text];
}
else
{
textCom.ID = _idIndex;
_zhStrDict[com.text] = _idIndex;
_idIndex++;
}
_activeSet.Add(textCom.ID);
}
_asset.RefreshDict();
foreach (var kv in _zhStrDict)
{
if (_asset.ItemDict.ContainsKey(kv.Value)) continue;
var item = new MultilingualItem();
item.ID = kv.Value;
item.ZH = kv.Key;
_asset.Items.Add(item);
}
// 排序 asset.items 保证id从小到大
_asset.Items = _asset.Items.OrderBy(i => i.ID).ToList();
string filePath = "../Tools/MultilingualTxt.txt";
if (!File.Exists(filePath))
{
using (File.Create(filePath))
{
} // 立即释放句柄
}
using (StreamWriter sw = new StreamWriter(filePath, false, Encoding.UTF8))
{
StringBuilder sb = new StringBuilder();
foreach (var item in _asset.Items)
{
if (isFilter)
{
if (_isActive && !_activeSet.Contains(item.ID)) continue;
if (_isProperNoun && !item.IsProperNoun) continue;
if (_isDialogue && !item.IsDialogue) continue;
if (_isDeprecated && !item.IsDeprecated) continue;
}
var active = _activeSet.Contains(item.ID) ? 1 : 0;
sb.Append($"{item.ID}%$#@!{active}%$#@!{item.ZH}%$#@!{item.TDZH}%$#@!{item.EN}%$#@!{item.JP}%$#@!{item.KR}" +
$"%$#@!{item.IsProperNoun}%$#@!{item.IsDialogue}%$#@!{item.DialogueSpeaker}%$#@!{item.IsDeprecated}!@#$%");
}
sw.Write(sb.ToString());
}
WriteToExcel();
foreach (var prefab in prefabList) EditorUtility.SetDirty(prefab);
EditorUtility.SetDirty(_asset);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
private void SaveExportAsset(string name, ScriptableObject target)
{
if (target == null) return;
// 处理目标路径
string targetPath = $"Assets/Resources/Export/{name}.asset";
if (AssetDatabase.LoadAssetAtPath<ScriptableObject>(targetPath) != null)
{
AssetDatabase.DeleteAsset(targetPath);
}
// 保存新实例
AssetDatabase.CreateAsset(target, targetPath);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
private void TraverseObject(object asset)
{
if (asset == null) return;
var assetType = asset.GetType();
var fields = asset.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);
// 检查是否拥有 GetSpeaker() 方法并调用
var getSpeakerMethod = assetType.GetMethod("GetSpeaker", BindingFlags.Public | BindingFlags.Instance);
string speaker = null;
if (getSpeakerMethod != null && getSpeakerMethod.ReturnType == typeof(string))
{
speaker = getSpeakerMethod.Invoke(asset, null) as string;
}
foreach (var field in fields)
{
var value = field.GetValue(asset);
var attr = field.GetCustomAttribute<MultilingualFieldAttribute>();
if (attr != null)
{
if (value is string s)
{
var str = s.Trim().Replace("\r\n", "\n");
if (string.IsNullOrEmpty(str)) continue;
if (_zhStrDict.TryGetValue(str, out var id))
{
field.SetValue(asset, id.ToString());
}
else
{
_zhStrDict[str] = _idIndex;
field.SetValue(asset, _zhStrDict[str].ToString());
_idIndex++;
}
_activeSet.Add(_zhStrDict[str]);
_descDict[_zhStrDict[str]] = assetType.Name + " " + field.Name;
_isProperNounDict[_zhStrDict[str]] = attr.IsProperNoun;
_isDialogueDict[_zhStrDict[str]] = attr.IsDialogue;
_isDeprecatedDict[_zhStrDict[str]] = attr.IsDeprecated;
if (!string.IsNullOrEmpty(speaker)) _speakerDict[_zhStrDict[str]] = speaker;
continue;
}
if (value is List<string> list)
{
for (int i = 0; i < list.Count; i++)
{
var str = list[i].Trim().Replace("\r\n", "\n");
if (string.IsNullOrEmpty(str)) continue;
if (_zhStrDict.TryGetValue(str, out var id))
{
list[i] = id.ToString();
}
else
{
_zhStrDict[str] = _idIndex;
list[i] = _zhStrDict[str].ToString();
_idIndex++;
}
_activeSet.Add(_zhStrDict[str]);
_descDict[_zhStrDict[str]] = assetType.Name + " " + field.Name;
_isProperNounDict[_zhStrDict[str]] = attr.IsProperNoun;
_isDialogueDict[_zhStrDict[str]] = attr.IsDialogue;
_isDeprecatedDict[_zhStrDict[str]] = attr.IsDeprecated;
if (!string.IsNullOrEmpty(speaker)) _speakerDict[_zhStrDict[str]] = speaker;
}
field.SetValue(asset, list);
continue;
}
}
if (value == null) continue;
if (value is IEnumerable enumerable)
{
foreach (object item in enumerable)
{
TraverseObject(item); // 递归处理集合项
}
}
// 如果是自定义对象(非基础类型),递归处理
else if (!value.GetType().IsPrimitive) TraverseObject(value);
}
}
public void WriteToExcel()
{
var pythonScript = $"../Tools/ExportStringToExcel.py";
ProcessStartInfo start = new ProcessStartInfo
{
FileName = "python",
Arguments = $"\"{pythonScript}\"",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
StandardErrorEncoding = Encoding.UTF8,
StandardOutputEncoding = Encoding.UTF8,
};
using (var process = Process.Start(start))
{
string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd(); // 获取错误信息
process.WaitForExit();
Debug.Log($"Exit Code: {process.ExitCode}"); // 打印退出码
Debug.Log($"Output: {output}");
Debug.Log($"Error: {error}"); // 打印错误信息
}
}
public void GetExcelData()
{
var pythonScript = $"../Tools/PrintExcelString.py";
ProcessStartInfo start = new ProcessStartInfo
{
FileName = "python",
Arguments = $"\"{pythonScript}\"",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
StandardErrorEncoding = Encoding.UTF8,
StandardOutputEncoding = Encoding.UTF8,
};
using (var process = Process.Start(start))
{
string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd(); // 获取错误信息
process.WaitForExit();
Debug.Log($"Exit Code: {process.ExitCode}"); // 打印退出码
Debug.Log($"Output: {output}");
Debug.Log($"Error: {error}"); // 打印错误信息
}
}
private void OnBuildChineseTxt()
{
characterSet.Clear();
AddBasicCharacters();
ExtractChineseFromI2Languages();
ExportCharacterSet();
}
private void AddBasicCharacters()
{
// 添加大写字母
for (char c = 'A'; c <= 'Z'; c++)
{
characterSet.Add(c);
}
// 添加小写字母
for (char c = 'a'; c <= 'z'; c++)
{
characterSet.Add(c);
}
// 添加数字
for (char c = '0'; c <= '9'; c++)
{
characterSet.Add(c);
}
// 添加常用标点符号
string punctuations = ",.!?;:\"'()[]{}+-*/=_<>@#$%^&|\\~|/\~{}[]【】「」『』《》〈〉:;“” ‘’"、,。?!…—--_+-=×÷";
foreach (char c in punctuations)
{
characterSet.Add(c);
}
}
private void ExtractChineseFromI2Languages()
{
var path = $"Assets/Resources/Export/Multilingual.asset";
var asset = AssetDatabase.LoadAssetAtPath<MultilingualData>(path);
if (asset == null)
{
EditorUtility.DisplayDialog("错误", "未找到多语言资源文件,请确认路径是否正确。", "确定");
return;
}
foreach (var item in asset.Items)
{
foreach (var c in item.ZH)
{
if (!IsChinese(c)) continue;
characterSet.Add(c);
}
}
}
private bool IsChinese(char c)
{
return (c >= 0x4E00 && c <= 0x9FFF) || // CJK统一汉字
(c >= 0x3400 && c <= 0x4DBF) || // CJK扩展A
(c >= 0x20000 && c <= 0x2A6DF); // CJK扩展B
}
private void ExportCharacterSet()
{
string path = EditorUtility.SaveFilePanel("保存字符集", "Assets/Fonts", "ChineseCharSet", "txt");
if (!string.IsNullOrEmpty(path))
{
File.WriteAllText(path, string.Join("", characterSet.OrderBy(c => c)), Encoding.UTF8);
EditorUtility.DisplayDialog("成功", "字符集已导出!", "确定");
}
}
}
}