using UnityEngine; using UnityEditor; using TMPro; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEngine.Localization.PropertyVariants; using UnityEngine.Localization.Settings; using UnityEngine.Localization; using UnityEngine.Localization.PropertyVariants.TrackedProperties; using UnityEngine.Localization.PropertyVariants.TrackedObjects; using UnityEngine.Events; using UnityEngine.Localization.Components; using UnityEngine.UI; namespace GameCore.Editor { /// /// 用于批量设置TMP字体和材质的本地化 /// public class SetupTmpTextAndFont : EditorWindow { private Vector2 scrollPosition; private bool processSelectedPrefabs = true; private bool processFolderPrefabs = false; private string folderPath = "Assets/"; private bool recursiveSearch = true; // Unity Localization设置 private string tableReference = "text_AssetTable"; // 处理结果 private List processedPrefabs = new List(); private List errorMessages = new List(); [MenuItem("Tools/Setup TMP Text And Font")] public static void ShowWindow() { SetupTmpTextAndFont window = GetWindow("Setup TMP Text And Font"); window.minSize = new Vector2(400, 500); window.Show(); } private void OnGUI() { scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); GUILayout.Label("Setup TMP Text And Font", EditorStyles.boldLabel); GUILayout.Space(10); // 处理选项 GUILayout.Label("处理选项", EditorStyles.boldLabel); processSelectedPrefabs = EditorGUILayout.Toggle("处理选中的Prefab", processSelectedPrefabs); processFolderPrefabs = EditorGUILayout.Toggle("处理文件夹下的Prefab", processFolderPrefabs); if (processFolderPrefabs) { EditorGUILayout.BeginHorizontal(); GUILayout.Label("文件夹路径:", GUILayout.Width(80)); folderPath = EditorGUILayout.TextField(folderPath); if (GUILayout.Button("选择", GUILayout.Width(60))) { string selectedPath = EditorUtility.OpenFolderPanel("选择文件夹", "Assets", ""); if (!string.IsNullOrEmpty(selectedPath)) { if (selectedPath.StartsWith(Application.dataPath)) { folderPath = "Assets" + selectedPath.Substring(Application.dataPath.Length); } else { folderPath = selectedPath; } } } EditorGUILayout.EndHorizontal(); recursiveSearch = EditorGUILayout.Toggle("递归搜索子文件夹", recursiveSearch); } GUILayout.Space(10); // Unity Localization设置 GUILayout.Label("Unity Localization 设置", EditorStyles.boldLabel); tableReference = EditorGUILayout.TextField("表格引用", tableReference); GUILayout.Space(10); // 操作按钮 EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("预览将要处理的Prefab")) { PreviewPrefabsToProcess(); } if (GUILayout.Button("执行清理设置")) { ClearPrefabAll(); } if (GUILayout.Button("执行批量设置")) { ProcessPrefabs(); } if (GUILayout.Button("执行批量个性化设置")) { ProcessIndividuationPrefabs(); } if (GUILayout.Button("执行批量WFC设置")) { ProcessWFCPrefabs(); } EditorGUILayout.EndHorizontal(); GUILayout.Space(10); // 显示处理结果 if (processedPrefabs.Count > 0 || errorMessages.Count > 0) { GUILayout.Label("处理结果", EditorStyles.boldLabel); if (processedPrefabs.Count > 0) { GUILayout.Label($"成功处理 {processedPrefabs.Count} 个Prefab:", EditorStyles.boldLabel); foreach (string prefab in processedPrefabs) { EditorGUILayout.BeginHorizontal(); GUILayout.Label("✓", GUILayout.Width(20)); if (GUILayout.Button(prefab, EditorStyles.label)) { Object prefabAsset = AssetDatabase.LoadAssetAtPath(prefab); if (prefabAsset != null) { Selection.activeObject = prefabAsset; EditorGUIUtility.PingObject(prefabAsset); } } EditorGUILayout.EndHorizontal(); } } if (errorMessages.Count > 0) { GUILayout.Label($"处理失败 {errorMessages.Count} 个:", EditorStyles.boldLabel); foreach (string error in errorMessages) { EditorGUILayout.BeginHorizontal(); GUILayout.Label("✗", GUILayout.Width(20)); GUILayout.Label(error, EditorStyles.label); EditorGUILayout.EndHorizontal(); } } } EditorGUILayout.EndScrollView(); } private void PreviewPrefabsToProcess() { processedPrefabs.Clear(); errorMessages.Clear(); List prefabsToProcess = GetPrefabsToProcess(); if (prefabsToProcess.Count == 0) { EditorUtility.DisplayDialog("预览结果", "没有找到要处理的Prefab", "确定"); return; } string message = $"将要处理以下 {prefabsToProcess.Count} 个Prefab:\n\n"; foreach (string prefab in prefabsToProcess) { message += $"• {prefab}\n"; } EditorUtility.DisplayDialog("预览结果", message, "确定"); } private void ProcessPrefabs() { processedPrefabs.Clear(); errorMessages.Clear(); List prefabsToProcess = GetPrefabsToProcess(); if (prefabsToProcess.Count == 0) { EditorUtility.DisplayDialog("处理结果", "没有找到要处理的Prefab", "确定"); return; } int processedCount = 0; int errorCount = 0; foreach (string prefabPath in prefabsToProcess) { try { if (ProcessSinglePrefab(prefabPath)) { processedPrefabs.Add(prefabPath); processedCount++; } else { errorMessages.Add(prefabPath); errorCount++; } } catch (System.Exception e) { errorMessages.Add($"{prefabPath}: {e.Message}"); errorCount++; } } // 刷新资源数据库 AssetDatabase.Refresh(); string resultMessage = $"处理完成!\n\n成功: {processedCount} 个\n失败: {errorCount} 个"; EditorUtility.DisplayDialog("处理结果", resultMessage, "确定"); } private List GetPrefabsToProcess() { List prefabs = new List(); // 处理选中的prefab if (processSelectedPrefabs) { foreach (Object obj in Selection.objects) { string path = AssetDatabase.GetAssetPath(obj); if (IsPrefab(path)) { prefabs.Add(path); } } } // 处理文件夹下的prefab if (processFolderPrefabs && !string.IsNullOrEmpty(folderPath)) { string fullPath = Path.Combine(Application.dataPath, folderPath.Replace("Assets/", "")); if (Directory.Exists(fullPath)) { string searchPattern = "*.prefab"; SearchOption searchOption = recursiveSearch ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; string[] prefabFiles = Directory.GetFiles(fullPath, searchPattern, searchOption); foreach (string file in prefabFiles) { string relativePath = "Assets" + file.Substring(Application.dataPath.Length); prefabs.Add(relativePath); } } } return prefabs.Distinct().ToList(); } private bool IsPrefab(string path) { return path.EndsWith(".prefab"); } private bool ProcessSinglePrefab(string prefabPath) { // 加载prefab GameObject prefabRoot = PrefabUtility.LoadPrefabContents(prefabPath); if (prefabRoot == null) { return false; } bool hasChanges = false; try { // 查找所有TMP组件 TextMeshProUGUI[] tmpUGUIComponents = prefabRoot.GetComponentsInChildren(true); TextMeshPro[] tmpComponents = prefabRoot.GetComponentsInChildren(true); // 处理TextMeshProUGUI组件 foreach (TextMeshProUGUI tmp in tmpUGUIComponents) { if (SetupTmpLocalization(tmp.gameObject, prefabRoot.gameObject.name)) { hasChanges = true; } } // 处理TextMeshPro组件 foreach (TextMeshPro tmp in tmpComponents) { if (SetupTmpLocalization(tmp.gameObject, prefabRoot.gameObject.name)) { hasChanges = true; } } // 如果有更改,保存prefab if (hasChanges) { PrefabUtility.SaveAsPrefabAsset(prefabRoot, prefabPath); } } finally { // 清理临时对象 PrefabUtility.UnloadPrefabContents(prefabRoot); } return hasChanges; } private bool SetupTmpLocalization(GameObject targetObject, string panelName) { try { if (targetObject == null) { Debug.LogError("目标GameObject为空"); return false; } // 获取TMP组件 TMP_Text text = targetObject.GetComponent(); if (text == null) { text = targetObject.GetComponent(); } if (text == null) { Debug.LogWarning($"未找到TMP组件: {targetObject.name}"); return false; } // 检查是否已经有GameObjectLocalizer组件 GameObjectLocalizer existingLocalizer = targetObject.GetComponent(); if (existingLocalizer != null) { SetupUnityLocalizationForTMP(text, existingLocalizer); Debug.Log($"已存在GameObjectLocalizer组件,跳过: {targetObject.name}"); return false; } // 添加GameObjectLocalizer组件 var localizer = targetObject.AddComponent(); if (localizer == null) { Debug.LogError($"无法添加GameObjectLocalizer组件:{panelName} == {targetObject.name}"); return false; } // 执行Unity Localization设置 SetupUnityLocalizationForTMP(targetObject, text, localizer, panelName); return true; } catch (System.Exception e) { Debug.LogError($"设置TMP本地化时出错 {panelName} == {targetObject?.name}: {e.Message}"); return false; } } private void SetupUnityLocalizationForTMP(GameObject targetObject, TMP_Text text, GameObjectLocalizer localizer, string panelName) { try { // 检查参数是否为空 if (text == null) { Debug.LogError($"TMP_Text组件为空:{panelName} {targetObject.name}"); return; } if (localizer == null) { Debug.LogError($"GameObjectLocalizer组件为空:{panelName} {targetObject.name}"); return; } // 检查Unity Localization系统是否初始化 if (!LocalizationSettings.InitializationOperation.IsDone) { Debug.LogWarning($"Unity Localization系统未初始化完成,跳过设置: {targetObject.name}"); return; } // Gets the Tracked text or creates a new tracker var trackedText = localizer.GetTrackedObject(text); if (trackedText == null) { Debug.LogError($"无法获取Tracked对象:{panelName} {targetObject.name}"); return; } // The LocalizedString can be modified directly var fVariant = trackedText.GetTrackedProperty("m_fontAsset"); if (fVariant != null) { string fontEntryReference = text.font.name; fVariant.LocalizedObject = new LocalizedTmpFont { TableReference = tableReference, TableEntryReference = fontEntryReference }; } else { Debug.LogWarning($"无法获取字体属性:{panelName} {targetObject.name}"); } var mtVariant = trackedText.GetTrackedProperty("m_sharedMaterial"); if (mtVariant != null) { string materialEntryReference = text.fontSharedMaterial.name; mtVariant.LocalizedObject = new LocalizedMaterial { TableReference = tableReference, TableEntryReference = materialEntryReference }; } else { Debug.LogWarning($"无法获取材质属性:{panelName} {targetObject.name}"); } // Force an Update if (LocalizationSettings.SelectedLocale != null) { localizer.ApplyLocaleVariant(LocalizationSettings.SelectedLocale); } } catch (System.Exception e) { Debug.LogError($"设置Unity Localization时出错{panelName} == {targetObject.name}: {e.Message}"); } } private void SetupUnityLocalizationForTMP(TMP_Text text, GameObjectLocalizer localizer) { try { // 检查参数是否为空 if (text == null) { Debug.LogError($"TMP_Text组件为空:"); return; } if (localizer == null) { Debug.LogError($"GameObjectLocalizer组件为空:"); return; } // 检查Unity Localization系统是否初始化 if (!LocalizationSettings.InitializationOperation.IsDone) { Debug.LogWarning($"Unity Localization系统未初始化完成,跳过设置:"); return; } // Gets the Tracked text or creates a new tracker var trackedText = localizer.GetTrackedObject(text); if (trackedText == null) { Debug.LogError($"无法获取Tracked对象:"); return; } // The LocalizedString can be modified directly var fVariant = trackedText.GetTrackedProperty("m_fontAsset"); if (fVariant != null) { string fontEntryReference = text.font.name; fVariant.LocalizedObject = new LocalizedTmpFont { TableReference = tableReference, TableEntryReference = fontEntryReference }; } else { Debug.LogWarning($"无法获取字体属性:"); } var mtVariant = trackedText.GetTrackedProperty("m_sharedMaterial"); if (mtVariant != null) { string materialEntryReference = text.fontSharedMaterial.name; mtVariant.LocalizedObject = new LocalizedMaterial { TableReference = tableReference, TableEntryReference = materialEntryReference }; } else { Debug.LogWarning($"无法获取材质属性:"); } // Force an Update if (LocalizationSettings.SelectedLocale != null) { localizer.ApplyLocaleVariant(LocalizationSettings.SelectedLocale); } } catch (System.Exception e) { Debug.LogError($"设置Unity Localization时出错: {e.Message}"); } } void ProcessIndividuationPrefabs() { processedPrefabs.Clear(); errorMessages.Clear(); List prefabsToProcess = GetPrefabsToProcess(); if (prefabsToProcess.Count == 0) { EditorUtility.DisplayDialog("处理结果", "没有找到要处理的Prefab", "确定"); return; } int processedCount = 0; int errorCount = 0; foreach (string prefabPath in prefabsToProcess) { try { if (ProcessIndividuationSinglePrefab(prefabPath)) { processedPrefabs.Add(prefabPath); processedCount++; } else { errorMessages.Add(prefabPath); errorCount++; } } catch (System.Exception e) { errorMessages.Add($"{prefabPath}: {e.Message}"); errorCount++; } } // 刷新资源数据库 AssetDatabase.Refresh(); string resultMessage = $"处理完成!\n\n成功: {processedCount} 个\n失败: {errorCount} 个"; EditorUtility.DisplayDialog("处理结果", resultMessage, "确定"); } private bool ProcessIndividuationSinglePrefab(string prefabPath) { // 加载prefab GameObject prefabRoot = PrefabUtility.LoadPrefabContents(prefabPath); if (prefabRoot == null) { return false; } bool hasChanges = false; try { // 查找所有TMP组件 TextMeshProUGUI[] tmpUGUIComponents = prefabRoot.GetComponentsInChildren(true); TextMeshPro[] tmpComponents = prefabRoot.GetComponentsInChildren(true); // 处理TextMeshProUGUI组件 foreach (TextMeshProUGUI tmp in tmpUGUIComponents) { if (SetupTmpIndividuationLocalization(tmp.gameObject, prefabRoot.gameObject.name)) { hasChanges = true; } } // 处理TextMeshPro组件 foreach (TextMeshPro tmp in tmpComponents) { if (SetupTmpIndividuationLocalization(tmp.gameObject, prefabRoot.gameObject.name)) { hasChanges = true; } } // 如果有更改,保存prefab if (hasChanges) { PrefabUtility.SaveAsPrefabAsset(prefabRoot, prefabPath); } } finally { // 清理临时对象 PrefabUtility.UnloadPrefabContents(prefabRoot); } return hasChanges; } private bool SetupTmpIndividuationLocalization(GameObject targetObject, string panelName) { try { if (targetObject == null) { Debug.LogError("目标GameObject为空"); return false; } // 获取TMP组件 TMP_Text text = targetObject.GetComponent(); if (text == null) { text = targetObject.GetComponent(); } if (text == null) { Debug.LogWarning($"未找到TMP组件: {targetObject.name}"); return false; } // 检查是否已经有GameObjectLocalizer组件 GameObjectLocalizer existingLocalizer = targetObject.GetComponent(); if (existingLocalizer != null) { SetupUnityLocalizationForTMP(text, existingLocalizer); Debug.Log($"已存在GameObjectLocalizer组件,跳过: {targetObject.name}"); return true; } return false; } catch (System.Exception e) { Debug.LogError($"设置TMP本地化时出错 {panelName} == {targetObject?.name}: {e.Message}"); return false; } } void ProcessWFCPrefabs() { processedPrefabs.Clear(); errorMessages.Clear(); List prefabsToProcess = GetPrefabsToProcess(); if (prefabsToProcess.Count == 0) { EditorUtility.DisplayDialog("处理结果", "没有找到要处理的Prefab", "确定"); return; } int processedCount = 0; int errorCount = 0; foreach (string prefabPath in prefabsToProcess) { try { if (ProcessWFCSinglePrefab(prefabPath)) { processedPrefabs.Add(prefabPath); processedCount++; } else { errorMessages.Add(prefabPath); errorCount++; } } catch (System.Exception e) { errorMessages.Add($"{prefabPath}: {e.Message}"); errorCount++; } } // 刷新资源数据库 AssetDatabase.Refresh(); string resultMessage = $"处理完成!\n\n成功: {processedCount} 个\n失败: {errorCount} 个"; EditorUtility.DisplayDialog("处理结果", resultMessage, "确定"); } private bool ProcessWFCSinglePrefab(string prefabPath) { // 加载prefab GameObject prefabRoot = PrefabUtility.LoadPrefabContents(prefabPath); if (prefabRoot == null) { return false; } bool hasChanges = false; try { // 查找所有TMP组件 TextMeshProUGUI[] tmpUGUIComponents = prefabRoot.GetComponentsInChildren(true); TextMeshPro[] tmpComponents = prefabRoot.GetComponentsInChildren(true); // 处理TextMeshProUGUI组件 foreach (TextMeshProUGUI tmp in tmpUGUIComponents) { if (SetupTmpWFCLocalization(tmp.gameObject, prefabRoot.gameObject.name)) { hasChanges = true; } } // 处理TextMeshPro组件 foreach (TextMeshPro tmp in tmpComponents) { if (SetupTmpWFCLocalization(tmp.gameObject, prefabRoot.gameObject.name)) { hasChanges = true; } } // 如果有更改,保存prefab if (hasChanges) { PrefabUtility.SaveAsPrefabAsset(prefabRoot, prefabPath); } } finally { // 清理临时对象 PrefabUtility.UnloadPrefabContents(prefabRoot); } return hasChanges; } private bool SetupTmpWFCLocalization(GameObject targetObject, string panelName) { try { if (targetObject == null) { Debug.LogError("目标GameObject为空"); return false; } // 获取TMP组件 TMP_Text text = targetObject.GetComponent(); if (text == null) { text = targetObject.GetComponent(); } if (text == null) { Debug.LogWarning($"未找到TMP组件: {targetObject.name}"); return false; } // 检查是否已经有GameObjectLocalizer组件 GameObjectLocalizer existingLocalizer = targetObject.GetComponent(); if (existingLocalizer != null) { SetupUnityLocalizationWFCForTMP(text, existingLocalizer); Debug.Log($"已存在GameObjectLocalizer组件,跳过: {targetObject.name}"); return true; } return false; } catch (System.Exception e) { Debug.LogError($"设置TMP本地化时出错 {panelName} == {targetObject?.name}: {e.Message}"); return false; } } private void SetupUnityLocalizationWFCForTMP(TMP_Text text, GameObjectLocalizer localizer) { try { // 检查参数是否为空 if (text == null) { Debug.LogError($"TMP_Text组件为空:"); return; } if (localizer == null) { Debug.LogError($"GameObjectLocalizer组件为空:"); return; } // 检查Unity Localization系统是否初始化 if (!LocalizationSettings.InitializationOperation.IsDone) { Debug.LogWarning($"Unity Localization系统未初始化完成,跳过设置:"); return; } // Gets the Tracked text or creates a new tracker var trackedText = localizer.GetTrackedObject(text); if (trackedText == null) { Debug.LogError($"无法获取Tracked对象:"); return; } // The LocalizedString can be modified directly var fVariant = trackedText.GetTrackedProperty("m_fontAsset"); if (fVariant != null) { string fontEntryReference = text.font.name; fVariant.LocalizedObject = new LocalizedTmpFont { TableReference = tableReference, TableEntryReference = fontEntryReference }; fVariant.LocalizedObject.WaitForCompletion = true; // 设置为true以确保WFC处理 } else { Debug.LogWarning($"无法获取字体属性:"); } var mtVariant = trackedText.GetTrackedProperty("m_sharedMaterial"); if (mtVariant != null) { string materialEntryReference = text.fontSharedMaterial.name; mtVariant.LocalizedObject = new LocalizedMaterial { TableReference = tableReference, TableEntryReference = materialEntryReference }; mtVariant.LocalizedObject.WaitForCompletion = true; // 设置为true以确保WFC处理 } else { Debug.LogWarning($"无法获取材质属性:"); } // Force an Update if (LocalizationSettings.SelectedLocale != null) { localizer.ApplyLocaleVariant(LocalizationSettings.SelectedLocale); } } catch (System.Exception e) { Debug.LogError($"设置Unity Localization时出错: {e.Message}"); } } void ClearPrefabAll() { processedPrefabs.Clear(); errorMessages.Clear(); List prefabsToProcess = GetPrefabsToProcess(); if (prefabsToProcess.Count == 0) { EditorUtility.DisplayDialog("处理结果", "没有找到要处理的Prefab", "确定"); return; } int processedCount = 0; int errorCount = 0; foreach (string prefabPath in prefabsToProcess) { try { if (ClearPrefab(prefabPath)) { processedPrefabs.Add(prefabPath); processedCount++; } else { errorMessages.Add(prefabPath); errorCount++; } } catch (System.Exception e) { errorMessages.Add($"{prefabPath}: {e.Message}"); errorCount++; } } // 刷新资源数据库 AssetDatabase.Refresh(); string resultMessage = $"处理完成!\n\n成功: {processedCount} 个\n失败: {errorCount} 个"; EditorUtility.DisplayDialog("处理结果", resultMessage, "确定"); } private bool ClearPrefab(string prefabPath) { // 加载prefab GameObject prefabRoot = PrefabUtility.LoadPrefabContents(prefabPath); if (prefabRoot == null) { return false; } bool hasChanges = false; try { // 查找所有TMP组件 GameObjectLocalizer[] tmpUGUIComponents = prefabRoot.GetComponentsInChildren(true); // 如果有更改,保存prefab if (tmpUGUIComponents.Length > 0) { hasChanges = true; for (int i = 0; i < tmpUGUIComponents.Length; i++) { DestroyImmediate(tmpUGUIComponents[i]); } PrefabUtility.SaveAsPrefabAsset(prefabRoot, prefabPath); } } finally { // 清理临时对象 PrefabUtility.UnloadPrefabContents(prefabRoot); } return hasChanges; } } /// /// 用于批量设置TMP文本的本地化 /// public class LocalizedTmpTextString : EditorWindow { /// /// 自动配置 UnityEvent 的核心逻辑 /// /// 要配置的 MyLocalizeSpriteEvent 组件实例 private void AutoSetupEvent(LocalizeStringEvent localizeEvent) { TMP_Text targetStringComponent = localizeEvent.gameObject.GetComponent(); if (targetStringComponent == null) { EditorUtility.DisplayDialog("错误", " 对象上没有找到 TMP_Text 组件。", "确定"); return; } // 2. 获取 UnityEvent 的序列化属性 // 这是关键!我们不能直接操作 localizeEvent.m_UpdateString, // 因为直接修改不会触发 Unity 的 "脏" 标记和序列化系统。 // 我们必须通过 SerializedObject 和 SerializedProperty 来操作。 SerializedObject serializedObject = new SerializedObject(localizeEvent); // 使用属性名 "m_UpdateString" 来找到对应的 SerializedProperty SerializedProperty eventProperty = serializedObject.FindProperty("m_UpdateString"); // 3. 准备添加新的监听器 // 获取 PersistentCalls 列表的属性 SerializedProperty persistentCallsProperty = eventProperty.FindPropertyRelative("m_PersistentCalls.m_Calls"); // --- 选项 A:仅添加一个空的监听器(相当于点击 + 号)--- // persistentCallsProperty.arraySize++; // 增加数组大小,Unity 会自动创建一个新元素 // Debug.Log("已添加一个空的监听器,请在 Inspector 中手动配置。"); // serializedObject.ApplyModifiedProperties(); // 应用修改 // return; // 如果只想实现点击 + 号的效果,到这里就够了 // --- 选项 B:添加并完全配置监听器(更强大)--- // 检查是否已经有监听器了,避免重复添加 bool listenerExists = false; for (int i = 0; i < persistentCallsProperty.arraySize; i++) { SerializedProperty call = persistentCallsProperty.GetArrayElementAtIndex(i); // 检查目标对象和方法名是否已经匹配 if (call.FindPropertyRelative("m_Target").objectReferenceValue == targetStringComponent && call.FindPropertyRelative("m_MethodName").stringValue == "set_text") { listenerExists = true; break; } } if (listenerExists) { EditorUtility.DisplayDialog("提示", "事件监听器已经配置过了,无需重复添加。", "确定"); return; } // 增加数组大小 int newIndex = persistentCallsProperty.arraySize; persistentCallsProperty.arraySize++; serializedObject.ApplyModifiedProperties(); // 应用大小修改 // 获取新添加的 PersistentCall SerializedProperty newCall = persistentCallsProperty.GetArrayElementAtIndex(newIndex); // 4. 设置新监听器的各个属性 // 设置目标对象 SerializedProperty targetProperty = newCall.FindPropertyRelative("m_Target"); targetProperty.objectReferenceValue = targetStringComponent; // --- 关键区别点 1: 设置方法名 --- // 对于 TextMeshProUGUI 的 text 属性,其 setter 方法名是 "set_text" SerializedProperty methodNameProperty = newCall.FindPropertyRelative("m_MethodName"); methodNameProperty.stringValue = "set_text"; // 设置模式 SerializedProperty modeProperty = newCall.FindPropertyRelative("m_Mode"); modeProperty.intValue = (int)UnityEventCallState.EditorAndRuntime; // --- 关键区别点 2: 设置参数 --- // 参数类型是 string,所以这里要设置 string 的完整程序集限定名 SerializedProperty argumentsProperty = newCall.FindPropertyRelative("m_Arguments"); SerializedProperty objectArgumentProperty = argumentsProperty.FindPropertyRelative("m_ObjectArgument"); objectArgumentProperty.objectReferenceValue = null; SerializedProperty argumentAssemblyTypeProperty = argumentsProperty.FindPropertyRelative("m_ObjectArgumentAssemblyTypeName"); argumentAssemblyTypeProperty.stringValue = "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"; // 这是 string 的完整类型名 // 5. 应用所有修改 serializedObject.ApplyModifiedProperties(); // 最后,给用户一个反馈 EditorUtility.DisplayDialog("成功", $"已成功为 '{localizeEvent.name}' 配置事件监听器,连接到 '{localizeEvent.gameObject.name}' 的 text 属性。", "确定"); Debug.Log($"自动配置完成: {localizeEvent.name}.OnStringUpdated -> {localizeEvent.gameObject.name}.TextMeshProUGUI.text", localizeEvent); } } /// /// 用于批量设置Image的本地化 /// public class LocalizedSpriteEditor : EditorWindow { /// /// 自动配置 UnityEvent 的核心逻辑 /// /// 要配置的 MyLocalizeSpriteEvent 组件实例 private void AutoSetupEvent(LocalizeSpriteEvent localizeEvent) { Image targetImageComponent = localizeEvent.gameObject.GetComponent(); if (targetImageComponent == null) { EditorUtility.DisplayDialog("错误", " 对象上没有找到 Image 组件。", "确定"); return; } // 2. 获取 UnityEvent 的序列化属性 // 这是关键!我们不能直接操作 localizeEvent.m_UpdateString, // 因为直接修改不会触发 Unity 的 "脏" 标记和序列化系统。 // 我们必须通过 SerializedObject 和 SerializedProperty 来操作。 SerializedObject serializedObject = new SerializedObject(localizeEvent); // 使用属性名 "m_UpdateString" 来找到对应的 SerializedProperty SerializedProperty eventProperty = serializedObject.FindProperty("m_UpdateAsset"); // 3. 准备添加新的监听器 // 获取 PersistentCalls 列表的属性 SerializedProperty persistentCallsProperty = eventProperty.FindPropertyRelative("m_PersistentCalls.m_Calls"); // --- 选项 A:仅添加一个空的监听器(相当于点击 + 号)--- // persistentCallsProperty.arraySize++; // 增加数组大小,Unity 会自动创建一个新元素 // Debug.Log("已添加一个空的监听器,请在 Inspector 中手动配置。"); // serializedObject.ApplyModifiedProperties(); // 应用修改 // return; // 如果只想实现点击 + 号的效果,到这里就够了 // --- 选项 B:添加并完全配置监听器(更强大)--- // 检查是否已经有监听器了,避免重复添加 bool listenerExists = false; for (int i = 0; i < persistentCallsProperty.arraySize; i++) { SerializedProperty call = persistentCallsProperty.GetArrayElementAtIndex(i); // 检查目标对象和方法名是否已经匹配 if (call.FindPropertyRelative("m_Target").objectReferenceValue == targetImageComponent && call.FindPropertyRelative("m_MethodName").stringValue == "set_sprite") { listenerExists = true; break; } } if (listenerExists) { EditorUtility.DisplayDialog("提示", "事件监听器已经配置过了,无需重复添加。", "确定"); return; } // 增加数组大小,为新的监听器腾出位置 int newIndex = persistentCallsProperty.arraySize; persistentCallsProperty.arraySize++; serializedObject.ApplyModifiedProperties(); // 应用大小修改,否则 GetArrayElementAtIndex 会越界 // 获取新添加的 PersistentCall 的 SerializedProperty SerializedProperty newCall = persistentCallsProperty.GetArrayElementAtIndex(newIndex); // 4. 设置新监听器的各个属性 // 设置目标对象 (m_Target) SerializedProperty targetProperty = newCall.FindPropertyRelative("m_Target"); targetProperty.objectReferenceValue = targetImageComponent; // 设置方法名 (m_MethodName) // 对于属性,Unity 的内部调用的是它的 setter 方法,命名为 "set_" + 属性名 SerializedProperty methodNameProperty = newCall.FindPropertyRelative("m_MethodName"); methodNameProperty.stringValue = "set_sprite"; // 设置模式 (m_Mode) // 0 = EventDefined (使用事件提供的参数), 1 = Void (无参数), 2 = Object (提供一个固定的对象), etc. // 我们需要的是 EventDefined,其值为 0 SerializedProperty modeProperty = newCall.FindPropertyRelative("m_Mode"); modeProperty.intValue = (int)UnityEventCallState.EditorAndRuntime; // 设置参数 (m_Arguments) // 因为我们使用的是事件提供的参数 (EventDefined),所以需要告诉它使用哪个参数 // 对于 UnityEvent,我们想将事件的 Sprite 参数传递给方法的 Sprite 参数 SerializedProperty argumentsProperty = newCall.FindPropertyRelative("m_Arguments"); SerializedProperty objectArgumentProperty = argumentsProperty.FindPropertyRelative("m_ObjectArgument"); objectArgumentProperty.objectReferenceValue = null; // 对于 EventDefined,这个通常为 null SerializedProperty argumentAssemblyTypeProperty = argumentsProperty.FindPropertyRelative("m_ObjectArgumentAssemblyTypeName"); argumentAssemblyTypeProperty.stringValue = "UnityEngine.Sprite, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"; // 必须是完整的类型名 // 5. 应用所有修改 // 这一步至关重要!它将我们在代码中做的所有更改写入到实际的组件和场景数据中。 // 同时,它会将对象标记为 "Dirty",提示 Unity 需要保存场景/预制体的更改。 serializedObject.ApplyModifiedProperties(); // 最后,给用户一个反馈 EditorUtility.DisplayDialog("成功", $"已成功为 '{localizeEvent.name}' 配置事件监听器,连接到 '{localizeEvent.gameObject.name}' 的 sprite 属性。", "确定"); Debug.Log($"自动配置完成: {localizeEvent.name}.OnAssetUpdated -> {localizeEvent.gameObject.name}.Image.sprite", localizeEvent); } } }