using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEditor; using UnityEditor.Build; using UnityEditor.Build.Reporting; using UnityEngine; #if UNITY_EDITOR using UnityEditor.AddressableAssets; using UnityEditor.AddressableAssets.Settings; #endif namespace BaseGames.Editor { /// /// Editor 工具:验证 中所有常量 /// 是否与 Addressable 分组中实际存在的地址同步(架构 13_AssetPoolModule §10)。 /// /// 菜单:BaseGames → Tools → Validate Address Keys /// Build 回调顺序 = 0(在 SOValidationRunner callbackOrder = 1 之前执行) /// public class AddressKeyValidatorBuildHook : IPreprocessBuildWithReport { public int callbackOrder => 0; public void OnPreprocessBuild(BuildReport report) { var results = AddressKeyValidator.RunValidation(); int missing = results.Count(r => !r.ExistsInAddressables); if (missing > 0) { var orphans = results .Where(r => !r.ExistsInAddressables) .Select(r => $"AddressKeys.{r.FieldName} = \"{r.Value}\""); throw new BuildFailedException( $"[AddressKeyValidator] {missing} 个孤儿 AddressKey,构建中止:\n" + string.Join("\n", orphans)); } } } /// /// Editor 静态工具类:验证逻辑和 MenuItem 入口。 /// public static class AddressKeyValidator { [MenuItem("BaseGames/Tools/Validate Address Keys")] public static void ValidateAll() { var results = RunValidation(); LogResults(results); } /// /// 执行验证,返回每个 key 的验证结果。供 Build Pre-process 或测试调用。 /// public static List RunValidation() { var results = new List(); var registeredAddresses = GetAllAddressableAddresses(); // 通过反射取出 AddressKeys 中所有 public const string 字段 var keyType = typeof(BaseGames.Core.Assets.AddressKeys); var fields = keyType.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy) .Where(f => f.IsLiteral && !f.IsInitOnly && f.FieldType == typeof(string)); foreach (var field in fields) { var value = (string)field.GetRawConstantValue(); var exists = registeredAddresses.Contains(value); results.Add(new ValidationResult(field.Name, value, exists)); } return results; } // ── Internal ────────────────────────────────────────────────────────── private static HashSet GetAllAddressableAddresses() { var addresses = new HashSet(StringComparer.Ordinal); #if UNITY_EDITOR var settings = AddressableAssetSettingsDefaultObject.Settings; if (settings == null) { Debug.LogWarning("[AddressKeyValidator] Addressable Settings 未找到,请先初始化 Addressables。"); return addresses; } foreach (var group in settings.groups) { if (group == null) continue; foreach (var entry in group.entries) { if (entry != null) addresses.Add(entry.address); } } #endif return addresses; } private static void LogResults(List results) { int missing = 0; foreach (var r in results) { if (!r.ExistsInAddressables) { Debug.LogWarning($"[AddressKeyValidator] ⚠ 孤儿 Key:AddressKeys.{r.FieldName} = \"{r.Value}\" — 未在 Addressable 分组中找到对应地址。"); missing++; } } if (missing == 0) Debug.Log($"[AddressKeyValidator] ✓ 所有 {results.Count} 个 AddressKeys 常量均在 Addressable 分组中存在。"); else Debug.LogWarning($"[AddressKeyValidator] 共 {results.Count} 个常量,发现 {missing} 个孤儿 Key。" + $"尚未创建的 Prefab/Scene 资产请在创建后添加至 Addressables 分组。"); } // ── 结果结构 ────────────────────────────────────────────────────────── public readonly struct ValidationResult { public readonly string FieldName; public readonly string Value; public readonly bool ExistsInAddressables; public ValidationResult(string fieldName, string value, bool exists) { FieldName = fieldName; Value = value; ExistsInAddressables = exists; } } } /// /// 资产导入后自动触发 AddressKey 验证(架构 13_AssetPoolModule §10)。 /// 仅在 Addressable Group 资产发生变更时触发,避免每次导入都验证。 /// public class AddressKeyImportWatcher : AssetPostprocessor { private const string AddressableGroupAssetExt = ".asset"; private const string AddressableGroupFolder = "Assets/AddressableAssetsData"; private static void OnPostprocessAllAssets( string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { bool addressablesChanged = false; foreach (var path in importedAssets) { if (path.StartsWith(AddressableGroupFolder, StringComparison.OrdinalIgnoreCase) && path.EndsWith(AddressableGroupAssetExt, StringComparison.OrdinalIgnoreCase)) { addressablesChanged = true; break; } } if (!addressablesChanged) { foreach (var path in deletedAssets) { if (path.StartsWith(AddressableGroupFolder, StringComparison.OrdinalIgnoreCase)) { addressablesChanged = true; break; } } } if (addressablesChanged) { // 延迟一帧执行,等待 AssetDatabase 完全刷新 EditorApplication.delayCall += () => { Debug.Log("[AddressKeyImportWatcher] 检测到 Addressable 分组变更,自动触发 Key 验证..."); AddressKeyValidator.ValidateAll(); }; } } } }