# 1. 收集 BuildSetting 中配置的所有新路径
1 2 3 4 ms_CollectBuildSettingFileProfiler.Start(); HashSet<string > files = buildSetting.Collect(); ms_CollectBuildSettingFileProfiler.Stop();
# 2. 收集每个文件的依赖关系
1 2 3 4 ms_CollectDependencyProfiler.Start(); Dictionary<string , List<string >> dependencyDic = CollectDependency(files); ms_CollectDependencyProfiler.Stop();
这里来讲一讲 CollectDependency 这个函数
# 2.1 管理进度条
1 2 float min = ms_GetDependencyProgress.x; float max = ms_GetDependencyProgress.y;
通过 ms_GetDependencyProgress 来管理进度条的进度
# 2.2 记录依赖关系并转化为列表,方便处理和拓展
1 2 3 4 5 Dictionary<string , List<string >> dependencyDic = new Dictionary<string , List<string >>(files.Count); List<string > fileList = new List<string >(files);
# 2.3 获取依赖既然已经获取了文件列表,下一步就是对文件列表进行遍历来得到各个文件依赖的函数了
# 2.3.1 更新进度条
1 2 3 4 5 6 if (i % 10 == 0 ) { float progress = min + (max - min) * ((float )i / (fileList.Count * 3 )); EditorUtility.DisplayProgressBar($"{nameof (CollectDependency)} " , "正在收集资源依赖关系..." , progress); }
# 2.3.2 通过 Unity 的 API 获取依赖并过滤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 string [] dependencies = AssetDatabase.GetDependencies(assetUrl, true ); List<string > dependencyList = new List<string >(dependencies.Length); for (int j = 0 ; j < dependencies.Length; j++) { string tempAssetUrl = dependencies[j]; string extension = Path.GetExtension(tempAssetUrl).ToLower(); if (string .IsNullOrEmpty(extension) || extension == ".cs" || extension == ".dll" ) continue ; dependencyList.Add(tempAssetUrl); if (!fileList.Contains(tempAssetUrl)) fileList.Add(tempAssetUrl); } dependencyDic.Add(assetUrl, dependencyList);
# 3. 构建一个资源 → 类型映射表(Direct 或 Dependency)
1 2 3 4 5 6 7 8 9 10 11 12 Dictionary<string , EResourceType> assetDic = new Dictionary<string , EResourceType>(); foreach (string url in files) { assetDic.Add(url, EResourceType.Direct); } foreach (string url in dependencyDic.Keys) { if (!assetDic.ContainsKey(url)) { assetDic.Add(url, EResourceType.Dependency); } }
# 4. 保存 Bundle 保存的资源
1 2 3 4 ms_CollectBundleProfiler.Start(); Dictionary<string ,List<string >> bundleDic = CollectBundle(buildSetting, assetDic, dependencyDic); ms_CollectBundleProfiler.Stop();
这里再来讲一讲 CollectBundle 这个函数
# 4.1 同样先记录进度条
1 2 3 4 5 6 7 8 9 float min = ms_CollectBundleInfoProgress.x; float max = ms_CollectBundleInfoProgress.y; EditorUtility.DisplayProgressBar( $"{nameof (CollectBundle)} " , "正在收集资源Bundle信息..." , min );
# 4.2 然后,根据 setting 的打包规则,对 bundle 进行分类并更新进度条
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 foreach (KeyValuePair<string , EResourceType> pair in assetDic) { index++; string assetUrl = pair.Key; EResourceType type = pair.Value; string bundleName = setting.GetBundleName(assetUrl, type); if (bundleName == null ) { notInRuleList.Add(assetUrl); continue ; } List<string > list; if (!bundleDic.TryGetValue(bundleName, out list)) { list = new List<string >(); bundleDic.Add(bundleName, list); } list.Add(assetUrl); EditorUtility.DisplayProgressBar( $"{nameof (CollectBundle)} " , "正在收集资源Bundle信息..." , min + (max - min) * ((float )index / assetDic.Count) );}
# 4.3 如果存在不在打包规则中的文件,报错并返回列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 if (notInRuleList.Count > 0 ) { string message = string .Empty; for (int i = 0 ; i < notInRuleList.Count; i++) { message += '\n' + notInRuleList[i]; } EditorUtility.ClearProgressBar(); throw new Exception($"以下资源未命中任何打包规则:{message} " ); }
# 5. 生成 manifest 文件
1 2 3 4 ms_GenerateManifestProfiler.Start(); GenerateManifestFile(assetDic,bundleDic,dependencyDic); ms_GenerateManifestProfiler.Stop();
Manifest 文件就像是打包的一个总帐本。因为打包之后的 AssetBundle 文件都是二进制的,无法直接阅读,所以我们要依靠 Manifest 文件来知道,我们想要某个 prefab 的话,需要加载哪个文件。
# 5.1 同样是,先加载进度条并生成临时文件夹
1 2 3 4 5 6 7 8 9 10 float min = ms_GenerateBuildInfoProgress.x; float max = ms_GenerateBuildInfoProgress.y; EditorUtility.DisplayProgressBar($"{nameof (GenerateManifestFile)} " , "正在生成打包信息..." , min); if (!Directory.Exists(TempPath)) { Directory.CreateDirectory(TempPath); } Dictionary<string ,ushort > assetIdDic = new Dictionary<string ,ushort >();
# 5.2 生成资源描述信息三个描述信息的 region 其实逻辑大体上都差不多,其实就是生成一个文本文件和一个二进制文件。
# 5.2.1首先就是检查本地是否存在文件,如果存在,那就覆盖掉
1 2 3 4 5 6 7 8 9 10 11 if (File.Exists(ResourcePath_Text)) File.Delete(ResourcePath_Text); if (File.Exists(ResourcePath_Binary)) File.Delete(ResourcePath_Binary); StringBuilder resourceSb = new StringBuilder(); MemoryStream resourceMs = new MemoryStream(); BinaryWriter resourceBw = new BinaryWriter(File.Open(ResourcePath_Binary, FileMode.Create));
# 5.2.2 写入文件然后,遍历 assetDic,就是存储着资源路径和类型的字典,将内容存入两个文件
1 2 3 4 5 6 7 8 9 10 11 resourceBw.Write((ushort )assetDic.Count); List<string > keys = new List<string >(assetDic.Keys); keys.Sort(); for (ushort i = 0 ; i < keys.Count; i++) { string assetUrl = keys[i]; assetIdDic.Add(assetUrl,i); resourceSb.AppendLine($"{i} \t{assetUrl} " ); resourceBw.Write(assetUrl); }
# 5.2.3 最后,清除临时区域并记录
1 2 3 4 5 6 resourceMs.Flush(); byte [] buffer = resourceMs.GetBuffer(); resourceBw.Close(); File.WriteAllText(ResourcePath_Text, resourceSb.ToString(),Encoding.UTF8); File.WriteAllBytes(ResourcePath_Binary, buffer);