diff --git a/Assets/TEngine/Editor/Resource.meta b/Assets/TEngine/Editor/Resource.meta new file mode 100644 index 00000000..e836b2bd --- /dev/null +++ b/Assets/TEngine/Editor/Resource.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 88744e5c17541134884ec72227b3f36b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Editor/Resource/YooAsset.meta b/Assets/TEngine/Editor/Resource/YooAsset.meta new file mode 100644 index 00000000..8ec971e3 --- /dev/null +++ b/Assets/TEngine/Editor/Resource/YooAsset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: be031d8b7b27267479e77a19628ac202 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder.meta b/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder.meta new file mode 100644 index 00000000..c1b5f4bc --- /dev/null +++ b/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1fdecc5500229d44887425ce619352fc +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilder.cs b/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilder.cs new file mode 100644 index 00000000..88b82e0b --- /dev/null +++ b/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilder.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; + +namespace YooAsset.Editor +{ + public class AssetBundleBuilder + { + private readonly BuildContext _buildContext = new BuildContext(); + + /// + /// 开始构建 + /// + public BuildResult Run(BuildParameters buildParameters) + { + // 清空旧数据 + _buildContext.ClearAllContext(); + + // 检测构建参数是否为空 + if (buildParameters == null) + throw new Exception($"{nameof(buildParameters)} is null !"); + + // 检测可编程构建管线参数 + 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 !"); + } + + // 构建参数 + var buildParametersContext = new BuildParametersContext(buildParameters); + _buildContext.SetContextObject(buildParametersContext); + + // 创建构建节点 + List pipeline; + if (buildParameters.BuildPipeline == EBuildPipeline.BuiltinBuildPipeline) + { + pipeline = new List + { + new TaskPrepare(), //前期准备工作 + new TaskGetBuildMap(), //获取构建列表 + new TaskBuilding(), //开始执行构建 + new TaskCopyRawFile(), //拷贝原生文件 + new TaskVerifyBuildResult(), //验证构建结果 + new TaskEncryption(), //加密资源文件 + new TaskUpdateBundleInfo(), //更新资源包信息 + new TaskCreateManifest(), //创建清单文件 + new TaskCreateReport(), //创建报告文件 + new TaskCreatePackage(), //制作包裹 + new TaskCopyBuildinFiles(), //拷贝内置文件 + }; + } + else if (buildParameters.BuildPipeline == EBuildPipeline.ScriptableBuildPipeline) + { + pipeline = new List + { + new TaskPrepare(), //前期准备工作 + new TaskGetBuildMap(), //获取构建列表 + new TaskBuilding_SBP(), //开始执行构建 + new TaskCopyRawFile(), //拷贝原生文件 + new TaskVerifyBuildResult_SBP(), //验证构建结果 + new TaskEncryption(), //加密资源文件 + new TaskUpdateBundleInfo(), //更新补丁信息 + new TaskCreateManifest(), //创建清单文件 + new TaskCreateReport(), //创建报告文件 + new TaskCreatePackage(), //制作补丁包 + new TaskCopyBuildinFiles(), //拷贝内置文件 + }; + } + else + { + throw new NotImplementedException(); + } + + // 初始化日志 + BuildLogger.InitLogger(buildParameters.EnableLog); + + // 执行构建流程 + var buildResult = BuildRunner.Run(pipeline, _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($"Build task error : {buildResult.FailedInfo}"); + } + + return buildResult; + } + } +} \ No newline at end of file diff --git a/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilder.cs.meta b/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilder.cs.meta new file mode 100644 index 00000000..8060114a --- /dev/null +++ b/Assets/TEngine/Editor/Resource/YooAsset/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: diff --git a/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderHelper.cs b/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderHelper.cs new file mode 100644 index 00000000..ab0f9336 --- /dev/null +++ b/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderHelper.cs @@ -0,0 +1,62 @@ +using System.Collections; +using System.Collections.Generic; +using System.IO; +using UnityEngine; +using UnityEditor; + +namespace YooAsset.Editor +{ + public static class AssetBundleBuilderHelper + { + /// + /// 获取默认的输出根路录 + /// + public static string GetDefaultOutputRoot() + { + string projectPath = EditorTools.GetProjectPath(); + return $"{projectPath}/Bundles"; + } + + /// + /// 获取流文件夹路径 + /// + public static string GetStreamingAssetsFolderPath() + { + return $"{Application.dataPath}/StreamingAssets/{YooAssetSettings.StreamingAssetsBuildinFolder}/"; + } + + /// + /// 清空流文件夹 + /// + public static void ClearStreamingAssetsFolder() + { + string streamingFolderPath = GetStreamingAssetsFolderPath(); + EditorTools.ClearFolder(streamingFolderPath); + } + + /// + /// 删除流文件夹内无关的文件 + /// 删除.manifest文件和.meta文件 + /// + public static void DeleteStreamingAssetsIgnoreFiles() + { + string streamingFolderPath = GetStreamingAssetsFolderPath(); + if (Directory.Exists(streamingFolderPath)) + { + string[] files = Directory.GetFiles(streamingFolderPath, "*.manifest", SearchOption.AllDirectories); + foreach (var file in files) + { + FileInfo info = new FileInfo(file); + info.Delete(); + } + + files = Directory.GetFiles(streamingFolderPath, "*.meta", SearchOption.AllDirectories); + foreach (var item in files) + { + FileInfo info = new FileInfo(item); + info.Delete(); + } + } + } + } +} \ No newline at end of file diff --git a/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderHelper.cs.meta b/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderHelper.cs.meta new file mode 100644 index 00000000..2cbe1606 --- /dev/null +++ b/Assets/TEngine/Editor/Resource/YooAsset/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: diff --git a/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderSetting.cs b/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderSetting.cs new file mode 100644 index 00000000..19f23d49 --- /dev/null +++ b/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderSetting.cs @@ -0,0 +1,48 @@ +using System; +using UnityEngine; + +namespace YooAsset.Editor +{ + public class AssetBundleBuilderSetting : ScriptableObject + { + /// + /// 构建管线 + /// + public EBuildPipeline BuildPipeline = EBuildPipeline.BuiltinBuildPipeline; + + /// + /// 构建模式 + /// + public EBuildMode BuildMode = EBuildMode.ForceRebuild; + + /// + /// 构建的包裹名称 + /// + public string BuildPackage = string.Empty; + + /// + /// 压缩方式 + /// + public ECompressOption CompressOption = ECompressOption.LZ4; + + /// + /// 输出文件名称样式 + /// + public EOutputNameStyle OutputNameStyle = EOutputNameStyle.HashName; + + /// + /// 首包资源文件的拷贝方式 + /// + public ECopyBuildinFileOption CopyBuildinFileOption = ECopyBuildinFileOption.None; + + /// + /// 首包资源文件的标签集合 + /// + public string CopyBuildinFileTags = string.Empty; + + /// + /// 加密类名称 + /// + public string EncyptionClassName = string.Empty; + } +} \ No newline at end of file diff --git a/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderSetting.cs.meta b/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderSetting.cs.meta new file mode 100644 index 00000000..a8a559c2 --- /dev/null +++ b/Assets/TEngine/Editor/Resource/YooAsset/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: diff --git a/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderSettingData.cs b/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderSettingData.cs new file mode 100644 index 00000000..353931b6 --- /dev/null +++ b/Assets/TEngine/Editor/Resource/YooAsset/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; + } + } + + /// + /// 配置数据是否被修改 + /// + public static bool IsDirty { set; get; } = false; + + /// + /// 加载配置文件 + /// + private static void LoadSettingData() + { + _setting = SettingLoader.LoadSettingData(); + } + + /// + /// 存储文件 + /// + public static void SaveFile() + { + if (Setting != null) + { + IsDirty = false; + EditorUtility.SetDirty(Setting); + AssetDatabase.SaveAssets(); + Debug.Log($"{nameof(AssetBundleBuilderSetting)}.asset is saved!"); + } + } + } +} \ No newline at end of file diff --git a/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderSettingData.cs.meta b/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderSettingData.cs.meta new file mode 100644 index 00000000..4d7c62ec --- /dev/null +++ b/Assets/TEngine/Editor/Resource/YooAsset/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: diff --git a/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderTools.cs b/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderTools.cs new file mode 100644 index 00000000..2216fc4f --- /dev/null +++ b/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderTools.cs @@ -0,0 +1,215 @@ +using System; +using System.IO; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using UnityEditor.Animations; + +namespace YooAsset.Editor +{ + public static class AssetBundleBuilderTools + { + /// + /// 检测所有损坏的预制体文件 + /// + public static void CheckCorruptionPrefab(List searchDirectorys) + { + if (searchDirectorys.Count == 0) + throw new Exception("路径列表不能为空!"); + + // 获取所有资源列表 + int checkCount = 0; + int invalidCount = 0; + string[] findAssets = EditorTools.FindAssets(EAssetSearchType.Prefab, searchDirectorys.ToArray()); + foreach (string assetPath in findAssets) + { + UnityEngine.Object prefab = AssetDatabase.LoadAssetAtPath(assetPath, typeof(UnityEngine.Object)); + if (prefab == null) + { + invalidCount++; + Debug.LogError($"发现损坏预制件:{assetPath}"); + } + EditorTools.DisplayProgressBar("检测预制件文件是否损坏", ++checkCount, findAssets.Length); + } + EditorTools.ClearProgressBar(); + + if (invalidCount == 0) + Debug.Log($"没有发现损坏预制件"); + } + + /// + /// 检测所有动画控制器的冗余状态 + /// + public static void FindRedundantAnimationState(List searchDirectorys) + { + if (searchDirectorys.Count == 0) + throw new Exception("路径列表不能为空!"); + + // 获取所有资源列表 + int checkCount = 0; + int findCount = 0; + string[] findAssets = EditorTools.FindAssets(EAssetSearchType.RuntimeAnimatorController, searchDirectorys.ToArray()); + foreach (string assetPath in findAssets) + { + AnimatorController animator= AssetDatabase.LoadAssetAtPath(assetPath); + if (FindRedundantAnimationState(animator)) + { + findCount++; + Debug.LogWarning($"发现冗余的动画控制器:{assetPath}"); + } + EditorTools.DisplayProgressBar("检测冗余的动画控制器", ++checkCount, findAssets.Length); + } + EditorTools.ClearProgressBar(); + + if (findCount == 0) + Debug.Log($"没有发现冗余的动画控制器"); + else + AssetDatabase.SaveAssets(); + } + + /// + /// 清理所有材质球的冗余属性 + /// + public static void ClearMaterialUnusedProperty(List searchDirectorys) + { + if (searchDirectorys.Count == 0) + throw new Exception("路径列表不能为空!"); + + // 获取所有资源列表 + int checkCount = 0; + int removedCount = 0; + string[] findAssets = EditorTools.FindAssets(EAssetSearchType.Material, searchDirectorys.ToArray()); + foreach (string assetPath in findAssets) + { + Material mat = AssetDatabase.LoadAssetAtPath(assetPath); + if (ClearMaterialUnusedProperty(mat)) + { + removedCount++; + Debug.LogWarning($"材质球已被处理:{assetPath}"); + } + EditorTools.DisplayProgressBar("清理冗余的材质球", ++checkCount, findAssets.Length); + } + EditorTools.ClearProgressBar(); + + if (removedCount == 0) + Debug.Log($"没有发现冗余的材质球"); + else + AssetDatabase.SaveAssets(); + } + + + /// + /// 清理无用的材质球属性 + /// + private static bool ClearMaterialUnusedProperty(Material mat) + { + bool removeUnused = false; + SerializedObject so = new SerializedObject(mat); + SerializedProperty sp = so.FindProperty("m_SavedProperties"); + + sp.Next(true); + do + { + if (sp.isArray == false) + continue; + + for (int i = sp.arraySize - 1; i >= 0; --i) + { + var p1 = sp.GetArrayElementAtIndex(i); + if (p1.isArray) + { + for (int ii = p1.arraySize - 1; ii >= 0; --ii) + { + var p2 = p1.GetArrayElementAtIndex(ii); + var val = p2.FindPropertyRelative("first"); + if (mat.HasProperty(val.stringValue) == false) + { + Debug.Log($"Material {mat.name} remove unused property : {val.stringValue}"); + p1.DeleteArrayElementAtIndex(ii); + removeUnused = true; + } + } + } + else + { + var val = p1.FindPropertyRelative("first"); + if (mat.HasProperty(val.stringValue) == false) + { + Debug.Log($"Material {mat.name} remove unused property : {val.stringValue}"); + sp.DeleteArrayElementAtIndex(i); + removeUnused = true; + } + } + } + } + while (sp.Next(false)); + so.ApplyModifiedProperties(); + return removeUnused; + } + + /// + /// 查找动画控制器里冗余的动画状态机 + /// + private static bool FindRedundantAnimationState(AnimatorController animatorController) + { + if (animatorController == null) + return false; + + string assetPath = AssetDatabase.GetAssetPath(animatorController); + + // 查找使用的状态机名称 + List usedStateNames = new List(); + foreach (var layer in animatorController.layers) + { + foreach (var state in layer.stateMachine.states) + { + usedStateNames.Add(state.state.name); + } + } + + List allLines = new List(); + List stateIndexList = new List(); + using (StreamReader reader = File.OpenText(assetPath)) + { + string content; + while (null != (content = reader.ReadLine())) + { + allLines.Add(content); + if (content.StartsWith("AnimatorState:")) + { + stateIndexList.Add(allLines.Count - 1); + } + } + } + + List allStateNames = new List(); + foreach (var index in stateIndexList) + { + for (int i = index; i < allLines.Count; i++) + { + string content = allLines[i]; + content = content.Trim(); + if (content.StartsWith("m_Name")) + { + string[] splits = content.Split(':'); + string name = splits[1].TrimStart(' '); //移除前面的空格 + allStateNames.Add(name); + break; + } + } + } + + bool foundRedundantState = false; + foreach (var stateName in allStateNames) + { + if (usedStateNames.Contains(stateName) == false) + { + Debug.LogWarning($"发现冗余的动画文件:{assetPath}={stateName}"); + foundRedundantState = true; + } + } + return foundRedundantState; + } + } +} \ No newline at end of file diff --git a/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderTools.cs.meta b/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderTools.cs.meta new file mode 100644 index 00000000..769e38e0 --- /dev/null +++ b/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderTools.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fe50795c51a46884088139b840c1557f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderWindow.cs b/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderWindow.cs new file mode 100644 index 00000000..9e4b8eae --- /dev/null +++ b/Assets/TEngine/Editor/Resource/YooAsset/AssetBundleBuilder/AssetBundleBuilderWindow.cs @@ -0,0 +1,358 @@ +#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 ShowExample() + { + AssetBundleBuilderWindow window = GetWindow("资源包构建工具", true, WindowsDefine.DockedWindowTypes); + window.minSize = new Vector2(800, 600); + } + + private BuildTarget _buildTarget; + private List _encryptionServicesClassTypes; + private List _encryptionServicesClassNames; + private List _buildPackageNames; + + private Button _saveButton; + private TextField _buildOutputField; + private EnumField _buildPipelineField; + private EnumField _buildModeField; + private TextField _buildVersionField; + private PopupField _buildPackageField; + private PopupField _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(); + if (visualAsset == null) + return; + + visualAsset.CloneTree(root); + + // 配置保存按钮 + _saveButton = root.Q