using XGame.Framework.i18n; using XGame.Framework.Quality; using XGame.Framework.Asset.Addressable; using XGame.Framework.Asset.Addressable.Data; using System.Collections.Generic; using UnityEditor; using UnityEngine; namespace XGame.Editor.Asset { internal class AddressableInfoManifest { private AddressableInfosSo manifest; private List assetInfos; /// /// 多语言资源配置,每次收集资源信息都重置 /// private Dictionary> i18nInfosMap; /// /// Lod资源配置,每次收集资源信息都重置 /// private Dictionary> lodInfosMap; /// /// 等待二次检测的Asset资源 /// 由于LOD资源在同一目录下,可能出现lod资源比Origin资源更早记录的情况 /// 第一次检测时找不到就先记录下来,等所有资源遍历结束再做一次检测 /// private List verifingLodAssets; public AddressableClassify ClassifyOperation { get; private set; } public AddressableInfoManifest(AddressableInfosSo manifest, AddressableClassify classify) { ClassifyOperation = classify; this.manifest = manifest; assetInfos = manifest?.GetAssetInfos(); } public bool AddAssetInfo(AssetInfo assetInfo) { if (manifest == null) return false; if (assetInfos == null) { assetInfos = new List(); } var index = assetInfos.FindIndex((a) => a.assetGUID.Equals(assetInfo.assetGUID) || a.addressableName.Equals(assetInfo.addressableName) || a.addressableId == assetInfo.addressableId); if (index >= 0) { Debug.LogError($"Asset Manifest AddAssetInfo Error. Asset Repeat Last:{assetInfos[index]} Next:{assetInfo}"); return false; } assetInfos.Add(assetInfo); OnAddAssetInfo(assetInfo); return true; } public void RemoveAssetInfoByName(string addressableName) { if (assetInfos != null) { var index = assetInfos.FindIndex((a) => a.addressableName.Equals(addressableName)); if (index >= 0) { assetInfos.RemoveAt(index); } } } public bool TryGetAssetInfoByName(string addressableName, out AssetInfo assetInfo) { if (assetInfos != null) { var index = assetInfos.FindIndex((a) => a.addressableName.Equals(addressableName)); if (index >= 0) { assetInfo = assetInfos[index]; return true; } } assetInfo = default; return false; } public bool TryGetAssetInfoByGUID(string guid, out AssetInfo assetInfo) { if (assetInfos != null) { var index = assetInfos.FindIndex((a) => a.assetGUID.Equals(guid)); if (index >= 0) { assetInfo = assetInfos[index]; return true; } } assetInfo = default; return false; } public bool ContainsName(string addressableName) { if (assetInfos != null) { var index = assetInfos.FindIndex((a) => a.addressableName.Equals(addressableName)); return index >= 0; } return false; } public bool ContainsGUID(string guid) { if (assetInfos != null) { var index = assetInfos.FindIndex((a) => a.assetGUID.Equals(guid)); return index >= 0; } return false; } public void Save() { if (manifest != null) { VerifyLodAssetsSecond(); SaveLodInfos(); SaveI18nInfos(); manifest.SetAssetInfos(assetInfos != null && assetInfos.Count > 0 ? assetInfos.ToArray() : null); } } public void DisposeInvalidAssets(System.Func moveAssetEvent) { if (assetInfos != null) { for (int i = assetInfos.Count - 1; i >= 0; i--) { var assetInfo = assetInfos[i]; if (!AddressableHelper.IsValidPath(assetInfo.GetAssetPath(), ClassifyOperation)) { Debug.LogWarning($"Invalid Asset:{assetInfo}"); if (moveAssetEvent != null && moveAssetEvent.Invoke(assetInfo)) { assetInfos.RemoveAt(i); } } } } } public void CollectAddressableAssets(ref IDictionary addressableNameMap, ref IDictionary addressableIdMap, ref IDictionary guidMap) { if (assetInfos != null) { for (int i = 0; i < assetInfos.Count; i++) { var assetInfo = assetInfos[i]; //刷新relativePath和assetType bool isInResources = AddressableHelper.IsInResources(assetInfo.GetAssetPath(), out assetInfo.relativePath); //assetInfo.assetType = isInResources ? AddressableAssetType.Resources : AddressableAssetType.Default; assetInfos[i] = assetInfo; // if (addressableNameMap.ContainsKey(assetInfo.addressableName)) { Debug.LogError($"Addressable Name Repeat. {assetInfo}"); } else { addressableNameMap.Add(assetInfo.addressableName, ClassifyOperation); } if (addressableIdMap.ContainsKey(assetInfo.addressableId)) { Debug.LogError($"Addressable ID Repeat. {assetInfo}"); } else { addressableIdMap.Add(assetInfo.addressableId, ClassifyOperation); } if (guidMap.ContainsKey(assetInfo.assetGUID)) { Debug.LogError($"Addressable GUID Repeat. {assetInfo}"); } else { guidMap.Add(assetInfo.assetGUID, ClassifyOperation); } } } } //private bool IsValidPath(string assetPath) //{ // if (string.IsNullOrEmpty(assetPath)) // { // return false; // } // switch (ClassifyOperation) // { // case AddressableClassifyOperation.BuiltIn: // if (assetPath.StartsWith(PathDefine.XGamePackageRelative)) // { // //在Package Framework目录下 // return true; // } // break; // case AddressableClassifyOperation.ResStatic: // if (assetPath.StartsWith(PathDefine.ResStaticToAddressable)) // { // //在ResStatic目录下 // return true; // } // break; // default: // if (assetPath.StartsWith(PathDefine.ResAddressableRelative)) // { // //在Res/Addressable目录下 // return true; // } // if (assetPath.StartsWith(PathDefine.ResourcesRelative)) // { // //在Resources目录下 // return true; // } // break; // } // return false; //} private void OnAddAssetInfo(AssetInfo assetInfo) { if (ClassifyOperation == AddressableClassify.Product) { //项目资源才需要检测LOD和多语言 VerifyLodAsset(assetInfo, true); VerifyI18nAsset(assetInfo); } } public void RefreshAssetInfoByGUID(string assetGUID) { if (ClassifyOperation == AddressableClassify.Product) { if (TryGetAssetInfoByGUID(assetGUID, out var assetInfo)) { OnAddAssetInfo(assetInfo); } } } #region i18n private void VerifyI18nAsset(AssetInfo assetInfo) { var assetPath = assetInfo.GetAssetPath(); var langFlag = AddressableHelper.GetLanguageTypeByAssetPath(assetPath); if (langFlag != LanguageType.NONE) { // 是多语言 var originPath = assetPath.Replace($"/{PathDefine.I18nName}/{langFlag.ToName()}", ""); var originGUID = AssetDatabase.AssetPathToGUID(originPath); if (string.IsNullOrEmpty(originGUID) == false && TryGetAssetInfoByGUID(originGUID, out var originAsset)) { AddI18nData(originAsset.addressableId, assetInfo.addressableId, langFlag); } else { Debug.LogError($"多语言资源错误,找不到对应的原始资源. {assetInfo} OriginPath:{originPath}"); } } } private bool AddI18nData(long originId, long i18nId, LanguageType langFlag) { if (i18nInfosMap == null) { i18nInfosMap = new Dictionary>(); } if (!i18nInfosMap.TryGetValue(originId, out var lstData)) { lstData = new List(); i18nInfosMap.Add(originId, lstData); } var index = lstData.FindIndex((a) => a.langFlag == langFlag); if (index >= 0) { var data = lstData[index]; data.addressableId = i18nId; lstData[index] = data; } else { lstData.Add(new AddressableI18nInfo.I18nData() { addressableId = i18nId, langFlag = langFlag, }); } return true; } private void SaveI18nInfos() { if (i18nInfosMap != null && i18nInfosMap.Count > 0) { AddressableI18nInfo[] i18nInfos = new AddressableI18nInfo[i18nInfosMap.Count]; var index = 0; foreach (var pair in i18nInfosMap) { var info = new AddressableI18nInfo() { addressableId = pair.Key, i18nDatas = pair.Value.ToArray() }; i18nInfos[index++] = info; } manifest.SetAddressableI18nInfos(i18nInfos, false); } else { manifest.SetAddressableI18nInfos(null, false); } } #endregion #region LOD /// /// 二次检验lod资源 /// private void VerifyLodAssetsSecond() { if (verifingLodAssets != null) { foreach (var assetInfo in verifingLodAssets) { VerifyLodAsset(assetInfo, false); } verifingLodAssets.Clear(); } } private void VerifyLodAsset(AssetInfo assetInfo, bool isFirst) { var assetPath = assetInfo.GetAssetPath(); if (IsLodAssetPath(assetPath, PathDefine.LodHigh, out var originPath, out var quality) || IsLodAssetPath(assetPath, PathDefine.LodLow, out originPath, out quality)) { Debug.Log($"LOD资源. {assetInfo} OriginPath:{originPath}"); var originGUID = AssetDatabase.AssetPathToGUID(originPath); if (string.IsNullOrEmpty(originGUID) == false && TryGetAssetInfoByGUID(originGUID, out var originAsset)) { AddLodData(originAsset.addressableId, assetInfo.addressableId, quality); } else if (isFirst) { //保存起来,等待二次校验 AddVerifingLodAsset(assetInfo); } else { Debug.LogError($"LOD资源错误,找不到对应的原始资源. {assetInfo} OriginPath:{originPath}"); } } } private void AddVerifingLodAsset(AssetInfo assetInfo) { if (verifingLodAssets == null) { verifingLodAssets = new List(); } verifingLodAssets.Add(assetInfo); } private bool IsLodAssetPath(string assetPath, string lodTag, out string originPath, out XGame.Framework.Quality.XQualityLevel quality) { var fileName = System.IO.Path.GetFileNameWithoutExtension(assetPath); if (fileName != null && fileName.ToLower().EndsWith(lodTag)) { var originName = fileName.Substring(0, fileName.Length - lodTag.Length); originPath = assetPath.Replace(fileName, originName); quality = LodTagToQualityLevel(lodTag); return true; } originPath = string.Empty; quality = XGame.Framework.Quality.XQualityLevel.UnKnow; return false; } private XGame.Framework.Quality.XQualityLevel LodTagToQualityLevel(string lodTag) { if (lodTag.Equals(PathDefine.LodHigh)) { return XGame.Framework.Quality.XQualityLevel.High; } else if (lodTag.Equals(PathDefine.LodLow)) { return XGame.Framework.Quality.XQualityLevel.Low; } return XGame.Framework.Quality.XQualityLevel.UnKnow; } private bool AddLodData(long originId, long lodId, XGame.Framework.Quality.XQualityLevel quality) { if (lodInfosMap == null) { lodInfosMap = new Dictionary>(); } if (!lodInfosMap.TryGetValue(originId, out var lstData)) { lstData = new List(); lodInfosMap.Add(originId, lstData); } var index = lstData.FindIndex(data => data.quality == quality); if (index >= 0) { var data = lstData[index]; data.addressableId = lodId; lstData[index] = data; } else { lstData.Add(new AddressableLodInfo.LodData() { addressableId = lodId, quality = quality }); } return true; } private void SaveLodInfos() { if (lodInfosMap != null && lodInfosMap.Count > 0) { var lodInfos = new AddressableLodInfo[lodInfosMap.Count]; var index = 0; foreach (var pair in lodInfosMap) { var info = new AddressableLodInfo() { addressableId = pair.Key, lodDatas = pair.Value.ToArray() }; lodInfos[index++] = info; } manifest.SetAddressableLodInfos(lodInfos, false); } else { manifest.SetAddressableLodInfos(null, false); } } #endregion } }