guodong 1 жил өмнө
parent
commit
85e8750872
100 өөрчлөгдсөн 6705 нэмэгдсэн , 1 устгасан
  1. 1 1
      GameClient/Assets/Game/Launcher/Game.Launcher.asmdef
  2. 8 0
      GameClient/Assets/YooAsset.meta
  3. 8 0
      GameClient/Assets/YooAsset/Editor.meta
  4. 8 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder.meta
  5. 112 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilder.cs
  6. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilder.cs.meta
  7. 28 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilderHelper.cs
  8. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilderHelper.cs.meta
  9. 49 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilderSetting.cs
  10. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilderSetting.cs.meta
  11. 49 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilderSettingData.cs
  12. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilderSettingData.cs.meta
  13. 359 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilderWindow.cs
  14. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilderWindow.cs.meta
  15. 18 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilderWindow.uxml
  16. 10 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilderWindow.uxml.meta
  17. 37 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleSimulateBuilder.cs
  18. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleSimulateBuilder.cs.meta
  19. 213 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildAssetInfo.cs
  20. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildAssetInfo.cs.meta
  21. 225 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildBundleInfo.cs
  22. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildBundleInfo.cs.meta
  23. 108 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildMapContext.cs
  24. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildMapContext.cs.meta
  25. 125 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildParameters.cs
  26. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildParameters.cs.meta
  27. 145 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildParametersContext.cs
  28. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildParametersContext.cs.meta
  29. 8 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem.meta
  30. 50 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/BuildContext.cs
  31. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/BuildContext.cs.meta
  32. 33 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/BuildLogger.cs
  33. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/BuildLogger.cs.meta
  34. 29 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/BuildResult.cs
  35. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/BuildResult.cs.meta
  36. 72 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/BuildRunner.cs
  37. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/BuildRunner.cs.meta
  38. 8 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/IBuildTask.cs
  39. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/IBuildTask.cs.meta
  40. 7 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/IContextObject.cs
  41. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/IContextObject.cs.meta
  42. 18 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/TaskAttribute.cs
  43. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/TaskAttribute.cs.meta
  44. 8 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks.meta
  45. 52 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/SBPBuildTasks.cs
  46. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/SBPBuildTasks.cs.meta
  47. 50 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskBuilding.cs
  48. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskBuilding.cs.meta
  49. 58 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskBuilding_SBP.cs
  50. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskBuilding_SBP.cs.meta
  51. 100 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskCopyBuildinFiles.cs
  52. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskCopyBuildinFiles.cs.meta
  53. 44 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskCopyRawFile.cs
  54. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskCopyRawFile.cs.meta
  55. 384 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskCreateManifest.cs
  56. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskCreateManifest.cs.meta
  57. 79 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskCreatePackage.cs
  58. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskCreatePackage.cs.meta
  59. 229 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskCreateReport.cs
  60. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskCreateReport.cs.meta
  61. 67 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskEncryption.cs
  62. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskEncryption.cs.meta
  63. 229 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskGetBuildMap.cs
  64. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskGetBuildMap.cs.meta
  65. 106 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskPrepare.cs
  66. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskPrepare.cs.meta
  67. 154 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskUpdateBundleInfo.cs
  68. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskUpdateBundleInfo.cs.meta
  69. 137 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskVerifyBuildResult.cs
  70. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskVerifyBuildResult.cs.meta
  71. 68 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskVerifyBuildResult_SBP.cs
  72. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskVerifyBuildResult_SBP.cs.meta
  73. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/DefaultEncryption.cs
  74. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/DefaultEncryption.cs.meta
  75. 29 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/EBuildMode.cs
  76. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/EBuildMode.cs.meta
  77. 19 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/EBuildPipeline.cs
  78. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/EBuildPipeline.cs.meta
  79. 13 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/ECompressOption.cs
  80. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/ECompressOption.cs.meta
  81. 34 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/ECopyBuildinFileOption.cs
  82. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/ECopyBuildinFileOption.cs.meta
  83. 19 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/EOutputNameStyle.cs
  84. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/EOutputNameStyle.cs.meta
  85. 8 0
      GameClient/Assets/YooAsset/Editor/AssetBundleCollector.meta
  86. 355 0
      GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollector.cs
  87. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollector.cs.meta
  88. 388 0
      GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorConfig.cs
  89. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorConfig.cs.meta
  90. 118 0
      GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorGroup.cs
  91. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorGroup.cs.meta
  92. 126 0
      GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorPackage.cs
  93. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorPackage.cs.meta
  94. 137 0
      GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorSetting.cs
  95. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorSetting.cs.meta
  96. 445 0
      GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorSettingData.cs
  97. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorSettingData.cs.meta
  98. 962 0
      GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorWindow.cs
  99. 11 0
      GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorWindow.cs.meta
  100. 53 0
      GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorWindow.uxml

+ 1 - 1
GameClient/Assets/Game/Launcher/Game.Launcher.asmdef

@@ -4,7 +4,7 @@
     "references": [
         "GUID:7a41fac89c3ce014e99efb3723e6a98e",
         "GUID:13ba8ce62aa80c74598530029cb2d649",
-        "GUID:7fe0a5544a813ea4ab3af6a4cf230f62"
+        "GUID:e34a5702dd353724aa315fb8011f08c3"
     ],
     "includePlatforms": [],
     "excludePlatforms": [],

+ 8 - 0
GameClient/Assets/YooAsset.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 1c47170d053a4f5489c4f180af21b3a4
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
GameClient/Assets/YooAsset/Editor.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: db6552b64a70aca4482e21d711ebf42d
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 1fdecc5500229d44887425ce619352fc
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 112 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilder.cs

@@ -0,0 +1,112 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Linq;
+using UnityEngine;
+using UnityEditor;
+
+namespace YooAsset.Editor
+{
+	public class AssetBundleBuilder
+	{
+		private readonly BuildContext _buildContext = new BuildContext();
+
+		/// <summary>
+		/// 构建资源包
+		/// </summary>
+		public BuildResult Run(BuildParameters buildParameters, List<IBuildTask> buildPipeline)
+		{
+			// 检测构建参数是否为空
+			if (buildParameters == null)
+				throw new Exception($"{nameof(buildParameters)} is null !");
+
+			// 检测构建参数是否为空
+			if (buildPipeline.Count == 0)
+				throw new Exception($"Build pipeline is empty !");	
+
+			// 清空旧数据
+			_buildContext.ClearAllContext();
+
+			// 构建参数
+			var buildParametersContext = new BuildParametersContext(buildParameters);
+			_buildContext.SetContextObject(buildParametersContext);
+
+			// 初始化日志
+			BuildLogger.InitLogger(buildParameters.EnableLog);
+
+			// 执行构建流程
+			var buildResult = BuildRunner.Run(buildPipeline, _buildContext);
+			if (buildResult.Success)
+			{
+				buildResult.OutputPackageDirectory = buildParametersContext.GetPackageOutputDirectory();
+				BuildLogger.Log($"{buildParameters.BuildMode} pipeline build succeed !");
+			}
+			else
+			{
+				BuildLogger.Warning($"{buildParameters.BuildMode} pipeline build failed !");
+				BuildLogger.Error($"Build task failed : {buildResult.FailedTask}");
+				BuildLogger.Error(buildResult.ErrorInfo);
+			}
+
+			return buildResult;
+		}
+
+		/// <summary>
+		/// 构建资源包
+		/// </summary>
+		public BuildResult Run(BuildParameters buildParameters)
+		{
+			var buildPipeline = GetDefaultBuildPipeline(buildParameters.BuildPipeline);
+			return Run(buildParameters, buildPipeline);
+		}
+
+		/// <summary>
+		/// 获取默认的构建流程
+		/// </summary>
+		private List<IBuildTask> GetDefaultBuildPipeline(EBuildPipeline buildPipeline)
+		{
+			// 获取任务节点的属性集合
+			if (buildPipeline == EBuildPipeline.BuiltinBuildPipeline)
+			{
+				List<IBuildTask> pipeline = new List<IBuildTask>
+				{
+					new TaskPrepare(), //前期准备工作
+					new TaskGetBuildMap(), //获取构建列表
+					new TaskBuilding(), //开始执行构建
+					new TaskCopyRawFile(), //拷贝原生文件
+					new TaskVerifyBuildResult(), //验证构建结果
+					new TaskEncryption(), //加密资源文件
+					new TaskUpdateBundleInfo(), //更新资源包信息
+					new TaskCreateManifest(), //创建清单文件
+					new TaskCreateReport(), //创建报告文件
+					new TaskCreatePackage(), //制作包裹
+					new TaskCopyBuildinFiles(), //拷贝内置文件
+				};
+				return pipeline;
+			}
+			else if (buildPipeline == EBuildPipeline.ScriptableBuildPipeline)
+			{
+				List<IBuildTask> pipeline = new List<IBuildTask>
+				{
+					new TaskPrepare(), //前期准备工作
+					new TaskGetBuildMap(), //获取构建列表
+					new TaskBuilding_SBP(), //开始执行构建
+					new TaskCopyRawFile(), //拷贝原生文件
+					new TaskVerifyBuildResult_SBP(), //验证构建结果
+					new TaskEncryption(), //加密资源文件
+					new TaskUpdateBundleInfo(), //更新补丁信息
+					new TaskCreateManifest(), //创建清单文件
+					new TaskCreateReport(), //创建报告文件
+					new TaskCreatePackage(), //制作补丁包
+					new TaskCopyBuildinFiles(), //拷贝内置文件
+				};
+				return pipeline;
+			}
+			else
+			{
+				throw new NotImplementedException();
+			}
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilder.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: de7563040250b4e4a835d1fc90238e38
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 28 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilderHelper.cs

@@ -0,0 +1,28 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using UnityEngine;
+using UnityEditor;
+
+namespace YooAsset.Editor
+{
+	public static class AssetBundleBuilderHelper
+	{
+		/// <summary>
+		/// 获取默认的输出根路录
+		/// </summary>
+		public static string GetDefaultBuildOutputRoot()
+		{
+			string projectPath = EditorTools.GetProjectPath();
+			return $"{projectPath}/Bundles";
+		}
+
+		/// <summary>
+		/// 获取流文件夹路径
+		/// </summary>
+		public static string GetDefaultStreamingAssetsRoot()
+		{
+			return $"{Application.dataPath}/StreamingAssets/{YooAssetSettings.DefaultYooFolderName}/";
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilderHelper.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f48abdec05f0dbe438a83e181fe6bc93
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 49 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilderSetting.cs

@@ -0,0 +1,49 @@
+using System;
+using UnityEngine;
+
+namespace YooAsset.Editor
+{
+	[CreateAssetMenu(fileName = "AssetBundleBuilderSetting", menuName = "YooAsset/Create AssetBundle Builder Settings")]
+	public class AssetBundleBuilderSetting : ScriptableObject
+	{
+		/// <summary>
+		/// 构建管线
+		/// </summary>
+		public EBuildPipeline BuildPipeline = EBuildPipeline.BuiltinBuildPipeline;
+
+		/// <summary>
+		/// 构建模式
+		/// </summary>
+		public EBuildMode BuildMode = EBuildMode.ForceRebuild;
+
+		/// <summary>
+		/// 构建的包裹名称
+		/// </summary>
+		public string BuildPackage = string.Empty;
+
+		/// <summary>
+		/// 压缩方式
+		/// </summary>
+		public ECompressOption CompressOption = ECompressOption.LZ4;
+
+		/// <summary>
+		/// 输出文件名称样式
+		/// </summary>
+		public EOutputNameStyle OutputNameStyle = EOutputNameStyle.HashName;
+
+		/// <summary>
+		/// 首包资源文件的拷贝方式
+		/// </summary>
+		public ECopyBuildinFileOption CopyBuildinFileOption = ECopyBuildinFileOption.None;
+
+		/// <summary>
+		/// 首包资源文件的标签集合
+		/// </summary>
+		public string CopyBuildinFileTags = string.Empty;
+
+		/// <summary>
+		/// 加密类名称
+		/// </summary>
+		public string EncyptionClassName = string.Empty;
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilderSetting.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 09788b4733bab2d4792fdd5d28e7653c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 49 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilderSettingData.cs

@@ -0,0 +1,49 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEditor;
+
+namespace YooAsset.Editor
+{
+	public class AssetBundleBuilderSettingData
+	{
+		private static AssetBundleBuilderSetting _setting = null;
+		public static AssetBundleBuilderSetting Setting
+		{
+			get
+			{
+				if (_setting == null)
+					LoadSettingData();
+				return _setting;
+			}
+		}
+
+		/// <summary>
+		/// 配置数据是否被修改
+		/// </summary>
+		public static bool IsDirty { set; get; } = false;
+
+		/// <summary>
+		/// 加载配置文件
+		/// </summary>
+		private static void LoadSettingData()
+		{
+			_setting = SettingLoader.LoadSettingData<AssetBundleBuilderSetting>();
+		}
+
+		/// <summary>
+		/// 存储文件
+		/// </summary>
+		public static void SaveFile()
+		{
+			if (Setting != null)
+			{
+				IsDirty = false;
+				EditorUtility.SetDirty(Setting);
+				AssetDatabase.SaveAssets();
+				Debug.Log($"{nameof(AssetBundleBuilderSetting)}.asset is saved!");
+			}
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilderSettingData.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 24698266f028e4a47bb88f091fd64547
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 359 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilderWindow.cs

@@ -0,0 +1,359 @@
+#if UNITY_2019_4_OR_NEWER
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEngine;
+using UnityEditor.UIElements;
+using UnityEngine.UIElements;
+
+namespace YooAsset.Editor
+{
+	public class AssetBundleBuilderWindow : EditorWindow
+	{
+		[MenuItem("YooAsset/AssetBundle Builder", false, 102)]
+		public static void OpenWindow()
+		{
+			AssetBundleBuilderWindow window = GetWindow<AssetBundleBuilderWindow>("资源包构建工具", true, WindowsDefine.DockedWindowTypes);
+			window.minSize = new Vector2(800, 600);
+		}
+
+		private BuildTarget _buildTarget;
+		private List<Type> _encryptionServicesClassTypes;
+		private List<string> _encryptionServicesClassNames;
+		private List<string> _buildPackageNames;
+
+		private Button _saveButton;
+		private TextField _buildOutputField;
+		private EnumField _buildPipelineField;
+		private EnumField _buildModeField;
+		private TextField _buildVersionField;
+		private PopupField<string> _buildPackageField;
+		private PopupField<string> _encryptionField;
+		private EnumField _compressionField;
+		private EnumField _outputNameStyleField;
+		private EnumField _copyBuildinFileOptionField;
+		private TextField _copyBuildinFileTagsField;
+
+		public void CreateGUI()
+		{
+			try
+			{
+				VisualElement root = this.rootVisualElement;
+
+				// 加载布局文件
+				var visualAsset = UxmlLoader.LoadWindowUXML<AssetBundleBuilderWindow>();
+				if (visualAsset == null)
+					return;
+
+				visualAsset.CloneTree(root);
+
+				// 配置保存按钮
+				_saveButton = root.Q<Button>("SaveButton");
+				_saveButton.clicked += SaveBtn_clicked;
+
+				// 构建平台
+				_buildTarget = EditorUserBuildSettings.activeBuildTarget;
+
+				// 包裹名称列表
+				_buildPackageNames = GetBuildPackageNames();
+
+				// 加密服务类
+				_encryptionServicesClassTypes = GetEncryptionServicesClassTypes();
+				_encryptionServicesClassNames = _encryptionServicesClassTypes.Select(t => t.Name).ToList();
+
+				// 输出目录
+				string defaultOutputRoot = AssetBundleBuilderHelper.GetDefaultBuildOutputRoot();
+				_buildOutputField = root.Q<TextField>("BuildOutput");
+				_buildOutputField.SetValueWithoutNotify(defaultOutputRoot);
+				_buildOutputField.SetEnabled(false);
+
+				// 构建管线
+				_buildPipelineField = root.Q<EnumField>("BuildPipeline");
+				_buildPipelineField.Init(AssetBundleBuilderSettingData.Setting.BuildPipeline);
+				_buildPipelineField.SetValueWithoutNotify(AssetBundleBuilderSettingData.Setting.BuildPipeline);
+				_buildPipelineField.style.width = 350;
+				_buildPipelineField.RegisterValueChangedCallback(evt =>
+				{
+					AssetBundleBuilderSettingData.IsDirty = true;
+					AssetBundleBuilderSettingData.Setting.BuildPipeline = (EBuildPipeline)_buildPipelineField.value;
+					RefreshWindow();
+				});
+
+				// 构建模式
+				_buildModeField = root.Q<EnumField>("BuildMode");
+				_buildModeField.Init(AssetBundleBuilderSettingData.Setting.BuildMode);
+				_buildModeField.SetValueWithoutNotify(AssetBundleBuilderSettingData.Setting.BuildMode);
+				_buildModeField.style.width = 350;
+				_buildModeField.RegisterValueChangedCallback(evt =>
+				{
+					AssetBundleBuilderSettingData.IsDirty = true;
+					AssetBundleBuilderSettingData.Setting.BuildMode = (EBuildMode)_buildModeField.value;
+					RefreshWindow();
+				});
+
+				// 构建版本
+				_buildVersionField = root.Q<TextField>("BuildVersion");
+				_buildVersionField.SetValueWithoutNotify(GetBuildPackageVersion());
+
+				// 构建包裹
+				var buildPackageContainer = root.Q("BuildPackageContainer");
+				if (_buildPackageNames.Count > 0)
+				{
+					int defaultIndex = GetDefaultPackageIndex(AssetBundleBuilderSettingData.Setting.BuildPackage);
+					_buildPackageField = new PopupField<string>(_buildPackageNames, defaultIndex);
+					_buildPackageField.label = "Build Package";
+					_buildPackageField.style.width = 350;
+					_buildPackageField.RegisterValueChangedCallback(evt =>
+					{
+						AssetBundleBuilderSettingData.IsDirty = true;
+						AssetBundleBuilderSettingData.Setting.BuildPackage = _buildPackageField.value;
+					});
+					buildPackageContainer.Add(_buildPackageField);
+				}
+				else
+				{
+					_buildPackageField = new PopupField<string>();
+					_buildPackageField.label = "Build Package";
+					_buildPackageField.style.width = 350;
+					buildPackageContainer.Add(_buildPackageField);
+				}
+
+				// 加密方法
+				var encryptionContainer = root.Q("EncryptionContainer");
+				if (_encryptionServicesClassNames.Count > 0)
+				{
+					int defaultIndex = GetDefaultEncryptionIndex(AssetBundleBuilderSettingData.Setting.EncyptionClassName);
+					_encryptionField = new PopupField<string>(_encryptionServicesClassNames, defaultIndex);
+					_encryptionField.label = "Encryption";
+					_encryptionField.style.width = 350;
+					_encryptionField.RegisterValueChangedCallback(evt =>
+					{
+						AssetBundleBuilderSettingData.IsDirty = true;
+						AssetBundleBuilderSettingData.Setting.EncyptionClassName = _encryptionField.value;
+					});
+					encryptionContainer.Add(_encryptionField);
+				}
+				else
+				{
+					_encryptionField = new PopupField<string>();
+					_encryptionField.label = "Encryption";
+					_encryptionField.style.width = 350;
+					encryptionContainer.Add(_encryptionField);
+				}
+
+				// 压缩方式选项
+				_compressionField = root.Q<EnumField>("Compression");
+				_compressionField.Init(AssetBundleBuilderSettingData.Setting.CompressOption);
+				_compressionField.SetValueWithoutNotify(AssetBundleBuilderSettingData.Setting.CompressOption);
+				_compressionField.style.width = 350;
+				_compressionField.RegisterValueChangedCallback(evt =>
+				{
+					AssetBundleBuilderSettingData.IsDirty = true;
+					AssetBundleBuilderSettingData.Setting.CompressOption = (ECompressOption)_compressionField.value;
+				});
+
+				// 输出文件名称样式
+				_outputNameStyleField = root.Q<EnumField>("OutputNameStyle");
+				_outputNameStyleField.Init(AssetBundleBuilderSettingData.Setting.OutputNameStyle);
+				_outputNameStyleField.SetValueWithoutNotify(AssetBundleBuilderSettingData.Setting.OutputNameStyle);
+				_outputNameStyleField.style.width = 350;
+				_outputNameStyleField.RegisterValueChangedCallback(evt =>
+				{
+					AssetBundleBuilderSettingData.IsDirty = true;
+					AssetBundleBuilderSettingData.Setting.OutputNameStyle = (EOutputNameStyle)_outputNameStyleField.value;
+				});
+
+				// 首包文件拷贝选项
+				_copyBuildinFileOptionField = root.Q<EnumField>("CopyBuildinFileOption");
+				_copyBuildinFileOptionField.Init(AssetBundleBuilderSettingData.Setting.CopyBuildinFileOption);
+				_copyBuildinFileOptionField.SetValueWithoutNotify(AssetBundleBuilderSettingData.Setting.CopyBuildinFileOption);
+				_copyBuildinFileOptionField.style.width = 350;
+				_copyBuildinFileOptionField.RegisterValueChangedCallback(evt =>
+				{
+					AssetBundleBuilderSettingData.IsDirty = true;
+					AssetBundleBuilderSettingData.Setting.CopyBuildinFileOption = (ECopyBuildinFileOption)_copyBuildinFileOptionField.value;
+					RefreshWindow();
+				});
+
+				// 首包文件的资源标签
+				_copyBuildinFileTagsField = root.Q<TextField>("CopyBuildinFileTags");
+				_copyBuildinFileTagsField.SetValueWithoutNotify(AssetBundleBuilderSettingData.Setting.CopyBuildinFileTags);
+				_copyBuildinFileTagsField.RegisterValueChangedCallback(evt =>
+				{
+					AssetBundleBuilderSettingData.IsDirty = true;
+					AssetBundleBuilderSettingData.Setting.CopyBuildinFileTags = _copyBuildinFileTagsField.value;
+				});
+
+				// 构建按钮
+				var buildButton = root.Q<Button>("Build");
+				buildButton.clicked += BuildButton_clicked; ;
+
+				RefreshWindow();
+			}
+			catch (Exception e)
+			{
+				Debug.LogError(e.ToString());
+			}
+		}
+		public void OnDestroy()
+		{
+			if (AssetBundleBuilderSettingData.IsDirty)
+				AssetBundleBuilderSettingData.SaveFile();
+		}
+		public void Update()
+		{
+			if (_saveButton != null)
+			{
+				if (AssetBundleBuilderSettingData.IsDirty)
+				{
+					if (_saveButton.enabledSelf == false)
+						_saveButton.SetEnabled(true);
+				}
+				else
+				{
+					if (_saveButton.enabledSelf)
+						_saveButton.SetEnabled(false);
+				}
+			}
+		}
+
+		private void RefreshWindow()
+		{
+			var buildPipeline = AssetBundleBuilderSettingData.Setting.BuildPipeline;
+			var buildMode = AssetBundleBuilderSettingData.Setting.BuildMode;
+			var copyOption = AssetBundleBuilderSettingData.Setting.CopyBuildinFileOption;
+			bool enableElement = buildMode == EBuildMode.ForceRebuild;
+			bool tagsFiledVisible = copyOption == ECopyBuildinFileOption.ClearAndCopyByTags || copyOption == ECopyBuildinFileOption.OnlyCopyByTags;
+
+			if (buildPipeline == EBuildPipeline.BuiltinBuildPipeline)
+			{
+				_compressionField.SetEnabled(enableElement);
+				_outputNameStyleField.SetEnabled(enableElement);
+				_copyBuildinFileOptionField.SetEnabled(enableElement);
+				_copyBuildinFileTagsField.SetEnabled(enableElement);
+			}
+			else
+			{
+				_compressionField.SetEnabled(true);
+				_outputNameStyleField.SetEnabled(true);
+				_copyBuildinFileOptionField.SetEnabled(true);
+				_copyBuildinFileTagsField.SetEnabled(true);
+			}
+
+			_copyBuildinFileTagsField.visible = tagsFiledVisible;
+		}
+		private void SaveBtn_clicked()
+		{
+			AssetBundleBuilderSettingData.SaveFile();
+		}
+		private void BuildButton_clicked()
+		{
+			var buildMode = AssetBundleBuilderSettingData.Setting.BuildMode;
+			if (EditorUtility.DisplayDialog("提示", $"通过构建模式【{buildMode}】来构建!", "Yes", "No"))
+			{
+				EditorTools.ClearUnityConsole();
+				EditorApplication.delayCall += ExecuteBuild;
+			}
+			else
+			{
+				Debug.LogWarning("[Build] 打包已经取消");
+			}
+		}
+
+		/// <summary>
+		/// 执行构建
+		/// </summary>
+		private void ExecuteBuild()
+		{
+			BuildParameters buildParameters = new BuildParameters();
+			buildParameters.StreamingAssetsRoot = AssetBundleBuilderHelper.GetDefaultStreamingAssetsRoot();
+			buildParameters.BuildOutputRoot = AssetBundleBuilderHelper.GetDefaultBuildOutputRoot();
+			buildParameters.BuildTarget = _buildTarget;
+			buildParameters.BuildPipeline = AssetBundleBuilderSettingData.Setting.BuildPipeline;
+			buildParameters.BuildMode = AssetBundleBuilderSettingData.Setting.BuildMode;
+			buildParameters.PackageName = AssetBundleBuilderSettingData.Setting.BuildPackage;
+			buildParameters.PackageVersion = _buildVersionField.value;
+			buildParameters.VerifyBuildingResult = true;
+			buildParameters.SharedPackRule = new ZeroRedundancySharedPackRule();
+			buildParameters.EncryptionServices = CreateEncryptionServicesInstance();
+			buildParameters.CompressOption = AssetBundleBuilderSettingData.Setting.CompressOption;
+			buildParameters.OutputNameStyle = AssetBundleBuilderSettingData.Setting.OutputNameStyle;
+			buildParameters.CopyBuildinFileOption = AssetBundleBuilderSettingData.Setting.CopyBuildinFileOption;
+			buildParameters.CopyBuildinFileTags = AssetBundleBuilderSettingData.Setting.CopyBuildinFileTags;
+
+			if (AssetBundleBuilderSettingData.Setting.BuildPipeline == EBuildPipeline.ScriptableBuildPipeline)
+			{
+				buildParameters.SBPParameters = new BuildParameters.SBPBuildParameters();
+				buildParameters.SBPParameters.WriteLinkXML = true;
+			}
+
+			var builder = new AssetBundleBuilder();
+			var buildResult = builder.Run(buildParameters);
+			if (buildResult.Success)
+			{
+				EditorUtility.RevealInFinder(buildResult.OutputPackageDirectory);
+			}
+		}
+
+		// 构建版本相关
+		private string GetBuildPackageVersion()
+		{
+			int totalMinutes = DateTime.Now.Hour * 60 + DateTime.Now.Minute;
+			return DateTime.Now.ToString("yyyy-MM-dd") + "-" + totalMinutes;
+		}
+
+		// 构建包裹相关
+		private int GetDefaultPackageIndex(string packageName)
+		{
+			for (int index = 0; index < _buildPackageNames.Count; index++)
+			{
+				if (_buildPackageNames[index] == packageName)
+				{
+					return index;
+				}
+			}
+
+			AssetBundleBuilderSettingData.IsDirty = true;
+			AssetBundleBuilderSettingData.Setting.BuildPackage = _buildPackageNames[0];
+			return 0;
+		}
+		private List<string> GetBuildPackageNames()
+		{
+			List<string> result = new List<string>();
+			foreach (var package in AssetBundleCollectorSettingData.Setting.Packages)
+			{
+				result.Add(package.PackageName);
+			}
+			return result;
+		}
+
+		// 加密类相关
+		private int GetDefaultEncryptionIndex(string className)
+		{
+			for (int index = 0; index < _encryptionServicesClassNames.Count; index++)
+			{
+				if (_encryptionServicesClassNames[index] == className)
+				{
+					return index;
+				}
+			}
+
+			AssetBundleBuilderSettingData.IsDirty = true;
+			AssetBundleBuilderSettingData.Setting.EncyptionClassName = _encryptionServicesClassNames[0];
+			return 0;
+		}
+		private List<Type> GetEncryptionServicesClassTypes()
+		{
+			return EditorTools.GetAssignableTypes(typeof(IEncryptionServices));
+		}
+		private IEncryptionServices CreateEncryptionServicesInstance()
+		{
+			if (_encryptionField.index < 0)
+				return null;
+			var classType = _encryptionServicesClassTypes[_encryptionField.index];
+			return (IEncryptionServices)Activator.CreateInstance(classType);
+		}
+	}
+}
+#endif

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilderWindow.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 66e43420e95cd0b4bae8803a31e9817b
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 18 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilderWindow.uxml

@@ -0,0 +1,18 @@
+<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True">
+    <uie:Toolbar name="Toolbar" style="display: flex; flex-direction: row-reverse;">
+        <ui:Button text="Save" display-tooltip-when-elided="true" name="SaveButton" style="background-color: rgb(56, 147, 58);" />
+    </uie:Toolbar>
+    <ui:VisualElement name="BuildContainer">
+        <ui:TextField picking-mode="Ignore" label="Build Output" name="BuildOutput" />
+        <uie:EnumField label="Build Pipeline" name="BuildPipeline" />
+        <uie:EnumField label="Build Mode" name="BuildMode" />
+        <ui:TextField picking-mode="Ignore" label="Build Version" name="BuildVersion" style="width: 350px;" />
+        <ui:VisualElement name="BuildPackageContainer" style="height: 24px;" />
+        <ui:VisualElement name="EncryptionContainer" style="height: 24px;" />
+        <uie:EnumField label="Compression" value="Center" name="Compression" />
+        <uie:EnumField label="Output Name Style" value="Center" name="OutputNameStyle" />
+        <uie:EnumField label="Copy Buildin File Option" value="Center" name="CopyBuildinFileOption" />
+        <ui:TextField picking-mode="Ignore" label="Copy Buildin File Tags" name="CopyBuildinFileTags" />
+        <ui:Button text="构建" display-tooltip-when-elided="true" name="Build" style="height: 50px; background-color: rgb(40, 106, 42); margin-top: 10px;" />
+    </ui:VisualElement>
+</ui:UXML>

+ 10 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleBuilderWindow.uxml.meta

@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 28ba29adb4949284e8c48893218b0d9a
+ScriptedImporter:
+  internalIDToNameTable: []
+  externalObjects: {}
+  serializedVersion: 2
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
+  script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}

+ 37 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleSimulateBuilder.cs

@@ -0,0 +1,37 @@
+using UnityEditor;
+using UnityEngine;
+
+namespace YooAsset.Editor
+{
+	public static class AssetBundleSimulateBuilder
+	{
+		/// <summary>
+		/// 模拟构建
+		/// </summary>
+		public static string SimulateBuild(string packageName)
+		{
+			Debug.Log($"Begin to create simulate package : {packageName}");
+			BuildParameters buildParameters = new BuildParameters();
+			buildParameters.StreamingAssetsRoot = AssetBundleBuilderHelper.GetDefaultStreamingAssetsRoot();
+			buildParameters.BuildOutputRoot = AssetBundleBuilderHelper.GetDefaultBuildOutputRoot();
+			buildParameters.BuildTarget = EditorUserBuildSettings.activeBuildTarget;
+			buildParameters.BuildMode = EBuildMode.SimulateBuild;
+			buildParameters.PackageName = packageName;
+			buildParameters.PackageVersion = "Simulate";
+			buildParameters.EnableLog = false;
+
+			AssetBundleBuilder builder = new AssetBundleBuilder();
+			var buildResult = builder.Run(buildParameters);
+			if (buildResult.Success)
+			{
+				string manifestFileName = YooAssetSettingsData.GetManifestBinaryFileName(buildParameters.PackageName, buildParameters.PackageVersion);
+				string manifestFilePath = $"{buildResult.OutputPackageDirectory}/{manifestFileName}";
+				return manifestFilePath;
+			}
+			else
+			{
+				return null;
+			}
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/AssetBundleSimulateBuilder.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f94918fa1ea63c34fa0e49fdad4119cf
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 213 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildAssetInfo.cs

@@ -0,0 +1,213 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace YooAsset.Editor
+{
+	public class BuildAssetInfo
+	{
+		private bool _isAddAssetTags = false;
+		private readonly HashSet<string> _referenceBundleNames = new HashSet<string>();
+
+		/// <summary>
+		/// 收集器类型
+		/// </summary>
+		public ECollectorType CollectorType { private set; get; }
+
+		/// <summary>
+		/// 资源包完整名称
+		/// </summary>
+		public string BundleName { private set; get; }
+
+		/// <summary>
+		/// 可寻址地址
+		/// </summary>
+		public string Address { private set; get; }
+
+		/// <summary>
+		/// 资源路径
+		/// </summary>
+		public string AssetPath { private set; get; }
+
+		/// <summary>
+		/// 资源GUID
+		/// </summary>
+		public string AssetGUID { private set; get; }
+
+		/// <summary>
+		/// 是否为原生资源
+		/// </summary>
+		public bool IsRawAsset { private set; get; }
+
+		/// <summary>
+		/// 是否为着色器资源
+		/// </summary>
+		public bool IsShaderAsset { private set; get; }
+
+		/// <summary>
+		/// 资源的分类标签
+		/// </summary>
+		public readonly List<string> AssetTags = new List<string>();
+
+		/// <summary>
+		/// 资源包的分类标签
+		/// </summary>
+		public readonly List<string> BundleTags = new List<string>();
+
+		/// <summary>
+		/// 依赖的所有资源
+		/// 注意:包括零依赖资源和冗余资源(资源包名无效)
+		/// </summary>
+		public List<BuildAssetInfo> AllDependAssetInfos { private set; get; }
+
+
+		public BuildAssetInfo(ECollectorType collectorType, string bundleName, string address, string assetPath, bool isRawAsset)
+		{
+			CollectorType = collectorType;
+			BundleName = bundleName;
+			Address = address;
+			AssetPath = assetPath;
+			IsRawAsset = isRawAsset;
+
+			AssetGUID = UnityEditor.AssetDatabase.AssetPathToGUID(assetPath);
+			System.Type assetType = UnityEditor.AssetDatabase.GetMainAssetTypeAtPath(assetPath);
+			if (assetType == typeof(UnityEngine.Shader) || assetType == typeof(UnityEngine.ShaderVariantCollection))
+				IsShaderAsset = true;
+			else
+				IsShaderAsset = false;
+		}
+		public BuildAssetInfo(string assetPath)
+		{
+			CollectorType = ECollectorType.None;
+			Address = string.Empty;
+			AssetPath = assetPath;
+			IsRawAsset = false;
+
+			AssetGUID = UnityEditor.AssetDatabase.AssetPathToGUID(assetPath);
+			System.Type assetType = UnityEditor.AssetDatabase.GetMainAssetTypeAtPath(assetPath);
+			if (assetType == typeof(UnityEngine.Shader) || assetType == typeof(UnityEngine.ShaderVariantCollection))
+				IsShaderAsset = true;
+			else
+				IsShaderAsset = false;
+		}
+
+
+		/// <summary>
+		/// 设置所有依赖的资源
+		/// </summary>
+		public void SetAllDependAssetInfos(List<BuildAssetInfo> dependAssetInfos)
+		{
+			if (AllDependAssetInfos != null)
+				throw new System.Exception("Should never get here !");
+
+			AllDependAssetInfos = dependAssetInfos;
+		}
+
+		/// <summary>
+		/// 添加资源的分类标签
+		/// 说明:原始定义的资源分类标签
+		/// </summary>
+		public void AddAssetTags(List<string> tags)
+		{
+			if (_isAddAssetTags)
+				throw new Exception("Should never get here !");
+			_isAddAssetTags = true;
+
+			foreach (var tag in tags)
+			{
+				if (AssetTags.Contains(tag) == false)
+				{
+					AssetTags.Add(tag);
+				}
+			}
+		}
+
+		/// <summary>
+		/// 添加资源包的分类标签
+		/// 说明:传染算法统计到的分类标签
+		/// </summary>
+		public void AddBundleTags(List<string> tags)
+		{
+			foreach (var tag in tags)
+			{
+				if (BundleTags.Contains(tag) == false)
+				{
+					BundleTags.Add(tag);
+				}
+			}
+		}
+
+		/// <summary>
+		/// 资源包名是否存在
+		/// </summary>
+		public bool HasBundleName()
+		{
+			if (string.IsNullOrEmpty(BundleName))
+				return false;
+			else
+				return true;
+		}
+
+		/// <summary>
+		/// 添加关联的资源包名称
+		/// </summary>
+		public void AddReferenceBundleName(string bundleName)
+		{
+			if (string.IsNullOrEmpty(bundleName))
+				throw new Exception("Should never get here !");
+
+			if (_referenceBundleNames.Contains(bundleName) == false)
+				_referenceBundleNames.Add(bundleName);
+		}
+
+		/// <summary>
+		/// 计算共享资源包的完整包名
+		/// </summary>
+		public void CalculateShareBundleName(ISharedPackRule sharedPackRule, bool uniqueBundleName, string packageName, string shadersBundleName)
+		{
+			if (CollectorType != ECollectorType.None)
+				return;
+
+			if (IsRawAsset)
+				throw new Exception("Should never get here !");
+
+			if (IsShaderAsset)
+			{
+				BundleName = shadersBundleName;
+			}
+			else
+			{
+				if (_referenceBundleNames.Count > 1)
+				{
+					PackRuleResult packRuleResult = sharedPackRule.GetPackRuleResult(AssetPath);
+					BundleName = packRuleResult.GetShareBundleName(packageName, uniqueBundleName);
+				}
+				else
+				{
+					// 注意:被引用次数小于1的资源不需要设置资源包名称
+					BundleName = string.Empty;
+				}
+			}
+		}
+
+		/// <summary>
+		/// 判断是否为冗余资源
+		/// </summary>
+		public bool IsRedundancyAsset()
+		{
+			if (HasBundleName())
+				return false;
+
+			return _referenceBundleNames.Count > 1;
+		}
+
+		/// <summary>
+		/// 获取关联资源包的数量
+		/// </summary>
+		public int GetReferenceBundleCount()
+		{
+			return _referenceBundleNames.Count;
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildAssetInfo.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 447008dd110b8d746aafbe88c78bee5d
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 225 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildBundleInfo.cs

@@ -0,0 +1,225 @@
+using System;
+using System.Linq;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEditor;
+
+namespace YooAsset.Editor
+{
+	public class BuildBundleInfo
+	{
+		#region 补丁文件的关键信息
+		/// <summary>
+		/// Unity引擎生成的哈希值(构建内容的哈希值)
+		/// </summary>
+		public string PackageUnityHash { set; get; }
+
+		/// <summary>
+		/// Unity引擎生成的CRC
+		/// </summary>
+		public uint PackageUnityCRC { set; get; }
+
+		/// <summary>
+		/// 文件哈希值
+		/// </summary>
+		public string PackageFileHash { set; get; }
+
+		/// <summary>
+		/// 文件哈希值
+		/// </summary>
+		public string PackageFileCRC { set; get; }
+
+		/// <summary>
+		/// 文件哈希值
+		/// </summary>
+		public long PackageFileSize { set; get; }
+
+		/// <summary>
+		/// 构建输出的文件路径
+		/// </summary>
+		public string BuildOutputFilePath { set; get; }
+
+		/// <summary>
+		/// 补丁包的源文件路径
+		/// </summary>
+		public string PackageSourceFilePath { set; get; }
+
+		/// <summary>
+		/// 补丁包的目标文件路径
+		/// </summary>
+		public string PackageDestFilePath { set; get; }
+
+		/// <summary>
+		/// 加密生成文件的路径
+		/// 注意:如果未加密该路径为空
+		/// </summary>
+		public string EncryptedFilePath { set; get; }
+		#endregion
+
+
+		/// <summary>
+		/// 资源包名称
+		/// </summary>
+		public string BundleName { private set; get; }
+
+		/// <summary>
+		/// 参与构建的资源列表
+		/// 注意:不包含零依赖资源和冗余资源
+		/// </summary>
+		public readonly List<BuildAssetInfo> AllMainAssets = new List<BuildAssetInfo>();
+
+		/// <summary>
+		/// Bundle文件的加载方法
+		/// </summary>
+		public EBundleLoadMethod LoadMethod { set; get; }
+
+		/// <summary>
+		/// 是否为原生文件
+		/// </summary>
+		public bool IsRawFile
+		{
+			get
+			{
+				foreach (var assetInfo in AllMainAssets)
+				{
+					if (assetInfo.IsRawAsset)
+						return true;
+				}
+				return false;
+			}
+		}
+
+		/// <summary>
+		/// 是否为加密文件
+		/// </summary>
+		public bool IsEncryptedFile
+		{
+			get
+			{
+				if (string.IsNullOrEmpty(EncryptedFilePath))
+					return false;
+				else
+					return true;
+			}
+		}
+
+
+		public BuildBundleInfo(string bundleName)
+		{
+			BundleName = bundleName;
+		}
+
+		/// <summary>
+		/// 添加一个打包资源
+		/// </summary>
+		public void PackAsset(BuildAssetInfo assetInfo)
+		{
+			if (IsContainsAsset(assetInfo.AssetPath))
+				throw new System.Exception($"Asset is existed : {assetInfo.AssetPath}");
+
+			AllMainAssets.Add(assetInfo);
+		}
+
+		/// <summary>
+		/// 是否包含指定资源
+		/// </summary>
+		public bool IsContainsAsset(string assetPath)
+		{
+			foreach (var assetInfo in AllMainAssets)
+			{
+				if (assetInfo.AssetPath == assetPath)
+				{
+					return true;
+				}
+			}
+			return false;
+		}
+
+		/// <summary>
+		/// 获取资源包的分类标签列表
+		/// </summary>
+		public string[] GetBundleTags()
+		{
+			List<string> result = new List<string>(AllMainAssets.Count);
+			foreach (var assetInfo in AllMainAssets)
+			{
+				foreach (var assetTag in assetInfo.BundleTags)
+				{
+					if (result.Contains(assetTag) == false)
+						result.Add(assetTag);
+				}
+			}
+			return result.ToArray();
+		}
+
+		/// <summary>
+		/// 获取构建的资源路径列表
+		/// </summary>
+		public string[] GetAllMainAssetPaths()
+		{
+			return AllMainAssets.Select(t => t.AssetPath).ToArray();
+		}
+
+		/// <summary>
+		/// 获取该资源包内的所有资源(包括零依赖资源和冗余资源)
+		/// </summary>
+		public List<string> GetAllBuiltinAssetPaths()
+		{
+			var packAssets = GetAllMainAssetPaths();
+			List<string> result = new List<string>(packAssets);
+			foreach (var assetInfo in AllMainAssets)
+			{
+				if (assetInfo.AllDependAssetInfos == null)
+					continue;
+				foreach (var dependAssetInfo in assetInfo.AllDependAssetInfos)
+				{
+					// 注意:依赖资源里只添加零依赖资源和冗余资源
+					if (dependAssetInfo.HasBundleName() == false)
+					{
+						if (result.Contains(dependAssetInfo.AssetPath) == false)
+							result.Add(dependAssetInfo.AssetPath);
+					}
+				}
+			}
+			return result;
+		}
+
+		/// <summary>
+		/// 创建AssetBundleBuild类
+		/// </summary>
+		public UnityEditor.AssetBundleBuild CreatePipelineBuild()
+		{
+			// 注意:我们不在支持AssetBundle的变种机制
+			AssetBundleBuild build = new AssetBundleBuild();
+			build.assetBundleName = BundleName;
+			build.assetBundleVariant = string.Empty;
+			build.assetNames = GetAllMainAssetPaths();
+			return build;
+		}
+
+		/// <summary>
+		/// 获取所有写入补丁清单的资源
+		/// </summary>
+		public BuildAssetInfo[] GetAllManifestAssetInfos()
+		{
+			return AllMainAssets.Where(t => t.CollectorType == ECollectorType.MainAssetCollector).ToArray();
+		}
+
+		/// <summary>
+		/// 创建PackageBundle类
+		/// </summary>
+		internal PackageBundle CreatePackageBundle()
+		{
+			PackageBundle packageBundle = new PackageBundle();
+			packageBundle.BundleName = BundleName;
+			packageBundle.FileHash = PackageFileHash;
+			packageBundle.FileCRC = PackageFileCRC;
+			packageBundle.FileSize = PackageFileSize;
+			packageBundle.UnityCRC = PackageUnityCRC;
+			packageBundle.IsRawFile = IsRawFile;
+			packageBundle.LoadMethod = (byte)LoadMethod;
+			packageBundle.Tags = GetBundleTags();
+			return packageBundle;
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildBundleInfo.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 826a9d7b4de0eba40b5c39b33747c011
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 108 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildMapContext.cs

@@ -0,0 +1,108 @@
+using System;
+using System.Linq;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEditor;
+
+namespace YooAsset.Editor
+{
+	public class BuildMapContext : IContextObject
+	{
+		private readonly Dictionary<string, BuildBundleInfo> _bundleInfoDic = new Dictionary<string, BuildBundleInfo>(10000);
+
+		/// <summary>
+		/// 冗余的资源列表
+		/// </summary>
+		public readonly List<ReportRedundancyInfo> RedundancyInfos= new List<ReportRedundancyInfo>(1000);
+		
+		/// <summary>
+		/// 参与构建的资源总数
+		/// 说明:包括主动收集的资源以及其依赖的所有资源
+		/// </summary>
+		public int AssetFileCount;
+
+		/// <summary>
+		/// 收集命令
+		/// </summary>
+		public CollectCommand Command { set; get; }
+
+		/// <summary>
+		/// 资源包信息列表
+		/// </summary>
+		public Dictionary<string, BuildBundleInfo>.ValueCollection Collection
+		{
+			get
+			{
+				return _bundleInfoDic.Values;
+			}
+		}
+
+
+		/// <summary>
+		/// 添加一个打包资源
+		/// </summary>
+		public void PackAsset(BuildAssetInfo assetInfo)
+		{
+			string bundleName = assetInfo.BundleName;
+			if (string.IsNullOrEmpty(bundleName))
+				throw new Exception("Should never get here !");
+
+			if (_bundleInfoDic.TryGetValue(bundleName, out BuildBundleInfo bundleInfo))
+			{
+				bundleInfo.PackAsset(assetInfo);
+			}
+			else
+			{
+				BuildBundleInfo newBundleInfo = new BuildBundleInfo(bundleName);
+				newBundleInfo.PackAsset(assetInfo);
+				_bundleInfoDic.Add(bundleName, newBundleInfo);
+			}
+		}
+
+		/// <summary>
+		/// 是否包含资源包
+		/// </summary>
+		public bool IsContainsBundle(string bundleName)
+		{
+			return _bundleInfoDic.ContainsKey(bundleName);
+		}
+
+		/// <summary>
+		/// 获取资源包信息,如果没找到返回NULL
+		/// </summary>
+		public BuildBundleInfo GetBundleInfo(string bundleName)
+		{
+			if (_bundleInfoDic.TryGetValue(bundleName, out BuildBundleInfo result))
+			{
+				return result;
+			}
+			throw new Exception($"Not found bundle : {bundleName}");
+		}
+
+		/// <summary>
+		/// 获取构建管线里需要的数据
+		/// </summary>
+		public UnityEditor.AssetBundleBuild[] GetPipelineBuilds()
+		{
+			List<UnityEditor.AssetBundleBuild> builds = new List<UnityEditor.AssetBundleBuild>(_bundleInfoDic.Count);
+			foreach (var bundleInfo in _bundleInfoDic.Values)
+			{
+				if (bundleInfo.IsRawFile == false)
+					builds.Add(bundleInfo.CreatePipelineBuild());
+			}
+			return builds.ToArray();
+		}
+
+		/// <summary>
+		/// 创建着色器信息类
+		/// </summary>
+		public void CreateShadersBundleInfo(string shadersBundleName)
+		{
+			if (IsContainsBundle(shadersBundleName) == false)
+			{
+				var shaderBundleInfo = new BuildBundleInfo(shadersBundleName);
+				_bundleInfoDic.Add(shadersBundleName, shaderBundleInfo);
+			}
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildMapContext.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8229aa3f8a369204db5c368715191e2f
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 125 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildParameters.cs

@@ -0,0 +1,125 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEditor;
+
+namespace YooAsset.Editor
+{
+	/// <summary>
+	/// 构建参数
+	/// </summary>
+	public class BuildParameters
+	{
+		/// <summary>
+		/// SBP构建参数
+		/// </summary>
+		public class SBPBuildParameters
+		{
+			/// <summary>
+			/// 生成代码防裁剪配置
+			/// </summary>
+			public bool WriteLinkXML = true;
+
+			/// <summary>
+			/// 缓存服务器地址
+			/// </summary>
+			public string CacheServerHost;
+
+			/// <summary>
+			/// 缓存服务器端口
+			/// </summary>
+			public int CacheServerPort;
+		}
+
+		/// <summary>
+		/// 可编程构建管线的参数
+		/// </summary>
+		public SBPBuildParameters SBPParameters;
+
+
+		/// <summary>
+		/// 内置资源的根目录
+		/// </summary>
+		public string StreamingAssetsRoot;
+
+		/// <summary>
+		/// 构建输出的根目录
+		/// </summary>
+		public string BuildOutputRoot;
+
+		/// <summary>
+		/// 构建的平台
+		/// </summary>
+		public BuildTarget BuildTarget;
+
+		/// <summary>
+		/// 构建管线
+		/// </summary>
+		public EBuildPipeline BuildPipeline;
+
+		/// <summary>
+		/// 构建模式
+		/// </summary>
+		public EBuildMode BuildMode;
+
+		/// <summary>
+		/// 构建的包裹名称
+		/// </summary>
+		public string PackageName;
+
+		/// <summary>
+		/// 构建的包裹版本
+		/// </summary>
+		public string PackageVersion;
+
+
+		/// <summary>
+		/// 是否显示普通日志
+		/// </summary>
+		public bool EnableLog = true;
+		
+		/// <summary>
+		/// 验证构建结果
+		/// </summary>
+		public bool VerifyBuildingResult = false;
+		
+		/// <summary>
+		/// 共享资源的打包规则
+		/// </summary>
+		public ISharedPackRule SharedPackRule = null;
+
+		/// <summary>
+		/// 资源的加密接口
+		/// </summary>
+		public IEncryptionServices EncryptionServices = null;
+
+		/// <summary>
+		/// 补丁文件名称的样式
+		/// </summary>
+		public EOutputNameStyle OutputNameStyle = EOutputNameStyle.HashName;
+
+		/// <summary>
+		/// 拷贝内置资源选项
+		/// </summary>
+		public ECopyBuildinFileOption CopyBuildinFileOption = ECopyBuildinFileOption.None;
+
+		/// <summary>
+		/// 拷贝内置资源的标签
+		/// </summary>
+		public string CopyBuildinFileTags = string.Empty;
+
+		/// <summary>
+		/// 压缩选项
+		/// </summary>
+		public ECompressOption CompressOption = ECompressOption.Uncompressed;
+
+		/// <summary>
+		/// 禁止写入类型树结构(可以降低包体和内存并提高加载效率)
+		/// </summary>
+		public bool DisableWriteTypeTree = false;
+
+		/// <summary>
+		/// 忽略类型树变化
+		/// </summary>
+		public bool IgnoreTypeTreeChanges = true;
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildParameters.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 98bb314dc26ba184fbb9e9fdcdb58a1d
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 145 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildParametersContext.cs

@@ -0,0 +1,145 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEditor;
+
+namespace YooAsset.Editor
+{
+	public class BuildParametersContext : IContextObject
+	{
+		private string _pipelineOutputDirectory = string.Empty;
+		private string _packageOutputDirectory = string.Empty;
+		private string _packageRootDirectory = string.Empty;
+		private string _streamingAssetsDirectory = string.Empty;
+
+		/// <summary>
+		/// 构建参数
+		/// </summary>
+		public BuildParameters Parameters { private set; get; }
+
+
+		public BuildParametersContext(BuildParameters parameters)
+		{
+			Parameters = parameters;
+		}
+
+		/// <summary>
+		/// 获取构建管线的输出目录
+		/// </summary>
+		/// <returns></returns>
+		public string GetPipelineOutputDirectory()
+		{
+			if (string.IsNullOrEmpty(_pipelineOutputDirectory))
+			{
+				_pipelineOutputDirectory = $"{Parameters.BuildOutputRoot}/{Parameters.BuildTarget}/{Parameters.PackageName}/{YooAssetSettings.OutputFolderName}";
+			}
+			return _pipelineOutputDirectory;
+		}
+
+		/// <summary>
+		/// 获取本次构建的补丁输出目录
+		/// </summary>
+		public string GetPackageOutputDirectory()
+		{
+			if (string.IsNullOrEmpty(_packageOutputDirectory))
+			{
+				_packageOutputDirectory = $"{Parameters.BuildOutputRoot}/{Parameters.BuildTarget}/{Parameters.PackageName}/{Parameters.PackageVersion}";
+			}
+			return _packageOutputDirectory;
+		}
+
+		/// <summary>
+		/// 获取本次构建的补丁根目录
+		/// </summary>
+		public string GetPackageRootDirectory()
+		{
+			if (string.IsNullOrEmpty(_packageRootDirectory))
+			{
+				_packageRootDirectory = $"{Parameters.BuildOutputRoot}/{Parameters.BuildTarget}/{Parameters.PackageName}";
+			}
+			return _packageRootDirectory;
+		}
+
+		/// <summary>
+		/// 获取内置资源的目录
+		/// </summary>
+		public string GetStreamingAssetsDirectory()
+		{
+			if (string.IsNullOrEmpty(_streamingAssetsDirectory))
+			{
+				_streamingAssetsDirectory = $"{Parameters.StreamingAssetsRoot}/{Parameters.PackageName}";
+			}
+			return _streamingAssetsDirectory;
+		}
+
+		/// <summary>
+		/// 获取内置构建管线的构建选项
+		/// </summary>
+		public BuildAssetBundleOptions GetPipelineBuildOptions()
+		{
+			// For the new build system, unity always need BuildAssetBundleOptions.CollectDependencies and BuildAssetBundleOptions.DeterministicAssetBundle
+			// 除非设置ForceRebuildAssetBundle标记,否则会进行增量打包
+
+			if (Parameters.BuildMode == EBuildMode.SimulateBuild)
+				throw new Exception("Should never get here !");
+
+			BuildAssetBundleOptions opt = BuildAssetBundleOptions.None;
+			opt |= BuildAssetBundleOptions.StrictMode; //Do not allow the build to succeed if any errors are reporting during it.
+
+			if (Parameters.BuildMode == EBuildMode.DryRunBuild)
+			{
+				opt |= BuildAssetBundleOptions.DryRunBuild;
+				return opt;
+			}
+
+			if (Parameters.CompressOption == ECompressOption.Uncompressed)
+				opt |= BuildAssetBundleOptions.UncompressedAssetBundle;
+			else if (Parameters.CompressOption == ECompressOption.LZ4)
+				opt |= BuildAssetBundleOptions.ChunkBasedCompression;
+
+			if (Parameters.BuildMode == EBuildMode.ForceRebuild)
+				opt |= BuildAssetBundleOptions.ForceRebuildAssetBundle; //Force rebuild the asset bundles
+			if (Parameters.DisableWriteTypeTree)
+				opt |= BuildAssetBundleOptions.DisableWriteTypeTree; //Do not include type information within the asset bundle (don't write type tree).
+			if (Parameters.IgnoreTypeTreeChanges)
+				opt |= BuildAssetBundleOptions.IgnoreTypeTreeChanges; //Ignore the type tree changes when doing the incremental build check.
+
+			opt |= BuildAssetBundleOptions.DisableLoadAssetByFileName; //Disables Asset Bundle LoadAsset by file name.
+			opt |= BuildAssetBundleOptions.DisableLoadAssetByFileNameWithExtension; //Disables Asset Bundle LoadAsset by file name with extension.			
+
+			return opt;
+		}
+
+		/// <summary>
+		/// 获取可编程构建管线的构建参数
+		/// </summary>
+		public UnityEditor.Build.Pipeline.BundleBuildParameters GetSBPBuildParameters()
+		{
+			if (Parameters.BuildMode == EBuildMode.SimulateBuild)
+				throw new Exception("Should never get here !");
+
+			var targetGroup = BuildPipeline.GetBuildTargetGroup(Parameters.BuildTarget);
+			var pipelineOutputDirectory = GetPipelineOutputDirectory();
+			var buildParams = new UnityEditor.Build.Pipeline.BundleBuildParameters(Parameters.BuildTarget, targetGroup, pipelineOutputDirectory);
+
+			if (Parameters.CompressOption == ECompressOption.Uncompressed)
+				buildParams.BundleCompression = UnityEngine.BuildCompression.Uncompressed;
+			else if (Parameters.CompressOption == ECompressOption.LZMA)
+				buildParams.BundleCompression = UnityEngine.BuildCompression.LZMA;
+			else if (Parameters.CompressOption == ECompressOption.LZ4)
+				buildParams.BundleCompression = UnityEngine.BuildCompression.LZ4;
+			else
+				throw new System.NotImplementedException(Parameters.CompressOption.ToString());
+
+			if (Parameters.DisableWriteTypeTree)
+				buildParams.ContentBuildFlags |= UnityEditor.Build.Content.ContentBuildFlags.DisableWriteTypeTree;
+
+			buildParams.UseCache = true;
+			buildParams.CacheServerHost = Parameters.SBPParameters.CacheServerHost;
+			buildParams.CacheServerPort = Parameters.SBPParameters.CacheServerPort;
+			buildParams.WriteLinkXML = Parameters.SBPParameters.WriteLinkXML;
+
+			return buildParams;
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildParametersContext.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b84510feab7cbe44a9b6d8ef0b3f559c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 2875232ad7f87c148b752d432f3a54f2
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 50 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/BuildContext.cs

@@ -0,0 +1,50 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace YooAsset.Editor
+{
+	public class BuildContext
+	{
+		private readonly Dictionary<System.Type, IContextObject> _contextObjects = new Dictionary<System.Type, IContextObject>();
+
+		/// <summary>
+		/// 清空所有情景对象
+		/// </summary>
+		public void ClearAllContext()
+		{
+			_contextObjects.Clear();
+		}
+
+		/// <summary>
+		/// 设置情景对象
+		/// </summary>
+		public void SetContextObject(IContextObject contextObject)
+		{
+			if (contextObject == null)
+				throw new ArgumentNullException("contextObject");
+
+			var type = contextObject.GetType();
+			if (_contextObjects.ContainsKey(type))
+				throw new Exception($"Context object {type} is already existed.");
+
+			_contextObjects.Add(type, contextObject);
+		}
+
+		/// <summary>
+		/// 获取情景对象
+		/// </summary>
+		public T GetContextObject<T>() where T : IContextObject
+		{
+			var type = typeof(T);
+			if (_contextObjects.TryGetValue(type, out IContextObject contextObject))
+			{
+				return (T)contextObject;
+			}
+			else
+			{
+				throw new Exception($"Not found context object : {type}");
+			}
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/BuildContext.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6a232601f99c4634ea17fca4979f80ab
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 33 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/BuildLogger.cs

@@ -0,0 +1,33 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace YooAsset.Editor
+{
+	public static class BuildLogger
+	{
+		private static bool _enableLog = true;
+
+		public static void InitLogger(bool enableLog)
+		{
+			_enableLog = enableLog;
+		}
+
+		public static void Log(string message)
+		{
+			if (_enableLog)
+			{
+				Debug.Log(message);
+			}
+		}
+		public static void Warning(string message)
+		{
+			Debug.LogWarning(message);
+		}
+		public static void Error(string message)
+		{
+			Debug.LogError(message);
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/BuildLogger.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2bc82466a51f50141975e4424095aa09
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 29 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/BuildResult.cs

@@ -0,0 +1,29 @@
+
+namespace YooAsset.Editor
+{
+    /// <summary>
+    /// 构建结果
+    /// </summary>
+    public class BuildResult
+    {
+        /// <summary>
+        /// 构建是否成功
+        /// </summary>
+        public bool Success;
+
+        /// <summary>
+        /// 构建失败的任务
+        /// </summary>
+        public string FailedTask;
+
+        /// <summary>
+        /// 构建失败的信息
+        /// </summary>
+        public string ErrorInfo;
+
+        /// <summary>
+        /// 输出的补丁包目录
+        /// </summary>
+        public string OutputPackageDirectory;
+    }
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/BuildResult.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e0855c4b5eaa26942bd7ad177fe3c288
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 72 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/BuildRunner.cs

@@ -0,0 +1,72 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Diagnostics;
+using UnityEngine;
+
+namespace YooAsset.Editor
+{
+	public class BuildRunner
+	{
+		private static Stopwatch _buildWatch;
+
+		/// <summary>
+		/// 总耗时
+		/// </summary>
+		public static int TotalSeconds = 0;
+
+		/// <summary>
+		/// 执行构建流程
+		/// </summary>
+		/// <returns>如果成功返回TRUE,否则返回FALSE</returns>
+		public static BuildResult Run(List<IBuildTask> pipeline, BuildContext context)
+		{
+			if (pipeline == null)
+				throw new ArgumentNullException("pipeline");
+			if (context == null)
+				throw new ArgumentNullException("context");
+
+			BuildResult buildResult = new BuildResult();
+			buildResult.Success = true;
+			TotalSeconds = 0;
+			for (int i = 0; i < pipeline.Count; i++)
+			{
+				IBuildTask task = pipeline[i];
+				try
+				{
+					_buildWatch = Stopwatch.StartNew();
+					var taskAttribute = task.GetType().GetCustomAttribute<TaskAttribute>();
+					if (taskAttribute != null)
+						BuildLogger.Log($"---------------------------------------->{taskAttribute.TaskDesc}<---------------------------------------");
+					task.Run(context);
+					_buildWatch.Stop();
+
+					// 统计耗时
+					int seconds = GetBuildSeconds();
+					TotalSeconds += seconds;
+					if (taskAttribute != null)
+						BuildLogger.Log($"{taskAttribute.TaskDesc}耗时:{seconds}秒");
+				}
+				catch (Exception e)
+				{
+					EditorTools.ClearProgressBar();
+					buildResult.FailedTask = task.GetType().Name;
+					buildResult.ErrorInfo = e.ToString();
+					buildResult.Success = false;
+					break;
+				}
+			}
+
+			// 返回运行结果
+			BuildLogger.Log($"构建过程总计耗时:{TotalSeconds}秒");
+			return buildResult;
+		}
+
+		private static int GetBuildSeconds()
+		{
+			float seconds = _buildWatch.ElapsedMilliseconds / 1000f;
+			return (int)seconds;
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/BuildRunner.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 79014124da678684388454d6ea892722
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/IBuildTask.cs

@@ -0,0 +1,8 @@
+
+namespace YooAsset.Editor
+{
+    public interface IBuildTask
+    {
+        void Run(BuildContext context);
+    }
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/IBuildTask.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8888657c45e12c646a8349bac6bf0326
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 7 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/IContextObject.cs

@@ -0,0 +1,7 @@
+
+namespace YooAsset.Editor
+{
+	public interface IContextObject
+	{
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/IContextObject.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 151a4acd5ad1c2046be20e080f9bdad4
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 18 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/TaskAttribute.cs

@@ -0,0 +1,18 @@
+using System;
+
+namespace YooAsset.Editor
+{
+	[AttributeUsage(AttributeTargets.Class)]
+	public class TaskAttribute : Attribute
+	{
+		/// <summary>
+		/// 任务说明
+		/// </summary>
+		public string TaskDesc;
+
+		public TaskAttribute(string taskDesc)
+		{
+			TaskDesc = taskDesc;
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildSystem/TaskAttribute.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 35749e57d9a3da84aa60c348bc6bbe9d
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 254f430e0264bd84387878f8d7280e44
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 52 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/SBPBuildTasks.cs

@@ -0,0 +1,52 @@
+using System;
+using System.Linq;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEditor.Build.Pipeline;
+using UnityEditor.Build.Pipeline.Interfaces;
+
+namespace UnityEditor.Build.Pipeline.Tasks
+{
+	public static class SBPBuildTasks
+	{
+		public static IList<IBuildTask> Create(string builtInShaderBundleName)
+		{
+			var buildTasks = new List<IBuildTask>();
+			
+			// Setup
+			buildTasks.Add(new SwitchToBuildPlatform());
+			buildTasks.Add(new RebuildSpriteAtlasCache());
+
+			// Player Scripts
+			buildTasks.Add(new BuildPlayerScripts());
+			buildTasks.Add(new PostScriptsCallback());
+
+			// Dependency
+			buildTasks.Add(new CalculateSceneDependencyData());
+#if UNITY_2019_3_OR_NEWER
+			buildTasks.Add(new CalculateCustomDependencyData());
+#endif
+			buildTasks.Add(new CalculateAssetDependencyData());
+			buildTasks.Add(new StripUnusedSpriteSources());
+			buildTasks.Add(new CreateBuiltInShadersBundle(builtInShaderBundleName));
+			buildTasks.Add(new PostDependencyCallback());
+
+			// Packing
+			buildTasks.Add(new GenerateBundlePacking());
+			buildTasks.Add(new UpdateBundleObjectLayout());
+			buildTasks.Add(new GenerateBundleCommands());
+			buildTasks.Add(new GenerateSubAssetPathMaps());
+			buildTasks.Add(new GenerateBundleMaps());
+			buildTasks.Add(new PostPackingCallback());
+
+			// Writing
+			buildTasks.Add(new WriteSerializedFiles());
+			buildTasks.Add(new ArchiveAndCompressBundles());
+			buildTasks.Add(new AppendBundleHash());
+			buildTasks.Add(new GenerateLinkXml());
+			buildTasks.Add(new PostWritingCallback());
+
+			return buildTasks;
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/SBPBuildTasks.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f38422f6a64300243af6b4fbf84644ba
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 50 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskBuilding.cs

@@ -0,0 +1,50 @@
+using System;
+using System.Linq;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEngine;
+
+namespace YooAsset.Editor
+{
+	[TaskAttribute("资源构建内容打包")]
+	public class TaskBuilding : IBuildTask
+	{
+		public class BuildResultContext : IContextObject
+		{
+			public AssetBundleManifest UnityManifest;
+		}
+
+		void IBuildTask.Run(BuildContext context)
+		{
+			var buildParametersContext = context.GetContextObject<BuildParametersContext>();
+			var buildMapContext = context.GetContextObject<BuildMapContext>();
+
+			// 模拟构建模式下跳过引擎构建
+			var buildMode = buildParametersContext.Parameters.BuildMode;
+			if (buildMode == EBuildMode.SimulateBuild)
+				return;
+
+			// 开始构建
+			string pipelineOutputDirectory = buildParametersContext.GetPipelineOutputDirectory();
+			BuildAssetBundleOptions buildOptions = buildParametersContext.GetPipelineBuildOptions();
+			AssetBundleManifest buildResults = BuildPipeline.BuildAssetBundles(pipelineOutputDirectory, buildMapContext.GetPipelineBuilds(), buildOptions, buildParametersContext.Parameters.BuildTarget);
+			if (buildResults == null)
+			{
+				throw new Exception("构建过程中发生错误!");
+			}
+
+			if (buildMode == EBuildMode.ForceRebuild || buildMode == EBuildMode.IncrementalBuild)
+			{
+				string unityOutputManifestFilePath = $"{pipelineOutputDirectory}/{YooAssetSettings.OutputFolderName}";
+				if (System.IO.File.Exists(unityOutputManifestFilePath) == false)
+					throw new Exception("构建过程中发生严重错误!请查阅上下文日志!");
+			}
+
+			BuildLogger.Log("Unity引擎打包成功!");
+			BuildResultContext buildResultContext = new BuildResultContext();
+			buildResultContext.UnityManifest = buildResults;
+			context.SetContextObject(buildResultContext);
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskBuilding.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9466e826c135e994c84961e4b921ca5f
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 58 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskBuilding_SBP.cs

@@ -0,0 +1,58 @@
+using System;
+using System.Linq;
+using System.Collections;
+using System.Collections.Generic;
+
+using UnityEditor.Build.Pipeline;
+using UnityEditor.Build.Pipeline.Interfaces;
+using UnityEditor.Build.Pipeline.Tasks;
+
+namespace YooAsset.Editor
+{
+	[TaskAttribute("资源构建内容打包")]
+	public class TaskBuilding_SBP : IBuildTask
+	{
+		public class BuildResultContext : IContextObject
+		{
+			public IBundleBuildResults Results;
+		}
+
+		void IBuildTask.Run(BuildContext context)
+		{
+			var buildParametersContext = context.GetContextObject<BuildParametersContext>();
+			var buildMapContext = context.GetContextObject<BuildMapContext>();
+
+			// 模拟构建模式下跳过引擎构建
+			var buildMode = buildParametersContext.Parameters.BuildMode;
+			if (buildMode == EBuildMode.SimulateBuild)
+				return;
+
+			// 构建内容
+			var buildContent = new BundleBuildContent(buildMapContext.GetPipelineBuilds());
+
+			// 开始构建
+			IBundleBuildResults buildResults;
+			var buildParameters = buildParametersContext.GetSBPBuildParameters();
+			var taskList = SBPBuildTasks.Create(buildMapContext.Command.ShadersBundleName);
+			ReturnCode exitCode = ContentPipeline.BuildAssetBundles(buildParameters, buildContent, out buildResults, taskList);
+			if (exitCode < 0)
+			{
+				throw new Exception($"构建过程中发生错误 : {exitCode}");
+			}
+
+			// 创建着色器信息
+			// 说明:解决因为着色器资源包导致验证失败。
+			// 例如:当项目里没有着色器,如果有依赖内置着色器就会验证失败。
+			string shadersBundleName = buildMapContext.Command.ShadersBundleName;
+			if (buildResults.BundleInfos.ContainsKey(shadersBundleName))
+			{
+				buildMapContext.CreateShadersBundleInfo(shadersBundleName);
+			}
+
+			BuildLogger.Log("Unity引擎打包成功!");
+			BuildResultContext buildResultContext = new BuildResultContext();
+			buildResultContext.Results = buildResults;
+			context.SetContextObject(buildResultContext);
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskBuilding_SBP.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1af5fed7e9f83174d868c12b41c4a79e
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 100 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskCopyBuildinFiles.cs

@@ -0,0 +1,100 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEngine;
+
+namespace YooAsset.Editor
+{
+	[TaskAttribute("拷贝内置文件到流目录")]
+	public class TaskCopyBuildinFiles : IBuildTask
+	{
+		void IBuildTask.Run(BuildContext context)
+		{
+			var buildParametersContext = context.GetContextObject<BuildParametersContext>();
+			var manifestContext = context.GetContextObject<ManifestContext>();
+			var buildMode = buildParametersContext.Parameters.BuildMode;
+			if (buildMode == EBuildMode.ForceRebuild || buildMode == EBuildMode.IncrementalBuild)
+			{
+				if (buildParametersContext.Parameters.CopyBuildinFileOption != ECopyBuildinFileOption.None)
+				{
+					CopyBuildinFilesToStreaming(buildParametersContext, manifestContext);
+				}
+			}
+		}
+
+		/// <summary>
+		/// 拷贝首包资源文件
+		/// </summary>
+		private void CopyBuildinFilesToStreaming(BuildParametersContext buildParametersContext, ManifestContext manifestContext)
+		{
+			ECopyBuildinFileOption option = buildParametersContext.Parameters.CopyBuildinFileOption;
+			string packageOutputDirectory = buildParametersContext.GetPackageOutputDirectory();
+			string streamingAssetsDirectory = buildParametersContext.GetStreamingAssetsDirectory();
+			string buildPackageName = buildParametersContext.Parameters.PackageName;
+			string buildPackageVersion = buildParametersContext.Parameters.PackageVersion;
+
+			// 加载补丁清单
+			PackageManifest manifest = manifestContext.Manifest;
+
+			// 清空流目录
+			if (option == ECopyBuildinFileOption.ClearAndCopyAll || option == ECopyBuildinFileOption.ClearAndCopyByTags)
+			{
+				EditorTools.ClearFolder(streamingAssetsDirectory);
+			}
+
+			// 拷贝补丁清单文件
+			{
+				string fileName = YooAssetSettingsData.GetManifestBinaryFileName(buildPackageName, buildPackageVersion);
+				string sourcePath = $"{packageOutputDirectory}/{fileName}";
+				string destPath = $"{streamingAssetsDirectory}/{fileName}";
+				EditorTools.CopyFile(sourcePath, destPath, true);
+			}
+
+			// 拷贝补丁清单哈希文件
+			{
+				string fileName = YooAssetSettingsData.GetPackageHashFileName(buildPackageName, buildPackageVersion);
+				string sourcePath = $"{packageOutputDirectory}/{fileName}";
+				string destPath = $"{streamingAssetsDirectory}/{fileName}";
+				EditorTools.CopyFile(sourcePath, destPath, true);
+			}
+
+			// 拷贝补丁清单版本文件
+			{
+				string fileName = YooAssetSettingsData.GetPackageVersionFileName(buildPackageName);
+				string sourcePath = $"{packageOutputDirectory}/{fileName}";
+				string destPath = $"{streamingAssetsDirectory}/{fileName}";
+				EditorTools.CopyFile(sourcePath, destPath, true);
+			}
+
+			// 拷贝文件列表(所有文件)
+			if (option == ECopyBuildinFileOption.ClearAndCopyAll || option == ECopyBuildinFileOption.OnlyCopyAll)
+			{
+				foreach (var packageBundle in manifest.BundleList)
+				{
+					string sourcePath = $"{packageOutputDirectory}/{packageBundle.FileName}";
+					string destPath = $"{streamingAssetsDirectory}/{packageBundle.FileName}";
+					EditorTools.CopyFile(sourcePath, destPath, true);
+				}
+			}
+
+			// 拷贝文件列表(带标签的文件)
+			if (option == ECopyBuildinFileOption.ClearAndCopyByTags || option == ECopyBuildinFileOption.OnlyCopyByTags)
+			{
+				string[] tags = buildParametersContext.Parameters.CopyBuildinFileTags.Split(';');
+				foreach (var packageBundle in manifest.BundleList)
+				{
+					if (packageBundle.HasTag(tags) == false)
+						continue;
+					string sourcePath = $"{packageOutputDirectory}/{packageBundle.FileName}";
+					string destPath = $"{streamingAssetsDirectory}/{packageBundle.FileName}";
+					EditorTools.CopyFile(sourcePath, destPath, true);
+				}
+			}
+
+			// 刷新目录
+			AssetDatabase.Refresh();
+			BuildLogger.Log($"内置文件拷贝完成:{streamingAssetsDirectory}");
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskCopyBuildinFiles.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5c77e17c3a3a57548a218f1cd26f5a55
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 44 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskCopyRawFile.cs

@@ -0,0 +1,44 @@
+using System;
+using System.Linq;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace YooAsset.Editor
+{
+	[TaskAttribute("拷贝原生文件")]
+	public class TaskCopyRawFile : IBuildTask
+	{
+		void IBuildTask.Run(BuildContext context)
+		{
+			var buildParametersContext = context.GetContextObject<BuildParametersContext>();
+			var buildParameters = context.GetContextObject<BuildParametersContext>();
+			var buildMapContext = context.GetContextObject<BuildMapContext>();
+
+			var buildMode = buildParameters.Parameters.BuildMode;
+			if (buildMode == EBuildMode.ForceRebuild || buildMode == EBuildMode.IncrementalBuild)
+			{
+				CopyRawBundle(buildMapContext, buildParametersContext);
+			}
+		}
+
+		/// <summary>
+		/// 拷贝原生文件
+		/// </summary>
+		private void CopyRawBundle(BuildMapContext buildMapContext, BuildParametersContext buildParametersContext)
+		{
+			string pipelineOutputDirectory = buildParametersContext.GetPipelineOutputDirectory();
+			foreach (var bundleInfo in buildMapContext.Collection)
+			{
+				if (bundleInfo.IsRawFile)
+				{
+					string dest = $"{pipelineOutputDirectory}/{bundleInfo.BundleName}";
+					foreach (var assetInfo in bundleInfo.AllMainAssets)
+					{
+						if (assetInfo.IsRawAsset)
+							EditorTools.CopyFile(assetInfo.AssetPath, dest, true);
+					}
+				}
+			}
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskCopyRawFile.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3625d4b8b5b79324ebf7ec19a87677e7
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 384 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskCreateManifest.cs

@@ -0,0 +1,384 @@
+using System;
+using System.Linq;
+using System.Collections;
+using System.Collections.Generic;
+
+using UnityEditor.Build.Pipeline;
+using UnityEditor.Build.Pipeline.Interfaces;
+
+namespace YooAsset.Editor
+{
+	public class ManifestContext : IContextObject
+	{
+		internal PackageManifest Manifest;
+	}
+
+	[TaskAttribute("创建清单文件")]
+	public class TaskCreateManifest : IBuildTask
+	{
+		void IBuildTask.Run(BuildContext context)
+		{
+			CreateManifestFile(context);
+		}
+
+		/// <summary>
+		/// 创建补丁清单文件到输出目录
+		/// </summary>
+		private void CreateManifestFile(BuildContext context)
+		{
+			var buildMapContext = context.GetContextObject<BuildMapContext>();
+			var buildParametersContext = context.GetContextObject<BuildParametersContext>();
+			var buildParameters = buildParametersContext.Parameters;
+			string packageOutputDirectory = buildParametersContext.GetPackageOutputDirectory();
+
+			// 创建新补丁清单
+			PackageManifest manifest = new PackageManifest();
+			manifest.FileVersion = YooAssetSettings.ManifestFileVersion;
+			manifest.EnableAddressable = buildMapContext.Command.EnableAddressable;
+			manifest.LocationToLower = buildMapContext.Command.LocationToLower;
+			manifest.IncludeAssetGUID = buildMapContext.Command.IncludeAssetGUID;
+			manifest.OutputNameStyle = (int)buildParameters.OutputNameStyle;
+			manifest.PackageName = buildParameters.PackageName;
+			manifest.PackageVersion = buildParameters.PackageVersion;
+
+			// 填充资源包集合
+			manifest.BundleList = GetAllPackageBundle(context);
+			CacheBundleIDs(manifest);
+
+			// 填充主资源集合
+			manifest.AssetList = GetAllPackageAsset(context, manifest);
+
+			// 更新Unity内置资源包的引用关系
+			if (buildParameters.BuildPipeline == EBuildPipeline.ScriptableBuildPipeline)
+			{
+				if (buildParameters.BuildMode == EBuildMode.IncrementalBuild)
+				{
+					var buildResultContext = context.GetContextObject<TaskBuilding_SBP.BuildResultContext>();
+					UpdateBuiltInBundleReference(manifest, buildResultContext, buildMapContext.Command.ShadersBundleName);
+				}
+			}
+
+			// 更新资源包之间的引用关系
+			if (buildParameters.BuildPipeline == EBuildPipeline.ScriptableBuildPipeline)
+			{
+				if (buildParameters.BuildMode == EBuildMode.IncrementalBuild)
+				{
+					var buildResultContext = context.GetContextObject<TaskBuilding_SBP.BuildResultContext>();
+					UpdateScriptPipelineReference(manifest, buildResultContext);
+				}
+			}
+
+			// 更新资源包之间的引用关系
+			if (buildParameters.BuildPipeline == EBuildPipeline.BuiltinBuildPipeline)
+			{
+				if (buildParameters.BuildMode != EBuildMode.SimulateBuild)
+				{
+					var buildResultContext = context.GetContextObject<TaskBuilding.BuildResultContext>();
+					UpdateBuiltinPipelineReference(manifest, buildResultContext);
+				}
+			}
+
+			// 创建补丁清单文本文件
+			{
+				string fileName = YooAssetSettingsData.GetManifestJsonFileName(buildParameters.PackageName, buildParameters.PackageVersion);
+				string filePath = $"{packageOutputDirectory}/{fileName}";
+				ManifestTools.SerializeToJson(filePath, manifest);
+				BuildLogger.Log($"创建补丁清单文件:{filePath}");
+			}
+
+			// 创建补丁清单二进制文件
+			string packageHash;
+			{
+				string fileName = YooAssetSettingsData.GetManifestBinaryFileName(buildParameters.PackageName, buildParameters.PackageVersion);
+				string filePath = $"{packageOutputDirectory}/{fileName}";
+				ManifestTools.SerializeToBinary(filePath, manifest);
+				packageHash = HashUtility.FileMD5(filePath);
+				BuildLogger.Log($"创建补丁清单文件:{filePath}");
+
+				ManifestContext manifestContext = new ManifestContext();
+				byte[] bytesData = FileUtility.ReadAllBytes(filePath);
+				manifestContext.Manifest = ManifestTools.DeserializeFromBinary(bytesData);
+				context.SetContextObject(manifestContext);
+			}
+
+			// 创建补丁清单哈希文件
+			{
+				string fileName = YooAssetSettingsData.GetPackageHashFileName(buildParameters.PackageName, buildParameters.PackageVersion);
+				string filePath = $"{packageOutputDirectory}/{fileName}";
+				FileUtility.WriteAllText(filePath, packageHash);
+				BuildLogger.Log($"创建补丁清单哈希文件:{filePath}");
+			}
+
+			// 创建补丁清单版本文件
+			{
+				string fileName = YooAssetSettingsData.GetPackageVersionFileName(buildParameters.PackageName);
+				string filePath = $"{packageOutputDirectory}/{fileName}";
+				FileUtility.WriteAllText(filePath, buildParameters.PackageVersion);
+				BuildLogger.Log($"创建补丁清单版本文件:{filePath}");
+			}
+		}
+
+		/// <summary>
+		/// 获取资源包列表
+		/// </summary>
+		private List<PackageBundle> GetAllPackageBundle(BuildContext context)
+		{
+			var buildMapContext = context.GetContextObject<BuildMapContext>();
+
+			List<PackageBundle> result = new List<PackageBundle>(1000);
+			foreach (var bundleInfo in buildMapContext.Collection)
+			{
+				var packageBundle = bundleInfo.CreatePackageBundle();
+				result.Add(packageBundle);
+			}
+			return result;
+		}
+
+		/// <summary>
+		/// 获取资源列表
+		/// </summary>
+		private List<PackageAsset> GetAllPackageAsset(BuildContext context, PackageManifest manifest)
+		{
+			var buildMapContext = context.GetContextObject<BuildMapContext>();
+
+			List<PackageAsset> result = new List<PackageAsset>(1000);
+			foreach (var bundleInfo in buildMapContext.Collection)
+			{
+				var assetInfos = bundleInfo.GetAllManifestAssetInfos();
+				foreach (var assetInfo in assetInfos)
+				{
+					PackageAsset packageAsset = new PackageAsset();
+					packageAsset.Address = buildMapContext.Command.EnableAddressable ? assetInfo.Address : string.Empty;
+					packageAsset.AssetPath = assetInfo.AssetPath;
+					packageAsset.AssetGUID = buildMapContext.Command.IncludeAssetGUID ? assetInfo.AssetGUID : string.Empty;
+					packageAsset.AssetTags = assetInfo.AssetTags.ToArray();
+					packageAsset.BundleID = GetCachedBundleID(assetInfo.BundleName);
+					packageAsset.DependIDs = GetAssetBundleDependIDs(packageAsset.BundleID, assetInfo, manifest);
+					result.Add(packageAsset);
+				}
+			}
+			return result;
+		}
+		private int[] GetAssetBundleDependIDs(int mainBundleID, BuildAssetInfo assetInfo, PackageManifest manifest)
+		{
+			HashSet<int> result = new HashSet<int>();
+			foreach (var dependAssetInfo in assetInfo.AllDependAssetInfos)
+			{
+				if (dependAssetInfo.HasBundleName())
+				{
+					int bundleID = GetCachedBundleID(dependAssetInfo.BundleName);
+					if (mainBundleID != bundleID)
+					{
+						if (result.Contains(bundleID) == false)
+							result.Add(bundleID);
+					}
+				}
+			}
+			return result.ToArray();
+		}
+
+		/// <summary>
+		/// 更新Unity内置资源包的引用关系
+		/// </summary>
+		private void UpdateBuiltInBundleReference(PackageManifest manifest, TaskBuilding_SBP.BuildResultContext buildResultContext, string shadersBunldeName)
+		{
+			// 获取所有依赖着色器资源包的资源包列表
+			List<string> shaderBundleReferenceList = new List<string>();
+			foreach (var valuePair in buildResultContext.Results.BundleInfos)
+			{
+				if (valuePair.Value.Dependencies.Any(t => t == shadersBunldeName))
+					shaderBundleReferenceList.Add(valuePair.Key);
+			}
+
+			// 注意:没有任何资源依赖着色器
+			if (shaderBundleReferenceList.Count == 0)
+				return;
+
+			// 获取着色器资源包索引
+			Predicate<PackageBundle> predicate = new Predicate<PackageBundle>(s => s.BundleName == shadersBunldeName);
+			int shaderBundleId = manifest.BundleList.FindIndex(predicate);
+			if (shaderBundleId == -1)
+				throw new Exception("没有发现着色器资源包!");
+
+			// 检测依赖交集并更新依赖ID
+			HashSet<string> tagTemps = new HashSet<string>();
+			foreach (var packageAsset in manifest.AssetList)
+			{
+				List<string> dependBundles = GetPackageAssetAllDependBundles(manifest, packageAsset);
+				List<string> conflictAssetPathList = dependBundles.Intersect(shaderBundleReferenceList).ToList();
+				if (conflictAssetPathList.Count > 0)
+				{
+					HashSet<int> newDependIDs = new HashSet<int>(packageAsset.DependIDs);
+					if (newDependIDs.Contains(shaderBundleId) == false)
+						newDependIDs.Add(shaderBundleId);
+					packageAsset.DependIDs = newDependIDs.ToArray();
+					foreach (var tag in packageAsset.AssetTags)
+					{
+						if (tagTemps.Contains(tag) == false)
+							tagTemps.Add(tag);
+					}
+				}
+			}
+
+			// 更新资源包标签
+			var packageBundle = manifest.BundleList[shaderBundleId];
+			HashSet<string> newTags = new HashSet<string>(packageBundle.Tags);
+			foreach (var tag in tagTemps)
+			{
+				if (newTags.Contains(tag) == false)
+					newTags.Add(tag);
+			}
+			packageBundle.Tags = newTags.ToArray();
+		}
+		private List<string> GetPackageAssetAllDependBundles(PackageManifest manifest, PackageAsset packageAsset)
+		{
+			List<string> result = new List<string>();
+			string mainBundle = manifest.BundleList[packageAsset.BundleID].BundleName;
+			result.Add(mainBundle);
+			foreach (var dependID in packageAsset.DependIDs)
+			{
+				string dependBundle = manifest.BundleList[dependID].BundleName;
+				result.Add(dependBundle);
+			}
+			return result;
+		}
+
+		#region 资源包引用关系相关
+		private readonly Dictionary<string, int> _cachedBundleID = new Dictionary<string, int>(10000);
+		private readonly Dictionary<string, string[]> _cachedBundleDepends = new Dictionary<string, string[]>(10000);
+		
+		private void CacheBundleIDs(PackageManifest manifest)
+		{
+			UnityEngine.Debug.Assert(manifest.BundleList.Count != 0, "Manifest bundle list is empty !");
+			for (int index = 0; index < manifest.BundleList.Count; index++)
+			{
+				string bundleName = manifest.BundleList[index].BundleName;
+				_cachedBundleID.Add(bundleName, index);
+			}
+		}
+
+		private void UpdateScriptPipelineReference(PackageManifest manifest, TaskBuilding_SBP.BuildResultContext buildResultContext)
+		{
+			int progressValue;
+			int totalCount = manifest.BundleList.Count;
+
+			// 缓存资源包依赖
+			_cachedBundleDepends.Clear();
+			progressValue = 0;
+			foreach (var packageBundle in manifest.BundleList)
+			{
+				if (packageBundle.IsRawFile)
+				{
+					_cachedBundleDepends.Add(packageBundle.BundleName, new string[] { });
+					continue;
+				}
+
+				if (buildResultContext.Results.BundleInfos.ContainsKey(packageBundle.BundleName) == false)
+					throw new Exception($"Not found bundle in SBP build results : {packageBundle.BundleName}");
+
+				var depends = buildResultContext.Results.BundleInfos[packageBundle.BundleName].Dependencies;
+				_cachedBundleDepends.Add(packageBundle.BundleName, depends);
+				int pro = ++progressValue;
+				if (pro % 100 == 0)
+				{
+					EditorTools.DisplayProgressBar("缓存资源包依赖列表", pro, totalCount);
+				}
+			}
+			EditorTools.ClearProgressBar();
+
+			// 计算资源包引用列表
+			foreach (var packageBundle in manifest.BundleList)
+			{
+				packageBundle.ReferenceIDs = GetBundleRefrenceIDs(manifest, packageBundle);
+				int pro = ++progressValue;
+				if (pro % 100 == 0)
+				{
+					EditorTools.DisplayProgressBar("计算资源包引用关系", pro, totalCount);
+				}
+			}
+			EditorTools.ClearProgressBar();
+		}
+		private void UpdateBuiltinPipelineReference(PackageManifest manifest, TaskBuilding.BuildResultContext buildResultContext)
+		{
+			int progressValue;
+			int totalCount = manifest.BundleList.Count;
+
+			// 缓存资源包依赖
+			_cachedBundleDepends.Clear();
+			progressValue = 0;
+			foreach (var packageBundle in manifest.BundleList)
+			{
+				if (packageBundle.IsRawFile)
+				{
+					_cachedBundleDepends.Add(packageBundle.BundleName, new string[] { });
+					continue;
+				}
+
+				var depends = buildResultContext.UnityManifest.GetDirectDependencies(packageBundle.BundleName);
+				_cachedBundleDepends.Add(packageBundle.BundleName, depends);
+				int pro = ++progressValue;
+				if (pro % 100 == 0)
+				{
+					EditorTools.DisplayProgressBar("缓存资源包依赖列表", pro, totalCount);
+				}
+			}
+			EditorTools.ClearProgressBar();
+
+			// 计算资源包引用列表
+			progressValue = 0;
+			foreach (var packageBundle in manifest.BundleList)
+			{
+				packageBundle.ReferenceIDs = GetBundleRefrenceIDs(manifest, packageBundle);
+				int pro = ++progressValue;
+				if (pro % 100 == 0)
+				{
+					EditorTools.DisplayProgressBar("计算资源包引用关系", ++progressValue, totalCount);
+				}
+			}
+			EditorTools.ClearProgressBar();
+		}
+
+		private int[] GetBundleRefrenceIDs(PackageManifest manifest, PackageBundle targetBundle)
+		{
+			List<string> referenceList = new List<string>();
+			foreach (var packageBundle in manifest.BundleList)
+			{
+				string bundleName = packageBundle.BundleName;
+				if (bundleName == targetBundle.BundleName)
+					continue;
+
+				string[] dependencies = GetCachedBundleDepends(bundleName);
+				if (dependencies.Contains(targetBundle.BundleName))
+				{
+					referenceList.Add(bundleName);
+				}
+			}
+
+			HashSet<int> result = new HashSet<int>();
+			foreach (var bundleName in referenceList)
+			{
+				int bundleID = GetCachedBundleID(bundleName);
+				if (result.Contains(bundleID) == false)
+					result.Add(bundleID);
+			}
+			return result.ToArray();
+		}
+		private int GetCachedBundleID(string bundleName)
+		{
+			if (_cachedBundleID.TryGetValue(bundleName, out int value) == false)
+			{
+				throw new Exception($"Not found cached bundle ID : {bundleName}");
+			}
+			return value;
+		}
+		private string[] GetCachedBundleDepends(string bundleName)
+		{
+			if (_cachedBundleDepends.TryGetValue(bundleName, out string[] value) == false)
+			{
+				throw new Exception($"Not found cached bundle depends : {bundleName}");
+			}
+			return value;
+		}
+		#endregion
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskCreateManifest.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 566c37f5a59f2e84397a9527981a7310
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 79 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskCreatePackage.cs

@@ -0,0 +1,79 @@
+using System.Collections;
+using System.Collections.Generic;
+
+namespace YooAsset.Editor
+{
+	[TaskAttribute("制作包裹")]
+	public class TaskCreatePackage : IBuildTask
+	{
+		void IBuildTask.Run(BuildContext context)
+		{
+			var buildParameters = context.GetContextObject<BuildParametersContext>();
+			var buildMapContext = context.GetContextObject<BuildMapContext>();
+			var buildMode = buildParameters.Parameters.BuildMode;
+			if (buildMode == EBuildMode.ForceRebuild || buildMode == EBuildMode.IncrementalBuild)
+			{
+				CopyPackageFiles(buildParameters, buildMapContext);
+			}
+		}
+
+		/// <summary>
+		/// 拷贝补丁文件到补丁包目录
+		/// </summary>
+		private void CopyPackageFiles(BuildParametersContext buildParametersContext, BuildMapContext buildMapContext)
+		{
+			var buildParameters = buildParametersContext.Parameters;
+			string pipelineOutputDirectory = buildParametersContext.GetPipelineOutputDirectory();
+			string packageOutputDirectory = buildParametersContext.GetPackageOutputDirectory();
+			BuildLogger.Log($"开始拷贝补丁文件到补丁包目录:{packageOutputDirectory}");
+
+			if (buildParameters.BuildPipeline == EBuildPipeline.ScriptableBuildPipeline)
+			{
+				// 拷贝构建日志
+				{
+					string sourcePath = $"{pipelineOutputDirectory}/buildlogtep.json";
+					string destPath = $"{packageOutputDirectory}/buildlogtep.json";
+					EditorTools.CopyFile(sourcePath, destPath, true);
+				}
+
+				// 拷贝代码防裁剪配置
+				if (buildParameters.SBPParameters.WriteLinkXML)
+				{
+					string sourcePath = $"{pipelineOutputDirectory}/link.xml";
+					string destPath = $"{packageOutputDirectory}/link.xml";
+					EditorTools.CopyFile(sourcePath, destPath, true);
+				}
+			}
+			else if (buildParameters.BuildPipeline == EBuildPipeline.BuiltinBuildPipeline)
+			{
+				// 拷贝UnityManifest序列化文件
+				{
+					string sourcePath = $"{pipelineOutputDirectory}/{YooAssetSettings.OutputFolderName}";
+					string destPath = $"{packageOutputDirectory}/{YooAssetSettings.OutputFolderName}";
+					EditorTools.CopyFile(sourcePath, destPath, true);
+				}
+
+				// 拷贝UnityManifest文本文件
+				{
+					string sourcePath = $"{pipelineOutputDirectory}/{YooAssetSettings.OutputFolderName}.manifest";
+					string destPath = $"{packageOutputDirectory}/{YooAssetSettings.OutputFolderName}.manifest";
+					EditorTools.CopyFile(sourcePath, destPath, true);
+				}
+			}
+			else
+			{
+				throw new System.NotImplementedException();
+			}
+
+			// 拷贝所有补丁文件
+			int progressValue = 0;
+			int fileTotalCount = buildMapContext.Collection.Count;
+			foreach (var bundleInfo in buildMapContext.Collection)
+			{
+				EditorTools.CopyFile(bundleInfo.PackageSourceFilePath, bundleInfo.PackageDestFilePath, true);
+				EditorTools.DisplayProgressBar("拷贝补丁文件", ++progressValue, fileTotalCount);
+			}
+			EditorTools.ClearProgressBar();
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskCreatePackage.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d110092f543e3fe40a4d3882e1a718e8
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 229 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskCreateReport.cs

@@ -0,0 +1,229 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using UnityEditor;
+
+namespace YooAsset.Editor
+{
+	[TaskAttribute("创建构建报告文件")]
+	public class TaskCreateReport : IBuildTask
+	{
+		void IBuildTask.Run(BuildContext context)
+		{
+			var buildParameters = context.GetContextObject<BuildParametersContext>();
+			var buildMapContext = context.GetContextObject<BuildMapContext>();
+			var manifestContext = context.GetContextObject<ManifestContext>();
+
+			var buildMode = buildParameters.Parameters.BuildMode;
+			if (buildMode != EBuildMode.SimulateBuild)
+			{
+				CreateReportFile(buildParameters, buildMapContext, manifestContext);
+			}
+		}
+
+		private void CreateReportFile(BuildParametersContext buildParametersContext, BuildMapContext buildMapContext, ManifestContext manifestContext)
+		{
+			var buildParameters = buildParametersContext.Parameters;
+
+			string packageOutputDirectory = buildParametersContext.GetPackageOutputDirectory();
+			PackageManifest manifest = manifestContext.Manifest;
+			BuildReport buildReport = new BuildReport();
+
+			// 概述信息
+			{
+#if UNITY_2019_4_OR_NEWER
+				UnityEditor.PackageManager.PackageInfo packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssembly(typeof(BuildReport).Assembly);
+				if (packageInfo != null)
+					buildReport.Summary.YooVersion = packageInfo.version;
+#endif
+				buildReport.Summary.UnityVersion = UnityEngine.Application.unityVersion;
+				buildReport.Summary.BuildDate = DateTime.Now.ToString();
+				buildReport.Summary.BuildSeconds = BuildRunner.TotalSeconds;
+				buildReport.Summary.BuildTarget = buildParameters.BuildTarget;
+				buildReport.Summary.BuildPipeline = buildParameters.BuildPipeline;
+				buildReport.Summary.BuildMode = buildParameters.BuildMode;
+				buildReport.Summary.BuildPackageName = buildParameters.PackageName;
+				buildReport.Summary.BuildPackageVersion = buildParameters.PackageVersion;
+				buildReport.Summary.EnableAddressable = buildMapContext.Command.EnableAddressable;
+				buildReport.Summary.LocationToLower = buildMapContext.Command.LocationToLower;
+				buildReport.Summary.IncludeAssetGUID = buildMapContext.Command.IncludeAssetGUID;
+				buildReport.Summary.UniqueBundleName = buildMapContext.Command.UniqueBundleName;
+				buildReport.Summary.SharedPackRuleClassName = buildParameters.SharedPackRule == null ?
+					"null" : buildParameters.SharedPackRule.GetType().FullName;
+				buildReport.Summary.EncryptionServicesClassName = buildParameters.EncryptionServices == null ?
+					"null" : buildParameters.EncryptionServices.GetType().FullName;
+
+				// 构建参数
+				buildReport.Summary.OutputNameStyle = buildParameters.OutputNameStyle;
+				buildReport.Summary.CompressOption = buildParameters.CompressOption;
+				buildReport.Summary.DisableWriteTypeTree = buildParameters.DisableWriteTypeTree;
+				buildReport.Summary.IgnoreTypeTreeChanges = buildParameters.IgnoreTypeTreeChanges;
+
+				// 构建结果
+				buildReport.Summary.AssetFileTotalCount = buildMapContext.AssetFileCount;
+				buildReport.Summary.MainAssetTotalCount = GetMainAssetCount(manifest);
+				buildReport.Summary.AllBundleTotalCount = GetAllBundleCount(manifest);
+				buildReport.Summary.AllBundleTotalSize = GetAllBundleSize(manifest);
+				buildReport.Summary.EncryptedBundleTotalCount = GetEncryptedBundleCount(manifest);
+				buildReport.Summary.EncryptedBundleTotalSize = GetEncryptedBundleSize(manifest);
+				buildReport.Summary.RawBundleTotalCount = GetRawBundleCount(manifest);
+				buildReport.Summary.RawBundleTotalSize = GetRawBundleSize(manifest);
+			}
+
+			// 资源对象列表
+			buildReport.AssetInfos = new List<ReportAssetInfo>(manifest.AssetList.Count);
+			foreach (var packageAsset in manifest.AssetList)
+			{
+				var mainBundle = manifest.BundleList[packageAsset.BundleID];
+				ReportAssetInfo reportAssetInfo = new ReportAssetInfo();
+				reportAssetInfo.Address = packageAsset.Address;
+				reportAssetInfo.AssetPath = packageAsset.AssetPath;
+				reportAssetInfo.AssetTags = packageAsset.AssetTags;
+				reportAssetInfo.AssetGUID = AssetDatabase.AssetPathToGUID(packageAsset.AssetPath);
+				reportAssetInfo.MainBundleName = mainBundle.BundleName;
+				reportAssetInfo.MainBundleSize = mainBundle.FileSize;
+				reportAssetInfo.DependBundles = GetDependBundles(manifest, packageAsset);
+				reportAssetInfo.DependAssets = GetDependAssets(buildMapContext, mainBundle.BundleName, packageAsset.AssetPath);
+				buildReport.AssetInfos.Add(reportAssetInfo);
+			}
+
+			// 资源包列表
+			buildReport.BundleInfos = new List<ReportBundleInfo>(manifest.BundleList.Count);
+			foreach (var packageBundle in manifest.BundleList)
+			{
+				ReportBundleInfo reportBundleInfo = new ReportBundleInfo();
+				reportBundleInfo.BundleName = packageBundle.BundleName;
+				reportBundleInfo.FileName = packageBundle.FileName;
+				reportBundleInfo.FileHash = packageBundle.FileHash;
+				reportBundleInfo.FileCRC = packageBundle.FileCRC;
+				reportBundleInfo.FileSize = packageBundle.FileSize;
+				reportBundleInfo.IsRawFile = packageBundle.IsRawFile;
+				reportBundleInfo.LoadMethod = (EBundleLoadMethod)packageBundle.LoadMethod;
+				reportBundleInfo.Tags = packageBundle.Tags;
+				reportBundleInfo.ReferenceIDs = packageBundle.ReferenceIDs;
+				reportBundleInfo.AllBuiltinAssets = GetAllBuiltinAssets(buildMapContext, packageBundle.BundleName);
+				buildReport.BundleInfos.Add(reportBundleInfo);
+			}
+
+			// 冗余资源列表
+			buildReport.RedundancyInfos = new List<ReportRedundancyInfo>(buildMapContext.RedundancyInfos);
+
+			// 序列化文件
+			string fileName = YooAssetSettingsData.GetReportFileName(buildParameters.PackageName, buildParameters.PackageVersion);
+			string filePath = $"{packageOutputDirectory}/{fileName}";
+			BuildReport.Serialize(filePath, buildReport);
+			BuildLogger.Log($"资源构建报告文件创建完成:{filePath}");
+		}
+
+		/// <summary>
+		/// 获取资源对象依赖的所有资源包
+		/// </summary>
+		private List<string> GetDependBundles(PackageManifest manifest, PackageAsset packageAsset)
+		{
+			List<string> dependBundles = new List<string>(packageAsset.DependIDs.Length);
+			foreach (int index in packageAsset.DependIDs)
+			{
+				string dependBundleName = manifest.BundleList[index].BundleName;
+				dependBundles.Add(dependBundleName);
+			}
+			return dependBundles;
+		}
+
+		/// <summary>
+		/// 获取资源对象依赖的其它所有资源
+		/// </summary>
+		private List<string> GetDependAssets(BuildMapContext buildMapContext, string bundleName, string assetPath)
+		{
+			List<string> result = new List<string>();
+			var bundleInfo = buildMapContext.GetBundleInfo(bundleName);
+			{
+				BuildAssetInfo findAssetInfo = null;
+				foreach (var assetInfo in bundleInfo.AllMainAssets)
+				{
+					if (assetInfo.AssetPath == assetPath)
+					{
+						findAssetInfo = assetInfo;
+						break;
+					}
+				}
+				if (findAssetInfo == null)
+				{
+					throw new Exception($"Not found asset {assetPath} in bunlde {bundleName}");
+				}
+				foreach (var dependAssetInfo in findAssetInfo.AllDependAssetInfos)
+				{
+					result.Add(dependAssetInfo.AssetPath);
+				}
+			}
+			return result;
+		}
+
+		/// <summary>
+		/// 获取该资源包内的所有资源
+		/// </summary>
+		private List<string> GetAllBuiltinAssets(BuildMapContext buildMapContext, string bundleName)
+		{
+			var bundleInfo = buildMapContext.GetBundleInfo(bundleName);
+			return bundleInfo.GetAllBuiltinAssetPaths();
+		}
+
+		private int GetMainAssetCount(PackageManifest manifest)
+		{
+			return manifest.AssetList.Count;
+		}
+		private int GetAllBundleCount(PackageManifest manifest)
+		{
+			return manifest.BundleList.Count;
+		}
+		private long GetAllBundleSize(PackageManifest manifest)
+		{
+			long fileBytes = 0;
+			foreach (var packageBundle in manifest.BundleList)
+			{
+				fileBytes += packageBundle.FileSize;
+			}
+			return fileBytes;
+		}
+		private int GetEncryptedBundleCount(PackageManifest manifest)
+		{
+			int fileCount = 0;
+			foreach (var packageBundle in manifest.BundleList)
+			{
+				if (packageBundle.LoadMethod != (byte)EBundleLoadMethod.Normal)
+					fileCount++;
+			}
+			return fileCount;
+		}
+		private long GetEncryptedBundleSize(PackageManifest manifest)
+		{
+			long fileBytes = 0;
+			foreach (var packageBundle in manifest.BundleList)
+			{
+				if (packageBundle.LoadMethod != (byte)EBundleLoadMethod.Normal)
+					fileBytes += packageBundle.FileSize;
+			}
+			return fileBytes;
+		}
+		private int GetRawBundleCount(PackageManifest manifest)
+		{
+			int fileCount = 0;
+			foreach (var packageBundle in manifest.BundleList)
+			{
+				if (packageBundle.IsRawFile)
+					fileCount++;
+			}
+			return fileCount;
+		}
+		private long GetRawBundleSize(PackageManifest manifest)
+		{
+			long fileBytes = 0;
+			foreach (var packageBundle in manifest.BundleList)
+			{
+				if (packageBundle.IsRawFile)
+					fileBytes += packageBundle.FileSize;
+			}
+			return fileBytes;
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskCreateReport.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: bd12814185b4c7044b0afd59f9c1c948
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 67 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskEncryption.cs

@@ -0,0 +1,67 @@
+using System;
+using System.Linq;
+using System.IO;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace YooAsset.Editor
+{
+	[TaskAttribute("资源包加密")]
+	public class TaskEncryption : IBuildTask
+	{
+		void IBuildTask.Run(BuildContext context)
+		{
+			var buildParameters = context.GetContextObject<BuildParametersContext>();
+			var buildMapContext = context.GetContextObject<BuildMapContext>();
+
+			var buildMode = buildParameters.Parameters.BuildMode;
+			if (buildMode == EBuildMode.ForceRebuild || buildMode == EBuildMode.IncrementalBuild)
+			{
+				EncryptingBundleFiles(buildParameters, buildMapContext);
+			}
+		}
+
+		/// <summary>
+		/// 加密文件
+		/// </summary>
+		private void EncryptingBundleFiles(BuildParametersContext buildParametersContext, BuildMapContext buildMapContext)
+		{
+			var encryptionServices = buildParametersContext.Parameters.EncryptionServices;
+			if (encryptionServices == null)
+				return;
+
+			if (encryptionServices.GetType() == typeof(EncryptionNone))
+				return;
+
+			int progressValue = 0;
+			string pipelineOutputDirectory = buildParametersContext.GetPipelineOutputDirectory();
+			foreach (var bundleInfo in buildMapContext.Collection)
+			{
+				EncryptFileInfo fileInfo = new EncryptFileInfo();
+				fileInfo.BundleName = bundleInfo.BundleName;
+				fileInfo.FilePath = $"{pipelineOutputDirectory}/{bundleInfo.BundleName}";
+
+				var encryptResult = encryptionServices.Encrypt(fileInfo);		
+				if (encryptResult.LoadMethod != EBundleLoadMethod.Normal)
+				{
+					// 注意:原生文件不支持加密
+					if (bundleInfo.IsRawFile)
+					{
+						BuildLogger.Warning($"Encryption not support raw file : {bundleInfo.BundleName}");
+						continue;
+					}
+
+					string filePath = $"{pipelineOutputDirectory}/{bundleInfo.BundleName}.encrypt";
+					FileUtility.WriteAllBytes(filePath, encryptResult.EncryptedData);
+					bundleInfo.EncryptedFilePath = filePath;
+					bundleInfo.LoadMethod = encryptResult.LoadMethod;
+					BuildLogger.Log($"Bundle文件加密完成:{filePath}");
+				}
+
+				// 进度条
+				EditorTools.DisplayProgressBar("加密资源包", ++progressValue, buildMapContext.Collection.Count);
+			}
+			EditorTools.ClearProgressBar();
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskEncryption.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c78d471226b9c8d429a2d962370f480b
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 229 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskGetBuildMap.cs

@@ -0,0 +1,229 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEditor;
+
+namespace YooAsset.Editor
+{
+	[TaskAttribute("获取资源构建内容")]
+	public class TaskGetBuildMap : IBuildTask
+	{
+		void IBuildTask.Run(BuildContext context)
+		{
+			var buildParametersContext = context.GetContextObject<BuildParametersContext>();
+			var buildMapContext = CreateBuildMap(buildParametersContext.Parameters);
+			context.SetContextObject(buildMapContext);
+			BuildLogger.Log("构建内容准备完毕!");
+
+			// 检测构建结果
+			CheckBuildMapContent(buildMapContext);
+		}
+
+		/// <summary>
+		/// 资源构建上下文
+		/// </summary>
+		public BuildMapContext CreateBuildMap(BuildParameters buildParameters)
+		{
+			var buildMode = buildParameters.BuildMode;
+			var packageName = buildParameters.PackageName;
+			var sharedPackRule = buildParameters.SharedPackRule;
+
+			Dictionary<string, BuildAssetInfo> allBuildAssetInfoDic = new Dictionary<string, BuildAssetInfo>(1000);
+
+			// 1. 检测配置合法性
+			AssetBundleCollectorSettingData.Setting.CheckPackageConfigError(packageName);
+
+			// 2. 获取所有收集器收集的资源
+			var collectResult = AssetBundleCollectorSettingData.Setting.GetPackageAssets(buildMode, packageName);
+			List<CollectAssetInfo> allCollectAssetInfos = collectResult.CollectAssets;
+
+			// 3. 剔除未被引用的依赖项资源
+			RemoveZeroReferenceAssets(allCollectAssetInfos);
+
+			// 4. 录入所有收集器收集的资源
+			foreach (var collectAssetInfo in allCollectAssetInfos)
+			{
+				if (allBuildAssetInfoDic.ContainsKey(collectAssetInfo.AssetPath) == false)
+				{
+					var buildAssetInfo = new BuildAssetInfo(collectAssetInfo.CollectorType, collectAssetInfo.BundleName,
+						collectAssetInfo.Address, collectAssetInfo.AssetPath, collectAssetInfo.IsRawAsset);
+					buildAssetInfo.AddAssetTags(collectAssetInfo.AssetTags);
+					buildAssetInfo.AddBundleTags(collectAssetInfo.AssetTags);
+					allBuildAssetInfoDic.Add(collectAssetInfo.AssetPath, buildAssetInfo);
+				}
+				else
+				{
+					throw new Exception($"Should never get here !");
+				}
+			}
+
+			// 5. 录入所有收集资源的依赖资源
+			foreach (var collectAssetInfo in allCollectAssetInfos)
+			{
+				string collectAssetBundleName = collectAssetInfo.BundleName;
+				foreach (var dependAssetPath in collectAssetInfo.DependAssets)
+				{
+					if (allBuildAssetInfoDic.ContainsKey(dependAssetPath))
+					{
+						allBuildAssetInfoDic[dependAssetPath].AddBundleTags(collectAssetInfo.AssetTags);
+						allBuildAssetInfoDic[dependAssetPath].AddReferenceBundleName(collectAssetBundleName);
+					}
+					else
+					{
+						var buildAssetInfo = new BuildAssetInfo(dependAssetPath);
+						buildAssetInfo.AddBundleTags(collectAssetInfo.AssetTags);
+						buildAssetInfo.AddReferenceBundleName(collectAssetBundleName);
+						allBuildAssetInfoDic.Add(dependAssetPath, buildAssetInfo);
+					}
+				}
+			}
+
+			// 6. 填充所有收集资源的依赖列表
+			foreach (var collectAssetInfo in allCollectAssetInfos)
+			{
+				var dependAssetInfos = new List<BuildAssetInfo>(collectAssetInfo.DependAssets.Count);
+				foreach (var dependAssetPath in collectAssetInfo.DependAssets)
+				{
+					if (allBuildAssetInfoDic.TryGetValue(dependAssetPath, out BuildAssetInfo value))
+						dependAssetInfos.Add(value);
+					else
+						throw new Exception("Should never get here !");
+				}
+				allBuildAssetInfoDic[collectAssetInfo.AssetPath].SetAllDependAssetInfos(dependAssetInfos);
+			}
+
+			// 7. 记录关键信息
+			BuildMapContext context = new BuildMapContext();
+			context.AssetFileCount = allBuildAssetInfoDic.Count;
+			context.Command = collectResult.Command;
+
+			// 8. 计算共享资源的包名		
+			var command = collectResult.Command;
+			foreach (var buildAssetInfo in allBuildAssetInfoDic.Values)
+			{
+				buildAssetInfo.CalculateShareBundleName(sharedPackRule, command.UniqueBundleName, command.PackageName, command.ShadersBundleName);
+			}
+
+			// 9. 记录冗余资源
+			foreach (var buildAssetInfo in allBuildAssetInfoDic.Values)
+			{
+				if (buildAssetInfo.IsRedundancyAsset())
+				{
+					var redundancyInfo = new ReportRedundancyInfo();
+					redundancyInfo.AssetPath = buildAssetInfo.AssetPath;
+					redundancyInfo.AssetType = AssetDatabase.GetMainAssetTypeAtPath(buildAssetInfo.AssetPath).Name;
+					redundancyInfo.AssetGUID = AssetDatabase.AssetPathToGUID(buildAssetInfo.AssetPath);
+					redundancyInfo.FileSize = FileUtility.GetFileSize(buildAssetInfo.AssetPath);
+					redundancyInfo.Number = buildAssetInfo.GetReferenceBundleCount();
+					context.RedundancyInfos.Add(redundancyInfo);
+				}
+			}
+
+			// 10. 移除不参与构建的资源
+			List<BuildAssetInfo> removeBuildList = new List<BuildAssetInfo>();
+			foreach (var buildAssetInfo in allBuildAssetInfoDic.Values)
+			{
+				if (buildAssetInfo.HasBundleName() == false)
+					removeBuildList.Add(buildAssetInfo);
+			}
+			foreach (var removeValue in removeBuildList)
+			{
+				allBuildAssetInfoDic.Remove(removeValue.AssetPath);
+			}
+
+			// 11. 构建资源列表
+			var allPackAssets = allBuildAssetInfoDic.Values.ToList();
+			if (allPackAssets.Count == 0)
+				throw new Exception("构建的资源列表不能为空");
+			foreach (var assetInfo in allPackAssets)
+			{
+				context.PackAsset(assetInfo);
+			}
+
+			return context;
+		}
+		private void RemoveZeroReferenceAssets(List<CollectAssetInfo> allCollectAssetInfos)
+		{
+			// 1. 检测是否任何存在依赖资源
+			bool hasAnyDependCollector = false;
+			foreach (var collectAssetInfo in allCollectAssetInfos)
+			{
+				var collectorType = collectAssetInfo.CollectorType;
+				if (collectorType == ECollectorType.DependAssetCollector)
+				{
+					hasAnyDependCollector = true;
+					break;
+				}
+			}
+			if (hasAnyDependCollector == false)
+				return;
+
+			// 2. 获取所有主资源的依赖资源集合
+			HashSet<string> allDependAsset = new HashSet<string>();
+			foreach (var collectAssetInfo in allCollectAssetInfos)
+			{
+				var collectorType = collectAssetInfo.CollectorType;
+				if (collectorType == ECollectorType.MainAssetCollector || collectorType == ECollectorType.StaticAssetCollector)
+				{
+					foreach (var dependAsset in collectAssetInfo.DependAssets)
+					{
+						if (allDependAsset.Contains(dependAsset) == false)
+							allDependAsset.Add(dependAsset);
+					}
+				}
+			}
+
+			// 3. 找出所有零引用的依赖资源集合
+			List<CollectAssetInfo> removeList = new List<CollectAssetInfo>();
+			foreach (var collectAssetInfo in allCollectAssetInfos)
+			{
+				var collectorType = collectAssetInfo.CollectorType;
+				if (collectorType == ECollectorType.DependAssetCollector)
+				{
+					if (allDependAsset.Contains(collectAssetInfo.AssetPath) == false)
+						removeList.Add(collectAssetInfo);
+				}
+			}
+
+			// 4. 移除所有零引用的依赖资源
+			foreach (var removeValue in removeList)
+			{
+				BuildLogger.Log($"发现未被依赖的资源并自动移除 : {removeValue.AssetPath}");
+				allCollectAssetInfos.Remove(removeValue);
+			}
+		}
+
+		/// <summary>
+		/// 检测构建结果
+		/// </summary>
+		private void CheckBuildMapContent(BuildMapContext buildMapContext)
+		{
+			foreach (var bundleInfo in buildMapContext.Collection)
+			{
+				// 注意:原生文件资源包只能包含一个原生文件
+				bool isRawFile = bundleInfo.IsRawFile;
+				if (isRawFile)
+				{
+					if (bundleInfo.AllMainAssets.Count != 1)
+						throw new Exception($"The bundle does not support multiple raw asset : {bundleInfo.BundleName}");
+					continue;
+				}
+
+				// 注意:原生文件不能被其它资源文件依赖
+				foreach (var assetInfo in bundleInfo.AllMainAssets)
+				{
+					if (assetInfo.AllDependAssetInfos != null)
+					{
+						foreach (var dependAssetInfo in assetInfo.AllDependAssetInfos)
+						{
+							if (dependAssetInfo.IsRawAsset)
+								throw new Exception($"{assetInfo.AssetPath} can not depend raw asset : {dependAssetInfo.AssetPath}");
+						}
+					}
+				}
+			}
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskGetBuildMap.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: baee18c5c7b76584b90413bf20fdae9a
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 106 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskPrepare.cs

@@ -0,0 +1,106 @@
+using System;
+using System.IO;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEditor;
+
+namespace YooAsset.Editor
+{
+	[TaskAttribute("资源构建准备工作")]
+	public class TaskPrepare : IBuildTask
+	{
+		void IBuildTask.Run(BuildContext context)
+		{
+			var buildParametersContext = context.GetContextObject<BuildParametersContext>();
+			var buildParameters = buildParametersContext.Parameters;
+
+			// 检测构建参数合法性
+			if (buildParameters.BuildTarget == BuildTarget.NoTarget)
+				throw new Exception("请选择目标平台!");
+			if (string.IsNullOrEmpty(buildParameters.PackageName))
+				throw new Exception("包裹名称不能为空!");
+			if (string.IsNullOrEmpty(buildParameters.PackageVersion))
+				throw new Exception("包裹版本不能为空!");
+			if (string.IsNullOrEmpty(buildParameters.BuildOutputRoot))
+				throw new Exception("构建输出的根目录为空!");
+			if (string.IsNullOrEmpty(buildParameters.StreamingAssetsRoot))
+				throw new Exception("内置资源根目录为空!");
+
+			if (buildParameters.BuildPipeline == EBuildPipeline.ScriptableBuildPipeline)
+			{
+				if (buildParameters.SBPParameters == null)
+					throw new Exception($"{nameof(BuildParameters.SBPParameters)} is null !");
+
+				if (buildParameters.BuildMode == EBuildMode.DryRunBuild)
+					throw new Exception($"{nameof(EBuildPipeline.ScriptableBuildPipeline)} not support {nameof(EBuildMode.DryRunBuild)} build mode !");
+
+				if (buildParameters.BuildMode == EBuildMode.ForceRebuild)
+					throw new Exception($"{nameof(EBuildPipeline.ScriptableBuildPipeline)} not support {nameof(EBuildMode.ForceRebuild)} build mode !");
+			}
+
+			if (buildParameters.BuildMode != EBuildMode.SimulateBuild)
+			{
+#if UNITY_2021_3_OR_NEWER
+				if (buildParameters.BuildPipeline == EBuildPipeline.BuiltinBuildPipeline)
+				{
+					BuildLogger.Warning("推荐使用可编程构建管线(SBP)!");
+				}
+#endif
+
+				// 检测当前是否正在构建资源包
+				if (BuildPipeline.isBuildingPlayer)
+					throw new Exception("当前正在构建资源包,请结束后再试");
+
+				// 检测是否有未保存场景
+				if (EditorTools.HasDirtyScenes())
+					throw new Exception("检测到未保存的场景文件");
+
+				// 检测首包资源标签
+				if (buildParameters.CopyBuildinFileOption == ECopyBuildinFileOption.ClearAndCopyByTags
+					|| buildParameters.CopyBuildinFileOption == ECopyBuildinFileOption.OnlyCopyByTags)
+				{
+					if (string.IsNullOrEmpty(buildParameters.CopyBuildinFileTags))
+						throw new Exception("首包资源标签不能为空!");
+				}
+
+				// 检测共享资源打包规则
+				if (buildParameters.SharedPackRule == null)
+					throw new Exception("共享资源打包规则不能为空!");
+
+#if UNITY_WEBGL
+				if (buildParameters.EncryptionServices != null)
+				{
+					if (buildParameters.EncryptionServices.GetType() != typeof(EncryptionNone))
+					{
+						throw new Exception("WebGL平台不支持加密!");
+					}
+				}
+#endif
+
+				// 检测包裹输出目录是否存在
+				string packageOutputDirectory = buildParametersContext.GetPackageOutputDirectory();
+				if (Directory.Exists(packageOutputDirectory))
+					throw new Exception($"本次构建的补丁目录已经存在:{packageOutputDirectory}");
+
+				// 保存改动的资源
+				AssetDatabase.SaveAssets();
+			}
+
+			if (buildParameters.BuildMode == EBuildMode.ForceRebuild)
+			{
+				string packageRootDirectory = buildParametersContext.GetPackageRootDirectory();
+				if (EditorTools.DeleteDirectory(packageRootDirectory))
+				{
+					BuildLogger.Log($"删除包裹目录:{packageRootDirectory}");
+				}
+			}
+
+			// 如果输出目录不存在
+			string pipelineOutputDirectory = buildParametersContext.GetPipelineOutputDirectory();
+			if (EditorTools.CreateDirectory(pipelineOutputDirectory))
+			{
+				BuildLogger.Log($"创建输出目录:{pipelineOutputDirectory}");
+			}
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskPrepare.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0dee755cccd608d48b6581ec33c99b39
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 154 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskUpdateBundleInfo.cs

@@ -0,0 +1,154 @@
+using System;
+using System.IO;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEditor;
+
+namespace YooAsset.Editor
+{
+	[TaskAttribute("更新资源包信息")]
+	public class TaskUpdateBundleInfo : IBuildTask
+	{
+		void IBuildTask.Run(BuildContext context)
+		{
+			var buildParametersContext = context.GetContextObject<BuildParametersContext>();
+			var buildMapContext = context.GetContextObject<BuildMapContext>();
+			string pipelineOutputDirectory = buildParametersContext.GetPipelineOutputDirectory();
+			string packageOutputDirectory = buildParametersContext.GetPackageOutputDirectory();
+			int outputNameStyle = (int)buildParametersContext.Parameters.OutputNameStyle;
+
+			// 1.检测文件名长度
+			foreach (var bundleInfo in buildMapContext.Collection)
+			{
+				// NOTE:检测文件名长度不要超过260字符。
+				string fileName = bundleInfo.BundleName;
+				if (fileName.Length >= 260)
+					throw new Exception($"The output bundle name is too long {fileName.Length} chars : {fileName}");
+			}
+
+			// 2.更新构建输出的文件路径
+			foreach (var bundleInfo in buildMapContext.Collection)
+			{
+				bundleInfo.BuildOutputFilePath = $"{pipelineOutputDirectory}/{bundleInfo.BundleName}";
+				if (bundleInfo.IsEncryptedFile)
+					bundleInfo.PackageSourceFilePath = bundleInfo.EncryptedFilePath;
+				else
+					bundleInfo.PackageSourceFilePath = bundleInfo.BuildOutputFilePath;
+			}
+
+			// 3.更新文件其它信息
+			foreach (var bundleInfo in buildMapContext.Collection)
+			{
+				bundleInfo.PackageUnityHash = GetUnityHash(bundleInfo, context);
+				bundleInfo.PackageUnityCRC = GetUnityCRC(bundleInfo, context);
+				bundleInfo.PackageFileHash = GetBundleFileHash(bundleInfo.PackageSourceFilePath, buildParametersContext);
+				bundleInfo.PackageFileCRC = GetBundleFileCRC(bundleInfo.PackageSourceFilePath, buildParametersContext);
+				bundleInfo.PackageFileSize = GetBundleFileSize(bundleInfo.PackageSourceFilePath, buildParametersContext);
+			}
+
+			// 4.更新补丁包输出的文件路径
+			foreach (var bundleInfo in buildMapContext.Collection)
+			{
+				string bundleName = bundleInfo.BundleName;
+				string fileHash = bundleInfo.PackageFileHash;
+				string fileExtension = ManifestTools.GetRemoteBundleFileExtension(bundleName);
+				string fileName = ManifestTools.GetRemoteBundleFileName(outputNameStyle, bundleName, fileExtension, fileHash);
+				bundleInfo.PackageDestFilePath = $"{packageOutputDirectory}/{fileName}";
+			}
+		}
+
+		private string GetUnityHash(BuildBundleInfo bundleInfo, BuildContext context)
+		{
+			var buildParametersContext = context.GetContextObject<BuildParametersContext>();
+			var parameters = buildParametersContext.Parameters;
+			var buildMode = parameters.BuildMode;
+			if (buildMode == EBuildMode.DryRunBuild || buildMode == EBuildMode.SimulateBuild)
+				return "00000000000000000000000000000000"; //32位
+
+			if (bundleInfo.IsRawFile)
+			{
+				string filePath = bundleInfo.PackageSourceFilePath;
+				return HashUtility.FileMD5(filePath);
+			}
+
+			if (parameters.BuildPipeline == EBuildPipeline.BuiltinBuildPipeline)
+			{
+				var buildResult = context.GetContextObject<TaskBuilding.BuildResultContext>();
+				var hash = buildResult.UnityManifest.GetAssetBundleHash(bundleInfo.BundleName);
+				if (hash.isValid)
+					return hash.ToString();
+				else
+					throw new Exception($"Not found bundle hash in build result : {bundleInfo.BundleName}");
+			}
+			else if (parameters.BuildPipeline == EBuildPipeline.ScriptableBuildPipeline)
+			{
+				// 注意:当资源包的依赖列表发生变化的时候,ContentHash也会发生变化!
+				var buildResult = context.GetContextObject<TaskBuilding_SBP.BuildResultContext>();
+				if (buildResult.Results.BundleInfos.TryGetValue(bundleInfo.BundleName, out var value))
+					return value.Hash.ToString();
+				else
+					throw new Exception($"Not found bundle hash in build result : {bundleInfo.BundleName}");
+			}
+			else
+			{
+				throw new System.NotImplementedException();
+			}
+		}
+		private uint GetUnityCRC(BuildBundleInfo bundleInfo, BuildContext context)
+		{
+			var buildParametersContext = context.GetContextObject<BuildParametersContext>();
+			var parameters = buildParametersContext.Parameters;
+			var buildMode = parameters.BuildMode;
+			if (buildMode == EBuildMode.DryRunBuild || buildMode == EBuildMode.SimulateBuild)
+				return 0;
+
+			if (bundleInfo.IsRawFile)
+				return 0;
+
+			if (parameters.BuildPipeline == EBuildPipeline.BuiltinBuildPipeline)
+			{
+				string filePath = bundleInfo.BuildOutputFilePath;
+				if (BuildPipeline.GetCRCForAssetBundle(filePath, out uint crc))
+					return crc;
+				else
+					throw new Exception($"Not found bundle crc in build result : {bundleInfo.BundleName}");
+			}
+			else if (parameters.BuildPipeline == EBuildPipeline.ScriptableBuildPipeline)
+			{
+				var buildResult = context.GetContextObject<TaskBuilding_SBP.BuildResultContext>();
+				if (buildResult.Results.BundleInfos.TryGetValue(bundleInfo.BundleName, out var value))
+					return value.Crc;
+				else
+					throw new Exception($"Not found bundle crc in build result : {bundleInfo.BundleName}");
+			}
+			else
+			{
+				throw new System.NotImplementedException();
+			}
+		}
+		private string GetBundleFileHash(string filePath, BuildParametersContext buildParametersContext)
+		{
+			var buildMode = buildParametersContext.Parameters.BuildMode;
+			if (buildMode == EBuildMode.DryRunBuild || buildMode == EBuildMode.SimulateBuild)
+				return "00000000000000000000000000000000"; //32位
+			else
+				return HashUtility.FileMD5(filePath);
+		}
+		private string GetBundleFileCRC(string filePath, BuildParametersContext buildParametersContext)
+		{
+			var buildMode = buildParametersContext.Parameters.BuildMode;
+			if (buildMode == EBuildMode.DryRunBuild || buildMode == EBuildMode.SimulateBuild)
+				return "00000000"; //8位
+			else
+				return HashUtility.FileCRC32(filePath);
+		}
+		private long GetBundleFileSize(string filePath, BuildParametersContext buildParametersContext)
+		{
+			var buildMode = buildParametersContext.Parameters.BuildMode;
+			if (buildMode == EBuildMode.DryRunBuild || buildMode == EBuildMode.SimulateBuild)
+				return 0;
+			else
+				return FileUtility.GetFileSize(filePath);
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskUpdateBundleInfo.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4882f54fbf0bcb04680fb581deae4889
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 137 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskVerifyBuildResult.cs

@@ -0,0 +1,137 @@
+using System;
+using System.Linq;
+using System.IO;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEngine;
+
+namespace YooAsset.Editor
+{
+	[TaskAttribute("验证构建结果")]
+	public class TaskVerifyBuildResult : IBuildTask
+	{
+		void IBuildTask.Run(BuildContext context)
+		{
+			var buildParametersContext = context.GetContextObject<BuildParametersContext>();
+
+			// 模拟构建模式下跳过验证
+			if (buildParametersContext.Parameters.BuildMode == EBuildMode.SimulateBuild)
+				return;
+
+			// 验证构建结果
+			if (buildParametersContext.Parameters.VerifyBuildingResult)
+			{
+				var buildResultContext = context.GetContextObject<TaskBuilding.BuildResultContext>();
+				VerifyingBuildingResult(context, buildResultContext.UnityManifest);
+			}
+		}
+
+		/// <summary>
+		/// 验证构建结果
+		/// </summary>
+		private void VerifyingBuildingResult(BuildContext context, AssetBundleManifest unityManifest)
+		{
+			var buildParametersContext = context.GetContextObject<BuildParametersContext>();
+			var buildMapContext = context.GetContextObject<BuildMapContext>();
+			string[] unityCreateBundles = unityManifest.GetAllAssetBundles();
+
+			// 1. 过滤掉原生Bundle
+			string[] mapBundles = buildMapContext.Collection.Where(t => t.IsRawFile == false).Select(t => t.BundleName).ToArray();
+
+			// 2. 验证Bundle
+			List<string> exceptBundleList1 = unityCreateBundles.Except(mapBundles).ToList();
+			if (exceptBundleList1.Count > 0)
+			{
+				foreach (var exceptBundle in exceptBundleList1)
+				{
+					BuildLogger.Warning($"差异资源包: {exceptBundle}");
+				}
+				throw new System.Exception("存在差异资源包!请查看警告信息!");
+			}
+
+			// 3. 验证Bundle
+			List<string> exceptBundleList2 = mapBundles.Except(unityCreateBundles).ToList();
+			if (exceptBundleList2.Count > 0)
+			{
+				foreach (var exceptBundle in exceptBundleList2)
+				{
+					BuildLogger.Warning($"差异资源包: {exceptBundle}");
+				}
+				throw new System.Exception("存在差异资源包!请查看警告信息!");
+			}
+
+			// 4. 验证Asset
+			/*
+			bool isPass = true;
+			var buildMode = buildParametersContext.Parameters.BuildMode;
+			if (buildMode == EBuildMode.ForceRebuild || buildMode == EBuildMode.IncrementalBuild)
+			{
+				int progressValue = 0;
+				string pipelineOutputDirectory = buildParametersContext.GetPipelineOutputDirectory();
+				foreach (var buildedBundle in buildedBundles)
+				{
+					string filePath = $"{pipelineOutputDirectory}/{buildedBundle}";
+					string[] buildedAssetPaths = GetAssetBundleAllAssets(filePath);
+					string[] mapAssetPaths = buildMapContext.GetBuildinAssetPaths(buildedBundle);
+					if (mapAssetPaths.Length != buildedAssetPaths.Length)
+					{
+						BuildLogger.Warning($"构建的Bundle文件内的资源对象数量和预期不匹配 : {buildedBundle}");
+						var exceptAssetList1 = mapAssetPaths.Except(buildedAssetPaths).ToList();
+						foreach (var excpetAsset in exceptAssetList1)
+						{
+							BuildLogger.Warning($"构建失败的资源对象路径为 : {excpetAsset}");
+						}
+						var exceptAssetList2 = buildedAssetPaths.Except(mapAssetPaths).ToList();
+						foreach (var excpetAsset in exceptAssetList2)
+						{
+							BuildLogger.Warning($"构建失败的资源对象路径为 : {excpetAsset}");
+						}
+						isPass = false;
+						continue;
+					}
+					EditorTools.DisplayProgressBar("验证构建结果", ++progressValue, buildedBundles.Length);
+				}
+				EditorTools.ClearProgressBar();
+
+				if (isPass == false)
+				{
+					throw new Exception("构建结果验证没有通过,请参考警告日志!");
+				}
+			}
+			*/
+
+			BuildLogger.Log("构建结果验证成功!");
+		}
+
+		/// <summary>
+		/// 解析.manifest文件并获取资源列表
+		/// </summary>
+		private string[] GetAssetBundleAllAssets(string filePath)
+		{
+			string manifestFilePath = $"{filePath}.manifest";
+			List<string> assetLines = new List<string>();
+			using (StreamReader reader = File.OpenText(manifestFilePath))
+			{
+				string content;
+				bool findTarget = false;
+				while (null != (content = reader.ReadLine()))
+				{
+					if (content.StartsWith("Dependencies:"))
+						break;
+					if (findTarget == false && content.StartsWith("Assets:"))
+						findTarget = true;
+					if (findTarget)
+					{
+						if (content.StartsWith("- "))
+						{
+							string assetPath = content.TrimStart("- ".ToCharArray());
+							assetLines.Add(assetPath);
+						}
+					}
+				}
+			}
+			return assetLines.ToArray();
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskVerifyBuildResult.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b883ac0c3c25e8143847a9326e2961cf
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 68 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskVerifyBuildResult_SBP.cs

@@ -0,0 +1,68 @@
+using System;
+using System.Linq;
+using System.IO;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEngine;
+using UnityEditor.Build.Pipeline.Interfaces;
+
+namespace YooAsset.Editor
+{
+	[TaskAttribute("验证构建结果")]
+	public class TaskVerifyBuildResult_SBP : IBuildTask
+	{
+		void IBuildTask.Run(BuildContext context)
+		{
+			var buildParametersContext = context.GetContextObject<BuildParametersContext>();
+
+			// 模拟构建模式下跳过验证
+			if (buildParametersContext.Parameters.BuildMode == EBuildMode.SimulateBuild)
+				return;
+
+			// 验证构建结果
+			if (buildParametersContext.Parameters.VerifyBuildingResult)
+			{
+				var buildResultContext = context.GetContextObject<TaskBuilding_SBP.BuildResultContext>();
+				VerifyingBuildingResult(context, buildResultContext.Results);
+			}
+		}
+
+		/// <summary>
+		/// 验证构建结果
+		/// </summary>
+		private void VerifyingBuildingResult(BuildContext context, IBundleBuildResults buildResults)
+		{
+			var buildParameters = context.GetContextObject<BuildParametersContext>();
+			var buildMapContext = context.GetContextObject<BuildMapContext>();
+			List<string> unityCreateBundles = buildResults.BundleInfos.Keys.ToList();
+
+			// 1. 过滤掉原生Bundle
+			List<string> expectBundles = buildMapContext.Collection.Where(t => t.IsRawFile == false).Select(t => t.BundleName).ToList();
+
+			// 2. 验证Bundle
+			List<string> exceptBundleList1 = unityCreateBundles.Except(expectBundles).ToList();
+			if (exceptBundleList1.Count > 0)
+			{
+				foreach (var exceptBundle in exceptBundleList1)
+				{
+					BuildLogger.Warning($"差异资源包: {exceptBundle}");
+				}
+				throw new System.Exception("存在差异资源包!请查看警告信息!");
+			}
+
+			// 3. 验证Bundle
+			List<string> exceptBundleList2 = expectBundles.Except(unityCreateBundles).ToList();
+			if (exceptBundleList2.Count > 0)
+			{
+				foreach (var exceptBundle in exceptBundleList2)
+				{
+					BuildLogger.Warning($"差异资源包: {exceptBundle}");
+				}
+				throw new System.Exception("存在差异资源包!请查看警告信息!");
+			}
+
+			BuildLogger.Log("构建结果验证成功!");
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/BuildTasks/TaskVerifyBuildResult_SBP.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 16aa7c2c37209a043b4f33d7854047c6
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/DefaultEncryption.cs

@@ -0,0 +1,11 @@
+
+namespace YooAsset.Editor
+{
+	public class EncryptionNone : IEncryptionServices
+	{
+		public EncryptResult Encrypt(EncryptFileInfo fileInfo)
+		{
+			throw new System.NotImplementedException();
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/DefaultEncryption.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 46b8b200b841799498896403d9d427c2
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 29 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/EBuildMode.cs

@@ -0,0 +1,29 @@
+
+namespace YooAsset.Editor
+{
+	/// <summary>
+	/// 资源包流水线的构建模式
+	/// </summary>
+	public enum EBuildMode
+	{
+		/// <summary>
+		/// 强制重建模式
+		/// </summary>
+		ForceRebuild,
+
+		/// <summary>
+		/// 增量构建模式
+		/// </summary>
+		IncrementalBuild,
+
+		/// <summary>
+		/// 演练构建模式
+		/// </summary>
+		DryRunBuild,
+
+		/// <summary>
+		/// 模拟构建模式
+		/// </summary>
+		SimulateBuild,
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/EBuildMode.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0b6f2523a865e454d8fa3f48a2852d5a
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 19 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/EBuildPipeline.cs

@@ -0,0 +1,19 @@
+
+namespace YooAsset.Editor
+{
+	/// <summary>
+	/// 构建管线类型
+	/// </summary>
+	public enum EBuildPipeline
+	{
+		/// <summary>
+		/// 传统内置构建管线
+		/// </summary>
+		BuiltinBuildPipeline,
+		
+		/// <summary>
+		/// 可编程构建管线
+		/// </summary>
+		ScriptableBuildPipeline,
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/EBuildPipeline.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e53e56a0f6b01dd4c933249d2bda8d78
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 13 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/ECompressOption.cs

@@ -0,0 +1,13 @@
+
+namespace YooAsset.Editor
+{
+	/// <summary>
+	/// AssetBundle压缩选项
+	/// </summary>
+	public enum ECompressOption
+	{
+		Uncompressed = 0,
+		LZMA,
+		LZ4,
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/ECompressOption.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1e8ac0dfc77bd1b4697db63d52ab4c6e
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 34 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/ECopyBuildinFileOption.cs

@@ -0,0 +1,34 @@
+
+namespace YooAsset.Editor
+{
+	/// <summary>
+	/// 首包资源文件的拷贝方式
+	/// </summary>
+	public enum ECopyBuildinFileOption
+	{
+		/// <summary>
+		/// 不拷贝任何文件
+		/// </summary>
+		None = 0,
+
+		/// <summary>
+		/// 先清空已有文件,然后拷贝所有文件
+		/// </summary>
+		ClearAndCopyAll,
+
+		/// <summary>
+		/// 先清空已有文件,然后按照资源标签拷贝文件
+		/// </summary>
+		ClearAndCopyByTags,
+
+		/// <summary>
+		/// 不清空已有文件,直接拷贝所有文件
+		/// </summary>
+		OnlyCopyAll,
+
+		/// <summary>
+		/// 不清空已有文件,直接按照资源标签拷贝文件
+		/// </summary>
+		OnlyCopyByTags,
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/ECopyBuildinFileOption.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4defd475b635cdf4b87108140d3a0ad1
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 19 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/EOutputNameStyle.cs

@@ -0,0 +1,19 @@
+
+namespace YooAsset.Editor
+{
+	/// <summary>
+	/// 输出文件名称的样式
+	/// </summary>
+	public enum EOutputNameStyle
+	{
+		/// <summary>
+		/// 哈希值名称
+		/// </summary>
+		HashName = 1,
+
+		/// <summary>
+		/// 资源包名称 + 哈希值名称
+		/// </summary>
+		BundleName_HashName = 4,
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleBuilder/EOutputNameStyle.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 84c5eff5dedf53343897e83f6b10eea6
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleCollector.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b19f23ac31124274d942d32bec434450
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 355 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollector.cs

@@ -0,0 +1,355 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEditor;
+using UnityEngine;
+
+namespace YooAsset.Editor
+{
+	[Serializable]
+	public class AssetBundleCollector
+	{
+		/// <summary>
+		/// 收集路径
+		/// 注意:支持文件夹或单个资源文件
+		/// </summary>
+		public string CollectPath = string.Empty;
+
+		/// <summary>
+		/// 收集器的GUID
+		/// </summary>
+		public string CollectorGUID = string.Empty;
+
+		/// <summary>
+		/// 收集器类型
+		/// </summary>
+		public ECollectorType CollectorType = ECollectorType.MainAssetCollector;
+
+		/// <summary>
+		/// 寻址规则类名
+		/// </summary>
+		public string AddressRuleName = nameof(AddressByFileName);
+
+		/// <summary>
+		/// 打包规则类名
+		/// </summary>
+		public string PackRuleName = nameof(PackDirectory);
+
+		/// <summary>
+		/// 过滤规则类名
+		/// </summary>
+		public string FilterRuleName = nameof(CollectAll);
+
+		/// <summary>
+		/// 资源分类标签
+		/// </summary>
+		public string AssetTags = string.Empty;
+
+		/// <summary>
+		/// 用户自定义数据
+		/// </summary>
+		public string UserData = string.Empty;
+
+
+		/// <summary>
+		/// 收集器是否有效
+		/// </summary>
+		public bool IsValid()
+		{
+			if (AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(CollectPath) == null)
+				return false;
+
+			if (CollectorType == ECollectorType.None)
+				return false;
+
+			if (AssetBundleCollectorSettingData.HasAddressRuleName(AddressRuleName) == false)
+				return false;
+
+			if (AssetBundleCollectorSettingData.HasPackRuleName(PackRuleName) == false)
+				return false;
+
+			if (AssetBundleCollectorSettingData.HasFilterRuleName(FilterRuleName) == false)
+				return false;
+
+			return true;
+		}
+
+		/// <summary>
+		/// 检测配置错误
+		/// </summary>
+		public void CheckConfigError()
+		{
+			string assetGUID = AssetDatabase.AssetPathToGUID(CollectPath);
+			if (string.IsNullOrEmpty(assetGUID))
+				throw new Exception($"Invalid collect path : {CollectPath}");
+
+			if (CollectorType == ECollectorType.None)
+				throw new Exception($"{nameof(ECollectorType)}.{ECollectorType.None} is invalid in collector : {CollectPath}");
+
+			if (AssetBundleCollectorSettingData.HasPackRuleName(PackRuleName) == false)
+				throw new Exception($"Invalid {nameof(IPackRule)} class type : {PackRuleName} in collector : {CollectPath}");
+
+			if (AssetBundleCollectorSettingData.HasFilterRuleName(FilterRuleName) == false)
+				throw new Exception($"Invalid {nameof(IFilterRule)} class type : {FilterRuleName} in collector : {CollectPath}");
+
+			if (AssetBundleCollectorSettingData.HasAddressRuleName(AddressRuleName) == false)
+				throw new Exception($"Invalid {nameof(IAddressRule)} class type : {AddressRuleName} in collector : {CollectPath}");
+		}
+
+		/// <summary>
+		/// 修复配置错误
+		/// </summary>
+		public bool FixConfigError()
+		{
+			bool isFixed = false;
+
+			if (string.IsNullOrEmpty(CollectorGUID) == false)
+			{
+				string convertAssetPath = AssetDatabase.GUIDToAssetPath(CollectorGUID);
+				if (string.IsNullOrEmpty(convertAssetPath))
+				{
+					Debug.LogWarning($"Collector GUID {CollectorGUID} is invalid and has been auto removed !");
+					CollectorGUID = string.Empty;
+					isFixed = true;
+				}
+				else
+				{
+					if (CollectPath != convertAssetPath)
+					{
+						CollectPath = convertAssetPath;
+						isFixed = true;
+						Debug.LogWarning($"Fix collect path : {CollectPath} -> {convertAssetPath}");
+					}
+				}
+			}
+
+			/*
+			string convertGUID = AssetDatabase.AssetPathToGUID(CollectPath);
+			if(string.IsNullOrEmpty(convertGUID) == false)
+			{
+				CollectorGUID = convertGUID;
+			}
+			*/
+
+			return isFixed;
+		}
+
+		/// <summary>
+		/// 获取打包收集的资源文件
+		/// </summary>
+		public List<CollectAssetInfo> GetAllCollectAssets(CollectCommand command, AssetBundleCollectorGroup group)
+		{
+			// 注意:模拟构建模式下只收集主资源
+			if (command.BuildMode == EBuildMode.SimulateBuild)
+			{
+				if (CollectorType != ECollectorType.MainAssetCollector)
+					return new List<CollectAssetInfo>();
+			}
+
+			Dictionary<string, CollectAssetInfo> result = new Dictionary<string, CollectAssetInfo>(1000);
+
+			// 检测是否为原生资源打包规则
+			IPackRule packRuleInstance = AssetBundleCollectorSettingData.GetPackRuleInstance(PackRuleName);
+			bool isRawFilePackRule = packRuleInstance.IsRawFilePackRule();
+
+			// 检测原生资源包的收集器类型
+			if (isRawFilePackRule && CollectorType != ECollectorType.MainAssetCollector)
+				throw new Exception($"The raw file pack rule must be set to {nameof(ECollectorType)}.{ECollectorType.MainAssetCollector} : {CollectPath}");
+
+			if (string.IsNullOrEmpty(CollectPath))
+				throw new Exception($"The collect path is null or empty in group : {group.GroupName}");
+
+			// 收集打包资源
+			if (AssetDatabase.IsValidFolder(CollectPath))
+			{
+				string collectDirectory = CollectPath;
+				string[] findAssets = EditorTools.FindAssets(EAssetSearchType.All, collectDirectory);
+				foreach (string assetPath in findAssets)
+				{
+					if (IsValidateAsset(assetPath, isRawFilePackRule) && IsCollectAsset(assetPath))
+					{
+						if (result.ContainsKey(assetPath) == false)
+						{
+							var collectAssetInfo = CreateCollectAssetInfo(command, group, assetPath, isRawFilePackRule);
+							result.Add(assetPath, collectAssetInfo);
+						}
+						else
+						{
+							throw new Exception($"The collecting asset file is existed : {assetPath} in collector : {CollectPath}");
+						}
+					}
+				}
+			}
+			else
+			{
+				string assetPath = CollectPath;
+				if (IsValidateAsset(assetPath, isRawFilePackRule) && IsCollectAsset(assetPath))
+				{
+					var collectAssetInfo = CreateCollectAssetInfo(command, group, assetPath, isRawFilePackRule);
+					result.Add(assetPath, collectAssetInfo);
+				}
+				else
+				{
+					throw new Exception($"The collecting single asset file is invalid : {assetPath} in collector : {CollectPath}");
+				}
+			}
+
+			// 检测可寻址地址是否重复
+			if (command.EnableAddressable)
+			{
+				var addressTemper = new Dictionary<string, string>();
+				foreach (var collectInfoPair in result)
+				{
+					if (collectInfoPair.Value.CollectorType == ECollectorType.MainAssetCollector)
+					{
+						string address = collectInfoPair.Value.Address;
+						string assetPath = collectInfoPair.Value.AssetPath;
+
+						if (address.StartsWith("Assets/") || address.StartsWith("assets/"))
+							throw new Exception($"The address can not set asset path in collector : {CollectPath} \nAssetPath: {assetPath}");
+
+						if (addressTemper.TryGetValue(address, out var existed) == false)
+							addressTemper.Add(address, assetPath);
+						else
+							throw new Exception($"The address is existed : {address} in collector : {CollectPath} \nAssetPath:\n     {existed}\n     {assetPath}");
+					}
+				}
+			}
+
+			// 返回列表
+			return result.Values.ToList();
+		}
+
+		private CollectAssetInfo CreateCollectAssetInfo(CollectCommand command, AssetBundleCollectorGroup group, string assetPath, bool isRawFilePackRule)
+		{
+			string address = GetAddress(command, group, assetPath);
+			string bundleName = GetBundleName(command, group, assetPath);
+			List<string> assetTags = GetAssetTags(group);
+			CollectAssetInfo collectAssetInfo = new CollectAssetInfo(CollectorType, bundleName, address, assetPath, isRawFilePackRule, assetTags);
+
+			// 注意:模拟构建模式下不需要收集依赖资源
+			if (command.BuildMode == EBuildMode.SimulateBuild)
+				collectAssetInfo.DependAssets = new List<string>();
+			else
+				collectAssetInfo.DependAssets = GetAllDependencies(assetPath);
+
+			return collectAssetInfo;
+		}
+		private bool IsValidateAsset(string assetPath, bool isRawFilePackRule)
+		{
+			if (assetPath.StartsWith("Assets/") == false && assetPath.StartsWith("Packages/") == false)
+			{
+				UnityEngine.Debug.LogError($"Invalid asset path : {assetPath}");
+				return false;
+			}
+
+			// 忽略文件夹
+			if (AssetDatabase.IsValidFolder(assetPath))
+				return false;
+
+			// 忽略编辑器下的类型资源
+			Type assetType = AssetDatabase.GetMainAssetTypeAtPath(assetPath);
+			if (assetType == typeof(LightingDataAsset))
+				return false;
+
+			// 检测原生文件是否合规
+			if (isRawFilePackRule)
+			{
+				string extension = EditorTools.RemoveFirstChar(System.IO.Path.GetExtension(assetPath));
+				if (extension == EAssetFileExtension.unity.ToString() || extension == EAssetFileExtension.prefab.ToString() ||
+					extension == EAssetFileExtension.fbx.ToString() || extension == EAssetFileExtension.mat.ToString() ||
+					extension == EAssetFileExtension.controller.ToString() || extension == EAssetFileExtension.anim.ToString() ||
+					extension == EAssetFileExtension.ttf.ToString() || extension == EAssetFileExtension.shader.ToString())
+				{
+					UnityEngine.Debug.LogWarning($"Raw file pack rule can not support file estension : {extension}");
+					return false;
+				}
+
+				// 注意:原生文件只支持无依赖关系的资源
+				/*
+				string[] depends = AssetDatabase.GetDependencies(assetPath, true);
+				if (depends.Length != 1)
+				{
+					UnityEngine.Debug.LogWarning($"Raw file pack rule can not support estension : {extension}");
+					return false;
+				}
+				*/
+			}
+			else
+			{
+				// 忽略Unity无法识别的无效文件
+				// 注意:只对非原生文件收集器处理
+				if (assetType == typeof(UnityEditor.DefaultAsset))
+				{
+					UnityEngine.Debug.LogWarning($"Cannot pack default asset : {assetPath}");
+					return false;
+				}
+			}
+
+			string fileExtension = System.IO.Path.GetExtension(assetPath);
+			if (DefaultFilterRule.IsIgnoreFile(fileExtension))
+				return false;
+
+			return true;
+		}
+		private bool IsCollectAsset(string assetPath)
+		{
+			// 根据规则设置过滤资源文件
+			IFilterRule filterRuleInstance = AssetBundleCollectorSettingData.GetFilterRuleInstance(FilterRuleName);
+			return filterRuleInstance.IsCollectAsset(new FilterRuleData(assetPath));
+		}
+		private string GetAddress(CollectCommand command, AssetBundleCollectorGroup group, string assetPath)
+		{
+			if (command.EnableAddressable == false)
+				return string.Empty;
+
+			if (CollectorType != ECollectorType.MainAssetCollector)
+				return string.Empty;
+
+			IAddressRule addressRuleInstance = AssetBundleCollectorSettingData.GetAddressRuleInstance(AddressRuleName);
+			string adressValue = addressRuleInstance.GetAssetAddress(new AddressRuleData(assetPath, CollectPath, group.GroupName, UserData));
+			return adressValue;
+		}
+		private string GetBundleName(CollectCommand command, AssetBundleCollectorGroup group, string assetPath)
+		{
+			System.Type assetType = AssetDatabase.GetMainAssetTypeAtPath(assetPath);
+			if (assetType == typeof(UnityEngine.Shader) || assetType == typeof(UnityEngine.ShaderVariantCollection))
+			{
+				// 获取着色器打包规则结果
+				PackRuleResult packRuleResult = DefaultPackRule.CreateShadersPackRuleResult();
+				return packRuleResult.GetMainBundleName(command.PackageName, command.UniqueBundleName);
+			}
+			else
+			{
+				// 获取其它资源打包规则结果
+				IPackRule packRuleInstance = AssetBundleCollectorSettingData.GetPackRuleInstance(PackRuleName);
+				PackRuleResult packRuleResult = packRuleInstance.GetPackRuleResult(new PackRuleData(assetPath, CollectPath, group.GroupName, UserData));
+				return packRuleResult.GetMainBundleName(command.PackageName, command.UniqueBundleName);
+			}
+		}
+		private List<string> GetAssetTags(AssetBundleCollectorGroup group)
+		{
+			List<string> tags = EditorTools.StringToStringList(group.AssetTags, ';');
+			List<string> temper = EditorTools.StringToStringList(AssetTags, ';');
+			tags.AddRange(temper);
+			return tags;
+		}
+		private List<string> GetAllDependencies(string mainAssetPath)
+		{
+			string[] depends = AssetDatabase.GetDependencies(mainAssetPath, true);
+			List<string> result = new List<string>(depends.Length);
+			foreach (string assetPath in depends)
+			{
+				// 注意:排除主资源对象
+				if (assetPath == mainAssetPath)
+					continue;
+
+				if (IsValidateAsset(assetPath, false))
+				{
+					result.Add(assetPath);
+				}
+			}
+			return result;
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollector.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f79866c315d53ef4480d6fa4083c09ad
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 388 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorConfig.cs

@@ -0,0 +1,388 @@
+using System;
+using System.IO;
+using System.Xml;
+using System.Text;
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEngine;
+
+namespace YooAsset.Editor
+{
+	public class AssetBundleCollectorConfig
+	{
+		public const string ConfigVersion = "2.4";
+
+		public const string XmlVersion = "Version";
+		public const string XmlCommon = "Common";
+		public const string XmlEnableAddressable = "AutoAddressable";
+		public const string XmlLocationToLower = "LocationToLower";
+		public const string XmlIncludeAssetGUID = "IncludeAssetGUID";
+		public const string XmlUniqueBundleName = "UniqueBundleName";
+		public const string XmlShowPackageView = "ShowPackageView";
+		public const string XmlShowEditorAlias = "ShowEditorAlias";
+
+		public const string XmlPackage = "Package";
+		public const string XmlPackageName = "PackageName";
+		public const string XmlPackageDesc = "PackageDesc";
+
+		public const string XmlGroup = "Group";
+		public const string XmlGroupActiveRule = "GroupActiveRule";
+		public const string XmlGroupName = "GroupName";
+		public const string XmlGroupDesc = "GroupDesc";
+
+		public const string XmlCollector = "Collector";
+		public const string XmlCollectPath = "CollectPath";
+		public const string XmlCollectorGUID = "CollectGUID";
+		public const string XmlCollectorType = "CollectType";
+		public const string XmlAddressRule = "AddressRule";
+		public const string XmlPackRule = "PackRule";
+		public const string XmlFilterRule = "FilterRule";
+		public const string XmlUserData = "UserData";
+		public const string XmlAssetTags = "AssetTags";
+
+		/// <summary>
+		/// 导入XML配置表
+		/// </summary>
+		public static void ImportXmlConfig(string filePath)
+		{
+			if (File.Exists(filePath) == false)
+				throw new FileNotFoundException(filePath);
+
+			if (Path.GetExtension(filePath) != ".xml")
+				throw new Exception($"Only support xml : {filePath}");
+
+			// 加载配置文件
+			XmlDocument xmlDoc = new XmlDocument();
+			xmlDoc.Load(filePath);
+			XmlElement root = xmlDoc.DocumentElement;
+
+			// 读取配置版本
+			string configVersion = root.GetAttribute(XmlVersion);
+			if (configVersion != ConfigVersion)
+			{
+				if (UpdateXmlConfig(xmlDoc) == false)
+					throw new Exception($"The config version update failed : {configVersion} -> {ConfigVersion}");
+				else
+					Debug.Log($"The config version update succeed : {configVersion} -> {ConfigVersion}");
+			}
+
+			// 读取公共配置
+			bool enableAddressable = false;
+			bool locationToLower = false;
+			bool includeAssetGUID = false;
+			bool uniqueBundleName = false;
+			bool showPackageView = false;
+			bool showEditorAlias = false;
+			var commonNodeList = root.GetElementsByTagName(XmlCommon);
+			if (commonNodeList.Count > 0)
+			{
+				XmlElement commonElement = commonNodeList[0] as XmlElement;
+				if (commonElement.HasAttribute(XmlEnableAddressable))
+					enableAddressable = commonElement.GetAttribute(XmlEnableAddressable) == "True" ? true : false;
+				if (commonElement.HasAttribute(XmlLocationToLower))
+					locationToLower = commonElement.GetAttribute(XmlLocationToLower) == "True" ? true : false;
+				if (commonElement.HasAttribute(XmlIncludeAssetGUID))
+					includeAssetGUID = commonElement.GetAttribute(XmlIncludeAssetGUID) == "True" ? true : false;
+				if (commonElement.HasAttribute(XmlUniqueBundleName))
+					uniqueBundleName = commonElement.GetAttribute(XmlUniqueBundleName) == "True" ? true : false;
+				if (commonElement.HasAttribute(XmlShowPackageView))
+					showPackageView = commonElement.GetAttribute(XmlShowPackageView) == "True" ? true : false;
+				if (commonElement.HasAttribute(XmlShowEditorAlias))
+					showEditorAlias = commonElement.GetAttribute(XmlShowEditorAlias) == "True" ? true : false;
+			}
+
+			// 读取包裹配置
+			List<AssetBundleCollectorPackage> packages = new List<AssetBundleCollectorPackage>();
+			var packageNodeList = root.GetElementsByTagName(XmlPackage);
+			foreach (var packageNode in packageNodeList)
+			{
+				XmlElement packageElement = packageNode as XmlElement;
+				if (packageElement.HasAttribute(XmlPackageName) == false)
+					throw new Exception($"Not found attribute {XmlPackageName} in {XmlPackage}");
+				if (packageElement.HasAttribute(XmlPackageDesc) == false)
+					throw new Exception($"Not found attribute {XmlPackageDesc} in {XmlPackage}");
+
+				AssetBundleCollectorPackage package = new AssetBundleCollectorPackage();
+				package.PackageName = packageElement.GetAttribute(XmlPackageName);
+				package.PackageDesc = packageElement.GetAttribute(XmlPackageDesc);
+				packages.Add(package);
+
+				// 读取分组配置
+				var groupNodeList = packageElement.GetElementsByTagName(XmlGroup);
+				foreach (var groupNode in groupNodeList)
+				{
+					XmlElement groupElement = groupNode as XmlElement;
+					if (groupElement.HasAttribute(XmlGroupActiveRule) == false)
+						throw new Exception($"Not found attribute {XmlGroupActiveRule} in {XmlGroup}");
+					if (groupElement.HasAttribute(XmlGroupName) == false)
+						throw new Exception($"Not found attribute {XmlGroupName} in {XmlGroup}");
+					if (groupElement.HasAttribute(XmlGroupDesc) == false)
+						throw new Exception($"Not found attribute {XmlGroupDesc} in {XmlGroup}");
+					if (groupElement.HasAttribute(XmlAssetTags) == false)
+						throw new Exception($"Not found attribute {XmlAssetTags} in {XmlGroup}");
+
+					AssetBundleCollectorGroup group = new AssetBundleCollectorGroup();
+					group.ActiveRuleName = groupElement.GetAttribute(XmlGroupActiveRule);
+					group.GroupName = groupElement.GetAttribute(XmlGroupName);
+					group.GroupDesc = groupElement.GetAttribute(XmlGroupDesc);
+					group.AssetTags = groupElement.GetAttribute(XmlAssetTags);
+					package.Groups.Add(group);
+
+					// 读取收集器配置
+					var collectorNodeList = groupElement.GetElementsByTagName(XmlCollector);
+					foreach (var collectorNode in collectorNodeList)
+					{
+						XmlElement collectorElement = collectorNode as XmlElement;
+						if (collectorElement.HasAttribute(XmlCollectPath) == false)
+							throw new Exception($"Not found attribute {XmlCollectPath} in {XmlCollector}");
+						if (collectorElement.HasAttribute(XmlCollectorGUID) == false)
+							throw new Exception($"Not found attribute {XmlCollectorGUID} in {XmlCollector}");
+						if (collectorElement.HasAttribute(XmlCollectorType) == false)
+							throw new Exception($"Not found attribute {XmlCollectorType} in {XmlCollector}");
+						if (collectorElement.HasAttribute(XmlAddressRule) == false)
+							throw new Exception($"Not found attribute {XmlAddressRule} in {XmlCollector}");
+						if (collectorElement.HasAttribute(XmlPackRule) == false)
+							throw new Exception($"Not found attribute {XmlPackRule} in {XmlCollector}");
+						if (collectorElement.HasAttribute(XmlFilterRule) == false)
+							throw new Exception($"Not found attribute {XmlFilterRule} in {XmlCollector}");
+						if (collectorElement.HasAttribute(XmlUserData) == false)
+							throw new Exception($"Not found attribute {XmlUserData} in {XmlCollector}");
+						if (collectorElement.HasAttribute(XmlAssetTags) == false)
+							throw new Exception($"Not found attribute {XmlAssetTags} in {XmlCollector}");
+
+						AssetBundleCollector collector = new AssetBundleCollector();
+						collector.CollectPath = collectorElement.GetAttribute(XmlCollectPath);
+						collector.CollectorGUID = collectorElement.GetAttribute(XmlCollectorGUID);
+						collector.CollectorType = EditorTools.NameToEnum<ECollectorType>(collectorElement.GetAttribute(XmlCollectorType));
+						collector.AddressRuleName = collectorElement.GetAttribute(XmlAddressRule);
+						collector.PackRuleName = collectorElement.GetAttribute(XmlPackRule);
+						collector.FilterRuleName = collectorElement.GetAttribute(XmlFilterRule);
+						collector.UserData = collectorElement.GetAttribute(XmlUserData);
+						collector.AssetTags = collectorElement.GetAttribute(XmlAssetTags);
+						group.Collectors.Add(collector);
+					}
+				}
+			}
+
+			// 检测配置错误
+			foreach (var package in packages)
+			{
+				package.CheckConfigError();
+			}
+
+			// 保存配置数据
+			AssetBundleCollectorSettingData.ClearAll();
+			AssetBundleCollectorSettingData.Setting.EnableAddressable = enableAddressable;
+			AssetBundleCollectorSettingData.Setting.LocationToLower = locationToLower;
+			AssetBundleCollectorSettingData.Setting.IncludeAssetGUID = includeAssetGUID;
+			AssetBundleCollectorSettingData.Setting.UniqueBundleName = uniqueBundleName;
+			AssetBundleCollectorSettingData.Setting.ShowPackageView = showPackageView;
+			AssetBundleCollectorSettingData.Setting.ShowEditorAlias = showEditorAlias;
+			AssetBundleCollectorSettingData.Setting.Packages.AddRange(packages);
+			AssetBundleCollectorSettingData.SaveFile();
+			Debug.Log($"导入配置完毕!");
+		}
+
+		/// <summary>
+		/// 导出XML配置表
+		/// </summary>
+		public static void ExportXmlConfig(string savePath)
+		{
+			if (File.Exists(savePath))
+				File.Delete(savePath);
+
+			StringBuilder sb = new StringBuilder();
+			sb.AppendLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
+			sb.AppendLine("<root>");
+			sb.AppendLine("</root>");
+
+			XmlDocument xmlDoc = new XmlDocument();
+			xmlDoc.LoadXml(sb.ToString());
+			XmlElement root = xmlDoc.DocumentElement;
+
+			// 设置配置版本
+			root.SetAttribute(XmlVersion, ConfigVersion);
+
+			// 设置公共配置
+			var commonElement = xmlDoc.CreateElement(XmlCommon);
+			commonElement.SetAttribute(XmlEnableAddressable, AssetBundleCollectorSettingData.Setting.EnableAddressable.ToString());
+			commonElement.SetAttribute(XmlLocationToLower, AssetBundleCollectorSettingData.Setting.LocationToLower.ToString());
+			commonElement.SetAttribute(XmlIncludeAssetGUID, AssetBundleCollectorSettingData.Setting.IncludeAssetGUID.ToString());
+			commonElement.SetAttribute(XmlUniqueBundleName, AssetBundleCollectorSettingData.Setting.UniqueBundleName.ToString());
+			commonElement.SetAttribute(XmlShowPackageView, AssetBundleCollectorSettingData.Setting.ShowPackageView.ToString());
+			commonElement.SetAttribute(XmlShowEditorAlias, AssetBundleCollectorSettingData.Setting.ShowEditorAlias.ToString());
+			root.AppendChild(commonElement);
+
+			// 设置Package配置
+			foreach (var package in AssetBundleCollectorSettingData.Setting.Packages)
+			{
+				var packageElement = xmlDoc.CreateElement(XmlPackage);
+				packageElement.SetAttribute(XmlPackageName, package.PackageName);
+				packageElement.SetAttribute(XmlPackageDesc, package.PackageDesc);
+				root.AppendChild(packageElement);
+
+				// 设置分组配置
+				foreach (var group in package.Groups)
+				{
+					var groupElement = xmlDoc.CreateElement(XmlGroup);
+					groupElement.SetAttribute(XmlGroupActiveRule, group.ActiveRuleName);
+					groupElement.SetAttribute(XmlGroupName, group.GroupName);
+					groupElement.SetAttribute(XmlGroupDesc, group.GroupDesc);
+					groupElement.SetAttribute(XmlAssetTags, group.AssetTags);
+					packageElement.AppendChild(groupElement);
+
+					// 设置收集器配置
+					foreach (var collector in group.Collectors)
+					{
+						var collectorElement = xmlDoc.CreateElement(XmlCollector);
+						collectorElement.SetAttribute(XmlCollectPath, collector.CollectPath);
+						collectorElement.SetAttribute(XmlCollectorGUID, collector.CollectorGUID);
+						collectorElement.SetAttribute(XmlCollectorType, collector.CollectorType.ToString());
+						collectorElement.SetAttribute(XmlAddressRule, collector.AddressRuleName);
+						collectorElement.SetAttribute(XmlPackRule, collector.PackRuleName);
+						collectorElement.SetAttribute(XmlFilterRule, collector.FilterRuleName);
+						collectorElement.SetAttribute(XmlUserData, collector.UserData);
+						collectorElement.SetAttribute(XmlAssetTags, collector.AssetTags);
+						groupElement.AppendChild(collectorElement);
+					}
+				}
+			}
+
+			// 生成配置文件
+			xmlDoc.Save(savePath);
+			Debug.Log($"导出配置完毕!");
+		}
+
+		/// <summary>
+		/// 升级XML配置表
+		/// </summary>
+		private static bool UpdateXmlConfig(XmlDocument xmlDoc)
+		{
+			XmlElement root = xmlDoc.DocumentElement;
+			string configVersion = root.GetAttribute(XmlVersion);
+			if (configVersion == ConfigVersion)
+				return true;
+
+			// 1.0 -> 2.0
+			if (configVersion == "1.0")
+			{
+				// 添加公共元素属性
+				var commonNodeList = root.GetElementsByTagName(XmlCommon);
+				if (commonNodeList.Count > 0)
+				{
+					XmlElement commonElement = commonNodeList[0] as XmlElement;
+					if (commonElement.HasAttribute(XmlShowPackageView) == false)
+						commonElement.SetAttribute(XmlShowPackageView, "False");
+				}
+
+				// 添加包裹元素
+				var packageElement = xmlDoc.CreateElement(XmlPackage);
+				packageElement.SetAttribute(XmlPackageName, "DefaultPackage");
+				packageElement.SetAttribute(XmlPackageDesc, string.Empty);
+				root.AppendChild(packageElement);
+
+				// 获取所有分组元素
+				var groupNodeList = root.GetElementsByTagName(XmlGroup);
+				List<XmlElement> temper = new List<XmlElement>(groupNodeList.Count);
+				foreach (var groupNode in groupNodeList)
+				{
+					XmlElement groupElement = groupNode as XmlElement;
+					var collectorNodeList = groupElement.GetElementsByTagName(XmlCollector);
+					foreach (var collectorNode in collectorNodeList)
+					{
+						XmlElement collectorElement = collectorNode as XmlElement;
+						if (collectorElement.HasAttribute(XmlCollectorGUID) == false)
+							collectorElement.SetAttribute(XmlCollectorGUID, string.Empty);
+					}
+					temper.Add(groupElement);
+				}
+
+				// 将分组元素转移至包裹元素下
+				foreach (var groupElement in temper)
+				{
+					root.RemoveChild(groupElement);
+					packageElement.AppendChild(groupElement);
+				}
+
+				// 更新版本
+				root.SetAttribute(XmlVersion, "2.0");
+				return UpdateXmlConfig(xmlDoc);
+			}
+
+			// 2.0 -> 2.1
+			if (configVersion == "2.0")
+			{
+				// 添加公共元素属性
+				var commonNodeList = root.GetElementsByTagName(XmlCommon);
+				if (commonNodeList.Count > 0)
+				{
+					XmlElement commonElement = commonNodeList[0] as XmlElement;
+					if (commonElement.HasAttribute(XmlUniqueBundleName) == false)
+						commonElement.SetAttribute(XmlUniqueBundleName, "False");
+				}
+
+				// 更新版本
+				root.SetAttribute(XmlVersion, "2.1");
+				return UpdateXmlConfig(xmlDoc);
+			}
+
+			// 2.1 -> 2.2
+			if (configVersion == "2.1")
+			{
+				// 添加公共元素属性
+				var commonNodeList = root.GetElementsByTagName(XmlCommon);
+				if (commonNodeList.Count > 0)
+				{
+					XmlElement commonElement = commonNodeList[0] as XmlElement;
+					if (commonElement.HasAttribute(XmlShowEditorAlias) == false)
+						commonElement.SetAttribute(XmlShowEditorAlias, "False");
+				}
+
+				// 更新版本
+				root.SetAttribute(XmlVersion, "2.2");
+				return UpdateXmlConfig(xmlDoc);
+			}
+
+			// 2.2 -> 2.3
+			if (configVersion == "2.2")
+			{
+				// 获取所有分组元素
+				var groupNodeList = root.GetElementsByTagName(XmlGroup);
+				foreach (var groupNode in groupNodeList)
+				{
+					XmlElement groupElement = groupNode as XmlElement;
+					var collectorNodeList = groupElement.GetElementsByTagName(XmlCollector);
+					foreach (var collectorNode in collectorNodeList)
+					{
+						XmlElement collectorElement = collectorNode as XmlElement;
+						if (collectorElement.HasAttribute(XmlUserData) == false)
+							collectorElement.SetAttribute(XmlUserData, string.Empty);
+					}
+				}
+
+				// 更新版本
+				root.SetAttribute(XmlVersion, "2.3");
+				return UpdateXmlConfig(xmlDoc);
+			}
+
+			// 2.3 -> 2.4
+			if (configVersion == "2.3")
+			{
+				// 获取所有分组元素
+				var groupNodeList = root.GetElementsByTagName(XmlGroup);
+				foreach (var groupNode in groupNodeList)
+				{
+					XmlElement groupElement = groupNode as XmlElement;
+					if (groupElement.HasAttribute(XmlGroupActiveRule) == false)
+						groupElement.SetAttribute(XmlGroupActiveRule, $"{nameof(EnableGroup)}");
+				}
+
+				// 更新版本
+				root.SetAttribute(XmlVersion, "2.4");
+				return UpdateXmlConfig(xmlDoc);
+			}
+
+			return false;
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorConfig.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f9a030381c5944145a285c3212153ceb
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 118 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorGroup.cs

@@ -0,0 +1,118 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using UnityEditor;
+
+namespace YooAsset.Editor
+{
+	[Serializable]
+	public class AssetBundleCollectorGroup
+	{
+		/// <summary>
+		/// 分组名称
+		/// </summary>
+		public string GroupName = string.Empty;
+
+		/// <summary>
+		/// 分组描述
+		/// </summary>
+		public string GroupDesc = string.Empty;
+
+		/// <summary>
+		/// 资源分类标签
+		/// </summary>
+		public string AssetTags = string.Empty;
+
+		/// <summary>
+		/// 分组激活规则
+		/// </summary>
+		public string ActiveRuleName = nameof(EnableGroup);
+
+		/// <summary>
+		/// 分组的收集器列表
+		/// </summary>
+		public List<AssetBundleCollector> Collectors = new List<AssetBundleCollector>();
+
+
+		/// <summary>
+		/// 检测配置错误
+		/// </summary>
+		public void CheckConfigError()
+		{
+			if (AssetBundleCollectorSettingData.HasActiveRuleName(ActiveRuleName) == false)
+				throw new Exception($"Invalid {nameof(IActiveRule)} class type : {ActiveRuleName} in group : {GroupName}");
+
+			foreach (var collector in Collectors)
+			{
+				collector.CheckConfigError();
+			}
+		}
+
+		/// <summary>
+		/// 修复配置错误
+		/// </summary>
+		public bool FixConfigError()
+		{
+			bool isFixed = false;
+			foreach (var collector in Collectors)
+			{
+				if (collector.FixConfigError())
+				{
+					isFixed = true;
+				}
+			}
+			return isFixed;
+		}
+
+		/// <summary>
+		/// 获取打包收集的资源文件
+		/// </summary>
+		public List<CollectAssetInfo> GetAllCollectAssets(CollectCommand command)
+		{
+			Dictionary<string, CollectAssetInfo> result = new Dictionary<string, CollectAssetInfo>(10000);
+
+			// 检测分组是否激活
+			IActiveRule activeRule = AssetBundleCollectorSettingData.GetActiveRuleInstance(ActiveRuleName);
+			if (activeRule.IsActiveGroup() == false)
+			{
+				return new List<CollectAssetInfo>();
+			}
+
+			// 收集打包资源
+			foreach (var collector in Collectors)
+			{
+				var temper = collector.GetAllCollectAssets(command, this);
+				foreach (var assetInfo in temper)
+				{
+					if (result.ContainsKey(assetInfo.AssetPath) == false)
+						result.Add(assetInfo.AssetPath, assetInfo);
+					else
+						throw new Exception($"The collecting asset file is existed : {assetInfo.AssetPath} in group : {GroupName}");
+				}
+			}
+
+			// 检测可寻址地址是否重复
+			if (command.EnableAddressable)
+			{
+				var addressTemper = new Dictionary<string, string>();
+				foreach (var collectInfoPair in result)
+				{
+					if (collectInfoPair.Value.CollectorType == ECollectorType.MainAssetCollector)
+					{
+						string address = collectInfoPair.Value.Address;
+						string assetPath = collectInfoPair.Value.AssetPath;
+						if (addressTemper.TryGetValue(address, out var existed) == false)
+							addressTemper.Add(address, assetPath);
+						else
+							throw new Exception($"The address is existed : {address} in group : {GroupName} \nAssetPath:\n     {existed}\n     {assetPath}");
+					}
+				}
+			}
+
+			// 返回列表
+			return result.Values.ToList();
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorGroup.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e9f7615a761107d4ebf96d66be2f6b45
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 126 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorPackage.cs

@@ -0,0 +1,126 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using UnityEditor;
+
+namespace YooAsset.Editor
+{
+	[Serializable]
+	public class AssetBundleCollectorPackage
+	{
+		/// <summary>
+		/// 包裹名称
+		/// </summary>
+		public string PackageName = string.Empty;
+
+		/// <summary>
+		/// 包裹描述
+		/// </summary>
+		public string PackageDesc = string.Empty;
+
+		/// <summary>
+		/// 分组列表
+		/// </summary>
+		public List<AssetBundleCollectorGroup> Groups = new List<AssetBundleCollectorGroup>();
+
+
+		/// <summary>
+		/// 检测配置错误
+		/// </summary>
+		public void CheckConfigError()
+		{
+			foreach (var group in Groups)
+			{
+				group.CheckConfigError();
+			}
+		}
+
+		/// <summary>
+		/// 修复配置错误
+		/// </summary>
+		public bool FixConfigError()
+		{
+			bool isFixed = false;
+			foreach (var group in Groups)
+			{
+				if (group.FixConfigError())
+				{
+					isFixed = true;
+				}
+			}
+			return isFixed;
+		}
+
+		/// <summary>
+		/// 获取打包收集的资源文件
+		/// </summary>
+		public List<CollectAssetInfo> GetAllCollectAssets(CollectCommand command)
+		{
+			Dictionary<string, CollectAssetInfo> result = new Dictionary<string, CollectAssetInfo>(10000);
+
+			// 收集打包资源
+			foreach (var group in Groups)
+			{
+				var temper = group.GetAllCollectAssets(command);
+				foreach (var assetInfo in temper)
+				{
+					if (result.ContainsKey(assetInfo.AssetPath) == false)
+						result.Add(assetInfo.AssetPath, assetInfo);
+					else
+						throw new Exception($"The collecting asset file is existed : {assetInfo.AssetPath}");
+				}
+			}
+
+			// 检测可寻址地址是否重复
+			if (command.EnableAddressable)
+			{
+				var addressTemper = new Dictionary<string, string>();
+				foreach (var collectInfoPair in result)
+				{
+					if (collectInfoPair.Value.CollectorType == ECollectorType.MainAssetCollector)
+					{
+						string address = collectInfoPair.Value.Address;
+						string assetPath = collectInfoPair.Value.AssetPath;
+						if (addressTemper.TryGetValue(address, out var existed) == false)
+							addressTemper.Add(address, assetPath);
+						else
+							throw new Exception($"The address is existed : {address} \nAssetPath:\n     {existed}\n     {assetPath}");
+					}
+				}
+			}
+
+			// 返回列表
+			return result.Values.ToList();
+		}
+
+		/// <summary>
+		/// 获取所有的资源标签
+		/// </summary>
+		public List<string> GetAllTags()
+		{
+			HashSet<string> result = new HashSet<string>();
+			foreach (var group in Groups)
+			{
+				List<string> groupTags = EditorTools.StringToStringList(group.AssetTags, ';');
+				foreach (var tag in groupTags)
+				{
+					if (result.Contains(tag) == false)
+						result.Add(tag);
+				}
+
+				foreach (var collector in group.Collectors)
+				{
+					List<string> collectorTags = EditorTools.StringToStringList(collector.AssetTags, ';');
+					foreach (var tag in collectorTags)
+					{
+						if (result.Contains(tag) == false)
+							result.Add(tag);
+					}
+				}
+			}
+			return result.ToList();
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorPackage.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 154d1124b6089254895b0f2b672394d5
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 137 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorSetting.cs

@@ -0,0 +1,137 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+
+namespace YooAsset.Editor
+{
+	[CreateAssetMenu(fileName = "AssetBundleCollectorSetting", menuName = "YooAsset/Create AssetBundle Collector Settings")]
+	public class AssetBundleCollectorSetting : ScriptableObject
+	{
+		/// <summary>
+		/// 显示包裹列表视图
+		/// </summary>
+		public bool ShowPackageView = false;
+
+		/// <summary>
+		/// 启用可寻址资源定位
+		/// </summary>
+		public bool EnableAddressable = false;
+
+		/// <summary>
+		/// 资源定位地址大小写不敏感
+		/// </summary>
+		public bool LocationToLower = false;
+
+		/// <summary>
+		/// 包含资源GUID数据
+		/// </summary>
+		public bool IncludeAssetGUID = false;
+
+		/// <summary>
+		/// 资源包名唯一化
+		/// </summary>
+		public bool UniqueBundleName = false;
+
+		/// <summary>
+		/// 是否显示编辑器别名
+		/// </summary>
+		public bool ShowEditorAlias = false;
+
+
+		/// <summary>
+		/// 包裹列表
+		/// </summary>
+		public List<AssetBundleCollectorPackage> Packages = new List<AssetBundleCollectorPackage>();
+
+
+		/// <summary>
+		/// 清空所有数据
+		/// </summary>
+		public void ClearAll()
+		{
+			ShowPackageView = false;
+			EnableAddressable = false;
+			LocationToLower = false;
+			IncludeAssetGUID = false;
+			UniqueBundleName = false;
+			ShowEditorAlias = false;
+			Packages.Clear();
+		}
+
+		/// <summary>
+		/// 检测包裹配置错误
+		/// </summary>
+		public void CheckPackageConfigError(string packageName)
+		{
+			var package = GetPackage(packageName);
+			package.CheckConfigError();
+		}
+
+		/// <summary>
+		/// 检测所有配置错误
+		/// </summary>
+		public void CheckAllPackageConfigError()
+		{
+			foreach (var package in Packages)
+			{
+				package.CheckConfigError();
+			}
+		}
+
+		/// <summary>
+		/// 修复所有配置错误
+		/// </summary>
+		public bool FixAllPackageConfigError()
+		{
+			bool isFixed = false;
+			foreach (var package in Packages)
+			{
+				if (package.FixConfigError())
+				{
+					isFixed = true;
+				}
+			}
+			return isFixed;
+		}
+
+		/// <summary>
+		/// 获取所有的资源标签
+		/// </summary>
+		public List<string> GetPackageAllTags(string packageName)
+		{
+			var package = GetPackage(packageName);
+			return package.GetAllTags();
+		}
+
+		/// <summary>
+		/// 获取包裹收集的资源文件
+		/// </summary>
+		public CollectResult GetPackageAssets(EBuildMode buildMode, string packageName)
+		{
+			if (string.IsNullOrEmpty(packageName))
+				throw new Exception("Build package name is null or mepty !");
+
+			var package = GetPackage(packageName);
+			CollectCommand command = new CollectCommand(buildMode, packageName,
+			EnableAddressable, LocationToLower, IncludeAssetGUID, UniqueBundleName);
+			CollectResult collectResult = new CollectResult(command);
+			collectResult.SetCollectAssets(package.GetAllCollectAssets(command));
+			return collectResult;
+		}
+
+		/// <summary>
+		/// 获取包裹类
+		/// </summary>
+		public AssetBundleCollectorPackage GetPackage(string packageName)
+		{
+			foreach (var package in Packages)
+			{
+				if (package.PackageName == packageName)
+					return package;
+			}
+			throw new Exception($"Not found pacakge : {packageName}");
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorSetting.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 185f6993d5150494d98da50e26cb1c25
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 445 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorSettingData.cs

@@ -0,0 +1,445 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using UnityEditor;
+
+namespace YooAsset.Editor
+{
+	public class AssetBundleCollectorSettingData
+	{
+		private static readonly Dictionary<string, System.Type> _cacheActiveRuleTypes = new Dictionary<string, Type>();
+		private static readonly Dictionary<string, IActiveRule> _cacheActiveRuleInstance = new Dictionary<string, IActiveRule>();
+
+		private static readonly Dictionary<string, System.Type> _cacheAddressRuleTypes = new Dictionary<string, System.Type>();
+		private static readonly Dictionary<string, IAddressRule> _cacheAddressRuleInstance = new Dictionary<string, IAddressRule>();
+
+		private static readonly Dictionary<string, System.Type> _cachePackRuleTypes = new Dictionary<string, System.Type>();
+		private static readonly Dictionary<string, IPackRule> _cachePackRuleInstance = new Dictionary<string, IPackRule>();
+
+		private static readonly Dictionary<string, System.Type> _cacheFilterRuleTypes = new Dictionary<string, System.Type>();
+		private static readonly Dictionary<string, IFilterRule> _cacheFilterRuleInstance = new Dictionary<string, IFilterRule>();
+
+		/// <summary>
+		/// 配置数据是否被修改
+		/// </summary>
+		public static bool IsDirty { private set; get; } = false;
+
+
+		static AssetBundleCollectorSettingData()
+		{
+			// IPackRule
+			{
+				// 清空缓存集合
+				_cachePackRuleTypes.Clear();
+				_cachePackRuleInstance.Clear();
+
+				// 获取所有类型
+				List<Type> types = new List<Type>(100)
+				{
+					typeof(PackSeparately),
+					typeof(PackDirectory),
+					typeof(PackTopDirectory),
+					typeof(PackCollector),
+					typeof(PackGroup),
+					typeof(PackRawFile),
+					typeof(PackShaderVariants)
+				};
+
+				var customTypes = EditorTools.GetAssignableTypes(typeof(IPackRule));
+				types.AddRange(customTypes);
+				for (int i = 0; i < types.Count; i++)
+				{
+					Type type = types[i];
+					if (_cachePackRuleTypes.ContainsKey(type.Name) == false)
+						_cachePackRuleTypes.Add(type.Name, type);
+				}
+			}
+
+			// IFilterRule
+			{
+				// 清空缓存集合
+				_cacheFilterRuleTypes.Clear();
+				_cacheFilterRuleInstance.Clear();
+
+				// 获取所有类型
+				List<Type> types = new List<Type>(100)
+				{
+					typeof(CollectAll),
+					typeof(CollectScene),
+					typeof(CollectPrefab),
+					typeof(CollectSprite)
+				};
+
+				var customTypes = EditorTools.GetAssignableTypes(typeof(IFilterRule));
+				types.AddRange(customTypes);
+				for (int i = 0; i < types.Count; i++)
+				{
+					Type type = types[i];
+					if (_cacheFilterRuleTypes.ContainsKey(type.Name) == false)
+						_cacheFilterRuleTypes.Add(type.Name, type);
+				}
+			}
+
+			// IAddressRule
+			{
+				// 清空缓存集合
+				_cacheAddressRuleTypes.Clear();
+				_cacheAddressRuleInstance.Clear();
+
+				// 获取所有类型
+				List<Type> types = new List<Type>(100)
+				{
+					typeof(AddressByFileName),
+					typeof(AddressByFilePath),
+					typeof(AddressByFolderAndFileName),
+					typeof(AddressByGroupAndFileName)
+				};
+
+				var customTypes = EditorTools.GetAssignableTypes(typeof(IAddressRule));
+				types.AddRange(customTypes);
+				for (int i = 0; i < types.Count; i++)
+				{
+					Type type = types[i];
+					if (_cacheAddressRuleTypes.ContainsKey(type.Name) == false)
+						_cacheAddressRuleTypes.Add(type.Name, type);
+				}
+			}
+
+			// IActiveRule
+			{
+				// 清空缓存集合
+				_cacheActiveRuleTypes.Clear();
+				_cacheActiveRuleInstance.Clear();
+
+				// 获取所有类型
+				List<Type> types = new List<Type>(100)
+				{
+					typeof(EnableGroup),
+					typeof(DisableGroup),
+				};
+
+				var customTypes = EditorTools.GetAssignableTypes(typeof(IActiveRule));
+				types.AddRange(customTypes);
+				for (int i = 0; i < types.Count; i++)
+				{
+					Type type = types[i];
+					if (_cacheActiveRuleTypes.ContainsKey(type.Name) == false)
+						_cacheActiveRuleTypes.Add(type.Name, type);
+				}
+			}
+		}
+
+		private static AssetBundleCollectorSetting _setting = null;
+		public static AssetBundleCollectorSetting Setting
+		{
+			get
+			{
+				if (_setting == null)
+					_setting = SettingLoader.LoadSettingData<AssetBundleCollectorSetting>();
+				return _setting;
+			}
+		}
+
+		/// <summary>
+		/// 存储配置文件
+		/// </summary>
+		public static void SaveFile()
+		{
+			if (Setting != null)
+			{
+				IsDirty = false;
+				EditorUtility.SetDirty(Setting);
+				AssetDatabase.SaveAssets();
+				Debug.Log($"{nameof(AssetBundleCollectorSetting)}.asset is saved!");
+			}
+		}
+
+		/// <summary>
+		/// 修复配置文件
+		/// </summary>
+		public static void FixFile()
+		{
+			bool isFixed = Setting.FixAllPackageConfigError();
+			if (isFixed)
+			{
+				IsDirty = true;
+			}
+		}
+
+		/// <summary>
+		/// 清空所有数据
+		/// </summary>
+		public static void ClearAll()
+		{
+			Setting.ClearAll();
+			SaveFile();
+		}
+
+		public static List<RuleDisplayName> GetActiveRuleNames()
+		{
+			List<RuleDisplayName> names = new List<RuleDisplayName>();
+			foreach (var pair in _cacheActiveRuleTypes)
+			{
+				RuleDisplayName ruleName = new RuleDisplayName();
+				ruleName.ClassName = pair.Key;
+				ruleName.DisplayName = GetRuleDisplayName(pair.Key, pair.Value);
+				names.Add(ruleName);
+			}
+			return names;
+		}
+		public static List<RuleDisplayName> GetAddressRuleNames()
+		{
+			List<RuleDisplayName> names = new List<RuleDisplayName>();
+			foreach (var pair in _cacheAddressRuleTypes)
+			{
+				RuleDisplayName ruleName = new RuleDisplayName();
+				ruleName.ClassName = pair.Key;
+				ruleName.DisplayName = GetRuleDisplayName(pair.Key, pair.Value);
+				names.Add(ruleName);
+			}
+			return names;
+		}
+		public static List<RuleDisplayName> GetPackRuleNames()
+		{
+			List<RuleDisplayName> names = new List<RuleDisplayName>();
+			foreach (var pair in _cachePackRuleTypes)
+			{
+				RuleDisplayName ruleName = new RuleDisplayName();
+				ruleName.ClassName = pair.Key;
+				ruleName.DisplayName = GetRuleDisplayName(pair.Key, pair.Value);
+				names.Add(ruleName);
+			}
+			return names;
+		}
+		public static List<RuleDisplayName> GetFilterRuleNames()
+		{
+			List<RuleDisplayName> names = new List<RuleDisplayName>();
+			foreach (var pair in _cacheFilterRuleTypes)
+			{
+				RuleDisplayName ruleName = new RuleDisplayName();
+				ruleName.ClassName = pair.Key;
+				ruleName.DisplayName = GetRuleDisplayName(pair.Key, pair.Value);
+				names.Add(ruleName);
+			}
+			return names;
+		}
+		private static string GetRuleDisplayName(string name, Type type)
+		{
+			var attribute = DisplayNameAttributeHelper.GetAttribute<DisplayNameAttribute>(type);
+			if (attribute != null && string.IsNullOrEmpty(attribute.DisplayName) == false)
+				return attribute.DisplayName;
+			else
+				return name;
+		}
+
+		public static bool HasActiveRuleName(string ruleName)
+		{
+			return _cacheActiveRuleTypes.Keys.Contains(ruleName);
+		}
+		public static bool HasAddressRuleName(string ruleName)
+		{
+			return _cacheAddressRuleTypes.Keys.Contains(ruleName);
+		}
+		public static bool HasPackRuleName(string ruleName)
+		{
+			return _cachePackRuleTypes.Keys.Contains(ruleName);
+		}
+		public static bool HasFilterRuleName(string ruleName)
+		{
+			return _cacheFilterRuleTypes.Keys.Contains(ruleName);
+		}
+
+		public static IActiveRule GetActiveRuleInstance(string ruleName)
+		{
+			if (_cacheActiveRuleInstance.TryGetValue(ruleName, out IActiveRule instance))
+				return instance;
+
+			// 如果不存在创建类的实例
+			if (_cacheActiveRuleTypes.TryGetValue(ruleName, out Type type))
+			{
+				instance = (IActiveRule)Activator.CreateInstance(type);
+				_cacheActiveRuleInstance.Add(ruleName, instance);
+				return instance;
+			}
+			else
+			{
+				throw new Exception($"{nameof(IActiveRule)}类型无效:{ruleName}");
+			}
+		}
+		public static IAddressRule GetAddressRuleInstance(string ruleName)
+		{
+			if (_cacheAddressRuleInstance.TryGetValue(ruleName, out IAddressRule instance))
+				return instance;
+
+			// 如果不存在创建类的实例
+			if (_cacheAddressRuleTypes.TryGetValue(ruleName, out Type type))
+			{
+				instance = (IAddressRule)Activator.CreateInstance(type);
+				_cacheAddressRuleInstance.Add(ruleName, instance);
+				return instance;
+			}
+			else
+			{
+				throw new Exception($"{nameof(IAddressRule)}类型无效:{ruleName}");
+			}
+		}
+		public static IPackRule GetPackRuleInstance(string ruleName)
+		{
+			if (_cachePackRuleInstance.TryGetValue(ruleName, out IPackRule instance))
+				return instance;
+
+			// 如果不存在创建类的实例
+			if (_cachePackRuleTypes.TryGetValue(ruleName, out Type type))
+			{
+				instance = (IPackRule)Activator.CreateInstance(type);
+				_cachePackRuleInstance.Add(ruleName, instance);
+				return instance;
+			}
+			else
+			{
+				throw new Exception($"{nameof(IPackRule)}类型无效:{ruleName}");
+			}
+		}
+		public static IFilterRule GetFilterRuleInstance(string ruleName)
+		{
+			if (_cacheFilterRuleInstance.TryGetValue(ruleName, out IFilterRule instance))
+				return instance;
+
+			// 如果不存在创建类的实例
+			if (_cacheFilterRuleTypes.TryGetValue(ruleName, out Type type))
+			{
+				instance = (IFilterRule)Activator.CreateInstance(type);
+				_cacheFilterRuleInstance.Add(ruleName, instance);
+				return instance;
+			}
+			else
+			{
+				throw new Exception($"{nameof(IFilterRule)}类型无效:{ruleName}");
+			}
+		}
+
+		// 公共参数编辑相关
+		public static void ModifyPackageView(bool showPackageView)
+		{
+			Setting.ShowPackageView = showPackageView;
+			IsDirty = true;
+		}
+		public static void ModifyAddressable(bool enableAddressable)
+		{
+			Setting.EnableAddressable = enableAddressable;
+			IsDirty = true;
+		}
+		public static void ModifyLocationToLower(bool locationToLower)
+		{
+			Setting.LocationToLower = locationToLower;
+			IsDirty = true;
+		}
+		public static void ModifyIncludeAssetGUID(bool includeAssetGUID)
+		{
+			Setting.IncludeAssetGUID = includeAssetGUID;
+			IsDirty = true;
+		}
+		public static void ModifyUniqueBundleName(bool uniqueBundleName)
+		{
+			Setting.UniqueBundleName = uniqueBundleName;
+			IsDirty = true;
+		}
+		public static void ModifyShowEditorAlias(bool showAlias)
+		{
+			Setting.ShowEditorAlias = showAlias;
+			IsDirty = true;
+		}
+
+		// 资源包裹编辑相关
+		public static AssetBundleCollectorPackage CreatePackage(string packageName)
+		{
+			AssetBundleCollectorPackage package = new AssetBundleCollectorPackage();
+			package.PackageName = packageName;
+			Setting.Packages.Add(package);
+			IsDirty = true;
+			return package;
+		}
+		public static void RemovePackage(AssetBundleCollectorPackage package)
+		{
+			if (Setting.Packages.Remove(package))
+			{
+				IsDirty = true;
+			}
+			else
+			{
+				Debug.LogWarning($"Failed remove package : {package.PackageName}");
+			}
+		}
+		public static void ModifyPackage(AssetBundleCollectorPackage package)
+		{
+			if (package != null)
+			{
+				IsDirty = true;
+			}
+		}
+
+		// 资源分组编辑相关
+		public static AssetBundleCollectorGroup CreateGroup(AssetBundleCollectorPackage package, string groupName)
+		{
+			AssetBundleCollectorGroup group = new AssetBundleCollectorGroup();
+			group.GroupName = groupName;
+			package.Groups.Add(group);
+			IsDirty = true;
+			return group;
+		}
+		public static void RemoveGroup(AssetBundleCollectorPackage package, AssetBundleCollectorGroup group)
+		{
+			if (package.Groups.Remove(group))
+			{
+				IsDirty = true;
+			}
+			else
+			{
+				Debug.LogWarning($"Failed remove group : {group.GroupName}");
+			}
+		}
+		public static void ModifyGroup(AssetBundleCollectorPackage package, AssetBundleCollectorGroup group)
+		{
+			if (package != null && group != null)
+			{
+				IsDirty = true;
+			}
+		}
+
+		// 资源收集器编辑相关
+		public static void CreateCollector(AssetBundleCollectorGroup group, AssetBundleCollector collector)
+		{
+			group.Collectors.Add(collector);
+			IsDirty = true;
+		}
+		public static void RemoveCollector(AssetBundleCollectorGroup group, AssetBundleCollector collector)
+		{
+			if (group.Collectors.Remove(collector))
+			{
+				IsDirty = true;
+			}
+			else
+			{
+				Debug.LogWarning($"Failed remove collector : {collector.CollectPath}");
+			}
+		}
+		public static void ModifyCollector(AssetBundleCollectorGroup group, AssetBundleCollector collector)
+		{
+			if (group != null && collector != null)
+			{
+				IsDirty = true;
+			}
+		}
+
+		/// <summary>
+		/// 获取所有的资源标签
+		/// </summary>
+		public static string GetPackageAllTags(string packageName)
+		{
+			var allTags = Setting.GetPackageAllTags(packageName);
+			return string.Join(";", allTags);
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorSettingData.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2fa91f25d246411459816310b09480a5
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 962 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorWindow.cs

@@ -0,0 +1,962 @@
+#if UNITY_2019_4_OR_NEWER
+using System.IO;
+using System.Linq;
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEngine;
+using UnityEditor.UIElements;
+using UnityEngine.UIElements;
+
+namespace YooAsset.Editor
+{
+	public class AssetBundleCollectorWindow : EditorWindow
+	{
+		[MenuItem("YooAsset/AssetBundle Collector", false, 101)]
+		public static void OpenWindow()
+		{
+			AssetBundleCollectorWindow window = GetWindow<AssetBundleCollectorWindow>("资源包收集工具", true, WindowsDefine.DockedWindowTypes);
+			window.minSize = new Vector2(800, 600);
+		}
+
+		private Button _saveButton;
+		private List<string> _collectorTypeList;
+		private List<RuleDisplayName> _activeRuleList;
+		private List<RuleDisplayName> _addressRuleList;
+		private List<RuleDisplayName> _packRuleList;
+		private List<RuleDisplayName> _filterRuleList;
+
+		private Button _settingsButton;
+		private VisualElement _helpBoxContainer;
+		private VisualElement _setting1Container;
+		private VisualElement _setting2Container;
+		private Toggle _showPackageToogle;
+		private Toggle _enableAddressableToogle;
+		private Toggle _locationToLowerToogle;
+		private Toggle _includeAssetGUIDToogle;
+		private Toggle _uniqueBundleNameToogle;
+		private Toggle _showEditorAliasToggle;
+
+		private VisualElement _packageContainer;
+		private ListView _packageListView;
+		private TextField _packageNameTxt;
+		private TextField _packageDescTxt;
+
+		private VisualElement _groupContainer;
+		private ListView _groupListView;
+		private TextField _groupNameTxt;
+		private TextField _groupDescTxt;
+		private TextField _groupAssetTagsTxt;
+
+		private VisualElement _collectorContainer;
+		private ScrollView _collectorScrollView;
+		private PopupField<RuleDisplayName> _activeRulePopupField;
+
+		private int _lastModifyPackageIndex = 0;
+		private int _lastModifyGroupIndex = 0;
+		private bool _showSettings = false;
+
+
+		public void CreateGUI()
+		{
+			Undo.undoRedoPerformed -= RefreshWindow;
+			Undo.undoRedoPerformed += RefreshWindow;
+
+			try
+			{
+				_collectorTypeList = new List<string>()
+				{
+					$"{nameof(ECollectorType.MainAssetCollector)}",
+					$"{nameof(ECollectorType.StaticAssetCollector)}",
+					$"{nameof(ECollectorType.DependAssetCollector)}"
+				};
+				_activeRuleList = AssetBundleCollectorSettingData.GetActiveRuleNames();
+				_addressRuleList = AssetBundleCollectorSettingData.GetAddressRuleNames();
+				_packRuleList = AssetBundleCollectorSettingData.GetPackRuleNames();
+				_filterRuleList = AssetBundleCollectorSettingData.GetFilterRuleNames();
+
+				VisualElement root = this.rootVisualElement;
+
+				// 加载布局文件
+				var visualAsset = UxmlLoader.LoadWindowUXML<AssetBundleCollectorWindow>();
+				if (visualAsset == null)
+					return;
+
+				visualAsset.CloneTree(root);
+
+				// 警示栏
+				_helpBoxContainer = root.Q("HelpBoxContainer");
+
+				// 公共设置相关
+				_settingsButton = root.Q<Button>("SettingsButton");
+				_settingsButton.clicked += SettingsBtn_clicked;
+				_setting1Container = root.Q("PublicContainer1");
+				_setting2Container = root.Q("PublicContainer2");
+				_showPackageToogle = root.Q<Toggle>("ShowPackages");
+				_showPackageToogle.RegisterValueChangedCallback(evt =>
+				{
+					AssetBundleCollectorSettingData.ModifyPackageView(evt.newValue);
+					RefreshWindow();
+				});
+				_enableAddressableToogle = root.Q<Toggle>("EnableAddressable");
+				_enableAddressableToogle.RegisterValueChangedCallback(evt =>
+				{
+					AssetBundleCollectorSettingData.ModifyAddressable(evt.newValue);
+					RefreshWindow();
+				});
+				_locationToLowerToogle = root.Q<Toggle>("LocationToLower");
+				_locationToLowerToogle.RegisterValueChangedCallback(evt =>
+				{
+					AssetBundleCollectorSettingData.ModifyLocationToLower(evt.newValue);
+					RefreshWindow();
+				});
+				_includeAssetGUIDToogle = root.Q<Toggle>("IncludeAssetGUID");
+				_includeAssetGUIDToogle.RegisterValueChangedCallback(evt =>
+				{
+					AssetBundleCollectorSettingData.ModifyIncludeAssetGUID(evt.newValue);
+					RefreshWindow();
+				});
+				_uniqueBundleNameToogle = root.Q<Toggle>("UniqueBundleName");
+				_uniqueBundleNameToogle.RegisterValueChangedCallback(evt =>
+				{
+					AssetBundleCollectorSettingData.ModifyUniqueBundleName(evt.newValue);
+					RefreshWindow();
+				});
+				_showEditorAliasToggle = root.Q<Toggle>("ShowEditorAlias");
+				_showEditorAliasToggle.RegisterValueChangedCallback(evt =>
+				{
+					AssetBundleCollectorSettingData.ModifyShowEditorAlias(evt.newValue);
+					RefreshWindow();
+				});
+
+				// 配置修复按钮
+				var fixBtn = root.Q<Button>("FixButton");
+				fixBtn.clicked += FixBtn_clicked;
+
+				// 导入导出按钮
+				var exportBtn = root.Q<Button>("ExportButton");
+				exportBtn.clicked += ExportBtn_clicked;
+				var importBtn = root.Q<Button>("ImportButton");
+				importBtn.clicked += ImportBtn_clicked;
+
+				// 配置保存按钮
+				_saveButton = root.Q<Button>("SaveButton");
+				_saveButton.clicked += SaveBtn_clicked;
+
+				// 包裹容器
+				_packageContainer = root.Q("PackageContainer");
+
+				// 包裹列表相关
+				_packageListView = root.Q<ListView>("PackageListView");
+				_packageListView.makeItem = MakePackageListViewItem;
+				_packageListView.bindItem = BindPackageListViewItem;
+#if UNITY_2020_1_OR_NEWER
+				_packageListView.onSelectionChange += PackageListView_onSelectionChange;
+#else
+				_packageListView.onSelectionChanged += PackageListView_onSelectionChange;
+#endif
+
+				// 包裹添加删除按钮
+				var packageAddContainer = root.Q("PackageAddContainer");
+				{
+					var addBtn = packageAddContainer.Q<Button>("AddBtn");
+					addBtn.clicked += AddPackageBtn_clicked;
+					var removeBtn = packageAddContainer.Q<Button>("RemoveBtn");
+					removeBtn.clicked += RemovePackageBtn_clicked;
+				}
+
+				// 包裹名称
+				_packageNameTxt = root.Q<TextField>("PackageName");
+				_packageNameTxt.RegisterValueChangedCallback(evt =>
+				{
+					var selectPackage = _packageListView.selectedItem as AssetBundleCollectorPackage;
+					if (selectPackage != null)
+					{
+						selectPackage.PackageName = evt.newValue;
+						AssetBundleCollectorSettingData.ModifyPackage(selectPackage);
+						FillPackageViewData();
+					}
+				});
+
+				// 包裹备注
+				_packageDescTxt = root.Q<TextField>("PackageDesc");
+				_packageDescTxt.RegisterValueChangedCallback(evt =>
+				{
+					var selectPackage = _packageListView.selectedItem as AssetBundleCollectorPackage;
+					if (selectPackage != null)
+					{
+						selectPackage.PackageDesc = evt.newValue;
+						AssetBundleCollectorSettingData.ModifyPackage(selectPackage);
+						FillPackageViewData();
+					}
+				});
+
+				// 分组列表相关
+				_groupListView = root.Q<ListView>("GroupListView");
+				_groupListView.makeItem = MakeGroupListViewItem;
+				_groupListView.bindItem = BindGroupListViewItem;
+#if UNITY_2020_1_OR_NEWER
+				_groupListView.onSelectionChange += GroupListView_onSelectionChange;
+#else
+				_groupListView.onSelectionChanged += GroupListView_onSelectionChange;
+#endif
+
+				// 分组添加删除按钮
+				var groupAddContainer = root.Q("GroupAddContainer");
+				{
+					var addBtn = groupAddContainer.Q<Button>("AddBtn");
+					addBtn.clicked += AddGroupBtn_clicked;
+					var removeBtn = groupAddContainer.Q<Button>("RemoveBtn");
+					removeBtn.clicked += RemoveGroupBtn_clicked;
+				}
+
+				// 分组容器
+				_groupContainer = root.Q("GroupContainer");
+
+				// 分组名称
+				_groupNameTxt = root.Q<TextField>("GroupName");
+				_groupNameTxt.RegisterValueChangedCallback(evt =>
+				{
+					var selectPackage = _packageListView.selectedItem as AssetBundleCollectorPackage;
+					var selectGroup = _groupListView.selectedItem as AssetBundleCollectorGroup;
+					if (selectPackage != null && selectGroup != null)
+					{
+						selectGroup.GroupName = evt.newValue;
+						AssetBundleCollectorSettingData.ModifyGroup(selectPackage, selectGroup);
+						FillGroupViewData();
+					}
+				});
+
+				// 分组备注
+				_groupDescTxt = root.Q<TextField>("GroupDesc");
+				_groupDescTxt.RegisterValueChangedCallback(evt =>
+				{
+					var selectPackage = _packageListView.selectedItem as AssetBundleCollectorPackage;
+					var selectGroup = _groupListView.selectedItem as AssetBundleCollectorGroup;
+					if (selectPackage != null && selectGroup != null)
+					{
+						selectGroup.GroupDesc = evt.newValue;
+						AssetBundleCollectorSettingData.ModifyGroup(selectPackage, selectGroup);
+						FillGroupViewData();
+					}
+				});
+
+				// 分组的资源标签
+				_groupAssetTagsTxt = root.Q<TextField>("GroupAssetTags");
+				_groupAssetTagsTxt.RegisterValueChangedCallback(evt =>
+				{
+					var selectPackage = _packageListView.selectedItem as AssetBundleCollectorPackage;
+					var selectGroup = _groupListView.selectedItem as AssetBundleCollectorGroup;
+					if (selectPackage != null && selectGroup != null)
+					{
+						selectGroup.AssetTags = evt.newValue;
+						AssetBundleCollectorSettingData.ModifyGroup(selectPackage, selectGroup);
+					}
+				});
+
+				// 收集列表容器
+				_collectorContainer = root.Q("CollectorContainer");
+
+				// 收集列表相关
+				_collectorScrollView = root.Q<ScrollView>("CollectorScrollView");
+				_collectorScrollView.style.height = new Length(100, LengthUnit.Percent);
+				_collectorScrollView.viewDataKey = "scrollView";
+
+				// 收集器创建按钮
+				var collectorAddContainer = root.Q("CollectorAddContainer");
+				{
+					var addBtn = collectorAddContainer.Q<Button>("AddBtn");
+					addBtn.clicked += AddCollectorBtn_clicked;
+				}
+
+				// 分组激活规则
+				var activeRuleContainer = root.Q("ActiveRuleContainer");
+				{
+					_activeRulePopupField = new PopupField<RuleDisplayName>("Active Rule", _activeRuleList, 0);
+					_activeRulePopupField.name = "ActiveRuleMaskField";
+					_activeRulePopupField.style.unityTextAlign = TextAnchor.MiddleLeft;
+					_activeRulePopupField.formatListItemCallback = FormatListItemCallback;
+					_activeRulePopupField.formatSelectedValueCallback = FormatSelectedValueCallback;
+					_activeRulePopupField.RegisterValueChangedCallback(evt =>
+					{
+						var selectPackage = _packageListView.selectedItem as AssetBundleCollectorPackage;
+						var selectGroup = _groupListView.selectedItem as AssetBundleCollectorGroup;
+						if (selectPackage != null && selectGroup != null)
+						{
+							selectGroup.ActiveRuleName = evt.newValue.ClassName;
+							AssetBundleCollectorSettingData.ModifyGroup(selectPackage, selectGroup);
+							FillGroupViewData();
+						}
+					});
+					activeRuleContainer.Add(_activeRulePopupField);
+				}
+
+				// 刷新窗体
+				RefreshWindow();
+			}
+			catch (System.Exception e)
+			{
+				Debug.LogError(e.ToString());
+			}
+		}
+		public void OnDestroy()
+		{
+			// 注意:清空所有撤销操作
+			Undo.ClearAll();
+
+			if (AssetBundleCollectorSettingData.IsDirty)
+				AssetBundleCollectorSettingData.SaveFile();
+		}
+		public void Update()
+		{
+			if (_saveButton != null)
+			{
+				if (AssetBundleCollectorSettingData.IsDirty)
+				{
+					if (_saveButton.enabledSelf == false)
+						_saveButton.SetEnabled(true);
+				}
+				else
+				{
+					if (_saveButton.enabledSelf)
+						_saveButton.SetEnabled(false);
+				}
+			}
+		}
+
+		private void RefreshWindow()
+		{
+			_showPackageToogle.SetValueWithoutNotify(AssetBundleCollectorSettingData.Setting.ShowPackageView);
+			_enableAddressableToogle.SetValueWithoutNotify(AssetBundleCollectorSettingData.Setting.EnableAddressable);
+			_locationToLowerToogle.SetValueWithoutNotify(AssetBundleCollectorSettingData.Setting.LocationToLower);
+			_includeAssetGUIDToogle.SetValueWithoutNotify(AssetBundleCollectorSettingData.Setting.IncludeAssetGUID);
+			_uniqueBundleNameToogle.SetValueWithoutNotify(AssetBundleCollectorSettingData.Setting.UniqueBundleName);
+			_showEditorAliasToggle.SetValueWithoutNotify(AssetBundleCollectorSettingData.Setting.ShowEditorAlias);
+
+			// 警示框
+			_helpBoxContainer.Clear();
+			if (_enableAddressableToogle.value && _locationToLowerToogle.value)
+			{
+				var helpBox = new HelpBox("无法同时开启[Enable Addressable]选项和[Location To Lower]选项", HelpBoxMessageType.Error);
+				_helpBoxContainer.Add(helpBox);
+			}
+			if (AssetBundleCollectorSettingData.Setting.Packages.Count > 1 && _uniqueBundleNameToogle.value == false)
+			{
+				var helpBox = new HelpBox("检测到当前配置存在多个Package,建议开启[Unique Bundle Name]选项", HelpBoxMessageType.Warning);
+				_helpBoxContainer.Add(helpBox);
+			}
+			if (_helpBoxContainer.childCount > 0)
+				_helpBoxContainer.style.display = DisplayStyle.Flex;
+			else
+				_helpBoxContainer.style.display = DisplayStyle.None;
+
+			// 设置栏
+			if (_showSettings)
+			{
+				_setting1Container.style.display = DisplayStyle.Flex;
+				_setting2Container.style.display = DisplayStyle.Flex;
+			}
+			else
+			{
+				_setting1Container.style.display = DisplayStyle.None;
+				_setting2Container.style.display = DisplayStyle.None;
+			}
+
+			_groupContainer.visible = false;
+			_collectorContainer.visible = false;
+
+			FillPackageViewData();
+		}
+		private void FixBtn_clicked()
+		{
+			AssetBundleCollectorSettingData.FixFile();
+			RefreshWindow();
+		}
+		private void ExportBtn_clicked()
+		{
+			string resultPath = EditorTools.OpenFolderPanel("Export XML", "Assets/");
+			if (resultPath != null)
+			{
+				AssetBundleCollectorConfig.ExportXmlConfig($"{resultPath}/{nameof(AssetBundleCollectorConfig)}.xml");
+			}
+		}
+		private void ImportBtn_clicked()
+		{
+			string resultPath = EditorTools.OpenFilePath("Import XML", "Assets/", "xml");
+			if (resultPath != null)
+			{
+				AssetBundleCollectorConfig.ImportXmlConfig(resultPath);
+				RefreshWindow();
+			}
+		}
+		private void SaveBtn_clicked()
+		{
+			AssetBundleCollectorSettingData.SaveFile();
+		}
+		private void SettingsBtn_clicked()
+		{
+			_showSettings = !_showSettings;
+			RefreshWindow();
+		}
+		private string FormatListItemCallback(RuleDisplayName ruleDisplayName)
+		{
+			if (_showEditorAliasToggle.value)
+				return ruleDisplayName.DisplayName;
+			else
+				return ruleDisplayName.ClassName;
+		}
+		private string FormatSelectedValueCallback(RuleDisplayName ruleDisplayName)
+		{
+			if (_showEditorAliasToggle.value)
+				return ruleDisplayName.DisplayName;
+			else
+				return ruleDisplayName.ClassName;
+		}
+
+		// 包裹列表相关
+		private void FillPackageViewData()
+		{
+			_packageListView.Clear();
+			_packageListView.ClearSelection();
+			_packageListView.itemsSource = AssetBundleCollectorSettingData.Setting.Packages;
+			_packageListView.Rebuild();
+
+			if (_lastModifyPackageIndex >= 0 && _lastModifyPackageIndex < _packageListView.itemsSource.Count)
+			{
+				_packageListView.selectedIndex = _lastModifyPackageIndex;
+			}
+
+			if (_showPackageToogle.value)
+				_packageContainer.style.display = DisplayStyle.Flex;
+			else
+				_packageContainer.style.display = DisplayStyle.None;
+		}
+		private VisualElement MakePackageListViewItem()
+		{
+			VisualElement element = new VisualElement();
+
+			{
+				var label = new Label();
+				label.name = "Label1";
+				label.style.unityTextAlign = TextAnchor.MiddleLeft;
+				label.style.flexGrow = 1f;
+				label.style.height = 20f;
+				element.Add(label);
+			}
+
+			return element;
+		}
+		private void BindPackageListViewItem(VisualElement element, int index)
+		{
+			var package = AssetBundleCollectorSettingData.Setting.Packages[index];
+
+			var textField1 = element.Q<Label>("Label1");
+			if (string.IsNullOrEmpty(package.PackageDesc))
+				textField1.text = package.PackageName;
+			else
+				textField1.text = $"{package.PackageName} ({package.PackageDesc})";
+		}
+		private void PackageListView_onSelectionChange(IEnumerable<object> objs)
+		{
+			var selectPackage = _packageListView.selectedItem as AssetBundleCollectorPackage;
+			if (selectPackage == null)
+			{
+				_groupContainer.visible = false;
+				_collectorContainer.visible = false;
+				return;
+			}
+
+			_groupContainer.visible = true;
+			_lastModifyPackageIndex = _packageListView.selectedIndex;
+			_packageNameTxt.SetValueWithoutNotify(selectPackage.PackageName);
+			_packageDescTxt.SetValueWithoutNotify(selectPackage.PackageDesc);
+			FillGroupViewData();
+		}
+		private void AddPackageBtn_clicked()
+		{
+			Undo.RecordObject(AssetBundleCollectorSettingData.Setting, "YooAsset.AssetBundleCollectorWindow AddPackage");
+			AssetBundleCollectorSettingData.CreatePackage("DefaultPackage");
+			FillPackageViewData();
+		}
+		private void RemovePackageBtn_clicked()
+		{
+			var selectPackage = _packageListView.selectedItem as AssetBundleCollectorPackage;
+			if (selectPackage == null)
+				return;
+
+			Undo.RecordObject(AssetBundleCollectorSettingData.Setting, "YooAsset.AssetBundleCollectorWindow RemovePackage");
+			AssetBundleCollectorSettingData.RemovePackage(selectPackage);
+			FillPackageViewData();
+		}
+
+		// 分组列表相关
+		private void FillGroupViewData()
+		{
+			var selectPackage = _packageListView.selectedItem as AssetBundleCollectorPackage;
+			if (selectPackage == null)
+				return;
+
+			_groupListView.Clear();
+			_groupListView.ClearSelection();
+			_groupListView.itemsSource = selectPackage.Groups;
+			_groupListView.Rebuild();
+
+			if (_lastModifyGroupIndex >= 0 && _lastModifyGroupIndex < _groupListView.itemsSource.Count)
+			{
+				_groupListView.selectedIndex = _lastModifyGroupIndex;
+			}
+		}
+		private VisualElement MakeGroupListViewItem()
+		{
+			VisualElement element = new VisualElement();
+
+			{
+				var label = new Label();
+				label.name = "Label1";
+				label.style.unityTextAlign = TextAnchor.MiddleLeft;
+				label.style.flexGrow = 1f;
+				label.style.height = 20f;
+				element.Add(label);
+			}
+
+			return element;
+		}
+		private void BindGroupListViewItem(VisualElement element, int index)
+		{
+			var selectPackage = _packageListView.selectedItem as AssetBundleCollectorPackage;
+			if (selectPackage == null)
+				return;
+
+			var group = selectPackage.Groups[index];
+
+			var textField1 = element.Q<Label>("Label1");
+			if (string.IsNullOrEmpty(group.GroupDesc))
+				textField1.text = group.GroupName;
+			else
+				textField1.text = $"{group.GroupName} ({group.GroupDesc})";
+
+			// 激活状态
+			IActiveRule activeRule = AssetBundleCollectorSettingData.GetActiveRuleInstance(group.ActiveRuleName);
+			bool isActive = activeRule.IsActiveGroup();
+			textField1.SetEnabled(isActive);
+		}
+		private void GroupListView_onSelectionChange(IEnumerable<object> objs)
+		{
+			var selectGroup = _groupListView.selectedItem as AssetBundleCollectorGroup;
+			if (selectGroup == null)
+			{
+				_collectorContainer.visible = false;
+				return;
+			}
+
+			_collectorContainer.visible = true;
+			_lastModifyGroupIndex = _groupListView.selectedIndex;
+			_activeRulePopupField.SetValueWithoutNotify(GetActiveRuleIndex(selectGroup.ActiveRuleName));
+			_groupNameTxt.SetValueWithoutNotify(selectGroup.GroupName);
+			_groupDescTxt.SetValueWithoutNotify(selectGroup.GroupDesc);
+			_groupAssetTagsTxt.SetValueWithoutNotify(selectGroup.AssetTags);
+
+			FillCollectorViewData();
+		}
+		private void AddGroupBtn_clicked()
+		{
+			var selectPackage = _packageListView.selectedItem as AssetBundleCollectorPackage;
+			if (selectPackage == null)
+				return;
+
+			Undo.RecordObject(AssetBundleCollectorSettingData.Setting, "YooAsset.AssetBundleCollectorWindow AddGroup");
+			AssetBundleCollectorSettingData.CreateGroup(selectPackage, "Default Group");
+			FillGroupViewData();
+		}
+		private void RemoveGroupBtn_clicked()
+		{
+			var selectPackage = _packageListView.selectedItem as AssetBundleCollectorPackage;
+			if (selectPackage == null)
+				return;
+
+			var selectGroup = _groupListView.selectedItem as AssetBundleCollectorGroup;
+			if (selectGroup == null)
+				return;
+
+			Undo.RecordObject(AssetBundleCollectorSettingData.Setting, "YooAsset.AssetBundleCollectorWindow RemoveGroup");
+			AssetBundleCollectorSettingData.RemoveGroup(selectPackage, selectGroup);
+			FillGroupViewData();
+		}
+
+		// 收集列表相关
+		private void FillCollectorViewData()
+		{
+			var selectGroup = _groupListView.selectedItem as AssetBundleCollectorGroup;
+			if (selectGroup == null)
+				return;
+
+			// 填充数据
+			_collectorScrollView.Clear();
+			for (int i = 0; i < selectGroup.Collectors.Count; i++)
+			{
+				VisualElement element = MakeCollectorListViewItem();
+				BindCollectorListViewItem(element, i);
+				_collectorScrollView.Add(element);
+			}
+		}
+		private VisualElement MakeCollectorListViewItem()
+		{
+			VisualElement element = new VisualElement();
+
+			VisualElement elementTop = new VisualElement();
+			elementTop.style.flexDirection = FlexDirection.Row;
+			element.Add(elementTop);
+
+			VisualElement elementBottom = new VisualElement();
+			elementBottom.style.flexDirection = FlexDirection.Row;
+			element.Add(elementBottom);
+
+			VisualElement elementFoldout = new VisualElement();
+			elementFoldout.style.flexDirection = FlexDirection.Row;
+			element.Add(elementFoldout);
+
+			VisualElement elementSpace = new VisualElement();
+			elementSpace.style.flexDirection = FlexDirection.Column;
+			element.Add(elementSpace);
+
+			// Top VisualElement
+			{
+				var button = new Button();
+				button.name = "Button1";
+				button.text = "-";
+				button.style.unityTextAlign = TextAnchor.MiddleCenter;
+				button.style.flexGrow = 0f;
+				elementTop.Add(button);
+			}
+			{
+				var objectField = new ObjectField();
+				objectField.name = "ObjectField1";
+				objectField.label = "Collector";
+				objectField.objectType = typeof(UnityEngine.Object);
+				objectField.style.unityTextAlign = TextAnchor.MiddleLeft;
+				objectField.style.flexGrow = 1f;
+				elementTop.Add(objectField);
+				var label = objectField.Q<Label>();
+				label.style.minWidth = 63;
+			}
+
+			// Bottom VisualElement
+			{
+				var label = new Label();
+				label.style.width = 90;
+				elementBottom.Add(label);
+			}
+			{
+				var popupField = new PopupField<string>(_collectorTypeList, 0);
+				popupField.name = "PopupField0";
+				popupField.style.unityTextAlign = TextAnchor.MiddleLeft;
+				popupField.style.width = 150;
+				elementBottom.Add(popupField);
+			}
+			if (_enableAddressableToogle.value)
+			{
+				var popupField = new PopupField<RuleDisplayName>(_addressRuleList, 0);
+				popupField.name = "PopupField1";
+				popupField.style.unityTextAlign = TextAnchor.MiddleLeft;
+				popupField.style.width = 220;
+				elementBottom.Add(popupField);
+			}
+			{
+				var popupField = new PopupField<RuleDisplayName>(_packRuleList, 0);
+				popupField.name = "PopupField2";
+				popupField.style.unityTextAlign = TextAnchor.MiddleLeft;
+				popupField.style.width = 220;
+				elementBottom.Add(popupField);
+			}
+			{
+				var popupField = new PopupField<RuleDisplayName>(_filterRuleList, 0);
+				popupField.name = "PopupField3";
+				popupField.style.unityTextAlign = TextAnchor.MiddleLeft;
+				popupField.style.width = 150;
+				elementBottom.Add(popupField);
+			}
+			{
+				var textField = new TextField();
+				textField.name = "TextField0";
+				textField.label = "UserData";
+				textField.style.width = 200;
+				elementBottom.Add(textField);
+				var label = textField.Q<Label>();
+				label.style.minWidth = 63;
+			}
+			{
+				var textField = new TextField();
+				textField.name = "TextField1";
+				textField.label = "Tags";
+				textField.style.width = 100;
+				textField.style.marginLeft = 20;
+				textField.style.flexGrow = 1;
+				elementBottom.Add(textField);
+				var label = textField.Q<Label>();
+				label.style.minWidth = 40;
+			}
+
+			// Foldout VisualElement
+			{
+				var label = new Label();
+				label.style.width = 90;
+				elementFoldout.Add(label);
+			}
+			{
+				var foldout = new Foldout();
+				foldout.name = "Foldout1";
+				foldout.value = false;
+				foldout.text = "Main Assets";
+				elementFoldout.Add(foldout);
+			}
+
+			// Space VisualElement
+			{
+				var label = new Label();
+				label.style.height = 10;
+				elementSpace.Add(label);
+			}
+
+			return element;
+		}
+		private void BindCollectorListViewItem(VisualElement element, int index)
+		{
+			var selectGroup = _groupListView.selectedItem as AssetBundleCollectorGroup;
+			if (selectGroup == null)
+				return;
+
+			var collector = selectGroup.Collectors[index];
+			var collectObject = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(collector.CollectPath);
+			if (collectObject != null)
+				collectObject.name = collector.CollectPath;
+
+			// Foldout
+			var foldout = element.Q<Foldout>("Foldout1");
+			foldout.RegisterValueChangedCallback(evt =>
+			{
+				if (evt.newValue)
+					RefreshFoldout(foldout, selectGroup, collector);
+				else
+					foldout.Clear();
+			});
+
+			// Remove Button
+			var removeBtn = element.Q<Button>("Button1");
+			removeBtn.clicked += () =>
+			{
+				RemoveCollectorBtn_clicked(collector);
+			};
+
+			// Collector Path
+			var objectField1 = element.Q<ObjectField>("ObjectField1");
+			objectField1.SetValueWithoutNotify(collectObject);
+			objectField1.RegisterValueChangedCallback(evt =>
+			{
+				collector.CollectPath = AssetDatabase.GetAssetPath(evt.newValue);
+				collector.CollectorGUID = AssetDatabase.AssetPathToGUID(collector.CollectPath);
+				objectField1.value.name = collector.CollectPath;
+				AssetBundleCollectorSettingData.ModifyCollector(selectGroup, collector);
+				if (foldout.value)
+				{
+					RefreshFoldout(foldout, selectGroup, collector);
+				}
+			});
+
+			// Collector Type
+			var popupField0 = element.Q<PopupField<string>>("PopupField0");
+			popupField0.index = GetCollectorTypeIndex(collector.CollectorType.ToString());
+			popupField0.RegisterValueChangedCallback(evt =>
+			{
+				collector.CollectorType = EditorTools.NameToEnum<ECollectorType>(evt.newValue);
+				AssetBundleCollectorSettingData.ModifyCollector(selectGroup, collector);
+				if (foldout.value)
+				{
+					RefreshFoldout(foldout, selectGroup, collector);
+				}
+			});
+
+			// Address Rule
+			var popupField1 = element.Q<PopupField<RuleDisplayName>>("PopupField1");
+			if (popupField1 != null)
+			{
+				popupField1.index = GetAddressRuleIndex(collector.AddressRuleName);
+				popupField1.formatListItemCallback = FormatListItemCallback;
+				popupField1.formatSelectedValueCallback = FormatSelectedValueCallback;
+				popupField1.RegisterValueChangedCallback(evt =>
+				{
+					collector.AddressRuleName = evt.newValue.ClassName;
+					AssetBundleCollectorSettingData.ModifyCollector(selectGroup, collector);
+					if (foldout.value)
+					{
+						RefreshFoldout(foldout, selectGroup, collector);
+					}
+				});
+			}
+
+			// Pack Rule
+			var popupField2 = element.Q<PopupField<RuleDisplayName>>("PopupField2");
+			popupField2.index = GetPackRuleIndex(collector.PackRuleName);
+			popupField2.formatListItemCallback = FormatListItemCallback;
+			popupField2.formatSelectedValueCallback = FormatSelectedValueCallback;
+			popupField2.RegisterValueChangedCallback(evt =>
+			{
+				collector.PackRuleName = evt.newValue.ClassName;
+				AssetBundleCollectorSettingData.ModifyCollector(selectGroup, collector);
+				if (foldout.value)
+				{
+					RefreshFoldout(foldout, selectGroup, collector);
+				}
+			});
+
+			// Filter Rule
+			var popupField3 = element.Q<PopupField<RuleDisplayName>>("PopupField3");
+			popupField3.index = GetFilterRuleIndex(collector.FilterRuleName);
+			popupField3.formatListItemCallback = FormatListItemCallback;
+			popupField3.formatSelectedValueCallback = FormatSelectedValueCallback;
+			popupField3.RegisterValueChangedCallback(evt =>
+			{
+				collector.FilterRuleName = evt.newValue.ClassName;
+				AssetBundleCollectorSettingData.ModifyCollector(selectGroup, collector);
+				if (foldout.value)
+				{
+					RefreshFoldout(foldout, selectGroup, collector);
+				}
+			});
+
+			// UserData
+			var textFiled0 = element.Q<TextField>("TextField0");
+			textFiled0.SetValueWithoutNotify(collector.UserData);
+			textFiled0.RegisterValueChangedCallback(evt =>
+			{
+				collector.UserData = evt.newValue;
+				AssetBundleCollectorSettingData.ModifyCollector(selectGroup, collector);
+			});
+
+			// Tags
+			var textFiled1 = element.Q<TextField>("TextField1");
+			textFiled1.SetValueWithoutNotify(collector.AssetTags);
+			textFiled1.RegisterValueChangedCallback(evt =>
+			{
+				collector.AssetTags = evt.newValue;
+				AssetBundleCollectorSettingData.ModifyCollector(selectGroup, collector);
+			});
+		}
+		private void RefreshFoldout(Foldout foldout, AssetBundleCollectorGroup group, AssetBundleCollector collector)
+		{
+			// 清空旧元素
+			foldout.Clear();
+
+			if (collector.IsValid() == false)
+			{
+				Debug.LogWarning($"The collector is invalid : {collector.CollectPath} in group : {group.GroupName}");
+				return;
+			}
+
+			if (collector.CollectorType == ECollectorType.MainAssetCollector || collector.CollectorType == ECollectorType.StaticAssetCollector)
+			{
+				List<CollectAssetInfo> collectAssetInfos = null;
+
+				try
+				{
+					CollectCommand command = new CollectCommand(EBuildMode.SimulateBuild, _packageNameTxt.value,
+						_enableAddressableToogle.value, _locationToLowerToogle.value, _includeAssetGUIDToogle.value, _uniqueBundleNameToogle.value);
+					collectAssetInfos = collector.GetAllCollectAssets(command, group);
+				}
+				catch (System.Exception e)
+				{
+					Debug.LogError(e.ToString());
+				}
+
+				if (collectAssetInfos != null)
+				{
+					foreach (var collectAssetInfo in collectAssetInfos)
+					{
+						VisualElement elementRow = new VisualElement();
+						elementRow.style.flexDirection = FlexDirection.Row;
+						foldout.Add(elementRow);
+
+						string showInfo = collectAssetInfo.AssetPath;
+						if (_enableAddressableToogle.value)
+							showInfo = $"[{collectAssetInfo.Address}] {collectAssetInfo.AssetPath}";
+
+						var label = new Label();
+						label.text = showInfo;
+						label.style.width = 300;
+						label.style.marginLeft = 0;
+						label.style.flexGrow = 1;
+						elementRow.Add(label);
+					}
+				}
+			}
+		}
+		private void AddCollectorBtn_clicked()
+		{
+			var selectGroup = _groupListView.selectedItem as AssetBundleCollectorGroup;
+			if (selectGroup == null)
+				return;
+
+			Undo.RecordObject(AssetBundleCollectorSettingData.Setting, "YooAsset.AssetBundleCollectorWindow AddCollector");
+			AssetBundleCollector collector = new AssetBundleCollector();
+			AssetBundleCollectorSettingData.CreateCollector(selectGroup, collector);
+			FillCollectorViewData();
+		}
+		private void RemoveCollectorBtn_clicked(AssetBundleCollector selectCollector)
+		{
+			var selectGroup = _groupListView.selectedItem as AssetBundleCollectorGroup;
+			if (selectGroup == null)
+				return;
+			if (selectCollector == null)
+				return;
+
+			Undo.RecordObject(AssetBundleCollectorSettingData.Setting, "YooAsset.AssetBundleCollectorWindow RemoveCollector");
+			AssetBundleCollectorSettingData.RemoveCollector(selectGroup, selectCollector);
+			FillCollectorViewData();
+		}
+
+		private int GetCollectorTypeIndex(string typeName)
+		{
+			for (int i = 0; i < _collectorTypeList.Count; i++)
+			{
+				if (_collectorTypeList[i] == typeName)
+					return i;
+			}
+			return 0;
+		}
+		private int GetAddressRuleIndex(string ruleName)
+		{
+			for (int i = 0; i < _addressRuleList.Count; i++)
+			{
+				if (_addressRuleList[i].ClassName == ruleName)
+					return i;
+			}
+			return 0;
+		}
+		private int GetPackRuleIndex(string ruleName)
+		{
+			for (int i = 0; i < _packRuleList.Count; i++)
+			{
+				if (_packRuleList[i].ClassName == ruleName)
+					return i;
+			}
+			return 0;
+		}
+		private int GetFilterRuleIndex(string ruleName)
+		{
+			for (int i = 0; i < _filterRuleList.Count; i++)
+			{
+				if (_filterRuleList[i].ClassName == ruleName)
+					return i;
+			}
+			return 0;
+		}
+		private RuleDisplayName GetActiveRuleIndex(string ruleName)
+		{
+			for (int i = 0; i < _activeRuleList.Count; i++)
+			{
+				if (_activeRuleList[i].ClassName == ruleName)
+					return _activeRuleList[i];
+			}
+			return _activeRuleList[0];
+		}
+	}
+}
+#endif

+ 11 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorWindow.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b730ebd685b28964a9f002593751e98a
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 53 - 0
GameClient/Assets/YooAsset/Editor/AssetBundleCollector/AssetBundleCollectorWindow.uxml

@@ -0,0 +1,53 @@
+<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="True">
+    <uie:Toolbar name="Toolbar" style="display: flex; flex-direction: row-reverse;">
+        <ui:Button text="Save" display-tooltip-when-elided="true" name="SaveButton" style="width: 50px; background-color: rgb(56, 147, 58);" />
+        <ui:Button text="导出" display-tooltip-when-elided="true" name="ExportButton" style="width: 50px; background-color: rgb(56, 147, 58);" />
+        <ui:Button text="导入" display-tooltip-when-elided="true" name="ImportButton" style="width: 50px; background-color: rgb(56, 147, 58);" />
+        <ui:Button text="修复" display-tooltip-when-elided="true" name="FixButton" style="width: 50px; background-color: rgb(56, 147, 58);" />
+    </uie:Toolbar>
+    <ui:VisualElement name="PublicContainer" style="background-color: rgb(79, 79, 79); flex-direction: column; border-left-width: 5px; border-right-width: 5px; border-top-width: 5px; border-bottom-width: 5px;">
+        <ui:VisualElement name="HelpBoxContainer" style="flex-grow: 1;" />
+        <ui:Button text="Settings" display-tooltip-when-elided="true" name="SettingsButton" />
+        <ui:VisualElement name="PublicContainer1" style="flex-direction: row; flex-wrap: nowrap; height: 28px;">
+            <ui:Toggle label="Show Packages" name="ShowPackages" style="width: 196px; -unity-text-align: middle-left;" />
+            <ui:Toggle label="Show Editor Alias" name="ShowEditorAlias" style="width: 196px; -unity-text-align: middle-left;" />
+            <ui:Toggle label="Enable Addressable" name="EnableAddressable" style="width: 196px; -unity-text-align: middle-left;" />
+            <ui:Toggle label="Unique Bundle Name" name="UniqueBundleName" style="width: 196px; -unity-text-align: middle-left;" />
+        </ui:VisualElement>
+        <ui:VisualElement name="PublicContainer2" style="flex-direction: row; flex-wrap: nowrap; height: 28px;">
+            <ui:Toggle label="Location To Lower" name="LocationToLower" style="width: 196px; -unity-text-align: middle-left;" />
+            <ui:Toggle label="Include Asset GUID" name="IncludeAssetGUID" style="width: 196px; -unity-text-align: middle-left;" />
+        </ui:VisualElement>
+    </ui:VisualElement>
+    <ui:VisualElement name="ContentContainer" style="flex-grow: 1; flex-direction: row;">
+        <ui:VisualElement name="PackageContainer" style="width: 200px; flex-grow: 0; background-color: rgb(67, 67, 67); border-left-width: 5px; border-right-width: 5px; border-top-width: 5px; border-bottom-width: 5px;">
+            <ui:Label text="Packages" display-tooltip-when-elided="true" name="PackageTitle" style="background-color: rgb(89, 89, 89); -unity-text-align: upper-center; -unity-font-style: bold; border-left-width: 5px; border-right-width: 5px; border-top-width: 5px; border-bottom-width: 5px; font-size: 12px;" />
+            <ui:ListView focusable="true" name="PackageListView" item-height="20" virtualization-method="DynamicHeight" style="flex-grow: 1;" />
+            <ui:VisualElement name="PackageAddContainer" style="height: 20px; flex-direction: row; justify-content: center;">
+                <ui:Button text=" - " display-tooltip-when-elided="true" name="RemoveBtn" />
+                <ui:Button text=" + " display-tooltip-when-elided="true" name="AddBtn" />
+            </ui:VisualElement>
+        </ui:VisualElement>
+        <ui:VisualElement name="GroupContainer" style="width: 200px; flex-grow: 0; background-color: rgb(67, 67, 67); border-left-width: 5px; border-right-width: 5px; border-top-width: 5px; border-bottom-width: 5px;">
+            <ui:Label text="Groups" display-tooltip-when-elided="true" name="GroupTitle" style="background-color: rgb(89, 89, 89); -unity-text-align: upper-center; -unity-font-style: bold; border-left-width: 5px; border-right-width: 5px; border-top-width: 5px; border-bottom-width: 5px; font-size: 12px;" />
+            <ui:TextField picking-mode="Ignore" label="Package Name" value="filler text" name="PackageName" style="flex-direction: column;" />
+            <ui:TextField picking-mode="Ignore" label="Package Desc" value="filler text" name="PackageDesc" style="flex-direction: column;" />
+            <ui:ListView focusable="true" name="GroupListView" item-height="20" virtualization-method="DynamicHeight" style="flex-grow: 1;" />
+            <ui:VisualElement name="GroupAddContainer" style="height: 20px; flex-direction: row; justify-content: center;">
+                <ui:Button text=" - " display-tooltip-when-elided="true" name="RemoveBtn" />
+                <ui:Button text=" + " display-tooltip-when-elided="true" name="AddBtn" />
+            </ui:VisualElement>
+        </ui:VisualElement>
+        <ui:VisualElement name="CollectorContainer" style="flex-grow: 1; border-left-width: 5px; border-right-width: 5px; border-top-width: 5px; border-bottom-width: 5px;">
+            <ui:Label text="Collectors" display-tooltip-when-elided="true" name="CollectorTitle" style="background-color: rgb(89, 89, 89); -unity-text-align: upper-center; -unity-font-style: bold; border-left-width: 5px; border-right-width: 5px; border-top-width: 5px; border-bottom-width: 5px; font-size: 12px;" />
+            <ui:VisualElement name="ActiveRuleContainer" style="height: 20px;" />
+            <ui:TextField picking-mode="Ignore" label="Group Name" name="GroupName" />
+            <ui:TextField picking-mode="Ignore" label="Group Desc" name="GroupDesc" />
+            <ui:TextField picking-mode="Ignore" label="Group Asset Tags" name="GroupAssetTags" />
+            <ui:VisualElement name="CollectorAddContainer" style="height: 20px; flex-direction: row-reverse;">
+                <ui:Button text="[ + ]" display-tooltip-when-elided="true" name="AddBtn" />
+            </ui:VisualElement>
+            <ui:ScrollView name="CollectorScrollView" style="flex-grow: 1;" />
+        </ui:VisualElement>
+    </ui:VisualElement>
+</ui:UXML>

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно