using XGame.Framework.i18n; using XGame.Framework.Asset.Addressable; using XGame.Framework.Asset.Addressable.Data; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; using System.Text; namespace XGame.Editor.Asset { public class AddressableBuildTask { /// /// 方便其它模块使用 /// public static void GenManifest() { var task = new AddressableBuildTask(); task.Run(); } private IDictionary manifests; private AddressableInfoManifest backupManifest; private IDictionary addressableNameMap; private IDictionary addressableIdMap; private IDictionary guidMap; private AddressableClassify[] queryOrder; public AddressableBuildTask() { manifests = new Dictionary(); addressableNameMap = new Dictionary(); addressableIdMap = new Dictionary(); guidMap = new Dictionary(); queryOrder = new AddressableClassify[] { AddressableClassify.Product, AddressableClassify.BuiltIn, AddressableClassify.Tests }; } private void Init() { LoadManifest(); DisposeInvalidAssets(); CollectAddressableAssets(); } public void Run() { Init(); Collect(); Save(); GenCustomSearch(); } private void Collect() { AddressableClassify[] classifys = { AddressableClassify.BuiltIn, AddressableClassify.Product, AddressableClassify.Tests }; for (int i = 0; i < classifys.Length; i++) { var classify = classifys[i]; string[] assetsRoots = AddressableHelper.GetAssetsRoots(classify); foreach (var assetsRoot in assetsRoots) { var i18nName = string.Empty; var langFlag = LanguageType.NONE; if (classify == AddressableClassify.Product) { langFlag = AddressableHelper.GetLanguageTypeByAssetPath(assetsRoot); if (langFlag != LanguageType.NONE) { i18nName = langFlag.ToString().ToLower(); } } FileUtil.FindFiles(assetsRoot, files => { foreach (var file in files) { var path = FileUtil.ToRelativePath(file);// file.Substring(file.IndexOf("Assets/", StringComparison.Ordinal)); var guid = AssetDatabase.AssetPathToGUID(path); if (ContainsGUID(guid)) { //Debug.Log($"GUID Repeat. Path:{path}"); if (langFlag != LanguageType.NONE || AddressableHelper.IsLodAssetPath(path)) { //多语言或者LOD资源 RefreshAssetInfoByGUID(guid, classify); } continue; } string fileName; //取备份信息 if (TryGetBackupAssetInfoByGUID(guid, out var backupInfo)) { fileName = backupInfo.addressableName; } else { fileName = AddressableHelper.GetFileName(path); if (langFlag != LanguageType.NONE) { fileName = $"{i18nName}_{fileName}"; } } if (ContainsName(fileName)) { //二次校验 TryGetAssetInfoByName(fileName, out var lastAssetInfo); var lastPath = lastAssetInfo.GetAssetPath(); string temp = string.Empty; if (string.IsNullOrEmpty(lastPath)) { Debug.LogError($"Two assets have the same name. But one of the files is missing. Last {lastAssetInfo}. Next Path:{path}"); } else if (path.Equals(lastPath)) { Debug.LogError($"Two assets have the same path. But the GUID is different.Last {lastAssetInfo}. Next Path:{path}"); } else { var ext = Path.GetExtension(path); if (!string.IsNullOrEmpty(ext) && !ext.Equals(Path.GetExtension(lastPath))) { //后缀不一样 temp = ($"{fileName}{ext.Replace('.', '_')}").ToLower(); //if (langFlag != LangFlag.NONE) //{ // temp = $"{i18nName}_{temp}"; //} if (ContainsName(temp)) { temp = string.Empty; } } } if (string.IsNullOrEmpty(temp)) { temp = ($"{fileName}_{guid.Substring(0, 8)}").ToLower(); //if (langFlag != LangFlag.NONE) //{ // temp = $"{i18nName}_{temp}"; //} if (ContainsName(temp)) { temp = ($"{fileName}_{guid}").ToLower(); //if (langFlag != LangFlag.NONE) //{ // temp = $"{i18nName}_{temp}"; //} } } fileName = temp; } //addressableId使用guid+path,预防相同路径的情况 var isInResources = AddressableHelper.IsInResources(path, out var relativePath); //var assetType = isInResources ? AddressableAssetType.Resources : AddressableAssetType.Default; var assetInfo = new AssetInfo() { addressableId = Crc32.GetCrc32($"{guid}+{path}"), addressableName = fileName, assetGUID = guid, //assetType = assetType, relativePath = relativePath, }; if (AddAssetInfo(assetInfo)) { Debug.Log($"guid:{guid} name:{fileName} path:{path}"); } } }); } } } private void Save() { for (int i = 0; i < queryOrder.Length; i++) { if (manifests.TryGetValue(queryOrder[i], out var manifest)) { manifest.Save(); } } backupManifest.Save(); } public bool TryGetAssetInfoByName(string addressableName, out AssetInfo assetInfo) { if (addressableNameMap.TryGetValue(addressableName, out var classify)) { if (manifests[classify].TryGetAssetInfoByName(addressableName, out var tempAsset)) { assetInfo = tempAsset; return true; } } assetInfo = default; return false; } public bool TryGetBackupAssetInfoByGUID(string guid, out AssetInfo assetInfo) { if (backupManifest.TryGetAssetInfoByGUID(guid, out var tempAsset)) { assetInfo = tempAsset; return true; } assetInfo = default; return false; } private bool ContainsName(string addressableName) { if (addressableNameMap.ContainsKey(addressableName)) { return true; } //for (int i = 0; i < queryOrder.Length; i++) //{ // if (manifests.TryGetValue(queryOrder[i], out var manifest)) // { // if (manifest.ContainsName(addressableName)) // { // return true; // } // } //} return false; } private bool ContainsGUID(string guid) { if (guidMap.ContainsKey(guid)) { return true; } //for (int i = 0; i < queryOrder.Length; i++) //{ // if (manifests.TryGetValue(queryOrder[i], out var manifest)) // { // if (manifest.ContainsGUID(guid)) // { // return true; // } // } //} return false; } private void RefreshAssetInfoByGUID(string guid, AddressableClassify classify) { if (manifests.TryGetValue(classify, out var manifest)) { manifest.RefreshAssetInfoByGUID(guid); } } private bool AddAssetInfo(AssetInfo assetInfo) { var assetPath = assetInfo.GetAssetPath(); if (AddressableHelper.TryAssetPathToClassify(assetPath, out var classify)) { if (manifests.TryGetValue(classify, out var manifest)) { if (manifest.AddAssetInfo(assetInfo)) { AddAddressableName(assetInfo.addressableName, classify); AddAddressableId(assetInfo.addressableId, classify); AddAssetGUID(assetInfo.assetGUID, classify); return true; } } } return false; } private void LoadManifest() { for (int i = 0; i < queryOrder.Length; i++) { var classify = queryOrder[i]; var manifest = CreateManifest(classify); if (manifest != null) manifests.Add(classify, manifest); } backupManifest = CreateManifest(AddressableClassify.Backup); } private AddressableInfoManifest CreateManifest(AddressableClassify classify) { var manifest = AddressableHelper.LoadOrCreateManifest(classify); if (manifest != null) { var adapter = new AddressableInfoManifest(manifest, classify); return adapter; } return null; } private void DisposeInvalidAssets() { for (int i = 0; i < queryOrder.Length; i++) { if (manifests.TryGetValue(queryOrder[i], out var manifest)) { manifest.DisposeInvalidAssets(AddAssetInfo); } } } private void CollectAddressableAssets() { addressableNameMap.Clear(); addressableIdMap.Clear(); guidMap.Clear(); for (int i = 0; i < queryOrder.Length; i++) { if (manifests.TryGetValue(queryOrder[i], out var manifest)) { manifest.CollectAddressableAssets(ref addressableNameMap, ref addressableIdMap, ref guidMap); } } } private void AddAddressableName(string addressableName, AddressableClassify classify) { if (addressableNameMap.ContainsKey(addressableName)) { addressableNameMap[addressableName] = classify; } else { addressableNameMap.Add(addressableName, classify); } } private void AddAssetGUID(string guid, AddressableClassify classify) { if (guidMap.ContainsKey(guid)) { guidMap[guid] = classify; } else { guidMap.Add(guid, classify); } } private void AddAddressableId(long id, AddressableClassify classify) { if (addressableIdMap.ContainsKey(id)) { addressableIdMap[id] = classify; } else { addressableIdMap.Add(id, classify); } } #region 资源搜索和引用标签 https://docs.unity.cn/cn/tuanjiemanual/1.2/Manual/AssetsReferences.html /// /// 生成CustomSearch.txt /// private void GenCustomSearch() { var manifest = AddressableHelper.LoadAssetManifest(AddressableClassify.Product); var assetInfos = manifest.assetInfos; var assetPaths = new List(); for(var i = 0; i < assetInfos.Length; i++) { var assetPath = assetInfos[i].GetAssetPath(); if (string.IsNullOrEmpty(assetPath)) { continue; } assetPaths.Add(assetPath); } var sb = new StringBuilder(); sb.AppendLine((assetPaths.Count + 4).ToString()); for(var i = 0;i < assetPaths.Count; i++) { sb.AppendLine(assetPaths[i]); } sb.AppendLine(PathDefine.BundleManifestPath); sb.AppendLine(PathDefine.SceneAssetBundleManifestPath); sb.AppendLine(PathDefine.ReferenceManifestPath); sb.AppendLine(PathDefine.ProductAssetManifestPath); var filePath = PathDefine.AddressableDataRelative + "CustomSearch.txt"; File.WriteAllText(filePath, sb.ToString()); AssetDatabase.Refresh(); } #endregion } }