先修复一下,错误的场景 删除不必要的钓场资产 修复into场景 update:更新meta文件,修复报错 update:修复资源 修复第一章节建造 修复建造第三章 update:删除多余内容 update:更新README.md update:还原图标 update:更新README update:更新配置
1160 lines
44 KiB
C#
1160 lines
44 KiB
C#
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
|
||
{
|
||
/// <summary>
|
||
/// 用于批量设置TMP字体和材质的本地化
|
||
/// </summary>
|
||
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<string> processedPrefabs = new List<string>();
|
||
private List<string> errorMessages = new List<string>();
|
||
|
||
[MenuItem("Tools/Setup TMP Text And Font")]
|
||
public static void ShowWindow()
|
||
{
|
||
SetupTmpTextAndFont window = GetWindow<SetupTmpTextAndFont>("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<Object>(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<string> 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<string> 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<string> GetPrefabsToProcess()
|
||
{
|
||
List<string> prefabs = new List<string>();
|
||
|
||
// 处理选中的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<TextMeshProUGUI>(true);
|
||
TextMeshPro[] tmpComponents = prefabRoot.GetComponentsInChildren<TextMeshPro>(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<TextMeshProUGUI>();
|
||
if (text == null)
|
||
{
|
||
text = targetObject.GetComponent<TextMeshPro>();
|
||
}
|
||
|
||
if (text == null)
|
||
{
|
||
Debug.LogWarning($"未找到TMP组件: {targetObject.name}");
|
||
return false;
|
||
}
|
||
|
||
// 检查是否已经有GameObjectLocalizer组件
|
||
GameObjectLocalizer existingLocalizer = targetObject.GetComponent<GameObjectLocalizer>();
|
||
if (existingLocalizer != null)
|
||
{
|
||
SetupUnityLocalizationForTMP(text, existingLocalizer);
|
||
Debug.Log($"已存在GameObjectLocalizer组件,跳过: {targetObject.name}");
|
||
return false;
|
||
}
|
||
|
||
// 添加GameObjectLocalizer组件
|
||
var localizer = targetObject.AddComponent<GameObjectLocalizer>();
|
||
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<TrackedUGuiGraphic>(text);
|
||
if (trackedText == null)
|
||
{
|
||
Debug.LogError($"无法获取Tracked对象:{panelName} {targetObject.name}");
|
||
return;
|
||
}
|
||
|
||
// The LocalizedString can be modified directly
|
||
var fVariant = trackedText.GetTrackedProperty<LocalizedAssetProperty>("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<LocalizedAssetProperty>("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<TrackedUGuiGraphic>(text);
|
||
if (trackedText == null)
|
||
{
|
||
Debug.LogError($"无法获取Tracked对象:");
|
||
return;
|
||
}
|
||
|
||
// The LocalizedString can be modified directly
|
||
var fVariant = trackedText.GetTrackedProperty<LocalizedAssetProperty>("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<LocalizedAssetProperty>("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<string> 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<TextMeshProUGUI>(true);
|
||
TextMeshPro[] tmpComponents = prefabRoot.GetComponentsInChildren<TextMeshPro>(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<TextMeshProUGUI>();
|
||
if (text == null)
|
||
{
|
||
text = targetObject.GetComponent<TextMeshPro>();
|
||
}
|
||
|
||
if (text == null)
|
||
{
|
||
Debug.LogWarning($"未找到TMP组件: {targetObject.name}");
|
||
return false;
|
||
}
|
||
|
||
// 检查是否已经有GameObjectLocalizer组件
|
||
GameObjectLocalizer existingLocalizer = targetObject.GetComponent<GameObjectLocalizer>();
|
||
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<string> 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<TextMeshProUGUI>(true);
|
||
TextMeshPro[] tmpComponents = prefabRoot.GetComponentsInChildren<TextMeshPro>(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<TextMeshProUGUI>();
|
||
if (text == null)
|
||
{
|
||
text = targetObject.GetComponent<TextMeshPro>();
|
||
}
|
||
|
||
if (text == null)
|
||
{
|
||
Debug.LogWarning($"未找到TMP组件: {targetObject.name}");
|
||
return false;
|
||
}
|
||
|
||
// 检查是否已经有GameObjectLocalizer组件
|
||
GameObjectLocalizer existingLocalizer = targetObject.GetComponent<GameObjectLocalizer>();
|
||
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<TrackedUGuiGraphic>(text);
|
||
if (trackedText == null)
|
||
{
|
||
Debug.LogError($"无法获取Tracked对象:");
|
||
return;
|
||
}
|
||
|
||
// The LocalizedString can be modified directly
|
||
var fVariant = trackedText.GetTrackedProperty<LocalizedAssetProperty>("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<LocalizedAssetProperty>("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<string> 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<GameObjectLocalizer>(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;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 用于批量设置TMP文本的本地化
|
||
/// </summary>
|
||
public class LocalizedTmpTextString : EditorWindow
|
||
{
|
||
/// <summary>
|
||
/// 自动配置 UnityEvent 的核心逻辑
|
||
/// </summary>
|
||
/// <param name="localizeEvent">要配置的 MyLocalizeSpriteEvent 组件实例</param>
|
||
private void AutoSetupEvent(LocalizeStringEvent localizeEvent)
|
||
{
|
||
TMP_Text targetStringComponent = localizeEvent.gameObject.GetComponent<TMP_Text>();
|
||
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($"<color=green>自动配置完成:</color> {localizeEvent.name}.OnStringUpdated -> {localizeEvent.gameObject.name}.TextMeshProUGUI.text", localizeEvent);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 用于批量设置Image的本地化
|
||
/// </summary>
|
||
public class LocalizedSpriteEditor : EditorWindow
|
||
{
|
||
/// <summary>
|
||
/// 自动配置 UnityEvent 的核心逻辑
|
||
/// </summary>
|
||
/// <param name="localizeEvent">要配置的 MyLocalizeSpriteEvent 组件实例</param>
|
||
private void AutoSetupEvent(LocalizeSpriteEvent localizeEvent)
|
||
{
|
||
Image targetImageComponent = localizeEvent.gameObject.GetComponent<Image>();
|
||
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 参数传递给方法的 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($"<color=green>自动配置完成:</color> {localizeEvent.name}.OnAssetUpdated -> {localizeEvent.gameObject.name}.Image.sprite", localizeEvent);
|
||
}
|
||
}
|
||
}
|