using System; using System.Linq; using System.Collections; using System.Collections.Generic; using UnityEditor.Build.Pipeline; using UnityEditor.Build.Pipeline.Interfaces; namespace YooAsset.Editor { [TaskAttribute("创建补丁清单文件")] public class TaskCreatePatchManifest : IBuildTask { void IBuildTask.Run(BuildContext context) { CreatePatchManifestFile(context); } /// /// 创建补丁清单文件到输出目录 /// private void CreatePatchManifestFile(BuildContext context) { var buildParameters = context.GetContextObject(); int resourceVersion = buildParameters.Parameters.BuildVersion; // 创建新补丁清单 PatchManifest patchManifest = new PatchManifest(); patchManifest.FileVersion = YooAssetSettings.PatchManifestFileVersion; patchManifest.ResourceVersion = buildParameters.Parameters.BuildVersion; patchManifest.EnableAddressable = buildParameters.Parameters.EnableAddressable; patchManifest.OutputNameStyle = (int)buildParameters.Parameters.OutputNameStyle; patchManifest.BuildinTags = buildParameters.Parameters.BuildinTags; patchManifest.BundleList = GetAllPatchBundle(context); patchManifest.AssetList = GetAllPatchAsset(context, patchManifest); // 更新Unity内置资源包的引用关系 if (buildParameters.Parameters.BuildPipeline == EBuildPipeline.ScriptableBuildPipeline) { if(buildParameters.Parameters.BuildMode == EBuildMode.IncrementalBuild) { var buildResultContext = context.GetContextObject(); UpdateBuiltInBundleReference(patchManifest, buildResultContext.Results); } } // 创建补丁清单文件 string manifestFilePath = $"{buildParameters.PipelineOutputDirectory}/{YooAssetSettingsData.GetPatchManifestFileName(resourceVersion)}"; BuildRunner.Log($"创建补丁清单文件:{manifestFilePath}"); PatchManifest.Serialize(manifestFilePath, patchManifest); // 创建补丁清单哈希文件 string manifestHashFilePath = $"{buildParameters.PipelineOutputDirectory}/{YooAssetSettingsData.GetPatchManifestHashFileName(resourceVersion)}"; string manifestHash = HashUtility.FileMD5(manifestFilePath); BuildRunner.Log($"创建补丁清单哈希文件:{manifestHashFilePath}"); FileUtility.CreateFile(manifestHashFilePath, manifestHash); // 创建静态版本文件 string staticVersionFilePath = $"{buildParameters.PipelineOutputDirectory}/{YooAssetSettings.VersionFileName}"; string staticVersion = resourceVersion.ToString(); BuildRunner.Log($"创建静态版本文件:{staticVersionFilePath}"); FileUtility.CreateFile(staticVersionFilePath, staticVersion); } /// /// 获取资源包列表 /// private List GetAllPatchBundle(BuildContext context) { var buildParameters = context.GetContextObject(); var buildMapContext = context.GetContextObject(); var encryptionContext = context.GetContextObject(); List result = new List(1000); List buildinTags = buildParameters.Parameters.GetBuildinTags(); foreach (var bundleInfo in buildMapContext.BundleInfos) { var bundleName = bundleInfo.BundleName; string fileHash = GetBundleFileHash(bundleInfo, buildParameters); string fileCRC = GetBundleFileCRC(bundleInfo, buildParameters); long fileSize = GetBundleFileSize(bundleInfo, buildParameters); string[] tags = buildMapContext.GetBundleTags(bundleName); bool isEncrypted = encryptionContext.IsEncryptFile(bundleName); bool isBuildin = IsBuildinBundle(tags, buildinTags); bool isRawFile = bundleInfo.IsRawFile; PatchBundle patchBundle = new PatchBundle(bundleName, fileHash, fileCRC, fileSize, tags); patchBundle.SetFlagsValue(isEncrypted, isBuildin, isRawFile); result.Add(patchBundle); } return result; } private bool IsBuildinBundle(string[] bundleTags, List buildinTags) { // 注意:没有任何分类标签的Bundle文件默认为内置文件 if (bundleTags.Length == 0) return true; foreach (var tag in bundleTags) { if (buildinTags.Contains(tag)) return true; } return false; } private string GetBundleFileHash(BuildBundleInfo bundleInfo, BuildParametersContext buildParametersContext) { var buildMode = buildParametersContext.Parameters.BuildMode; if (buildMode == EBuildMode.DryRunBuild || buildMode == EBuildMode.SimulateBuild) return "00000000000000000000000000000000"; //32位 string filePath = $"{buildParametersContext.PipelineOutputDirectory}/{bundleInfo.BundleName}"; return HashUtility.FileMD5(filePath); } private string GetBundleFileCRC(BuildBundleInfo bundleInfo, BuildParametersContext buildParametersContext) { var buildMode = buildParametersContext.Parameters.BuildMode; if (buildMode == EBuildMode.DryRunBuild || buildMode == EBuildMode.SimulateBuild) return "00000000"; //8位 string filePath = $"{buildParametersContext.PipelineOutputDirectory}/{bundleInfo.BundleName}"; return HashUtility.FileCRC32(filePath); } private long GetBundleFileSize(BuildBundleInfo bundleInfo, BuildParametersContext buildParametersContext) { var buildMode = buildParametersContext.Parameters.BuildMode; if (buildMode == EBuildMode.DryRunBuild || buildMode == EBuildMode.SimulateBuild) return 0; string filePath = $"{buildParametersContext.PipelineOutputDirectory}/{bundleInfo.BundleName}"; return FileUtility.GetFileSize(filePath); } /// /// 获取资源列表 /// private List GetAllPatchAsset(BuildContext context, PatchManifest patchManifest) { var buildParameters = context.GetContextObject(); var buildMapContext = context.GetContextObject(); List result = new List(1000); foreach (var bundleInfo in buildMapContext.BundleInfos) { var assetInfos = bundleInfo.GetAllPatchAssetInfos(); foreach (var assetInfo in assetInfos) { PatchAsset patchAsset = new PatchAsset(); if (buildParameters.Parameters.EnableAddressable) patchAsset.Address = assetInfo.Address; else patchAsset.Address = string.Empty; patchAsset.AssetPath = assetInfo.AssetPath; patchAsset.AssetTags = assetInfo.AssetTags.ToArray(); patchAsset.BundleID = GetAssetBundleID(assetInfo.GetBundleName(), patchManifest); patchAsset.DependIDs = GetAssetBundleDependIDs(patchAsset.BundleID, assetInfo, patchManifest); result.Add(patchAsset); } } return result; } private int[] GetAssetBundleDependIDs(int mainBundleID, BuildAssetInfo assetInfo, PatchManifest patchManifest) { List result = new List(); foreach (var dependAssetInfo in assetInfo.AllDependAssetInfos) { if (dependAssetInfo.HasBundleName()) { int bundleID = GetAssetBundleID(dependAssetInfo.GetBundleName(), patchManifest); if (mainBundleID != bundleID) { if (result.Contains(bundleID) == false) result.Add(bundleID); } } } return result.ToArray(); } private int GetAssetBundleID(string bundleName, PatchManifest patchManifest) { for (int index = 0; index < patchManifest.BundleList.Count; index++) { if (patchManifest.BundleList[index].BundleName == bundleName) return index; } throw new Exception($"Not found bundle name : {bundleName}"); } /// /// 更新Unity内置资源包的引用关系 /// private void UpdateBuiltInBundleReference(PatchManifest patchManifest, IBundleBuildResults buildResults) { // 获取所有依赖着色器资源包的资源包列表 string shadersBunldeName = YooAssetSettingsData.GetUnityShadersBundleFullName(); List shaderBundleReferenceList = new List(); foreach (var valuePair in buildResults.BundleInfos) { if (valuePair.Value.Dependencies.Any(t => t == shadersBunldeName)) shaderBundleReferenceList.Add(valuePair.Key); } // 获取着色器资源包索引 Predicate predicate = new Predicate(s => s.BundleName == shadersBunldeName); int shaderBundleId = patchManifest.BundleList.FindIndex(predicate); if (shaderBundleId == -1) throw new Exception("没有发现着色器资源包!"); // 检测依赖交集并更新依赖ID foreach (var patchAsset in patchManifest.AssetList) { List dependBundles = GetPatchAssetAllDependBundles(patchManifest, patchAsset); List conflictAssetPathList = dependBundles.Intersect(shaderBundleReferenceList).ToList(); if (conflictAssetPathList.Count > 0) { List newDependIDs = new List(patchAsset.DependIDs); if (newDependIDs.Contains(shaderBundleId) == false) newDependIDs.Add(shaderBundleId); patchAsset.DependIDs = newDependIDs.ToArray(); } } } private List GetPatchAssetAllDependBundles(PatchManifest patchManifest, PatchAsset patchAsset) { List result = new List(); string mainBundle = patchManifest.BundleList[patchAsset.BundleID].BundleName; result.Add(mainBundle); foreach (var dependID in patchAsset.DependIDs) { string dependBundle = patchManifest.BundleList[dependID].BundleName; result.Add(dependBundle); } return result; } } }