Initial commit

pull/4/head
hevinci 2022-03-01 10:44:12 +08:00
parent 1d54164bfb
commit e66853fd46
237 changed files with 14462 additions and 2 deletions

8
Assets/YooAsset.meta Normal file
View File

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

View File

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

View File

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

View File

@ -0,0 +1,8 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AssetBundleBrowserWindow
{
}

View File

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

View File

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

View File

@ -0,0 +1,197 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using YooAsset.Utility;
namespace YooAsset.Editor
{
public class AssetBundleBuilder
{
/// <summary>
/// 构建参数
/// </summary>
public class BuildParameters
{
/// <summary>
/// 是否验证构建结果
/// </summary>
public bool IsVerifyBuildingResult = false;
/// <summary>
/// 输出的根目录
/// </summary>
public string OutputRoot;
/// <summary>
/// 构建的平台
/// </summary>
public BuildTarget BuildTarget;
/// <summary>
/// 构建的版本(资源版本号)
/// </summary>
public int BuildVersion;
/// <summary>
/// 是否允许冗余机制
/// 说明:冗余机制可以帮助我们减少包体数量
/// </summary>
public bool ApplyRedundancy = false;
/// <summary>
/// 是否附加上文件扩展名
/// </summary>
public bool AppendFileExtension = false;
/// <summary>
/// 压缩选项
/// </summary>
public ECompressOption CompressOption;
/// <summary>
/// 是否强制重新构建整个项目如果为FALSE则是增量打包
/// </summary>
public bool IsForceRebuild;
/// <summary>
/// 内置资源的标记列表
/// 注意:分号为分隔符
/// </summary>
public string BuildinTags;
#region 高级选项
/// <summary>
/// 文件名附加上哈希值
/// </summary>
public bool IsAppendHash = false;
/// <summary>
/// 禁止写入类型树结构(可以降低包体和内存并提高加载效率)
/// </summary>
public bool IsDisableWriteTypeTree = false;
/// <summary>
/// 忽略类型树变化
/// </summary>
public bool IsIgnoreTypeTreeChanges = true;
/// <summary>
/// 禁用名称查找资源(可以降内存并提高加载效率)
/// </summary>
public bool IsDisableLoadAssetByFileName = false;
#endregion
/// <summary>
/// 获取内置标记列表
/// </summary>
public List<string> GetBuildinTags()
{
return StringUtility.StringToStringList(BuildinTags, ';');
}
}
/// <summary>
/// 构建参数环境
/// </summary>
public class BuildParametersContext : IContextObject
{
/// <summary>
/// 构建参数
/// </summary>
public BuildParameters Parameters { private set; get; }
/// <summary>
/// 构建管线的输出目录
/// </summary>
public string PipelineOutputDirectory { private set; get; }
public BuildParametersContext(BuildParameters parameters)
{
Parameters = parameters;
PipelineOutputDirectory = AssetBundleBuilderHelper.MakePipelineOutputDirectory(parameters.OutputRoot, parameters.BuildTarget);
}
/// <summary>
/// 获取本次构建的补丁目录
/// </summary>
public string GetPackageDirectory()
{
return $"{Parameters.OutputRoot}/{Parameters.BuildTarget}/{Parameters.BuildVersion}";
}
/// <summary>
/// 获取构建选项
/// </summary>
public BuildAssetBundleOptions GetPipelineBuildOptions()
{
// For the new build system, unity always need BuildAssetBundleOptions.CollectDependencies and BuildAssetBundleOptions.DeterministicAssetBundle
// 除非设置ForceRebuildAssetBundle标记否则会进行增量打包
BuildAssetBundleOptions opt = BuildAssetBundleOptions.None;
opt |= BuildAssetBundleOptions.StrictMode; //Do not allow the build to succeed if any errors are reporting during it.
if (Parameters.CompressOption == ECompressOption.Uncompressed)
opt |= BuildAssetBundleOptions.UncompressedAssetBundle;
else if (Parameters.CompressOption == ECompressOption.LZ4)
opt |= BuildAssetBundleOptions.ChunkBasedCompression;
if (Parameters.IsForceRebuild)
opt |= BuildAssetBundleOptions.ForceRebuildAssetBundle; //Force rebuild the asset bundles
if (Parameters.IsAppendHash)
opt |= BuildAssetBundleOptions.AppendHashToAssetBundleName; //Append the hash to the assetBundle name
if (Parameters.IsDisableWriteTypeTree)
opt |= BuildAssetBundleOptions.DisableWriteTypeTree; //Do not include type information within the asset bundle (don't write type tree).
if (Parameters.IsIgnoreTypeTreeChanges)
opt |= BuildAssetBundleOptions.IgnoreTypeTreeChanges; //Ignore the type tree changes when doing the incremental build check.
if (Parameters.IsDisableLoadAssetByFileName)
{
opt |= BuildAssetBundleOptions.DisableLoadAssetByFileName; //Disables Asset Bundle LoadAsset by file name.
opt |= BuildAssetBundleOptions.DisableLoadAssetByFileNameWithExtension; //Disables Asset Bundle LoadAsset by file name with extension.
}
return opt;
}
}
private readonly BuildContext _buildContext = new BuildContext();
/// <summary>
/// 开始构建
/// </summary>
public bool Run(BuildParameters buildParameters)
{
// 清空旧数据
_buildContext.ClearAllContext();
// 构建参数
var buildParametersContext = new BuildParametersContext(buildParameters);
_buildContext.SetContextObject(buildParametersContext);
// 执行构建流程
List<IBuildTask> pipeline = new List<IBuildTask>
{
new TaskPrepare(), //前期准备工作
new TaskGetBuildMap(), //获取构建列表
new TaskBuilding(), //开始执行构建
new TaskEncryption(), //加密资源文件
new TaskCreatePatchManifest(), //创建清单文件
new TaskCreateReadme(), //创建说明文件
new TaskCreatePatchPackage(), //制作补丁包
new TaskCopyBuildinFiles(), //拷贝内置文件
};
bool succeed = BuildRunner.Run(pipeline, _buildContext);
if (succeed)
Debug.Log($"构建成功!");
else
Debug.LogWarning($"构建失败!");
return succeed;
}
}
}

View File

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

View File

@ -0,0 +1,140 @@
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEditor;
using YooAsset.Utility;
namespace YooAsset.Editor
{
public static class AssetBundleBuilderHelper
{
/// <summary>
/// 获取默认的输出根路录
/// </summary>
public static string GetDefaultOutputRoot()
{
string projectPath = EditorTools.GetProjectPath();
return $"{projectPath}/Bundles";
}
/// <summary>
/// 获取构建管线的输出目录
/// </summary>
public static string MakePipelineOutputDirectory(string outputRoot, BuildTarget buildTarget)
{
return $"{outputRoot}/{buildTarget}/{ResourceSettingData.Setting.UnityManifestFileName}";
}
/// <summary>
/// 制作AssetBundle的完整名称
/// 注意:名称为全部小写并且包含后缀名
/// </summary>
public static string MakeBundleName(string bundleLabel, string bundleVariant)
{
if (string.IsNullOrEmpty(bundleVariant))
return bundleLabel.ToLower();
else
return $"{bundleLabel}.{bundleVariant}".ToLower();
}
/// <summary>
/// 清空流文件夹
/// </summary>
public static void ClearStreamingAssetsFolder()
{
string streamingPath = Application.dataPath + "/StreamingAssets";
EditorTools.ClearFolder(streamingPath);
}
/// <summary>
/// 删除流文件夹内无关的文件
/// 删除.manifest文件和.meta文件
/// </summary>
public static void DeleteStreamingAssetsIgnoreFiles()
{
string streamingPath = Application.dataPath + "/StreamingAssets";
if (Directory.Exists(streamingPath))
{
string[] files = Directory.GetFiles(streamingPath, "*.manifest", SearchOption.AllDirectories);
foreach (var file in files)
{
FileInfo info = new FileInfo(file);
info.Delete();
}
files = Directory.GetFiles(streamingPath, "*.meta", SearchOption.AllDirectories);
foreach (var item in files)
{
FileInfo info = new FileInfo(item);
info.Delete();
}
}
}
/// <summary>
/// 获取所有补丁包版本列表
/// 注意:列表会按照版本号从小到大排序
/// </summary>
private static List<int> GetPackageVersionList(BuildTarget buildTarget, string outputRoot)
{
List<int> versionList = new List<int>();
string parentPath = $"{outputRoot}/{buildTarget}";
if (Directory.Exists(parentPath) == false)
return versionList;
// 获取所有补丁包文件夹
string[] allFolders = Directory.GetDirectories(parentPath);
for (int i = 0; i < allFolders.Length; i++)
{
string folderName = Path.GetFileNameWithoutExtension(allFolders[i]);
if (int.TryParse(folderName, out int version))
versionList.Add(version);
}
// 从小到大排序
versionList.Sort();
return versionList;
}
/// <summary>
/// 获取当前最大的补丁包版本号
/// </summary>
/// <returns>如果没有任何补丁版本,那么返回-1</returns>
public static int GetMaxPackageVersion(BuildTarget buildTarget, string outputRoot)
{
List<int> versionList = GetPackageVersionList(buildTarget, outputRoot);
if (versionList.Count == 0)
return -1;
return versionList[versionList.Count - 1];
}
/// <summary>
/// 是否存在任何补丁包版本
/// </summary>
public static bool HasAnyPackageVersion(BuildTarget buildTarget, string outputRoot)
{
List<int> versionList = GetPackageVersionList(buildTarget, outputRoot);
return versionList.Count > 0;
}
/// <summary>
/// 从输出目录加载补丁清单文件
/// </summary>
public static PatchManifest LoadPatchManifestFile(string fileDirectory)
{
string filePath = $"{fileDirectory}/{ResourceSettingData.Setting.PatchManifestFileName}";
if (File.Exists(filePath) == false)
{
throw new System.Exception($"Not found patch manifest file : {filePath}");
}
string jsonData = FileUtility.ReadFile(filePath);
return PatchManifest.Deserialize(jsonData);
}
}
}

View File

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

View File

@ -0,0 +1,101 @@
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
{
/// <summary>
/// 检测所有损坏的预制体文件
/// </summary>
public static void CheckCorruptionPrefab(List<string> 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($"没有发现损坏预制件");
}
/// <summary>
/// 检测所有动画控制器的冗余状态
/// </summary>
public static void FindRedundantAnimationState(List<string> 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<AnimatorController>(assetPath);
if (EditorTools.FindRedundantAnimationState(animator))
{
findCount++;
Debug.LogWarning($"发现冗余的动画控制器:{assetPath}");
}
EditorTools.DisplayProgressBar("检测冗余的动画控制器", ++checkCount, findAssets.Length);
}
EditorTools.ClearProgressBar();
if (findCount == 0)
Debug.Log($"没有发现冗余的动画控制器");
else
AssetDatabase.SaveAssets();
}
/// <summary>
/// 清理所有材质球的冗余属性
/// </summary>
public static void ClearMaterialUnusedProperty(List<string> 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<Material>(assetPath);
if (EditorTools.ClearMaterialUnusedProperty(mat))
{
removedCount++;
Debug.LogWarning($"材质球已被处理:{assetPath}");
}
EditorTools.DisplayProgressBar("清理冗余的材质球", ++checkCount, findAssets.Length);
}
EditorTools.ClearProgressBar();
if (removedCount == 0)
Debug.Log($"没有发现冗余的材质球");
else
AssetDatabase.SaveAssets();
}
}
}

View File

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

View File

@ -0,0 +1,158 @@
using System;
using System.IO;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace YooAsset.Editor
{
public class AssetBundleBuilderWindow : EditorWindow
{
static AssetBundleBuilderWindow _thisInstance;
[MenuItem("YooAsset/AssetBundle Builder", false, 102)]
static void ShowWindow()
{
if (_thisInstance == null)
{
_thisInstance = EditorWindow.GetWindow(typeof(AssetBundleBuilderWindow), false, "资源包构建工具", true) as AssetBundleBuilderWindow;
_thisInstance.minSize = new Vector2(800, 600);
}
_thisInstance.Show();
}
// 构建器
private readonly AssetBundleBuilder _assetBuilder = new AssetBundleBuilder();
// 构建参数
private int _buildVersion;
private BuildTarget _buildTarget;
private ECompressOption _compressOption = ECompressOption.Uncompressed;
private bool _isAppendExtension = false;
private bool _isForceRebuild = false;
private string _buildinTags = string.Empty;
// GUI相关
private bool _isInit = false;
private GUIStyle _centerStyle;
private GUIStyle _leftStyle;
private void OnGUI()
{
InitInternal();
// 标题
EditorGUILayout.LabelField("Build setup", _centerStyle);
EditorGUILayout.Space();
// 输出路径
string defaultOutputRoot = AssetBundleBuilderHelper.GetDefaultOutputRoot();
string pipelineOutputDirectory = AssetBundleBuilderHelper.MakePipelineOutputDirectory(defaultOutputRoot, _buildTarget);
EditorGUILayout.LabelField("Build Output", pipelineOutputDirectory);
// 构建参数
_buildVersion = EditorGUILayout.IntField("Build Version", _buildVersion, GUILayout.MaxWidth(250));
_compressOption = (ECompressOption)EditorGUILayout.EnumPopup("Compression", _compressOption, GUILayout.MaxWidth(250));
_isAppendExtension = GUILayout.Toggle(_isAppendExtension, "Append Extension", GUILayout.MaxWidth(120));
_isForceRebuild = GUILayout.Toggle(_isForceRebuild, "Force Rebuild", GUILayout.MaxWidth(120));
if (_isForceRebuild)
_buildinTags = EditorGUILayout.TextField("Buildin Tags", _buildinTags);
// 构建按钮
EditorGUILayout.Space();
if (GUILayout.Button("Build", GUILayout.MaxHeight(40)))
{
string title;
string content;
if (_isForceRebuild)
{
title = "警告";
content = "确定开始强制构建吗,这样会删除所有已有构建的文件";
}
else
{
title = "提示";
content = "确定开始增量构建吗";
}
if (EditorUtility.DisplayDialog(title, content, "Yes", "No"))
{
SaveSettingsToPlayerPrefs();
EditorTools.ClearUnityConsole();
EditorApplication.delayCall += ExecuteBuild;
}
else
{
Debug.LogWarning("[Build] 打包已经取消");
}
}
}
private void InitInternal()
{
if (_isInit)
return;
_isInit = true;
// GUI相关
_centerStyle = new GUIStyle(GUI.skin.GetStyle("Label"));
_centerStyle.alignment = TextAnchor.UpperCenter;
_leftStyle = new GUIStyle(GUI.skin.GetStyle("Label"));
_leftStyle.alignment = TextAnchor.MiddleLeft;
// 构建参数
var appVersion = new Version(Application.version);
_buildVersion = appVersion.Revision;
_buildTarget = EditorUserBuildSettings.activeBuildTarget;
// 读取配置
LoadSettingsFromPlayerPrefs();
}
/// <summary>
/// 执行构建
/// </summary>
private void ExecuteBuild()
{
string defaultOutputRoot = AssetBundleBuilderHelper.GetDefaultOutputRoot();
AssetBundleBuilder.BuildParameters buildParameters = new AssetBundleBuilder.BuildParameters();
buildParameters.IsVerifyBuildingResult = true;
buildParameters.OutputRoot = defaultOutputRoot;
buildParameters.BuildTarget = _buildTarget;
buildParameters.BuildVersion = _buildVersion;
buildParameters.CompressOption = _compressOption;
buildParameters.AppendFileExtension = _isAppendExtension;
buildParameters.IsForceRebuild = _isForceRebuild;
buildParameters.BuildinTags = _buildinTags;
_assetBuilder.Run(buildParameters);
}
#region 配置相关
private const string StrEditorCompressOption = "StrEditorCompressOption";
private const string StrEditorIsAppendExtension = "StrEditorIsAppendExtension";
private const string StrEditorIsForceRebuild = "StrEditorIsForceRebuild";
private const string StrEditorBuildinTags = "StrEditorBuildinTags";
/// <summary>
/// 存储配置
/// </summary>
private void SaveSettingsToPlayerPrefs()
{
EditorTools.PlayerSetEnum<ECompressOption>(StrEditorCompressOption, _compressOption);
EditorPrefs.SetBool(StrEditorIsAppendExtension, _isAppendExtension);
EditorPrefs.SetBool(StrEditorIsForceRebuild, _isForceRebuild);
EditorPrefs.SetString(StrEditorBuildinTags, _buildinTags);
}
/// <summary>
/// 读取配置
/// </summary>
private void LoadSettingsFromPlayerPrefs()
{
_compressOption = EditorTools.PlayerGetEnum<ECompressOption>(StrEditorCompressOption, ECompressOption.Uncompressed);
_isAppendExtension = EditorPrefs.GetBool(StrEditorIsAppendExtension, false);
_isForceRebuild = EditorPrefs.GetBool(StrEditorIsForceRebuild, false);
_buildinTags = EditorPrefs.GetString(StrEditorBuildinTags, string.Empty);
}
#endregion
}
}

View File

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

View File

@ -0,0 +1,117 @@
using System.Collections;
using System.Collections.Generic;
namespace YooAsset.Editor
{
/// <summary>
/// 构建的资源信息类
/// </summary>
public class BuildAssetInfo
{
/// <summary>
/// 资源路径
/// </summary>
public string AssetPath { private set; get; }
/// <summary>
/// 资源包标签
/// </summary>
public string BundleLabel { private set; get; }
/// <summary>
/// 资源包文件格式
/// </summary>
public string BundleVariant { private set; get; }
/// <summary>
/// 是否为原生资源
/// </summary>
public bool IsRawAsset = false;
/// <summary>
/// 是否为主动收集资源
/// </summary>
public bool IsCollectAsset = false;
/// <summary>
/// 资源标记列表
/// </summary>
public List<string> AssetTags = new List<string>();
/// <summary>
/// 被依赖次数
/// </summary>
public int DependCount = 0;
/// <summary>
/// 依赖的所有资源信息
/// 注意:包括零依赖资源(零依赖资源的资源包名无效)
/// </summary>
public List<BuildAssetInfo> AllDependAssetInfos { private set; get; } = null;
public BuildAssetInfo(string assetPath)
{
AssetPath = assetPath;
}
/// <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 SetBundleLabelAndVariant(string bundleLabel, string bundleVariant)
{
if (string.IsNullOrEmpty(BundleLabel) == false || string.IsNullOrEmpty(BundleVariant) == false)
throw new System.Exception("Should never get here !");
BundleLabel = bundleLabel;
BundleVariant = bundleVariant;
}
/// <summary>
/// 添加资源标记
/// </summary>
public void AddAssetTags(List<string> tags)
{
foreach (var tag in tags)
{
if (AssetTags.Contains(tag) == false)
{
AssetTags.Add(tag);
}
}
}
/// <summary>
/// 获取资源包的完整名称
/// </summary>
public string GetBundleName()
{
if (string.IsNullOrEmpty(BundleLabel) || string.IsNullOrEmpty(BundleVariant))
throw new System.ArgumentNullException();
return AssetBundleBuilderHelper.MakeBundleName(BundleLabel, BundleVariant);
}
/// <summary>
/// 检测资源包名是否有效
/// </summary>
public bool CheckBundleNameValid()
{
if (string.IsNullOrEmpty(BundleLabel) == false && string.IsNullOrEmpty(BundleVariant) == false)
return true;
else
return false;
}
}
}

View File

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

View File

@ -0,0 +1,148 @@
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
namespace YooAsset.Editor
{
/// <summary>
/// 构建的资源包信息类
/// </summary>
public class BuildBundleInfo
{
/// <summary>
/// 资源包完整名称
/// </summary>
public string BundleName { private set; get; }
/// <summary>
/// 资源包标签名
/// </summary>
public string BundleLabel { private set; get; }
/// <summary>
/// 资源包文件格式
/// </summary>
public string BundleVariant { private set; get; }
/// <summary>
/// 包含的资源列表
/// </summary>
public readonly List<BuildAssetInfo> Assets = new List<BuildAssetInfo>();
/// <summary>
/// 是否为原生文件
/// </summary>
public bool IsRawFile
{
get
{
foreach (var asset in Assets)
{
if (asset.IsRawAsset)
return true;
}
return false;
}
}
public BuildBundleInfo(string bundleLabel, string bundleVariant)
{
BundleLabel = bundleLabel;
BundleVariant = bundleVariant;
BundleName = AssetBundleBuilderHelper.MakeBundleName(bundleLabel, bundleVariant);
}
/// <summary>
/// 是否包含指定资源
/// </summary>
public bool IsContainsAsset(string assetPath)
{
foreach (var assetInfo in Assets)
{
if (assetInfo.AssetPath == assetPath)
{
return true;
}
}
return false;
}
/// <summary>
/// 添加一个打包资源
/// </summary>
public void PackAsset(BuildAssetInfo assetInfo)
{
if (IsContainsAsset(assetInfo.AssetPath))
throw new System.Exception($"Asset is existed : {assetInfo.AssetPath}");
Assets.Add(assetInfo);
}
/// <summary>
/// 获取文件的扩展名
/// </summary>
public string GetAppendExtension()
{
if (IsRawFile)
return $".{ResourceSettingData.Setting.RawFileVariant}";
else
return $".{ResourceSettingData.Setting.AssetBundleFileVariant}";
}
/// <summary>
/// 获取资源标记列表
/// </summary>
public string[] GetAssetTags()
{
List<string> result = new List<string>(Assets.Count);
foreach (var assetInfo in Assets)
{
foreach (var assetTag in assetInfo.AssetTags)
{
if (result.Contains(assetTag) == false)
result.Add(assetTag);
}
}
return result.ToArray();
}
/// <summary>
/// 获取主动收集的资源路径列表
/// </summary>
public string[] GetCollectAssetPaths()
{
return Assets.Where(t => t.IsCollectAsset).Select(t => t.AssetPath).ToArray();
}
/// <summary>
/// 获取构建的资源路径列表
/// </summary>
public string[] GetBuildinAssetPaths()
{
return Assets.Select(t => t.AssetPath).ToArray();
}
/// <summary>
/// 获取主动收集的资源信息列表
/// </summary>
public BuildAssetInfo[] GetCollectAssetInfos()
{
return Assets.Where(t => t.IsCollectAsset).ToArray();
}
/// <summary>
/// 创建AssetBundleBuild类
/// </summary>
public UnityEditor.AssetBundleBuild CreatePipelineBuild()
{
// 注意我们不在支持AssetBundle的变种机制
AssetBundleBuild build = new AssetBundleBuild();
build.assetBundleName = BundleName;
build.assetBundleVariant = string.Empty;
build.assetNames = GetBuildinAssetPaths();
return build;
}
}
}

View File

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

View File

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

View File

@ -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}");
}
}
}
}

View File

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

View File

@ -0,0 +1,41 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace YooAsset.Editor
{
public class BuildRunner
{
/// <summary>
/// 执行构建流程
/// </summary>
/// <returns>如果成功返回TRUE否则返回FALSE</returns>
public static bool Run(List<IBuildTask> pipeline, BuildContext context)
{
if (pipeline == null)
throw new ArgumentNullException("pipeline");
if (context == null)
throw new ArgumentNullException("context");
bool succeed = true;
for (int i = 0; i < pipeline.Count; i++)
{
IBuildTask task = pipeline[i];
try
{
task.Run(context);
}
catch (Exception e)
{
Debug.LogError($"Build task {task.GetType().Name} failed : {e}");
succeed = false;
break;
}
}
// 返回运行结果
return succeed;
}
}
}

View File

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

View File

@ -0,0 +1,8 @@

namespace YooAsset.Editor
{
public interface IBuildTask
{
void Run(BuildContext context);
}
}

View File

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

View File

@ -0,0 +1,7 @@

namespace YooAsset.Editor
{
public interface IContextObject
{
}
}

View File

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

View File

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

View File

@ -0,0 +1,194 @@
using System;
using System.Linq;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace YooAsset.Editor
{
public class TaskBuilding : IBuildTask
{
public class UnityManifestContext : IContextObject
{
public AssetBundleManifest UnityManifest;
}
void IBuildTask.Run(BuildContext context)
{
var buildParametersContext = context.GetContextObject<AssetBundleBuilder.BuildParametersContext>();
var buildMapContext = context.GetContextObject<TaskGetBuildMap.BuildMapContext>();
Debug.Log($"开始构建......");
BuildAssetBundleOptions opt = buildParametersContext.GetPipelineBuildOptions();
AssetBundleManifest unityManifest = BuildPipeline.BuildAssetBundles(buildParametersContext.PipelineOutputDirectory, buildMapContext.GetPipelineBuilds(), opt, buildParametersContext.Parameters.BuildTarget);
if (unityManifest == null)
throw new Exception("构建过程中发生错误!");
UnityManifestContext unityManifestContext = new UnityManifestContext();
unityManifestContext.UnityManifest = unityManifest;
context.SetContextObject(unityManifestContext);
// 拷贝原生文件
foreach (var bundleInfo in buildMapContext.BundleInfos)
{
if (bundleInfo.IsRawFile)
{
string dest = $"{buildParametersContext.PipelineOutputDirectory}/{bundleInfo.BundleName}";
foreach(var buildAsset in bundleInfo.Assets)
{
if(buildAsset.IsRawAsset)
EditorTools.CopyFile(buildAsset.AssetPath, dest, true);
}
}
}
// 验证构建结果
if (buildParametersContext.Parameters.IsVerifyBuildingResult)
{
VerifyingBuildingResult(context, unityManifest);
}
}
/// <summary>
/// 验证构建结果
/// </summary>
private void VerifyingBuildingResult(BuildContext context, AssetBundleManifest unityManifest)
{
var buildParameters = context.GetContextObject<AssetBundleBuilder.BuildParametersContext>();
var buildMapContext = context.GetContextObject<TaskGetBuildMap.BuildMapContext>();
string[] buildedBundles = unityManifest.GetAllAssetBundles();
// 1. 过滤掉原生Bundle
List<BuildBundleInfo> expectBundles = new List<BuildBundleInfo>(buildedBundles.Length);
foreach(var bundleInfo in buildMapContext.BundleInfos)
{
if (bundleInfo.IsRawFile == false)
expectBundles.Add(bundleInfo);
}
// 2. 验证数量
if (buildedBundles.Length != expectBundles.Count)
{
Debug.LogWarning($"构建过程中可能存在无效的资源导致和预期构建的Bundle数量不一致");
}
// 3. 正向验证Bundle
foreach (var bundleName in buildedBundles)
{
if (buildMapContext.IsContainsBundle(bundleName) == false)
{
throw new Exception($"Should never get here !");
}
}
// 4. 反向验证Bundle
bool isPass = true;
foreach (var expectBundle in expectBundles)
{
bool isMatch = false;
foreach (var buildedBundle in buildedBundles)
{
if (buildedBundle == expectBundle.BundleName)
{
isMatch = true;
break;
}
}
if (isMatch == false)
{
isPass = false;
Debug.LogWarning($"没有找到预期构建的Bundle文件 : {expectBundle.BundleName}");
}
}
if(isPass == false)
{
throw new Exception("构建结果验证没有通过,请参考警告日志!");
}
// 5. 验证Asset
int progressValue = 0;
foreach (var buildedBundle in buildedBundles)
{
string filePath = $"{buildParameters.PipelineOutputDirectory}/{buildedBundle}";
string[] allBuildinAssetPaths = GetAssetBundleAllAssets(filePath);
string[] expectBuildinAssetPaths = buildMapContext.GetBuildinAssetPaths(buildedBundle);
if (expectBuildinAssetPaths.Length != allBuildinAssetPaths.Length)
{
Debug.LogWarning($"构建的Bundle文件内的资源对象数量和预期不匹配 : {buildedBundle}");
isPass = false;
continue;
}
foreach (var buildinAssetPath in allBuildinAssetPaths)
{
var guid = AssetDatabase.AssetPathToGUID(buildinAssetPath);
if (string.IsNullOrEmpty(guid))
{
Debug.LogWarning($"无效的资源路径,请检查路径是否带有特殊符号或中文:{buildinAssetPath}");
isPass = false;
continue;
}
bool isMatch = false;
foreach (var exceptBuildAssetPath in expectBuildinAssetPaths)
{
var guidExcept = AssetDatabase.AssetPathToGUID(exceptBuildAssetPath);
if (guid == guidExcept)
{
isMatch = true;
break;
}
}
if (isMatch == false)
{
Debug.LogWarning($"在构建的Bundle文件里发现了没有匹配的资源对象{buildinAssetPath}");
isPass = false;
continue;
}
}
EditorTools.DisplayProgressBar("验证构建结果", ++progressValue, buildedBundles.Length);
}
EditorTools.ClearProgressBar();
if (isPass == false)
{
throw new Exception("构建结果验证没有通过,请参考警告日志!");
}
// 卸载所有加载的Bundle
Debug.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();
}
}
}

View File

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

View File

@ -0,0 +1,64 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace YooAsset.Editor
{
/// <summary>
/// 拷贝内置文件到StreamingAssets
/// </summary>
public class TaskCopyBuildinFiles : IBuildTask
{
void IBuildTask.Run(BuildContext context)
{
// 注意:我们只有在强制重建的时候才会拷贝
var buildParameters = context.GetContextObject<AssetBundleBuilder.BuildParametersContext>();
if(buildParameters.Parameters.IsForceRebuild)
{
// 清空流目录
AssetBundleBuilderHelper.ClearStreamingAssetsFolder();
// 拷贝内置文件
var pipelineOutputDirectory = buildParameters.PipelineOutputDirectory;
CopyBuildinFilesToStreaming(pipelineOutputDirectory);
}
}
private void CopyBuildinFilesToStreaming(string pipelineOutputDirectory)
{
// 加载补丁清单
PatchManifest patchManifest = AssetBundleBuilderHelper.LoadPatchManifestFile(pipelineOutputDirectory);
// 拷贝文件列表
foreach (var patchBundle in patchManifest.BundleList)
{
if (patchBundle.IsBuildin == false)
continue;
string sourcePath = $"{pipelineOutputDirectory}/{patchBundle.BundleName}";
string destPath = $"{Application.dataPath}/StreamingAssets/{patchBundle.Hash}";
Debug.Log($"拷贝内置文件到流目录:{patchBundle.BundleName}");
EditorTools.CopyFile(sourcePath, destPath, true);
}
// 拷贝清单文件
{
string sourcePath = $"{pipelineOutputDirectory}/{ResourceSettingData.Setting.PatchManifestFileName}";
string destPath = $"{Application.dataPath}/StreamingAssets/{ResourceSettingData.Setting.PatchManifestFileName}";
EditorTools.CopyFile(sourcePath, destPath, true);
}
// 拷贝清单哈希文件
{
string sourcePath = $"{pipelineOutputDirectory}/{ResourceSettingData.Setting.PatchManifestHashFileName}";
string destPath = $"{Application.dataPath}/StreamingAssets/{ResourceSettingData.Setting.PatchManifestHashFileName}";
EditorTools.CopyFile(sourcePath, destPath, true);
}
// 刷新目录
AssetDatabase.Refresh();
}
}
}

View File

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

View File

@ -0,0 +1,155 @@
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using YooAsset.Utility;
namespace YooAsset.Editor
{
/// <summary>
/// 创建补丁清单文件
/// </summary>
public class TaskCreatePatchManifest : IBuildTask
{
void IBuildTask.Run(BuildContext context)
{
var buildParameters = context.GetContextObject<AssetBundleBuilder.BuildParametersContext>();
var encryptionContext = context.GetContextObject<TaskEncryption.EncryptionContext>();
var buildMapContext = context.GetContextObject<TaskGetBuildMap.BuildMapContext>();
CreatePatchManifestFile(buildParameters, buildMapContext, encryptionContext);
}
/// <summary>
/// 创建补丁清单文件到输出目录
/// </summary>
private void CreatePatchManifestFile(AssetBundleBuilder.BuildParametersContext buildParameters,
TaskGetBuildMap.BuildMapContext buildMapContext, TaskEncryption.EncryptionContext encryptionContext)
{
// 创建新补丁清单
PatchManifest patchManifest = new PatchManifest();
patchManifest.ResourceVersion = buildParameters.Parameters.BuildVersion;
patchManifest.BuildinTags = buildParameters.Parameters.BuildinTags;
patchManifest.BundleList = GetAllPatchBundle(buildParameters, buildMapContext, encryptionContext);
patchManifest.AssetList = GetAllPatchAsset(buildMapContext, patchManifest.BundleList);
// 创建补丁清单文件
string manifestFilePath = $"{buildParameters.PipelineOutputDirectory}/{ResourceSettingData.Setting.PatchManifestFileName}";
UnityEngine.Debug.Log($"创建补丁清单文件:{manifestFilePath}");
PatchManifest.Serialize(manifestFilePath, patchManifest);
// 创建补丁清单哈希文件
string manifestHashFilePath = $"{buildParameters.PipelineOutputDirectory}/{ResourceSettingData.Setting.PatchManifestHashFileName}";
string manifestHash = HashUtility.FileMD5(manifestFilePath);
UnityEngine.Debug.Log($"创建补丁清单哈希文件:{manifestHashFilePath}");
FileUtility.CreateFile(manifestHashFilePath, manifestHash);
}
/// <summary>
/// 获取资源包列表
/// </summary>
private List<PatchBundle> GetAllPatchBundle(AssetBundleBuilder.BuildParametersContext buildParameters,
TaskGetBuildMap.BuildMapContext buildMapContext, TaskEncryption.EncryptionContext encryptionContext)
{
List<PatchBundle> result = new List<PatchBundle>(1000);
// 内置标记列表
List<string> buildinTags = buildParameters.Parameters.GetBuildinTags();
// 加载旧补丁清单
PatchManifest oldPatchManifest = null;
if (buildParameters.Parameters.IsForceRebuild == false)
{
oldPatchManifest = AssetBundleBuilderHelper.LoadPatchManifestFile(buildParameters.PipelineOutputDirectory);
}
foreach (var bundleInfo in buildMapContext.BundleInfos)
{
var bundleName = bundleInfo.BundleName;
string filePath = $"{buildParameters.PipelineOutputDirectory}/{bundleName}";
string hash = HashUtility.FileMD5(filePath);
string crc = HashUtility.FileCRC32(filePath);
long size = FileUtility.GetFileSize(filePath);
int version = buildParameters.Parameters.BuildVersion;
string[] tags = buildMapContext.GetAssetTags(bundleName);
bool isEncrypted = encryptionContext.IsEncryptFile(bundleName);
bool isBuildin = IsBuildinBundle(tags, buildinTags);
bool isRawFile = bundleInfo.IsRawFile;
// 附加文件扩展名
if (buildParameters.Parameters.AppendFileExtension)
{
hash += bundleInfo.GetAppendExtension();
}
// 注意:如果文件没有变化使用旧版本号
if (oldPatchManifest != null && oldPatchManifest.Bundles.TryGetValue(bundleName, out PatchBundle value))
{
if (value.Hash == hash)
version = value.Version;
}
PatchBundle patchBundle = new PatchBundle(bundleName, hash, crc, size, version, tags);
patchBundle.SetFlagsValue(isEncrypted, isBuildin, isRawFile);
result.Add(patchBundle);
}
return result;
}
private bool IsBuildinBundle(string[] bundleTags, List<string> buildinTags)
{
// 注意没有任何标记的Bundle文件默认为内置文件
if (bundleTags.Length == 0)
return true;
foreach (var tag in bundleTags)
{
if (buildinTags.Contains(tag))
return true;
}
return false;
}
/// <summary>
/// 获取资源列表
/// </summary>
private List<PatchAsset> GetAllPatchAsset(TaskGetBuildMap.BuildMapContext buildMapContext, List<PatchBundle> bundleList)
{
List<PatchAsset> result = new List<PatchAsset>(1000);
foreach (var bundleInfo in buildMapContext.BundleInfos)
{
var assetInfos = bundleInfo.GetCollectAssetInfos();
foreach (var assetInfo in assetInfos)
{
PatchAsset patchAsset = new PatchAsset();
patchAsset.AssetPath = assetInfo.AssetPath;
patchAsset.BundleID = GetAssetBundleID(assetInfo.GetBundleName(), bundleList);
patchAsset.DependIDs = GetAssetBundleDependIDs(assetInfo, bundleList);
result.Add(patchAsset);
}
}
return result;
}
private int[] GetAssetBundleDependIDs(BuildAssetInfo assetInfo, List<PatchBundle> bundleList)
{
List<int> result = new List<int>();
foreach (var dependAssetInfo in assetInfo.AllDependAssetInfos)
{
if (dependAssetInfo.CheckBundleNameValid() == false)
continue;
int bundleID = GetAssetBundleID(dependAssetInfo.GetBundleName(), bundleList);
if (result.Contains(bundleID) == false)
result.Add(bundleID);
}
return result.ToArray();
}
private int GetAssetBundleID(string bundleName, List<PatchBundle> bundleList)
{
for (int index = 0; index < bundleList.Count; index++)
{
if (bundleList[index].BundleName == bundleName)
return index;
}
throw new Exception($"Not found bundle name : {bundleName}");
}
}
}

View File

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

View File

@ -0,0 +1,83 @@
using System.Collections;
using System.Collections.Generic;
namespace YooAsset.Editor
{
/// <summary>
/// 制作补丁包
/// </summary>
public class TaskCreatePatchPackage : IBuildTask
{
void IBuildTask.Run(BuildContext context)
{
var buildParameters = context.GetContextObject<AssetBundleBuilder.BuildParametersContext>();
CopyPatchFiles(buildParameters);
}
/// <summary>
/// 拷贝补丁文件到补丁包目录
/// </summary>
private void CopyPatchFiles(AssetBundleBuilder.BuildParametersContext buildParameters)
{
string packageDirectory = buildParameters.GetPackageDirectory();
UnityEngine.Debug.Log($"开始拷贝补丁文件到补丁包目录:{packageDirectory}");
// 拷贝Readme文件
{
string sourcePath = $"{buildParameters.PipelineOutputDirectory}/{ResourceSettingData.Setting.ReadmeFileName}";
string destPath = $"{packageDirectory}/{ResourceSettingData.Setting.ReadmeFileName}";
EditorTools.CopyFile(sourcePath, destPath, true);
UnityEngine.Debug.Log($"拷贝Readme文件到{destPath}");
}
// 拷贝PatchManifest文件
{
string sourcePath = $"{buildParameters.PipelineOutputDirectory}/{ResourceSettingData.Setting.PatchManifestFileName}";
string destPath = $"{packageDirectory}/{ResourceSettingData.Setting.PatchManifestFileName}";
EditorTools.CopyFile(sourcePath, destPath, true);
UnityEngine.Debug.Log($"拷贝PatchManifest文件到{destPath}");
}
// 拷贝PatchManifest哈希文件
{
string sourcePath = $"{buildParameters.PipelineOutputDirectory}/{ResourceSettingData.Setting.PatchManifestHashFileName}";
string destPath = $"{packageDirectory}/{ResourceSettingData.Setting.PatchManifestHashFileName}";
EditorTools.CopyFile(sourcePath, destPath, true);
UnityEngine.Debug.Log($"拷贝PatchManifest哈希文件到{destPath}");
}
// 拷贝UnityManifest序列化文件
{
string sourcePath = $"{buildParameters.PipelineOutputDirectory}/{ResourceSettingData.Setting.UnityManifestFileName}";
string destPath = $"{packageDirectory}/{ResourceSettingData.Setting.UnityManifestFileName}";
EditorTools.CopyFile(sourcePath, destPath, true);
UnityEngine.Debug.Log($"拷贝UnityManifest文件到{destPath}");
}
// 拷贝UnityManifest文本文件
{
string sourcePath = $"{buildParameters.PipelineOutputDirectory}/{ResourceSettingData.Setting.UnityManifestFileName}.manifest";
string destPath = $"{packageDirectory}/{ResourceSettingData.Setting.UnityManifestFileName}.manifest";
EditorTools.CopyFile(sourcePath, destPath, true);
}
// 拷贝所有补丁文件
// 注意:拷贝的补丁文件都是需要玩家热更新的文件
int progressValue = 0;
PatchManifest patchManifest = AssetBundleBuilderHelper.LoadPatchManifestFile(buildParameters.PipelineOutputDirectory);
int patchFileTotalCount = patchManifest.BundleList.Count;
foreach (var patchBundle in patchManifest.BundleList)
{
if (patchBundle.Version == buildParameters.Parameters.BuildVersion)
{
string sourcePath = $"{buildParameters.PipelineOutputDirectory}/{patchBundle.BundleName}";
string destPath = $"{packageDirectory}/{patchBundle.Hash}";
EditorTools.CopyFile(sourcePath, destPath, true);
UnityEngine.Debug.Log($"拷贝补丁文件到补丁包:{patchBundle.BundleName}");
EditorTools.DisplayProgressBar("拷贝补丁文件", ++progressValue, patchFileTotalCount);
}
}
EditorTools.ClearProgressBar();
}
}
}

View File

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

View File

@ -0,0 +1,205 @@
using System;
using System.IO;
using System.Text;
using System.Collections;
using System.Collections.Generic;
namespace YooAsset.Editor
{
/// <summary>
/// 创建说明文件
/// </summary>
public class TaskCreateReadme : IBuildTask
{
void IBuildTask.Run(BuildContext context)
{
var buildParameters = context.GetContextObject<AssetBundleBuilder.BuildParametersContext>();
var buildMapContext = context.GetContextObject<TaskGetBuildMap.BuildMapContext>();
CreateReadmeFile(buildParameters, buildMapContext);
}
/// <summary>
/// 创建Readme文件到输出目录
/// </summary>
private void CreateReadmeFile(AssetBundleBuilder.BuildParametersContext buildParameters, TaskGetBuildMap.BuildMapContext buildMapContext)
{
PatchManifest patchManifest = AssetBundleBuilderHelper.LoadPatchManifestFile(buildParameters.PipelineOutputDirectory);
// 删除旧文件
string filePath = $"{buildParameters.PipelineOutputDirectory}/{ResourceSettingData.Setting.ReadmeFileName}";
if (File.Exists(filePath))
File.Delete(filePath);
UnityEngine.Debug.Log($"创建说明文件:{filePath}");
StringBuilder content = new StringBuilder();
AppendData(content, $"构建时间:{DateTime.Now}");
AppendData(content, $"构建平台:{buildParameters.Parameters.BuildTarget}");
AppendData(content, $"构建版本:{buildParameters.Parameters.BuildVersion}");
AppendData(content, $"冗余机制:{buildParameters.Parameters.ApplyRedundancy}");
AppendData(content, "");
AppendData(content, $"--着色器--");
AppendData(content, $"IsCollectAllShaders{AssetBundleCollectorSettingData.Setting.IsCollectAllShaders}");
AppendData(content, $"ShadersBundleName{AssetBundleCollectorSettingData.Setting.ShadersBundleName}");
AppendData(content, "");
AppendData(content, $"--配置信息--");
for (int i = 0; i < AssetBundleCollectorSettingData.Setting.Collectors.Count; i++)
{
AssetBundleCollectorSetting.Collector wrapper = AssetBundleCollectorSettingData.Setting.Collectors[i];
AppendData(content, wrapper.ToString());
}
AppendData(content, "");
AppendData(content, $"--构建参数--");
AppendData(content, $"CompressOption{buildParameters.Parameters.CompressOption}");
AppendData(content, $"IsForceRebuild{buildParameters.Parameters.IsForceRebuild}");
AppendData(content, $"BuildinTags{buildParameters.Parameters.BuildinTags}");
AppendData(content, $"IsAppendHash{buildParameters.Parameters.IsAppendHash}");
AppendData(content, $"IsDisableWriteTypeTree{buildParameters.Parameters.IsDisableWriteTypeTree}");
AppendData(content, $"IsIgnoreTypeTreeChanges{buildParameters.Parameters.IsIgnoreTypeTreeChanges}");
AppendData(content, $"IsDisableLoadAssetByFileName : {buildParameters.Parameters.IsDisableLoadAssetByFileName}");
AppendData(content, "");
AppendData(content, $"--构建信息--");
AppendData(content, $"参与构建的资源总数:{buildMapContext.GetAllAssets().Count}");
GetBundleFileCountAndTotalSize(patchManifest, out int fileCount1, out long fileTotalSize1);
AppendData(content, $"构建的资源包总数:{fileCount1} 文件总大小:{fileTotalSize1 / (1024 * 1024)}MB");
GetBuildinFileCountAndTotalSize(patchManifest, out int fileCount2, out long fileTotalSize2);
AppendData(content, $"内置的资源包总数:{fileCount2} 文件总大小:{fileTotalSize2 / (1024 * 1024)}MB");
GetNotBuildinFileCountAndTotalSize(patchManifest, out int fileCount3, out long fileTotalSize3);
AppendData(content, $"非内置的资源包总数:{fileCount3} 文件总大小:{fileTotalSize3 / (1024 * 1024)}MB");
GetEncryptedFileCountAndTotalSize(patchManifest, out int fileCount4, out long fileTotalSize4);
AppendData(content, $"加密的资源包总数:{fileCount4} 文件总大小:{fileTotalSize4 / (1024 * 1024)}MB");
GetRawFileCountAndTotalSize(patchManifest, out int fileCount5, out long fileTotalSize5);
AppendData(content, $"原生的资源包总数:{fileCount5} 文件总大小:{fileTotalSize5 / (1024 * 1024)}MB");
AppendData(content, "");
AppendData(content, $"--冗余列表--");
for (int i = 0; i < buildMapContext.RedundancyList.Count; i++)
{
string redundancyAssetPath = buildMapContext.RedundancyList[i];
AppendData(content, redundancyAssetPath);
}
AppendData(content, "");
AppendData(content, $"--构建列表--");
for (int i = 0; i < buildMapContext.BundleInfos.Count; i++)
{
string bundleName = buildMapContext.BundleInfos[i].BundleName;
AppendData(content, bundleName);
}
AppendData(content, "");
AppendData(content, $"--内置文件列表--");
foreach (var patchBundle in patchManifest.BundleList)
{
if (patchBundle.IsBuildin)
{
AppendData(content, patchBundle.BundleName);
}
}
AppendData(content, "");
AppendData(content, $"--非内置文件列表--");
foreach (var patchBundle in patchManifest.BundleList)
{
if (patchBundle.IsBuildin == false)
{
AppendData(content, patchBundle.BundleName);
}
}
AppendData(content, "");
AppendData(content, $"--加密文件列表--");
foreach (var patchBundle in patchManifest.BundleList)
{
if (patchBundle.IsEncrypted)
{
AppendData(content, patchBundle.BundleName);
}
}
AppendData(content, "");
AppendData(content, $"--原生文件列表--");
foreach (var patchBundle in patchManifest.BundleList)
{
if (patchBundle.IsRawFile)
{
AppendData(content, patchBundle.BundleName);
}
}
// 创建新文件
File.WriteAllText(filePath, content.ToString(), Encoding.UTF8);
}
private void AppendData(StringBuilder sb, string data)
{
sb.Append(data);
sb.Append("\r\n");
}
private void GetBundleFileCountAndTotalSize(PatchManifest patchManifest, out int fileCount, out long fileBytes)
{
fileCount = patchManifest.BundleList.Count;
fileBytes = 0;
foreach (var patchBundle in patchManifest.BundleList)
{
fileBytes += patchBundle.SizeBytes;
}
}
private void GetBuildinFileCountAndTotalSize(PatchManifest patchManifest, out int fileCount, out long fileBytes)
{
fileCount = 0;
fileBytes = 0;
foreach (var patchBundle in patchManifest.BundleList)
{
if (patchBundle.IsBuildin)
{
fileCount++;
fileBytes += patchBundle.SizeBytes;
}
}
}
private void GetNotBuildinFileCountAndTotalSize(PatchManifest patchManifest, out int fileCount, out long fileBytes)
{
fileCount = 0;
fileBytes = 0;
foreach (var patchBundle in patchManifest.BundleList)
{
if (patchBundle.IsBuildin == false)
{
fileCount++;
fileBytes += patchBundle.SizeBytes;
}
}
}
private void GetEncryptedFileCountAndTotalSize(PatchManifest patchManifest, out int fileCount, out long fileBytes)
{
fileCount = 0;
fileBytes = 0;
foreach (var patchBundle in patchManifest.BundleList)
{
if (patchBundle.IsEncrypted)
{
fileCount++;
fileBytes += patchBundle.SizeBytes;
}
}
}
private void GetRawFileCountAndTotalSize(PatchManifest patchManifest, out int fileCount, out long fileBytes)
{
fileCount = 0;
fileBytes = 0;
foreach (var patchBundle in patchManifest.BundleList)
{
if (patchBundle.IsRawFile)
{
fileCount++;
fileBytes += patchBundle.SizeBytes;
}
}
}
}
}

View File

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

View File

@ -0,0 +1,94 @@
using System;
using System.Linq;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using YooAsset.Utility;
namespace YooAsset.Editor
{
public class TaskEncryption : IBuildTask
{
public class EncryptionContext : IContextObject
{
public List<string> EncryptList;
/// <summary>
/// 检测是否为加密文件
/// </summary>
public bool IsEncryptFile(string bundleName)
{
return EncryptList.Contains(bundleName);
}
}
void IBuildTask.Run(BuildContext context)
{
var buildParameters = context.GetContextObject<AssetBundleBuilder.BuildParametersContext>();
var buildMapContext = context.GetContextObject<TaskGetBuildMap.BuildMapContext>();
var encrypter = CreateAssetEncrypter();
List<string> encryptList = EncryptFiles(encrypter, buildParameters, buildMapContext);
EncryptionContext encryptionContext = new EncryptionContext();
encryptionContext.EncryptList = encryptList;
context.SetContextObject(encryptionContext);
}
/// <summary>
/// 创建加密类
/// </summary>
/// <returns>如果没有定义类型则返回NULL</returns>
private IAssetEncrypter CreateAssetEncrypter()
{
var types = AssemblyUtility.GetAssignableTypes(AssemblyUtility.UnityDefaultAssemblyEditorName, typeof(IAssetEncrypter));
if (types.Count == 0)
return null;
if (types.Count != 1)
throw new Exception($"Found more {nameof(IAssetEncrypter)} types. We only support one.");
UnityEngine.Debug.Log($"创建实例类 : {types[0].FullName}");
return (IAssetEncrypter)Activator.CreateInstance(types[0]);
}
/// <summary>
/// 加密文件
/// </summary>
private List<string> EncryptFiles(IAssetEncrypter encrypter, AssetBundleBuilder.BuildParametersContext buildParameters, TaskGetBuildMap.BuildMapContext buildMapContext)
{
// 加密资源列表
List<string> encryptList = new List<string>();
// 如果没有设置加密类
if (encrypter == null)
return encryptList;
UnityEngine.Debug.Log($"开始加密资源文件");
int progressValue = 0;
foreach (var bundleInfo in buildMapContext.BundleInfos)
{
var bundleName = bundleInfo.BundleName;
string filePath = $"{buildParameters.PipelineOutputDirectory}/{bundleName}";
if (encrypter.Check(filePath))
{
encryptList.Add(bundleName);
// 注意:通过判断文件合法性,规避重复加密一个文件
byte[] fileData = File.ReadAllBytes(filePath);
if (EditorTools.CheckBundleFileValid(fileData))
{
byte[] bytes = encrypter.Encrypt(fileData);
File.WriteAllBytes(filePath, bytes);
UnityEngine.Debug.Log($"文件加密完成:{filePath}");
}
}
// 进度条
EditorTools.DisplayProgressBar("加密资源包", ++progressValue, buildMapContext.BundleInfos.Count);
}
EditorTools.ClearProgressBar();
return encryptList;
}
}
}

View File

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

View File

@ -0,0 +1,331 @@
using System;
using System.IO;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using YooAsset.Utility;
namespace YooAsset.Editor
{
public class TaskGetBuildMap : IBuildTask
{
public class BuildMapContext : IContextObject
{
/// <summary>
/// 资源包列表
/// </summary>
public readonly List<BuildBundleInfo> BundleInfos = new List<BuildBundleInfo>(1000);
/// <summary>
/// 冗余的资源列表
/// </summary>
public readonly List<string> RedundancyList = new List<string>(1000);
/// <summary>
/// 添加一个打包资源
/// </summary>
public void PackAsset(BuildAssetInfo assetInfo)
{
if (TryGetBundleInfo(assetInfo.GetBundleName(), out BuildBundleInfo bundleInfo))
{
bundleInfo.PackAsset(assetInfo);
}
else
{
BuildBundleInfo newBundleInfo = new BuildBundleInfo(assetInfo.BundleLabel, assetInfo.BundleVariant);
newBundleInfo.PackAsset(assetInfo);
BundleInfos.Add(newBundleInfo);
}
}
/// <summary>
/// 获取所有的打包资源
/// </summary>
public List<BuildAssetInfo> GetAllAssets()
{
List<BuildAssetInfo> result = new List<BuildAssetInfo>(BundleInfos.Count);
foreach (var bundleInfo in BundleInfos)
{
result.AddRange(bundleInfo.Assets);
}
return result;
}
/// <summary>
/// 获取AssetBundle内包含的标记列表
/// </summary>
public string[] GetAssetTags(string bundleFullName)
{
if (TryGetBundleInfo(bundleFullName, out BuildBundleInfo bundleInfo))
{
return bundleInfo.GetAssetTags();
}
throw new Exception($"Not found {nameof(BuildBundleInfo)} : {bundleFullName}");
}
/// <summary>
/// 获取AssetBundle内收集的资源路径列表
/// </summary>
public string[] GetCollectAssetPaths(string bundleFullName)
{
if (TryGetBundleInfo(bundleFullName, out BuildBundleInfo bundleInfo))
{
return bundleInfo.GetCollectAssetPaths();
}
throw new Exception($"Not found {nameof(BuildBundleInfo)} : {bundleFullName}");
}
/// <summary>
/// 获取AssetBundle内构建的资源路径列表
/// </summary>
public string[] GetBuildinAssetPaths(string bundleFullName)
{
if (TryGetBundleInfo(bundleFullName, out BuildBundleInfo bundleInfo))
{
return bundleInfo.GetBuildinAssetPaths();
}
throw new Exception($"Not found {nameof(BuildBundleInfo)} : {bundleFullName}");
}
/// <summary>
/// 获取构建管线里需要的数据
/// </summary>
public UnityEditor.AssetBundleBuild[] GetPipelineBuilds()
{
List<AssetBundleBuild> builds = new List<AssetBundleBuild>(BundleInfos.Count);
foreach (var bundleInfo in BundleInfos)
{
if (bundleInfo.IsRawFile == false)
builds.Add(bundleInfo.CreatePipelineBuild());
}
return builds.ToArray();
}
/// <summary>
/// 检测是否包含BundleName
/// </summary>
public bool IsContainsBundle(string bundleFullName)
{
return TryGetBundleInfo(bundleFullName, out BuildBundleInfo bundleInfo);
}
private bool TryGetBundleInfo(string bundleFullName, out BuildBundleInfo result)
{
foreach (var bundleInfo in BundleInfos)
{
if (bundleInfo.BundleName == bundleFullName)
{
result = bundleInfo;
return true;
}
}
result = null;
return false;
}
}
void IBuildTask.Run(BuildContext context)
{
var buildParametersContext = context.GetContextObject<AssetBundleBuilder.BuildParametersContext>();
BuildMapContext buildMapContext = new BuildMapContext();
context.SetContextObject(buildMapContext);
SetupBuildMap(buildMapContext, buildParametersContext);
// 检测构建结果
CheckBuildMapContent(buildMapContext);
}
/// <summary>
/// 组织构建的资源包
/// </summary>
private void SetupBuildMap(BuildMapContext buildMapContext, AssetBundleBuilder.BuildParametersContext buildParameters)
{
Dictionary<string, BuildAssetInfo> buildAssets = new Dictionary<string, BuildAssetInfo>();
// 1. 获取主动收集的资源
List<AssetCollectInfo> allCollectInfos = AssetBundleCollectorSettingData.GetAllCollectAssets();
// 2. 对收集的资源进行依赖分析
int progressValue = 0;
foreach (AssetCollectInfo collectInfo in allCollectInfos)
{
string mainAssetPath = collectInfo.AssetPath;
// 获取所有依赖资源
List<BuildAssetInfo> depends = GetAllDependencies(mainAssetPath);
for (int i = 0; i < depends.Count; i++)
{
string assetPath = depends[i].AssetPath;
// 如果已经存在,则增加该资源的依赖计数
if (buildAssets.ContainsKey(assetPath))
{
buildAssets[assetPath].DependCount++;
}
else
{
buildAssets.Add(assetPath, depends[i]);
}
// 添加资源标记
buildAssets[assetPath].AddAssetTags(collectInfo.AssetTags);
// 注意:检测是否为主动收集资源
if (assetPath == mainAssetPath)
{
buildAssets[mainAssetPath].IsCollectAsset = true;
buildAssets[mainAssetPath].IsRawAsset = collectInfo.IsRawAsset;
}
}
// 添加所有的依赖资源列表
// 注意:不包括自己
var allDependAssetInfos = new List<BuildAssetInfo>(depends.Count);
for (int i = 0; i < depends.Count; i++)
{
string assetPath = depends[i].AssetPath;
if (assetPath != mainAssetPath)
allDependAssetInfos.Add(buildAssets[assetPath]);
}
buildAssets[mainAssetPath].SetAllDependAssetInfos(allDependAssetInfos);
EditorTools.DisplayProgressBar("依赖文件分析", ++progressValue, allCollectInfos.Count);
}
EditorTools.ClearProgressBar();
// 3. 移除零依赖的资源
var redundancy = CreateAssetRedundancy();
List<BuildAssetInfo> undependentAssets = new List<BuildAssetInfo>();
foreach (KeyValuePair<string, BuildAssetInfo> pair in buildAssets)
{
var buildAssetInfo = pair.Value;
if (buildAssetInfo.IsCollectAsset)
continue;
if (buildAssetInfo.DependCount == 0)
{
undependentAssets.Add(buildAssetInfo);
continue;
}
// 冗余扩展
if(redundancy != null)
{
if(redundancy.Check(buildAssetInfo.AssetPath))
{
undependentAssets.Add(buildAssetInfo);
buildMapContext.RedundancyList.Add(buildAssetInfo.AssetPath);
continue;
}
}
// 冗余机制
if (buildParameters.Parameters.ApplyRedundancy)
{
if (AssetBundleCollectorSettingData.HasCollector(buildAssetInfo.AssetPath) == false)
{
undependentAssets.Add(buildAssetInfo);
buildMapContext.RedundancyList.Add(buildAssetInfo.AssetPath);
}
}
}
foreach (var assetInfo in undependentAssets)
{
buildAssets.Remove(assetInfo.AssetPath);
}
// 4. 设置资源包名
progressValue = 0;
foreach (KeyValuePair<string, BuildAssetInfo> pair in buildAssets)
{
var assetInfo = pair.Value;
var bundleLabel = AssetBundleCollectorSettingData.GetBundleLabel(assetInfo.AssetPath);
if (assetInfo.IsRawAsset)
assetInfo.SetBundleLabelAndVariant(bundleLabel, ResourceSettingData.Setting.RawFileVariant);
else
assetInfo.SetBundleLabelAndVariant(bundleLabel, ResourceSettingData.Setting.AssetBundleFileVariant);
EditorTools.DisplayProgressBar("设置资源包名", ++progressValue, buildAssets.Count);
}
EditorTools.ClearProgressBar();
// 4. 构建资源包
var allAssets = buildAssets.Values.ToList();
if (allAssets.Count == 0)
throw new Exception("构建的资源列表不能为空");
UnityEngine.Debug.Log($"构建的资源列表里总共有{allAssets.Count}个资源");
foreach (var assetInfo in allAssets)
{
buildMapContext.PackAsset(assetInfo);
}
}
/// <summary>
/// 获取指定资源依赖的所有资源列表
/// 注意:返回列表里已经包括主资源自己
/// </summary>
private List<BuildAssetInfo> GetAllDependencies(string mainAssetPath)
{
List<BuildAssetInfo> result = new List<BuildAssetInfo>();
string[] depends = AssetDatabase.GetDependencies(mainAssetPath, true);
foreach (string assetPath in depends)
{
if (AssetBundleCollectorSettingData.IsValidateAsset(assetPath))
{
BuildAssetInfo assetInfo = new BuildAssetInfo(assetPath);
result.Add(assetInfo);
}
}
return result;
}
/// <summary>
/// 检测构建结果
/// </summary>
private void CheckBuildMapContent(BuildMapContext buildMapContext)
{
foreach (var bundleInfo in buildMapContext.BundleInfos)
{
// 注意:原生文件资源包只能包含一个原生文件
bool isRawFile = bundleInfo.IsRawFile;
if (isRawFile)
{
if (bundleInfo.Assets.Count != 1)
throw new Exception("The bundle does not support multiple raw asset : {bundleInfo.BundleName}");
continue;
}
// 注意:原生文件不能被其它资源文件依赖
foreach (var assetInfo in bundleInfo.Assets)
{
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}");
}
}
}
}
}
/// <summary>
/// 创建冗余类
/// </summary>
/// <returns>如果没有定义类型则返回NULL</returns>
private IAssetRedundancy CreateAssetRedundancy()
{
var types = AssemblyUtility.GetAssignableTypes(AssemblyUtility.UnityDefaultAssemblyEditorName, typeof(IAssetRedundancy));
if (types.Count == 0)
return null;
if (types.Count != 1)
throw new Exception($"Found more {nameof(IAssetRedundancy)} types. We only support one.");
UnityEngine.Debug.Log($"创建实例类 : {types[0].FullName}");
return (IAssetRedundancy)Activator.CreateInstance(types[0]);
}
}
}

View File

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

View File

@ -0,0 +1,72 @@
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
namespace YooAsset.Editor
{
public class TaskPrepare : IBuildTask
{
void IBuildTask.Run(BuildContext context)
{
var buildParameters = context.GetContextObject<AssetBundleBuilder.BuildParametersContext>();
// 检测构建平台是否合法
if (buildParameters.Parameters.BuildTarget == BuildTarget.NoTarget)
throw new Exception("请选择目标平台");
// 检测构建版本是否合法
if (buildParameters.Parameters.BuildVersion <= 0)
throw new Exception("请先设置版本号");
// 检测输出目录是否为空
if (string.IsNullOrEmpty(buildParameters.PipelineOutputDirectory))
throw new Exception("输出目录不能为空");
// 检测资源收集配置文件
if (AssetBundleCollectorSettingData.GetCollecterCount() == 0)
throw new Exception("配置的资源收集路径为空");
// 增量更新时候的必要检测
if (buildParameters.Parameters.IsForceRebuild == false)
{
// 检测历史版本是否存在
if (AssetBundleBuilderHelper.HasAnyPackageVersion(buildParameters.Parameters.BuildTarget, buildParameters.Parameters.OutputRoot) == false)
throw new Exception("没有发现任何历史版本,请尝试强制重建");
// 检测构建版本是否合法
int maxPackageVersion = AssetBundleBuilderHelper.GetMaxPackageVersion(buildParameters.Parameters.BuildTarget, buildParameters.Parameters.OutputRoot);
if (buildParameters.Parameters.BuildVersion <= maxPackageVersion)
throw new Exception("构建版本不能小于历史版本");
// 检测补丁包是否已经存在
string packageDirectory = buildParameters.GetPackageDirectory();
if (Directory.Exists(packageDirectory))
throw new Exception($"补丁包已经存在:{packageDirectory}");
// 检测内置资源标记是否一致
PatchManifest oldPatchManifest = AssetBundleBuilderHelper.LoadPatchManifestFile(buildParameters.PipelineOutputDirectory);
if (buildParameters.Parameters.BuildinTags != oldPatchManifest.BuildinTags)
throw new Exception($"增量更新时内置资源标记必须一致:{buildParameters.Parameters.BuildinTags} != {oldPatchManifest.BuildinTags}");
}
// 如果是强制重建
if (buildParameters.Parameters.IsForceRebuild)
{
// 删除平台总目录
string platformDirectory = $"{buildParameters.Parameters.OutputRoot}/{buildParameters.Parameters.BuildTarget}";
if (EditorTools.DeleteDirectory(platformDirectory))
{
UnityEngine.Debug.Log($"删除平台总目录:{platformDirectory}");
}
}
// 如果输出目录不存在
if (EditorTools.CreateDirectory(buildParameters.PipelineOutputDirectory))
{
UnityEngine.Debug.Log($"创建输出目录:{buildParameters.PipelineOutputDirectory}");
}
}
}
}

View File

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

View File

@ -0,0 +1,13 @@

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

View File

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

View File

@ -0,0 +1,18 @@

namespace YooAsset.Editor
{
public interface IAssetEncrypter
{
/// <summary>
/// 检测是否需要加密
/// </summary>
bool Check(string filePath);
/// <summary>
/// 加密方法
/// </summary>
/// <param name="fileData">要加密的文件数据</param>
/// <returns>返回加密后的字节数据</returns>
byte[] Encrypt(byte[] fileData);
}
}

View File

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

View File

@ -0,0 +1,11 @@

namespace YooAsset.Editor
{
public interface IAssetRedundancy
{
/// <summary>
/// 检测是否冗余
/// </summary>
bool Check(string filePath);
}
}

View File

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

View File

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

View File

@ -0,0 +1,69 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using YooAsset.Utility;
namespace YooAsset.Editor
{
public class AssetBundleCollectorSetting : ScriptableObject
{
[Serializable]
public class Collector
{
/// <summary>
/// 收集的文件夹路径
/// </summary>
public string CollectDirectory = string.Empty;
/// <summary>
/// 打包规则类名
/// </summary>
public string PackRuleName = string.Empty;
/// <summary>
/// 过滤规则类名
/// </summary>
public string FilterRuleName = string.Empty;
/// <summary>
/// 不写入资源路径到清单文件
/// 注意:对于不依赖于代码加载的收集资源,可以禁止写入资源路径信息到清单文件
/// </summary>
public bool DontWriteAssetPath = false;
/// <summary>
/// 资源标记
/// </summary>
public string AssetTags = string.Empty;
/// <summary>
/// 获取资源标记列表
/// </summary>
public List<string> GetAssetTags()
{
return StringUtility.StringToStringList(AssetTags, ';');
}
public override string ToString()
{
return $"Directory : {CollectDirectory} | {PackRuleName} | {FilterRuleName} | {DontWriteAssetPath} | {AssetTags}";
}
}
/// <summary>
/// 是否收集全路径的着色器
/// </summary>
public bool IsCollectAllShaders = false;
/// <summary>
/// 收集的着色器Bundle名称
/// </summary>
public string ShadersBundleName = "myshaders";
/// <summary>
/// 收集列表
/// </summary>
public List<Collector> Collectors = new List<Collector>();
}
}

View File

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

View File

@ -0,0 +1,478 @@
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEditor;
using YooAsset.Utility;
namespace YooAsset.Editor
{
public static class AssetBundleCollectorSettingData
{
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>();
private static AssetBundleCollectorSetting _setting = null;
public static AssetBundleCollectorSetting Setting
{
get
{
if (_setting == null)
LoadSettingData();
return _setting;
}
}
public static List<string> GetPackRuleNames()
{
if (_setting == null)
LoadSettingData();
List<string> names = new List<string>();
foreach (var pair in _cachePackRuleTypes)
{
names.Add(pair.Key);
}
return names;
}
public static List<string> GetFilterRuleNames()
{
if (_setting == null)
LoadSettingData();
List<string> names = new List<string>();
foreach (var pair in _cacheFilterRuleTypes)
{
names.Add(pair.Key);
}
return names;
}
public static bool HasPackRuleName(string ruleName)
{
foreach (var pair in _cachePackRuleTypes)
{
if (pair.Key == ruleName)
return true;
}
return false;
}
public static bool HasFilterRuleName(string ruleName)
{
foreach (var pair in _cacheFilterRuleTypes)
{
if (pair.Key == ruleName)
return true;
}
return false;
}
/// <summary>
/// 加载配置文件
/// </summary>
private static void LoadSettingData()
{
// 加载配置文件
_setting = AssetDatabase.LoadAssetAtPath<AssetBundleCollectorSetting>(EditorDefine.AssetBundleCollectorSettingFilePath);
if (_setting == null)
{
Debug.LogWarning($"Create new {nameof(AssetBundleCollectorSetting)}.asset : {EditorDefine.AssetBundleCollectorSettingFilePath}");
_setting = ScriptableObject.CreateInstance<AssetBundleCollectorSetting>();
EditorTools.CreateFileDirectory(EditorDefine.AssetBundleCollectorSettingFilePath);
AssetDatabase.CreateAsset(Setting, EditorDefine.AssetBundleCollectorSettingFilePath);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
else
{
Debug.Log($"Load {nameof(AssetBundleCollectorSetting)}.asset ok");
}
// IPackRule
{
// 清空缓存集合
_cachePackRuleTypes.Clear();
_cachePackRuleInstance.Clear();
// 获取所有类型
List<Type> types = new List<Type>(100)
{
typeof(PackExplicit),
typeof(PackDirectory),
typeof(PackRawFile),
};
var customTypes = AssemblyUtility.GetAssignableTypes(AssemblyUtility.UnityDefaultAssemblyEditorName, 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 = AssemblyUtility.GetAssignableTypes(AssemblyUtility.UnityDefaultAssemblyEditorName, 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);
}
}
}
/// <summary>
/// 存储文件
/// </summary>
public static void SaveFile()
{
if (Setting != null)
{
EditorUtility.SetDirty(Setting);
AssetDatabase.SaveAssets();
}
}
// 着色器相关
public static void ModifyShader(bool isCollectAllShaders, string shadersBundleName)
{
if (string.IsNullOrEmpty(shadersBundleName))
return;
Setting.IsCollectAllShaders = isCollectAllShaders;
Setting.ShadersBundleName = shadersBundleName;
SaveFile();
}
// 收集器相关
public static void ClearAllCollector()
{
Setting.Collectors.Clear();
SaveFile();
}
public static void AddCollector(string directory, string packRuleName, string filterRuleName, bool dontWriteAssetPath, string assetTags, bool saveFile = true)
{
// 末尾添加路径分隔符号
if (directory.EndsWith("/") == false)
directory = $"{directory}/";
// 检测收集器路径冲突
if (CheckConflict(directory))
return;
// 检测资源标签
if (dontWriteAssetPath && string.IsNullOrEmpty(assetTags) == false)
Debug.LogWarning($"Collector {directory} has asset tags : {assetTags}, It is not vliad when enable dontWriteAssetPath.");
AssetBundleCollectorSetting.Collector element = new AssetBundleCollectorSetting.Collector();
element.CollectDirectory = directory;
element.PackRuleName = packRuleName;
element.FilterRuleName = filterRuleName;
element.DontWriteAssetPath = dontWriteAssetPath;
element.AssetTags = assetTags;
Setting.Collectors.Add(element);
if (saveFile)
SaveFile();
}
public static void RemoveCollector(string directory)
{
for (int i = 0; i < Setting.Collectors.Count; i++)
{
if (Setting.Collectors[i].CollectDirectory == directory)
{
Setting.Collectors.RemoveAt(i);
break;
}
}
SaveFile();
}
public static void ModifyCollector(string directory, string packRuleName, string filterRuleName, bool dontWriteAssetPath, string assetTags)
{
// 检测资源标签
if (dontWriteAssetPath && string.IsNullOrEmpty(assetTags) == false)
Debug.LogWarning($"Collector '{directory}' has asset tags '{assetTags}', It is invalid when enable dontWriteAssetPath.");
for (int i = 0; i < Setting.Collectors.Count; i++)
{
var collector = Setting.Collectors[i];
if (collector.CollectDirectory == directory)
{
collector.PackRuleName = packRuleName;
collector.FilterRuleName = filterRuleName;
collector.DontWriteAssetPath = dontWriteAssetPath;
collector.AssetTags = assetTags;
break;
}
}
SaveFile();
}
public static bool IsContainsCollector(string directory)
{
for (int i = 0; i < Setting.Collectors.Count; i++)
{
if (Setting.Collectors[i].CollectDirectory == directory)
return true;
}
return false;
}
public static bool CheckConflict(string directory)
{
if (IsContainsCollector(directory))
{
Debug.LogError($"Asset collector already existed : {directory}");
return true;
}
for (int i = 0; i < Setting.Collectors.Count; i++)
{
var wrapper = Setting.Collectors[i];
string compareDirectory = wrapper.CollectDirectory;
if (directory.StartsWith(compareDirectory))
{
Debug.LogError($"New asset collector \"{directory}\" conflict with \"{compareDirectory}\"");
return true;
}
if (compareDirectory.StartsWith(directory))
{
Debug.LogError($"New asset collector {directory} conflict with {compareDirectory}");
return true;
}
}
return false;
}
/// <summary>
/// 获取所有的DLC标记
/// </summary>
public static List<string> GetAllAssetTags()
{
List<string> result = new List<string>();
for (int i = 0; i < Setting.Collectors.Count; i++)
{
var tags = Setting.Collectors[i].GetAssetTags();
foreach (var tag in tags)
{
if (result.Contains(tag) == false)
result.Add(tag);
}
}
return result;
}
/// <summary>
/// 获取收集器总数
/// </summary>
public static int GetCollecterCount()
{
return Setting.Collectors.Count;
}
/// <summary>
/// 获取所有的收集路径
/// </summary>
public static List<string> GetAllCollectDirectory()
{
List<string> result = new List<string>();
for (int i = 0; i < Setting.Collectors.Count; i++)
{
AssetBundleCollectorSetting.Collector collector = Setting.Collectors[i];
result.Add(collector.CollectDirectory);
}
return result;
}
/// <summary>
/// 获取所有收集的资源
/// 注意:跳过了不写入资源路径的收集器
/// </summary>
public static List<AssetCollectInfo> GetAllCollectAssets()
{
Dictionary<string, AssetCollectInfo> result = new Dictionary<string, AssetCollectInfo>(10000);
for (int i = 0; i < Setting.Collectors.Count; i++)
{
AssetBundleCollectorSetting.Collector collector = Setting.Collectors[i];
// 注意:跳过不需要写入资源路径的收集器
if (collector.DontWriteAssetPath)
continue;
bool isRawAsset = collector.PackRuleName == nameof(PackRawFile);
string[] findAssets = EditorTools.FindAssets(EAssetSearchType.All, collector.CollectDirectory);
foreach (string assetPath in findAssets)
{
if (IsValidateAsset(assetPath) == false)
continue;
if (IsCollectAsset(assetPath, collector.FilterRuleName) == false)
continue;
if (result.ContainsKey(assetPath) == false)
{
var assetCollectInfo = new AssetCollectInfo(assetPath, collector.GetAssetTags(), isRawAsset);
result.Add(assetPath, assetCollectInfo);
}
}
}
return result.Values.ToList();
}
private static bool IsCollectAsset(string assetPath, string filterRuleName)
{
if (Setting.IsCollectAllShaders)
{
Type assetType = AssetDatabase.GetMainAssetTypeAtPath(assetPath);
if (assetType == typeof(UnityEngine.Shader))
return true;
}
// 根据规则设置获取标签名称
IFilterRule filterRuleInstance = GetFilterRuleInstance(filterRuleName);
return filterRuleInstance.IsCollectAsset(assetPath);
}
/// <summary>
/// 检测资源路径是否被收集器覆盖
/// </summary>
public static bool HasCollector(string assetPath)
{
// 如果收集全路径着色器
if (Setting.IsCollectAllShaders)
{
System.Type assetType = AssetDatabase.GetMainAssetTypeAtPath(assetPath);
if (assetType == typeof(UnityEngine.Shader))
return true;
}
for (int i = 0; i < Setting.Collectors.Count; i++)
{
AssetBundleCollectorSetting.Collector collector = Setting.Collectors[i];
if (assetPath.StartsWith(collector.CollectDirectory))
return true;
}
return false;
}
/// <summary>
/// 检测资源是否有效
/// </summary>
public static bool IsValidateAsset(string assetPath)
{
if (assetPath.StartsWith("Assets/") == false && assetPath.StartsWith("Packages/") == false)
return false;
if (AssetDatabase.IsValidFolder(assetPath))
return false;
// 注意:忽略编辑器下的类型资源
Type type = AssetDatabase.GetMainAssetTypeAtPath(assetPath);
if (type == typeof(LightingDataAsset))
return false;
string ext = System.IO.Path.GetExtension(assetPath);
if (ext == "" || ext == ".dll" || ext == ".cs" || ext == ".js" || ext == ".boo" || ext == ".meta")
return false;
return true;
}
/// <summary>
/// 获取资源包名
/// </summary>
public static string GetBundleLabel(string assetPath)
{
// 如果收集全路径着色器
if (Setting.IsCollectAllShaders)
{
System.Type assetType = AssetDatabase.GetMainAssetTypeAtPath(assetPath);
if (assetType == typeof(UnityEngine.Shader))
{
return EditorTools.GetRegularPath(Setting.ShadersBundleName);
}
}
// 获取收集器
AssetBundleCollectorSetting.Collector findCollector = null;
for (int i = 0; i < Setting.Collectors.Count; i++)
{
AssetBundleCollectorSetting.Collector collector = Setting.Collectors[i];
if (assetPath.StartsWith(collector.CollectDirectory))
{
findCollector = collector;
break;
}
}
// 如果没有找到收集器
string bundleLabel;
if (findCollector == null)
{
IPackRule defaultInstance = new PackDirectory();
bundleLabel = defaultInstance.GetAssetBundleLabel(assetPath);
}
else
{
// 根据规则设置获取标签名称
IPackRule getInstance = GetPackRuleInstance(findCollector.PackRuleName);
bundleLabel = getInstance.GetAssetBundleLabel(assetPath);
}
// 返回包名
return EditorTools.GetRegularPath(bundleLabel);
}
private 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}");
}
}
private 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}");
}
}
}
}

View File

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

View File

@ -0,0 +1,243 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace YooAsset.Editor
{
public class AssetBundleCollectorWindow : EditorWindow
{
static AssetBundleCollectorWindow _thisInstance;
[MenuItem("YooAsset/AssetBundle Collector", false, 101)]
static void ShowWindow()
{
if (_thisInstance == null)
{
_thisInstance = EditorWindow.GetWindow(typeof(AssetBundleCollectorWindow), false, "资源包收集工具", true) as AssetBundleCollectorWindow;
_thisInstance.minSize = new Vector2(800, 600);
}
_thisInstance.Show();
}
/// <summary>
/// 上次打开的文件夹路径
/// </summary>
private string _lastOpenFolderPath = "Assets/";
// GUI相关
private const float GuiDirecotryMinSize = 300f;
private const float GuiDirecotryMaxSize = 800f;
private const float GuiPackRuleSize = 130f;
private const float GuiFilterRuleSize = 130f;
private const float GuiDontWriteAssetPathSize = 130f;
private const float GuiAssetTagsMinSize = 100f;
private const float GuiAssetTagsMaxSize = 300f;
private const float GuiBtnSize = 40f;
private Vector2 _scrollPos = Vector2.zero;
// 初始化相关
private string[] _packRuleArray = null;
private string[] _filterRuleArray = null;
private bool _isInit = false;
private void Init()
{
List<string> packRuleNames = AssetBundleCollectorSettingData.GetPackRuleNames();
_packRuleArray = packRuleNames.ToArray();
List<string> filterRuleNames = AssetBundleCollectorSettingData.GetFilterRuleNames();
_filterRuleArray = filterRuleNames.ToArray();
}
private int PackRuleNameToIndex(string name)
{
for (int i = 0; i < _packRuleArray.Length; i++)
{
if (_packRuleArray[i] == name)
return i;
}
return 0;
}
private string IndexToPackRuleName(int index)
{
for (int i = 0; i < _packRuleArray.Length; i++)
{
if (i == index)
return _packRuleArray[i];
}
return string.Empty;
}
private int FilterRuleNameToIndex(string name)
{
for (int i = 0; i < _filterRuleArray.Length; i++)
{
if (_filterRuleArray[i] == name)
return i;
}
return 0;
}
private string IndexToFilterRuleName(int index)
{
for (int i = 0; i < _filterRuleArray.Length; i++)
{
if (i == index)
return _filterRuleArray[i];
}
return string.Empty;
}
private void OnGUI()
{
if (_isInit == false)
{
_isInit = true;
Init();
}
OnDrawShader();
OnDrawHeadBar();
OnDrawCollector();
}
private void OnDrawShader()
{
bool isCollectAllShader = AssetBundleCollectorSettingData.Setting.IsCollectAllShaders;
string shadersBundleName = AssetBundleCollectorSettingData.Setting.ShadersBundleName;
EditorGUILayout.Space();
bool newToggleValue = EditorGUILayout.Toggle("收集所有着色器", isCollectAllShader);
if (newToggleValue != isCollectAllShader)
{
isCollectAllShader = newToggleValue;
AssetBundleCollectorSettingData.ModifyShader(isCollectAllShader, shadersBundleName);
}
if (isCollectAllShader)
{
string newTextValue = EditorGUILayout.TextField("AssetBundle名称", shadersBundleName, GUILayout.MaxWidth(300));
if (newTextValue != shadersBundleName)
{
shadersBundleName = newTextValue;
AssetBundleCollectorSettingData.ModifyShader(isCollectAllShader, shadersBundleName);
}
}
}
private void OnDrawHeadBar()
{
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Directory", GUILayout.MinWidth(GuiDirecotryMinSize), GUILayout.MaxWidth(GuiDirecotryMaxSize));
EditorGUILayout.LabelField("PackRule", GUILayout.MinWidth(GuiPackRuleSize), GUILayout.MaxWidth(GuiPackRuleSize));
EditorGUILayout.LabelField("FilterRule", GUILayout.MinWidth(GuiFilterRuleSize), GUILayout.MaxWidth(GuiFilterRuleSize));
EditorGUILayout.LabelField("DontWriteAssetPath", GUILayout.MinWidth(GuiDontWriteAssetPathSize), GUILayout.MaxWidth(GuiDontWriteAssetPathSize));
EditorGUILayout.LabelField("AssetTags", GUILayout.MinWidth(GuiAssetTagsMinSize), GUILayout.MaxWidth(GuiAssetTagsMaxSize));
EditorGUILayout.LabelField("", GUILayout.MinWidth(GuiBtnSize), GUILayout.MaxWidth(GuiBtnSize));
EditorGUILayout.EndHorizontal();
}
private void OnDrawCollector()
{
// 列表显示
EditorGUILayout.Space();
_scrollPos = EditorGUILayout.BeginScrollView(_scrollPos);
for (int i = 0; i < AssetBundleCollectorSettingData.Setting.Collectors.Count; i++)
{
var collector = AssetBundleCollectorSettingData.Setting.Collectors[i];
string directory = collector.CollectDirectory;
string packRuleName = collector.PackRuleName;
string filterRuleName = collector.FilterRuleName;
bool dontWriteAssetPath = collector.DontWriteAssetPath;
string assetTags = collector.AssetTags;
EditorGUILayout.BeginHorizontal();
{
// Directory
EditorGUILayout.LabelField(directory, GUILayout.MinWidth(GuiDirecotryMinSize), GUILayout.MaxWidth(GuiDirecotryMaxSize));
// IPackRule
{
int index = PackRuleNameToIndex(packRuleName);
int newIndex = EditorGUILayout.Popup(index, _packRuleArray, GUILayout.MinWidth(GuiPackRuleSize), GUILayout.MaxWidth(GuiPackRuleSize));
if (newIndex != index)
{
packRuleName = IndexToPackRuleName(newIndex);
AssetBundleCollectorSettingData.ModifyCollector(directory, packRuleName, filterRuleName, dontWriteAssetPath, assetTags);
}
}
// IFilterRule
{
int index = FilterRuleNameToIndex(filterRuleName);
int newIndex = EditorGUILayout.Popup(index, _filterRuleArray, GUILayout.MinWidth(GuiFilterRuleSize), GUILayout.MaxWidth(GuiFilterRuleSize));
if (newIndex != index)
{
filterRuleName = IndexToFilterRuleName(newIndex);
AssetBundleCollectorSettingData.ModifyCollector(directory, packRuleName, filterRuleName, dontWriteAssetPath, assetTags);
}
}
// DontWriteAssetPath
{
bool newToggleValue = EditorGUILayout.Toggle(dontWriteAssetPath, GUILayout.MinWidth(GuiDontWriteAssetPathSize), GUILayout.MaxWidth(GuiDontWriteAssetPathSize));
if (newToggleValue != dontWriteAssetPath)
{
dontWriteAssetPath = newToggleValue;
AssetBundleCollectorSettingData.ModifyCollector(directory, packRuleName, filterRuleName, dontWriteAssetPath, assetTags);
}
}
// AssetTags
{
if (collector.DontWriteAssetPath)
{
EditorGUILayout.LabelField(assetTags, GUILayout.MinWidth(GuiAssetTagsMinSize), GUILayout.MaxWidth(GuiAssetTagsMaxSize));
}
else
{
string newTextValue = EditorGUILayout.TextField(assetTags, GUILayout.MinWidth(GuiAssetTagsMinSize), GUILayout.MaxWidth(GuiAssetTagsMaxSize));
if (newTextValue != assetTags)
{
assetTags = newTextValue;
AssetBundleCollectorSettingData.ModifyCollector(directory, packRuleName, filterRuleName, dontWriteAssetPath, assetTags);
}
}
}
if (GUILayout.Button("-", GUILayout.MinWidth(GuiBtnSize), GUILayout.MaxWidth(GuiBtnSize)))
{
AssetBundleCollectorSettingData.RemoveCollector(directory);
break;
}
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndScrollView();
// 添加按钮
if (GUILayout.Button("+"))
{
string resultPath = EditorTools.OpenFolderPanel("Select Folder", _lastOpenFolderPath);
if (resultPath != null)
{
_lastOpenFolderPath = EditorTools.AbsolutePathToAssetPath(resultPath);
string defaultPackRuleName = nameof(PackExplicit);
string defaultFilterRuleName = nameof(CollectAll);
bool defaultDontWriteAssetPathValue = false;
string defaultAssetTag = string.Empty;
AssetBundleCollectorSettingData.AddCollector(_lastOpenFolderPath, defaultPackRuleName, defaultFilterRuleName, defaultDontWriteAssetPathValue, defaultAssetTag);
}
}
// 导入配置按钮
if (GUILayout.Button("Import Config"))
{
string resultPath = EditorTools.OpenFilePath("Select File", "Assets/", "xml");
if (resultPath != null)
{
CollectorConfigImporter.ImportXmlConfig(resultPath);
}
}
}
}
}

View File

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

View File

@ -0,0 +1,30 @@
using System.Collections;
using System.Collections.Generic;
namespace YooAsset.Editor
{
public class AssetCollectInfo
{
/// <summary>
/// 资源路径
/// </summary>
public string AssetPath { private set; get; }
/// <summary>
/// 资源标记列表
/// </summary>
public List<string> AssetTags { private set; get; }
/// <summary>
/// 是否为原生资源
/// </summary>
public bool IsRawAsset { private set; get; }
public AssetCollectInfo(string assetPath, List<string> assetTags, bool isRawAsset)
{
AssetPath = assetPath;
AssetTags = assetTags;
IsRawAsset = isRawAsset;
}
}
}

View File

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

View File

@ -0,0 +1,100 @@
using System;
using System.IO;
using System.Xml;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using YooAsset.Utility;
namespace YooAsset.Editor
{
public static class CollectorConfigImporter
{
private class CollectWrapper
{
public string CollectDirectory;
public string PackRuleName;
public string FilterRuleName;
public bool DontWriteAssetPath;
public string AssetTags;
public CollectWrapper(string directory, string packRuleName, string filterRuleName, bool dontWriteAssetPath, string assetTags)
{
CollectDirectory = directory;
PackRuleName = packRuleName;
FilterRuleName = filterRuleName;
DontWriteAssetPath = dontWriteAssetPath;
AssetTags = assetTags;
}
}
public const string XmlCollector = "Collector";
public const string XmlDirectory = "Directory";
public const string XmlPackRule = "PackRule";
public const string XmlFilterRule = "FilterRule";
public const string XmlDontWriteAssetPath = "DontWriteAssetPath";
public const string XmlAssetTags = "AssetTags";
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}");
List<CollectWrapper> wrappers = new List<CollectWrapper>();
// 加载文件
XmlDocument xml = new XmlDocument();
xml.Load(filePath);
// 解析文件
XmlElement root = xml.DocumentElement;
XmlNodeList nodeList = root.GetElementsByTagName(XmlCollector);
if (nodeList.Count == 0)
throw new Exception($"Not found any {XmlCollector}");
foreach (XmlNode node in nodeList)
{
XmlElement collect = node as XmlElement;
string directory = collect.GetAttribute(XmlDirectory);
string packRuleName = collect.GetAttribute(XmlPackRule);
string filterRuleName = collect.GetAttribute(XmlFilterRule);
string dontWriteAssetPath = collect.GetAttribute(XmlDontWriteAssetPath);
string assetTags = collect.GetAttribute(XmlAssetTags);
if (Directory.Exists(directory) == false)
throw new Exception($"Not found directory : {directory}");
if (collect.HasAttribute(XmlPackRule) == false)
throw new Exception($"Not found attribute {XmlPackRule} in collector : {directory}");
if (collect.HasAttribute(XmlFilterRule) == false)
throw new Exception($"Not found attribute {XmlFilterRule} in collector : {directory}");
if (collect.HasAttribute(XmlDontWriteAssetPath) == false)
throw new Exception($"Not found attribute {XmlDontWriteAssetPath} in collector : {directory}");
if (collect.HasAttribute(XmlAssetTags) == false)
throw new Exception($"Not found attribute {XmlAssetTags} in collector : {directory}");
if (AssetBundleCollectorSettingData.HasPackRuleName(packRuleName) == false)
throw new Exception($"Invalid {nameof(IPackRule)} class type : {packRuleName}");
if (AssetBundleCollectorSettingData.HasFilterRuleName(filterRuleName) == false)
throw new Exception($"Invalid {nameof(IFilterRule)} class type : {filterRuleName}");
bool dontWriteAssetPathValue = StringUtility.StringToBool(dontWriteAssetPath);
CollectWrapper collectWrapper = new CollectWrapper(directory, packRuleName, filterRuleName, dontWriteAssetPathValue, assetTags);
wrappers.Add(collectWrapper);
}
// 导入配置数据
AssetBundleCollectorSettingData.ClearAllCollector();
foreach (var wrapper in wrappers)
{
AssetBundleCollectorSettingData.AddCollector(wrapper.CollectDirectory, wrapper.PackRuleName, wrapper.FilterRuleName, wrapper.DontWriteAssetPath, wrapper.AssetTags, false);
}
// 保存配置数据
AssetBundleCollectorSettingData.SaveFile();
Debug.Log($"导入配置完毕,一共导入{wrappers.Count}个收集器。");
}
}
}

View File

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

View File

@ -0,0 +1,53 @@
using UnityEngine;
using UnityEditor;
using System.IO;
namespace YooAsset.Editor
{
/// <summary>
/// 收集所有资源
/// </summary>
public class CollectAll : IFilterRule
{
public bool IsCollectAsset(string assetPath)
{
return true;
}
}
/// <summary>
/// 只收集场景
/// </summary>
public class CollectScene : IFilterRule
{
public bool IsCollectAsset(string assetPath)
{
return Path.GetExtension(assetPath) == ".unity";
}
}
/// <summary>
/// 只收集预制体
/// </summary>
public class CollectPrefab : IFilterRule
{
public bool IsCollectAsset(string assetPath)
{
return Path.GetExtension(assetPath) == ".prefab";
}
}
/// <summary>
/// 只收集精灵类型的资源
/// </summary>
public class CollectSprite : IFilterRule
{
public bool IsCollectAsset(string assetPath)
{
if (AssetDatabase.GetMainAssetTypeAtPath(assetPath) == typeof(Sprite))
return true;
else
return false;
}
}
}

View File

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

View File

@ -0,0 +1,56 @@
using System;
using System.IO;
using UnityEditor;
using YooAsset.Utility;
namespace YooAsset.Editor
{
/// <summary>
/// 以文件路径作为AssetBundle标签名
/// </summary>
public class PackExplicit : IPackRule
{
string IPackRule.GetAssetBundleLabel(string assetPath)
{
return StringUtility.RemoveExtension(assetPath); //"Assets/Config/test.txt" --> "Assets/Config/test"
}
}
/// <summary>
/// 以父文件夹路径作为AssetBundle标签名
/// 注意该文件夹下所有资源被打到一个AssetBundle文件里
/// </summary>
public class PackDirectory : IPackRule
{
string IPackRule.GetAssetBundleLabel(string assetPath)
{
return Path.GetDirectoryName(assetPath); //"Assets/Config/test.txt" --> "Assets/Config"
}
}
/// <summary>
/// 原生文件打包模式
/// 注意:原生文件打包支持:图片,音频,视频,文本
/// </summary>
public class PackRawFile : IPackRule
{
string IPackRule.GetAssetBundleLabel(string assetPath)
{
string extension = StringUtility.RemoveFirstChar(Path.GetExtension(assetPath));
if (extension == EAssetFileExtension.unity.ToString() || extension == EAssetFileExtension.prefab.ToString() ||
extension == EAssetFileExtension.mat.ToString() || extension == EAssetFileExtension.controller.ToString() ||
extension == EAssetFileExtension.fbx.ToString() || extension == EAssetFileExtension.anim.ToString() ||
extension == EAssetFileExtension.shader.ToString())
{
throw new Exception($"{nameof(PackRawFile)} is not support file estension : {extension}");
}
// 注意:原生文件只支持无依赖关系的资源
string[] depends = AssetDatabase.GetDependencies(assetPath, true);
if (depends.Length != 1)
throw new Exception($"{nameof(PackRawFile)} is not support estension : {extension}");
return StringUtility.RemoveExtension(assetPath);
}
}
}

View File

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

View File

@ -0,0 +1,16 @@

namespace YooAsset.Editor
{
/// <summary>
/// 资源过滤规则接口
/// </summary>
public interface IFilterRule
{
/// <summary>
/// 是否为收集资源
/// </summary>
/// <param name="assetPath">资源路径</param>
/// <returns>如果收集该资源返回TRUE</returns>
bool IsCollectAsset(string assetPath);
}
}

View File

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

View File

@ -0,0 +1,14 @@

namespace YooAsset.Editor
{
/// <summary>
/// 资源打包规则接口
/// </summary>
public interface IPackRule
{
/// <summary>
/// 获取资源的打包标签
/// </summary>
string GetAssetBundleLabel(string assetPath);
}
}

View File

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

View File

@ -0,0 +1,53 @@

namespace YooAsset.Editor
{
public class EditorDefine
{
/// <summary>
/// 资源包收集工具的配置文件存储路径
/// </summary>
public const string AssetBundleCollectorSettingFilePath = "Assets/YooAssetSetting/AssetBundleCollectorSetting.asset";
}
/// <summary>
/// 资源搜索类型
/// </summary>
public enum EAssetSearchType
{
All,
RuntimeAnimatorController,
AnimationClip,
AudioClip,
AudioMixer,
Font,
Material,
Mesh,
Model,
PhysicMaterial,
Prefab,
Scene,
Script,
Shader,
Sprite,
Texture,
VideoClip,
}
/// <summary>
/// 资源文件格式
/// </summary>
public enum EAssetFileExtension
{
prefab, //预制体
unity, //场景
fbx, //模型
anim, //动画
controller, //控制器
png, //图片
jpg, //图片
mat, //材质球
shader, //着色器
ttf, //字体
cs, //脚本
}
}

View File

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

View File

@ -0,0 +1,878 @@
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEditor;
using UnityEditor.Animations;
using YooAsset.Utility;
namespace YooAsset.Editor
{
/// <summary>
/// 编辑器工具类
/// </summary>
public static class EditorTools
{
#region NGUI
/// <summary>
/// Draw a distinctly different looking header label
/// </summary>
public static bool DrawHeader(string text)
{
return DrawHeader(text, text, false, true);
}
public static bool DrawHeader(string text, string key, bool forceOn, bool minimalistic)
{
bool state = EditorPrefs.GetBool(key, true);
if (!minimalistic) GUILayout.Space(3f);
if (!forceOn && !state) GUI.backgroundColor = new Color(0.8f, 0.8f, 0.8f);
GUILayout.BeginHorizontal();
GUI.changed = false;
if (minimalistic)
{
if (state) text = "\u25BC" + (char)0x200a + text;
else text = "\u25BA" + (char)0x200a + text;
GUILayout.BeginHorizontal();
GUI.contentColor = EditorGUIUtility.isProSkin ? new Color(1f, 1f, 1f, 0.7f) : new Color(0f, 0f, 0f, 0.7f);
if (!GUILayout.Toggle(true, text, "PreToolbar2", GUILayout.MinWidth(20f))) state = !state;
GUI.contentColor = Color.white;
GUILayout.EndHorizontal();
}
else
{
text = "<b><size=11>" + text + "</size></b>";
if (state) text = "\u25BC " + text;
else text = "\u25BA " + text;
if (!GUILayout.Toggle(true, text, "dragtab", GUILayout.MinWidth(20f))) state = !state;
}
if (GUI.changed) EditorPrefs.SetBool(key, state);
if (!minimalistic) GUILayout.Space(2f);
GUILayout.EndHorizontal();
GUI.backgroundColor = Color.white;
if (!forceOn && !state) GUILayout.Space(3f);
return state;
}
#endregion
#region Assembly
/// <summary>
/// 调用私有的静态方法
/// </summary>
/// <param name="type">类的类型</param>
/// <param name="method">类里要调用的方法名</param>
/// <param name="parameters">调用方法传入的参数</param>
public static object InvokeNonPublicStaticMethod(System.Type type, string method, params object[] parameters)
{
var methodInfo = type.GetMethod(method, BindingFlags.NonPublic | BindingFlags.Static);
if (methodInfo == null)
{
UnityEngine.Debug.LogError($"{type.FullName} not found method : {method}");
return null;
}
return methodInfo.Invoke(null, parameters);
}
/// <summary>
/// 调用公开的静态方法
/// </summary>
/// <param name="type">类的类型</param>
/// <param name="method">类里要调用的方法名</param>
/// <param name="parameters">调用方法传入的参数</param>
public static object InvokePublicStaticMethod(System.Type type, string method, params object[] parameters)
{
var methodInfo = type.GetMethod(method, BindingFlags.Public | BindingFlags.Static);
if (methodInfo == null)
{
UnityEngine.Debug.LogError($"{type.FullName} not found method : {method}");
return null;
}
return methodInfo.Invoke(null, parameters);
}
#endregion
#region EditorUtility
/// <summary>
/// 搜集资源
/// </summary>
/// <param name="searchType">搜集的资源类型</param>
/// <param name="searchInFolders">指定搜索的文件夹列表</param>
/// <returns>返回搜集到的资源路径列表</returns>
public static string[] FindAssets(EAssetSearchType searchType, string[] searchInFolders)
{
// 注意AssetDatabase.FindAssets()不支持末尾带分隔符的文件夹路径
for (int i = 0; i < searchInFolders.Length; i++)
{
string folderPath = searchInFolders[i];
searchInFolders[i] = folderPath.TrimEnd('/');
}
// 注意:获取指定目录下的所有资源对象(包括子文件夹)
string[] guids;
if (searchType == EAssetSearchType.All)
guids = AssetDatabase.FindAssets(string.Empty, searchInFolders);
else
guids = AssetDatabase.FindAssets($"t:{searchType}", searchInFolders);
// 注意AssetDatabase.FindAssets()可能会获取到重复的资源
List<string> result = new List<string>();
for (int i = 0; i < guids.Length; i++)
{
string guid = guids[i];
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
if (result.Contains(assetPath) == false)
{
result.Add(assetPath);
}
}
// 返回结果
return result.ToArray();
}
/// <summary>
/// 搜集资源
/// </summary>
/// <param name="searchType">搜集的资源类型</param>
/// <param name="searchInFolder">指定搜索的文件夹</param>
/// <returns>返回搜集到的资源路径列表</returns>
public static string[] FindAssets(EAssetSearchType searchType, string searchInFolder)
{
return FindAssets(searchType, new string[] { searchInFolder });
}
/// <summary>
/// 打开搜索面板
/// </summary>
/// <param name="title">标题名称</param>
/// <param name="defaultPath">默认搜索路径</param>
/// <returns>返回选择的文件夹绝对路径如果无效返回NULL</returns>
public static string OpenFolderPanel(string title, string defaultPath)
{
string openPath = EditorUtility.OpenFolderPanel(title, defaultPath, string.Empty);
if (string.IsNullOrEmpty(openPath))
return null;
if (openPath.Contains("/Assets") == false)
{
Debug.LogWarning("Please select unity assets folder.");
return null;
}
return openPath;
}
/// <summary>
/// 打开搜索面板
/// </summary>
/// <param name="title">标题名称</param>
/// <param name="defaultPath">默认搜索路径</param>
/// <returns>返回选择的文件绝对路径如果无效返回NULL</returns>
public static string OpenFilePath(string title, string defaultPath, string extension = "")
{
string openPath = EditorUtility.OpenFilePanel(title, defaultPath, extension);
if (string.IsNullOrEmpty(openPath))
return null;
if (openPath.Contains("/Assets") == false)
{
Debug.LogWarning("Please select unity assets file.");
return null;
}
return openPath;
}
/// <summary>
/// 显示进度框
/// </summary>
public static void DisplayProgressBar(string tips, int progressValue, int totalValue)
{
EditorUtility.DisplayProgressBar("进度", $"{tips} : {progressValue}/{totalValue}", (float)progressValue / totalValue);
}
/// <summary>
/// 隐藏进度框
/// </summary>
public static void ClearProgressBar()
{
EditorUtility.ClearProgressBar();
}
#endregion
#region 编辑器窗口
public static void FocusUnitySceneWindow()
{
EditorWindow.FocusWindowIfItsOpen<SceneView>();
}
public static void FocusUnityGameWindow()
{
System.Type T = Assembly.Load("UnityEditor").GetType("UnityEditor.GameView");
EditorWindow.GetWindow(T, false, "GameView", true);
}
public static void FocueUnityProjectWindow()
{
System.Type T = Assembly.Load("UnityEditor").GetType("UnityEditor.ProjectBrowser");
EditorWindow.GetWindow(T, false, "Project", true);
}
public static void FocusUnityHierarchyWindow()
{
System.Type T = Assembly.Load("UnityEditor").GetType("UnityEditor.SceneHierarchyWindow");
EditorWindow.GetWindow(T, false, "Hierarchy", true);
}
public static void FocusUnityInspectorWindow()
{
System.Type T = Assembly.Load("UnityEditor").GetType("UnityEditor.InspectorWindow");
EditorWindow.GetWindow(T, false, "Inspector", true);
}
public static void FocusUnityConsoleWindow()
{
System.Type T = Assembly.Load("UnityEditor").GetType("UnityEditor.ConsoleWindow");
EditorWindow.GetWindow(T, false, "Console", true);
}
#endregion
#region 引用关系
/// <summary>
/// 获取场景里的克隆预制体
/// </summary>
public static GameObject GetClonePrefabInScene(GameObject sourcePrefab)
{
GameObject[] findObjects = GameObject.FindObjectsOfType<GameObject>();
if (findObjects.Length == 0)
return null;
for (int i = 0; i < findObjects.Length; i++)
{
GameObject findObject = findObjects[i];
#if UNITY_2017_4
// 判断对象是否为一个预制体的引用
if (PrefabUtility.GetPrefabType(findObject) == PrefabType.PrefabInstance)
{
// 判断是否为同一个预制体
Object source = PrefabUtility.GetPrefabParent(findObject);
if (source.GetInstanceID() == sourcePrefab.GetInstanceID())
return findObject;
}
#else
// 判断对象是否为一个预制体的引用
if (PrefabUtility.GetPrefabInstanceStatus(findObject) == PrefabInstanceStatus.Connected)
{
// 判断是否为同一个预制体
Object source = PrefabUtility.GetCorrespondingObjectFromSource(findObject);
if (source.GetInstanceID() == sourcePrefab.GetInstanceID())
return findObject;
}
#endif
}
return null; //没有找到合适的对象
}
/// <summary>
/// 查找场景里的引用对象
/// </summary>
public static void FindReferencesInScene(UnityEngine.Object to)
{
var referencedBy = new List<Object>();
GameObject[] findObjects = GameObject.FindObjectsOfType<GameObject>(); //注意只能获取激活的GameObject
for (int j = 0; j < findObjects.Length; j++)
{
GameObject findObject = findObjects[j];
#if UNITY_2017_4
// 如果Prefab匹配
if (PrefabUtility.GetPrefabType(findObject) == PrefabType.PrefabInstance)
{
if (PrefabUtility.GetPrefabParent(findObject) == to)
referencedBy.Add(findObject);
}
#else
// 如果Prefab匹配
if (PrefabUtility.GetPrefabInstanceStatus(findObject) == PrefabInstanceStatus.Connected)
{
if (PrefabUtility.GetCorrespondingObjectFromSource(findObject) == to)
referencedBy.Add(findObject);
}
#endif
// 如果组件匹配
Component[] components = findObject.GetComponents<Component>();
for (int i = 0; i < components.Length; i++)
{
Component c = components[i];
if (!c) continue;
SerializedObject so = new SerializedObject(c);
SerializedProperty sp = so.GetIterator();
while (sp.NextVisible(true))
{
if (sp.propertyType == SerializedPropertyType.ObjectReference)
{
if (sp.objectReferenceValue != null && sp.objectReferenceValue == to)
referencedBy.Add(c.gameObject);
}
}
}
}
if (referencedBy.Any())
Selection.objects = referencedBy.ToArray();
}
/// <summary>
/// 查找场景里的引用对象
/// </summary>
public static void FindReferencesInPrefabs(UnityEngine.Object to, GameObject[] sourcePrefabs)
{
var referencedBy = new List<Object>();
for (int j = 0; j < sourcePrefabs.Length; j++)
{
GameObject clonePrefab = GetClonePrefabInScene(sourcePrefabs[j]);
if (clonePrefab == null)
continue;
#if UNITY_2017_4
// 如果Prefab匹配
if (PrefabUtility.GetPrefabParent(clonePrefab) == to)
referencedBy.Add(clonePrefab);
#else
// 如果Prefab匹配
if (PrefabUtility.GetCorrespondingObjectFromSource(clonePrefab) == to)
referencedBy.Add(clonePrefab);
#endif
// 如果组件匹配
Component[] components = clonePrefab.GetComponentsInChildren<Component>(true); //GetComponents<Component>();
for (int i = 0; i < components.Length; i++)
{
Component c = components[i];
if (!c) continue;
SerializedObject so = new SerializedObject(c);
SerializedProperty sp = so.GetIterator();
while (sp.NextVisible(true))
{
if (sp.propertyType == SerializedPropertyType.ObjectReference)
{
if (sp.objectReferenceValue != null && sp.objectReferenceValue == to)
referencedBy.Add(c.gameObject);
}
}
}
}
if (referencedBy.Any())
Selection.objects = referencedBy.ToArray();
}
#endregion
#region 材质球
/// <summary>
/// 清理无用的材质球属性
/// </summary>
public 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;
}
#endregion
#region 动画控制器
/// <summary>
/// 查找动画控制器里冗余的动画状态机
/// </summary>
public static bool FindRedundantAnimationState(AnimatorController animatorController)
{
if (animatorController == null)
return false;
string assetPath = AssetDatabase.GetAssetPath(animatorController);
// 查找使用的状态机名称
List<string> usedStateNames = new List<string>();
foreach (var layer in animatorController.layers)
{
foreach (var state in layer.stateMachine.states)
{
usedStateNames.Add(state.state.name);
}
}
List<string> allLines = new List<string>();
List<int> stateIndexList = new List<int>();
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<string> allStateNames = new List<string>();
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;
}
#endregion
#region 控制台
private static MethodInfo _clearConsoleMethod;
private static MethodInfo ClearConsoleMethod
{
get
{
if (_clearConsoleMethod == null)
{
Assembly assembly = Assembly.GetAssembly(typeof(SceneView));
System.Type logEntries = assembly.GetType("UnityEditor.LogEntries");
_clearConsoleMethod = logEntries.GetMethod("Clear");
}
return _clearConsoleMethod;
}
}
/// <summary>
/// 清空控制台
/// </summary>
public static void ClearUnityConsole()
{
ClearConsoleMethod.Invoke(new object(), null);
}
#endregion
#region 文件
/// <summary>
/// 测试写入权限
/// </summary>
public static bool HasWriteAccess(string directoryPath)
{
try
{
string tmpFilePath = Path.Combine(directoryPath, Path.GetRandomFileName());
using (FileStream fs = new FileStream(tmpFilePath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.ReadWrite))
{
StreamWriter writer = new StreamWriter(fs);
writer.Write(0);
}
File.Delete(tmpFilePath);
return true;
}
catch
{
return false;
}
}
/// <summary>
/// 创建文件所在的目录
/// </summary>
/// <param name="filePath">文件路径</param>
public static void CreateFileDirectory(string filePath)
{
string destDirectory = Path.GetDirectoryName(filePath);
CreateDirectory(destDirectory);
}
/// <summary>
/// 创建文件夹
/// </summary>
public static bool CreateDirectory(string directory)
{
if (Directory.Exists(directory) == false)
{
Directory.CreateDirectory(directory);
return true;
}
else
{
return false;
}
}
/// <summary>
/// 删除文件夹及子目录
/// </summary>
public static bool DeleteDirectory(string directory)
{
if (Directory.Exists(directory))
{
Directory.Delete(directory, true);
return true;
}
else
{
return false;
}
}
/// <summary>
/// 文件重命名
/// </summary>
public static void FileRename(string filePath, string newName)
{
string dirPath = Path.GetDirectoryName(filePath);
string destPath;
if (Path.HasExtension(filePath))
{
string extentsion = Path.GetExtension(filePath);
destPath = $"{dirPath}/{newName}{extentsion}";
}
else
{
destPath = $"{dirPath}/{newName}";
}
FileInfo fileInfo = new FileInfo(filePath);
fileInfo.MoveTo(destPath);
}
/// <summary>
/// 文件移动
/// </summary>
public static void FileMoveTo(string filePath, string destPath)
{
FileInfo fileInfo = new FileInfo(filePath);
fileInfo.MoveTo(destPath);
}
/// <summary>
/// 拷贝文件夹
/// 注意:包括所有子目录的文件
/// </summary>
public static void CopyDirectory(string sourcePath, string destPath)
{
sourcePath = EditorTools.GetRegularPath(sourcePath);
// If the destination directory doesn't exist, create it.
if (Directory.Exists(destPath) == false)
Directory.CreateDirectory(destPath);
string[] fileList = Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories);
foreach (string file in fileList)
{
string temp = EditorTools.GetRegularPath(file);
string savePath = temp.Replace(sourcePath, destPath);
CopyFile(file, savePath, true);
}
}
/// <summary>
/// 拷贝文件
/// </summary>
public static void CopyFile(string sourcePath, string destPath, bool overwrite)
{
if (File.Exists(sourcePath) == false)
throw new FileNotFoundException(sourcePath);
// 创建目录
CreateFileDirectory(destPath);
// 复制文件
File.Copy(sourcePath, destPath, overwrite);
}
/// <summary>
/// 清空文件夹
/// </summary>
/// <param name="folderPath">要清理的文件夹路径</param>
public static void ClearFolder(string directoryPath)
{
if (Directory.Exists(directoryPath) == false)
return;
// 删除文件
string[] allFiles = Directory.GetFiles(directoryPath);
for (int i = 0; i < allFiles.Length; i++)
{
File.Delete(allFiles[i]);
}
// 删除文件夹
string[] allFolders = Directory.GetDirectories(directoryPath);
for (int i = 0; i < allFolders.Length; i++)
{
Directory.Delete(allFolders[i], true);
}
}
/// <summary>
/// 获取文件字节大小
/// </summary>
public static long GetFileSize(string filePath)
{
FileInfo fileInfo = new FileInfo(filePath);
return fileInfo.Length;
}
/// <summary>
/// 读取文件的所有文本内容
/// </summary>
public static string ReadFileAllText(string filePath)
{
if (File.Exists(filePath) == false)
return string.Empty;
return File.ReadAllText(filePath, Encoding.UTF8);
}
/// <summary>
/// 读取文本的所有文本内容
/// </summary>
public static string[] ReadFileAllLine(string filePath)
{
if (File.Exists(filePath) == false)
return null;
return File.ReadAllLines(filePath, Encoding.UTF8);
}
/// <summary>
/// 检测AssetBundle文件是否合法
/// </summary>
public static bool CheckBundleFileValid(byte[] fileData)
{
string signature = ReadStringToNull(fileData, 20);
if (signature == "UnityFS" || signature == "UnityRaw" || signature == "UnityWeb" || signature == "\xFA\xFA\xFA\xFA\xFA\xFA\xFA\xFA")
return true;
else
return false;
}
private static string ReadStringToNull(byte[] data, int maxLength)
{
List<byte> bytes = new List<byte>();
for (int i = 0; i < data.Length; i++)
{
if (i >= maxLength)
break;
byte bt = data[i];
if (bt == 0)
break;
bytes.Add(bt);
}
if (bytes.Count == 0)
return string.Empty;
else
return Encoding.UTF8.GetString(bytes.ToArray());
}
#endregion
#region 路径
/// <summary>
/// 获取规范的路径
/// </summary>
public static string GetRegularPath(string path)
{
return path.Replace('\\', '/').Replace("\\", "/"); //替换为Linux路径格式
}
/// <summary>
/// 获取项目工程路径
/// </summary>
public static string GetProjectPath()
{
string projectPath = Path.GetDirectoryName(Application.dataPath);
return GetRegularPath(projectPath);
}
/// <summary>
/// 转换文件的绝对路径为Unity资源路径
/// 例如 D:\\YourPorject\\Assets\\Works\\file.txt 替换为 Assets/Works/file.txt
/// </summary>
public static string AbsolutePathToAssetPath(string absolutePath)
{
string content = GetRegularPath(absolutePath);
return Substring(content, "Assets", true);
}
/// <summary>
/// 转换Unity资源路径为文件的绝对路径
/// 例如Assets/Works/file.txt 替换为 D:\\YourPorject/Assets/Works/file.txt
/// </summary>
public static string AssetPathToAbsolutePath(string assetPath)
{
string projectPath = GetProjectPath();
return $"{projectPath}/{assetPath}";
}
/// <summary>
/// 递归查找目标文件夹路径
/// </summary>
/// <param name="root">搜索的根目录</param>
/// <param name="folderName">目标文件夹名称</param>
/// <returns>返回找到的文件夹路径,如果没有找到返回空字符串</returns>
public static string FindFolder(string root, string folderName)
{
DirectoryInfo rootInfo = new DirectoryInfo(root);
DirectoryInfo[] infoList = rootInfo.GetDirectories();
for (int i = 0; i < infoList.Length; i++)
{
string fullPath = infoList[i].FullName;
if (infoList[i].Name == folderName)
return fullPath;
string result = FindFolder(fullPath, folderName);
if (string.IsNullOrEmpty(result) == false)
return result;
}
return string.Empty;
}
#endregion
#region 字符串
/// <summary>
/// 是否含有中文
/// </summary>
public static bool IncludeChinese(string content)
{
foreach (var c in content)
{
if (c >= 0x4e00 && c <= 0x9fbb)
return true;
}
return false;
}
/// <summary>
/// 是否是数字
/// </summary>
public static bool IsNumber(string content)
{
if (string.IsNullOrEmpty(content))
return false;
string pattern = @"^\d*$";
return Regex.IsMatch(content, pattern);
}
/// <summary>
/// 首字母大写
/// </summary>
public static string Capitalize(string content)
{
return content.Substring(0, 1).ToUpper() + (content.Length > 1 ? content.Substring(1).ToLower() : "");
}
/// <summary>
/// 截取字符串
/// 获取匹配到的后面内容
/// </summary>
/// <param name="content">内容</param>
/// <param name="key">关键字</param>
/// <param name="includeKey">分割的结果里是否包含关键字</param>
/// <param name="searchBegin">是否使用初始匹配的位置,否则使用末尾匹配的位置</param>
public static string Substring(string content, string key, bool includeKey, bool firstMatch = true)
{
if (string.IsNullOrEmpty(key))
return content;
int startIndex = -1;
if (firstMatch)
startIndex = content.IndexOf(key); //返回子字符串第一次出现位置
else
startIndex = content.LastIndexOf(key); //返回子字符串最后出现的位置
// 如果没有找到匹配的关键字
if (startIndex == -1)
return content;
if (includeKey)
return content.Substring(startIndex);
else
return content.Substring(startIndex + key.Length);
}
#endregion
#region 玩家偏好
// 枚举
public static void PlayerSetEnum<T>(string key, T value)
{
string enumName = value.ToString();
EditorPrefs.SetString(key, enumName);
}
public static T PlayerGetEnum<T>(string key, T defaultValue)
{
string enumName = EditorPrefs.GetString(key, defaultValue.ToString());
return StringUtility.NameToEnum<T>(enumName);
}
#endregion
}
}

View File

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

View File

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

View File

@ -0,0 +1,112 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEditor;
namespace YooAsset.Editor
{
public static class ShaderVariantCollectionHelper
{
[Serializable]
public class ShaderVariantWrapper
{
/// <summary>
/// Shader asset path in editor.
/// </summary>
public string AssetPath;
/// <summary>
/// Shader name.
/// </summary>
public string ShaderName;
/// <summary>
/// Pass type to use in this variant.
/// </summary>
public PassType PassType;
/// <summary>
/// Array of shader keywords to use in this variant.
/// </summary>
public string[] Keywords;
public ShaderVariantWrapper(string assetPath, string shaderName, PassType passType, params string[] keywords)
{
AssetPath = assetPath;
ShaderName = shaderName;
PassType = passType;
Keywords = keywords;
}
}
[Serializable]
public class ShaderVariantCollectionWrapper
{
/// <summary>
/// Number of shaders in this collection
/// </summary>
public int ShaderCount;
/// <summary>
/// Number of total varians in this collection
/// </summary>
public int VariantCount;
/// <summary>
/// Shader variants list.
/// </summary>
public List<ShaderVariantWrapper> ShaderVariants = new List<ShaderVariantWrapper>(1000);
public void Add(ShaderVariantWrapper variant)
{
ShaderVariants.Add(variant);
}
}
public static ShaderVariantCollectionWrapper Extract(ShaderVariantCollection svc)
{
var result = new ShaderVariantCollectionWrapper();
using (var so = new SerializedObject(svc))
{
var shaderArray = so.FindProperty("m_Shaders.Array");
if (shaderArray != null && shaderArray.isArray)
{
for (int i = 0; i < shaderArray.arraySize; ++i)
{
var shaderRef = shaderArray.FindPropertyRelative($"data[{i}].first");
var shaderVariantsArray = shaderArray.FindPropertyRelative($"data[{i}].second.variants");
if (shaderRef != null && shaderRef.propertyType == SerializedPropertyType.ObjectReference && shaderVariantsArray != null && shaderVariantsArray.isArray)
{
var shader = shaderRef.objectReferenceValue as Shader;
if (shader == null)
{
throw new Exception("Invalid shader in ShaderVariantCollection file.");
}
string shaderAssetPath = AssetDatabase.GetAssetPath(shader);
string shaderName = shader.name;
// 添加变种信息
for (int j = 0; j < shaderVariantsArray.arraySize; ++j)
{
var propKeywords = shaderVariantsArray.FindPropertyRelative($"Array.data[{j}].keywords");
var propPassType = shaderVariantsArray.FindPropertyRelative($"Array.data[{j}].passType");
if (propKeywords != null && propPassType != null && propKeywords.propertyType == SerializedPropertyType.String)
{
string[] keywords = propKeywords.stringValue.Split(' ');
PassType pathType = (PassType)propPassType.intValue;
result.Add(new ShaderVariantWrapper(shaderAssetPath, shaderName, pathType, keywords));
}
}
}
}
}
}
return result;
}
}
}

View File

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

View File

@ -0,0 +1,213 @@
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.IO;
using UnityEngine;
using UnityEditor;
using UnityEditor.SceneManagement;
namespace YooAsset.Editor
{
[InitializeOnLoad]
public static class ShaderVariantCollector
{
private const float WaitMilliseconds = 1000f;
private static string _saveFilePath;
private static bool _isStarted = false;
private static readonly Stopwatch _elapsedTime = new Stopwatch();
static ShaderVariantCollector()
{
EditorApplication.update += EditorUpdate;
}
private static void EditorUpdate()
{
// 注意:一定要延迟保存才会起效
if (_isStarted && _elapsedTime.ElapsedMilliseconds > WaitMilliseconds)
{
_isStarted = false;
_elapsedTime.Stop();
// 保存结果
SaveCurrentShaderVariantCollection();
// 创建说明文件
CreateReadme();
}
}
private static void CreateReadme()
{
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
ShaderVariantCollection svc = AssetDatabase.LoadAssetAtPath<ShaderVariantCollection>(_saveFilePath);
if(svc != null)
{
var wrapper = ShaderVariantCollectionHelper.Extract(svc);
string jsonContents = JsonUtility.ToJson(wrapper, true);
string savePath = _saveFilePath.Replace(".shadervariants", ".json");
File.WriteAllText(savePath, jsonContents);
}
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
}
/// <summary>
/// 开始收集
/// </summary>
public static void Run(string saveFilePath)
{
if (_isStarted)
return;
if (Path.HasExtension(saveFilePath) == false)
saveFilePath = $"{saveFilePath}.shadervariants";
if (Path.GetExtension(saveFilePath) != ".shadervariants")
throw new System.Exception("Shader variant file extension is invalid.");
EditorTools.CreateFileDirectory(saveFilePath);
_saveFilePath = saveFilePath;
// 聚焦到游戏窗口
EditorTools.FocusUnityGameWindow();
// 清空旧数据
ClearCurrentShaderVariantCollection();
// 创建临时测试场景
CreateTemperScene();
// 收集着色器变种
var materials = GetAllMaterials();
CollectVariants(materials);
_isStarted = true;
_elapsedTime.Reset();
_elapsedTime.Start();
UnityEngine.Debug.LogWarning("已经启动着色器变种收集工作,该工具只支持在编辑器下人工操作!");
}
private static void CreateTemperScene()
{
// 创建临时场景
EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects);
}
private static List<Material> GetAllMaterials()
{
int progressValue = 0;
List<string> allAssets = new List<string>(1000);
// 获取所有打包的资源
List<AssetCollectInfo> allCollectInfos = AssetBundleCollectorSettingData.GetAllCollectAssets();
List<string> collectAssets = allCollectInfos.Select(t => t.AssetPath).ToList();
foreach (var assetPath in collectAssets)
{
string[] depends = AssetDatabase.GetDependencies(assetPath, true);
foreach (var depend in depends)
{
if (allAssets.Contains(depend) == false)
allAssets.Add(depend);
}
EditorTools.DisplayProgressBar("获取所有打包资源", ++progressValue, collectAssets.Count);
}
EditorTools.ClearProgressBar();
// 搜集所有材质球
progressValue = 0;
var shaderDic = new Dictionary<Shader, List<Material>>(100);
foreach (var assetPath in allAssets)
{
System.Type assetType = AssetDatabase.GetMainAssetTypeAtPath(assetPath);
if (assetType == typeof(UnityEngine.Material))
{
var material = AssetDatabase.LoadAssetAtPath<Material>(assetPath);
var shader = material.shader;
if (shader == null)
continue;
if (shaderDic.ContainsKey(shader) == false)
{
shaderDic.Add(shader, new List<Material>());
}
if (shaderDic[shader].Contains(material) == false)
{
shaderDic[shader].Add(material);
}
}
EditorTools.DisplayProgressBar("搜集所有材质球", ++progressValue, allAssets.Count);
}
EditorTools.ClearProgressBar();
// 返回结果
var materials = new List<Material>(1000);
foreach (var valuePair in shaderDic)
{
materials.AddRange(valuePair.Value);
}
return materials;
}
private static void CollectVariants(List<Material> materials)
{
Camera camera = Camera.main;
if(camera == null)
throw new System.Exception("Not found main camera.");
// 设置主相机
float aspect = camera.aspect;
int totalMaterials = materials.Count;
float height = Mathf.Sqrt(totalMaterials / aspect) + 1;
float width = Mathf.Sqrt(totalMaterials / aspect) * aspect + 1;
float halfHeight = Mathf.CeilToInt(height / 2f);
float halfWidth = Mathf.CeilToInt(width / 2f);
camera.orthographic = true;
camera.orthographicSize = halfHeight;
camera.transform.position = new Vector3(0f, 0f, -10f);
// 创建测试球体
int xMax = (int)(width - 1);
int x = 0, y = 0;
int progressValue = 0;
for (int i = 0; i < materials.Count; i++)
{
var material = materials[i];
var position = new Vector3(x - halfWidth + 1f, y - halfHeight + 1f, 0f);
CreateSphere(material, position, i);
if (x == xMax)
{
x = 0;
y++;
}
else
{
x++;
}
EditorTools.DisplayProgressBar("测试所有材质球", ++progressValue, materials.Count);
}
EditorTools.ClearProgressBar();
}
private static void CreateSphere(Material material, Vector3 position, int index)
{
var go = GameObject.CreatePrimitive(PrimitiveType.Sphere);
go.GetComponent<Renderer>().material = material;
go.transform.position = position;
go.name = $"Sphere_{index}|{material.name}";
}
private static void ClearCurrentShaderVariantCollection()
{
EditorTools.InvokeNonPublicStaticMethod(typeof(ShaderUtil), "ClearCurrentShaderVariantCollection");
}
private static void SaveCurrentShaderVariantCollection()
{
EditorTools.InvokeNonPublicStaticMethod(typeof(ShaderUtil), "SaveCurrentShaderVariantCollection", _saveFilePath);
}
public static int GetCurrentShaderVariantCollectionShaderCount()
{
return (int)EditorTools.InvokeNonPublicStaticMethod(typeof(ShaderUtil), "GetCurrentShaderVariantCollectionShaderCount");
}
public static int GetCurrentShaderVariantCollectionVariantCount()
{
return (int)EditorTools.InvokeNonPublicStaticMethod(typeof(ShaderUtil), "GetCurrentShaderVariantCollectionVariantCount");
}
}
}

View File

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

View File

@ -0,0 +1,58 @@
using UnityEngine;
using UnityEditor;
namespace YooAsset.Editor
{
public class ShaderVariantCollectionWindow : EditorWindow
{
static ShaderVariantCollectionWindow _thisInstance;
[MenuItem("YooAsset/ShaderVariant Collector", false, 203)]
static void ShowWindow()
{
if (_thisInstance == null)
{
_thisInstance = EditorWindow.GetWindow(typeof(ShaderVariantCollectionWindow), false, "着色器变种收集工具", true) as ShaderVariantCollectionWindow;
_thisInstance.minSize = new Vector2(800, 600);
}
_thisInstance.Show();
}
private string _saveFilePath = "Assets/MyShaderVariants.shadervariants";
private ShaderVariantCollection _selectSVC;
private void OnGUI()
{
EditorGUILayout.Space();
_saveFilePath = EditorGUILayout.TextField("收集文件保存路径", _saveFilePath);
int currentShaderCount = ShaderVariantCollector.GetCurrentShaderVariantCollectionShaderCount();
int currentVariantCount = ShaderVariantCollector.GetCurrentShaderVariantCollectionVariantCount();
EditorGUILayout.LabelField($"CurrentShaderCount : {currentShaderCount}");
EditorGUILayout.LabelField($"CurrentVariantCount : {currentVariantCount}");
// 搜集变种
EditorGUILayout.Space();
if (GUILayout.Button("搜集变种", GUILayout.MaxWidth(80)))
{
ShaderVariantCollector.Run(_saveFilePath);
}
// 查询
EditorGUILayout.Space();
if (GUILayout.Button("查询", GUILayout.MaxWidth(80)))
{
string resultPath = EditorTools.OpenFilePath("Select File", "Assets/", "shadervariants");
if (string.IsNullOrEmpty(resultPath))
return;
string assetPath = EditorTools.AbsolutePathToAssetPath(resultPath);
_selectSVC = AssetDatabase.LoadAssetAtPath<ShaderVariantCollection>(assetPath);
}
if (_selectSVC != null)
{
EditorGUILayout.LabelField($"ShaderCount : {_selectSVC.shaderCount}");
EditorGUILayout.LabelField($"VariantCount : {_selectSVC.variantCount}");
}
}
}
}

View File

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

View File

@ -0,0 +1,18 @@
{
"name": "YooAsset.Editor",
"rootNamespace": "",
"references": [
"YooAsset"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4d1926c9df5b052469a1c63448b7609a
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,6 +1,7 @@
MIT License MIT License
Copyright (c) 2022 tuyoogame Copyright (c) 2018-2021 何冠峰
Copyright (c) 2021-2022 TuYoo Games
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 685cb35952719f9499a0cdb255604729
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,2 @@
# YooAsset
Unity assets system

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: cf18b110959e74849b675ca29f77c7dd
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

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

View File

@ -0,0 +1,149 @@

namespace YooAsset
{
public class AssetBundleInfo
{
private readonly PatchBundle _patchBundle;
/// <summary>
/// 资源包名称
/// </summary>
public string BundleName { private set; get; }
/// <summary>
/// 本地存储的路径
/// </summary>
public string LocalPath { private set; get; }
/// <summary>
/// 远端下载地址
/// </summary>
public string RemoteMainURL { private set; get; }
/// <summary>
/// 远端下载备用地址
/// </summary>
public string RemoteFallbackURL { private set; get; }
/// <summary>
/// 文件哈希值
/// </summary>
public string Hash
{
get
{
if (_patchBundle == null)
return string.Empty;
else
return _patchBundle.Hash;
}
}
/// <summary>
/// 校验的CRC
/// </summary>
public string CRC
{
get
{
if (_patchBundle == null)
return string.Empty;
else
return _patchBundle.CRC;
}
}
/// <summary>
/// 文件大小
/// </summary>
public long SizeBytes
{
get
{
if (_patchBundle == null)
return 0;
else
return _patchBundle.SizeBytes;
}
}
/// <summary>
/// 资源版本
/// </summary>
public int Version
{
get
{
if (_patchBundle == null)
return 0;
else
return _patchBundle.Version;
}
}
/// <summary>
/// 是否为加密文件
/// </summary>
public bool IsEncrypted
{
get
{
if (_patchBundle == null)
return false;
else
return _patchBundle.IsEncrypted;
}
}
/// <summary>
/// 是否为原生文件
/// </summary>
public bool IsRawFile
{
get
{
if (_patchBundle == null)
return false;
else
return _patchBundle.IsRawFile;
}
}
private AssetBundleInfo()
{
}
internal AssetBundleInfo(PatchBundle patchBundle, string localPath, string mainURL, string fallbackURL)
{
_patchBundle = patchBundle;
BundleName = patchBundle.BundleName;
LocalPath = localPath;
RemoteMainURL = mainURL;
RemoteFallbackURL = fallbackURL;
}
internal AssetBundleInfo(PatchBundle patchBundle, string localPath)
{
_patchBundle = patchBundle;
BundleName = patchBundle.BundleName;
LocalPath = localPath;
RemoteMainURL = string.Empty;
RemoteFallbackURL = string.Empty;
}
internal AssetBundleInfo(string bundleName, string localPath)
{
_patchBundle = null;
BundleName = bundleName;
LocalPath = localPath;
RemoteMainURL = string.Empty;
RemoteFallbackURL = string.Empty;
}
/// <summary>
/// 是否为JAR包内文件
/// </summary>
public bool IsBuildinJarFile()
{
return LocalPath.StartsWith("jar:");
}
}
}

View File

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

View File

@ -0,0 +1,185 @@
using System.Collections;
using UnityEngine;
namespace YooAsset
{
public struct AssetOperationHandle : IEnumerator
{
private IAssetProvider _provider;
internal AssetOperationHandle(IAssetProvider provider)
{
_provider = provider;
}
/// <summary>
/// 句柄是否有效AssetFileLoader销毁会导致所有句柄失效
/// </summary>
public bool IsValid
{
get
{
return _provider != null && _provider.IsValid;
}
}
/// <summary>
/// 当前的加载状态
/// </summary>
public EAssetStates States
{
get
{
if (IsValid == false)
return EAssetStates.None;
return _provider.States;
}
}
/// <summary>
/// 加载进度
/// </summary>
public float Progress
{
get
{
if (IsValid == false)
return 0;
return _provider.Progress;
}
}
/// <summary>
/// 是否加载完毕
/// </summary>
public bool IsDone
{
get
{
if (IsValid == false)
return false;
return _provider.IsDone;
}
}
/// <summary>
/// 完成委托
/// </summary>
public event System.Action<AssetOperationHandle> Completed
{
add
{
if (IsValid == false)
throw new System.Exception($"{nameof(AssetOperationHandle)} is invalid");
if (_provider.IsDone)
value.Invoke(this);
else
_provider.Callback += value;
}
remove
{
if (IsValid == false)
throw new System.Exception($"{nameof(AssetOperationHandle)} is invalid");
_provider.Callback -= value;
}
}
/// <summary>
/// 资源对象
/// </summary>
public UnityEngine.Object AssetObject
{
get
{
if (IsValid == false)
return null;
return _provider.AssetObject;
}
}
/// <summary>
/// 资源对象集合
/// </summary>
public UnityEngine.Object[] AllAssets
{
get
{
if (IsValid == false)
return null;
return _provider.AllAssets;
}
}
/// <summary>
/// 扩展的实例对象
/// </summary>
public IAssetInstance AssetInstance
{
get
{
if (IsValid == false)
return null;
return _provider.AssetInstance;
}
}
/// <summary>
/// 初始化的游戏对象只限于请求的资源对象类型为GameObject
/// </summary>
public GameObject InstantiateObject
{
get
{
if (IsValid == false)
return null;
if (_provider.AssetObject == null)
return null;
return UnityEngine.Object.Instantiate(_provider.AssetObject as GameObject);
}
}
/// <summary>
/// 释放资源句柄
/// </summary>
public void Release()
{
if (IsValid == false)
return;
_provider.Release();
_provider = null;
}
/// <summary>
/// 等待异步执行完毕
/// </summary>
public void WaitForAsyncComplete()
{
if (IsValid == false)
return;
_provider.WaitForAsyncComplete();
}
#region 异步操作相关
/// <summary>
/// 异步操作任务
/// </summary>
public System.Threading.Tasks.Task<object> Task
{
get { return _provider.Task; }
}
// 协程相关
bool IEnumerator.MoveNext()
{
return !IsDone;
}
void IEnumerator.Reset()
{
}
object IEnumerator.Current
{
get { return AssetObject; }
}
#endregion
}
}

View File

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

View File

@ -0,0 +1,123 @@
using System.IO;
using YooAsset.Utility;
namespace YooAsset
{
internal static class AssetPathHelper
{
/// <summary>
/// 获取规范化的路径
/// </summary>
public static string GetRegularPath(string path)
{
return path.Replace('\\', '/').Replace("\\", "/"); //替换为Linux路径格式
}
/// <summary>
/// 获取文件所在的目录路径Linux格式
/// </summary>
public static string GetDirectory(string filePath)
{
string directory = Path.GetDirectoryName(filePath);
return GetRegularPath(directory);
}
/// <summary>
/// 获取基于流文件夹的加载路径
/// </summary>
public static string MakeStreamingLoadPath(string path)
{
return StringUtility.Format("{0}/{1}", UnityEngine.Application.streamingAssetsPath, path);
}
/// <summary>
/// 获取基于沙盒文件夹的加载路径
/// </summary>
public static string MakePersistentLoadPath(string path)
{
string root = MakePersistentRootPath();
return StringUtility.Format("{0}/{1}", root, path);
}
/// <summary>
/// 获取沙盒文件夹路径
/// </summary>
public static string MakePersistentRootPath()
{
#if UNITY_EDITOR
// 注意:为了方便调试查看,编辑器下把存储目录放到项目里
string projectPath = GetDirectory(UnityEngine.Application.dataPath);
return StringUtility.Format("{0}/Sandbox", projectPath);
#else
return StringUtility.Format("{0}/Sandbox", UnityEngine.Application.persistentDataPath);
#endif
}
/// <summary>
/// 获取网络资源加载路径
/// </summary>
public static string ConvertToWWWPath(string path)
{
// 注意WWW加载方式必须要在路径前面加file://
#if UNITY_EDITOR
return StringUtility.Format("file:///{0}", path);
#elif UNITY_IPHONE
return StringUtility.Format("file://{0}", path);
#elif UNITY_ANDROID
return path;
#elif UNITY_STANDALONE
return StringUtility.Format("file:///{0}", path);
#endif
}
/// <summary>
/// 合并资源路径
/// </summary>
internal static string CombineAssetPath(string root, string location)
{
if (string.IsNullOrEmpty(root))
return location;
else
return $"{root}/{location}";
}
/// <summary>
/// 获取AssetDatabase的加载路径
/// </summary>
internal static string FindDatabaseAssetPath(string filePath)
{
#if UNITY_EDITOR
if (File.Exists(filePath))
return filePath;
// AssetDatabase加载资源需要提供文件后缀格式然而资源定位地址并没有文件格式信息。
// 所以我们通过查找该文件所在文件夹内同名的首个文件来确定AssetDatabase的加载路径。
// 注意AssetDatabase.FindAssets() 返回文件内包括递归文件夹内所有资源的GUID
string fileName = Path.GetFileName(filePath);
string directory = GetDirectory(filePath);
string[] guids = UnityEditor.AssetDatabase.FindAssets(string.Empty, new[] { directory });
for (int i = 0; i < guids.Length; i++)
{
string assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[i]);
if (UnityEditor.AssetDatabase.IsValidFolder(assetPath))
continue;
string assetDirectory = GetDirectory(assetPath);
if (assetDirectory != directory)
continue;
string assetName = Path.GetFileNameWithoutExtension(assetPath);
if (assetName == fileName)
return assetPath;
}
// 没有找到同名的资源文件
Logger.Warning($"Not found asset : {filePath}");
return filePath;
#else
throw new System.NotImplementedException();
#endif
}
}
}

View File

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

View File

@ -0,0 +1,326 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace YooAsset
{
internal static class AssetSystem
{
private static readonly List<BundleFileLoader> _loaders = new List<BundleFileLoader>(1000);
private static readonly List<AssetProviderBase> _providers = new List<AssetProviderBase>(1000);
/// <summary>
/// 在编辑器下模拟运行
/// </summary>
public static bool SimulationOnEditor { private set; get; }
/// <summary>
/// 运行时的最大加载个数
/// </summary>
public static int AssetLoadingMaxNumber { private set; get; }
public static IDecryptServices DecryptServices { private set; get; }
public static IBundleServices BundleServices { private set; get; }
/// <summary>
/// 初始化资源系统
/// 注意在使用AssetSystem之前需要初始化
/// </summary>
public static void Initialize(bool simulationOnEditor, int assetLoadingMaxNumber, IDecryptServices decryptServices, IBundleServices bundleServices)
{
SimulationOnEditor = simulationOnEditor;
AssetLoadingMaxNumber = assetLoadingMaxNumber;
DecryptServices = decryptServices;
BundleServices = bundleServices;
}
/// <summary>
/// 轮询更新
/// </summary>
public static void UpdatePoll()
{
// 更新加载器
foreach (var loader in _loaders)
{
loader.Update();
}
// 更新资源提供者
// 注意:循环更新的时候,可能会扩展列表
// 注意:不能限制场景对象的加载
int loadingCount = 0;
for (int i = 0; i < _providers.Count; i++)
{
var provider = _providers[i];
if (provider.IsSceneProvider())
{
provider.Update();
}
else
{
if (loadingCount < AssetLoadingMaxNumber)
provider.Update();
if (provider.IsDone == false)
loadingCount++;
}
}
// 注意:需要立刻卸载场景
if (SimulationOnEditor)
{
for (int i = _providers.Count - 1; i >= 0; i--)
{
AssetProviderBase provider = _providers[i];
if (provider.IsSceneProvider() && provider.CanDestroy())
{
provider.Destory();
_providers.RemoveAt(i);
}
}
}
else
{
for (int i = _loaders.Count - 1; i >= 0; i--)
{
BundleFileLoader loader = _loaders[i];
if (loader.IsSceneLoader())
{
loader.TryDestroyAllProviders();
}
}
for (int i = _loaders.Count - 1; i >= 0; i--)
{
BundleFileLoader loader = _loaders[i];
if (loader.IsSceneLoader() && loader.CanDestroy())
{
loader.Destroy(false);
_loaders.RemoveAt(i);
}
}
}
}
/// <summary>
/// 资源回收(卸载引用计数为零的资源)
/// </summary>
public static void UnloadUnusedAssets()
{
if (SimulationOnEditor)
{
for (int i = _providers.Count - 1; i >= 0; i--)
{
if (_providers[i].CanDestroy())
{
_providers[i].Destory();
_providers.RemoveAt(i);
}
}
}
else
{
for (int i = _loaders.Count - 1; i >= 0; i--)
{
BundleFileLoader loader = _loaders[i];
loader.TryDestroyAllProviders();
}
for (int i = _loaders.Count - 1; i >= 0; i--)
{
BundleFileLoader loader = _loaders[i];
if (loader.CanDestroy())
{
loader.Destroy(false);
_loaders.RemoveAt(i);
}
}
}
}
/// <summary>
/// 强制回收所有资源
/// </summary>
public static void ForceUnloadAllAssets()
{
foreach (var provider in _providers)
{
provider.Destory();
}
_providers.Clear();
foreach (var loader in _loaders)
{
loader.Destroy(true);
}
_loaders.Clear();
// 注意:调用底层接口释放所有资源
Resources.UnloadUnusedAssets();
}
/// <summary>
/// 异步加载场景
/// </summary>
/// <param name="scenePath">场景名称</param>
public static AssetOperationHandle LoadSceneAsync(string scenePath, SceneInstanceParam instanceParam)
{
AssetProviderBase provider = TryGetAssetProvider(scenePath);
if (provider == null)
{
if (SimulationOnEditor)
provider = new DatabaseSceneProvider(scenePath, instanceParam);
else
provider = new BundledSceneProvider(scenePath, instanceParam);
_providers.Add(provider);
}
// 引用计数增加
provider.Reference();
return provider.Handle;
}
/// <summary>
/// 异步加载资源对象
/// </summary>
/// <param name="assetPath">资源路径</param>
/// <param name="assetType">资源类型</param>
public static AssetOperationHandle LoadAssetAsync(string assetPath, System.Type assetType)
{
AssetProviderBase provider = TryGetAssetProvider(assetPath);
if (provider == null)
{
if (SimulationOnEditor)
provider = new DatabaseAssetProvider(assetPath, assetType);
else
provider = new BundledAssetProvider(assetPath, assetType);
_providers.Add(provider);
}
// 引用计数增加
provider.Reference();
return provider.Handle;
}
/// <summary>
/// 异步加载所有子资源对象
/// </summary>
/// <param name="assetPath">资源路径</param>
/// <param name="assetType">资源类型</param>、
public static AssetOperationHandle LoadSubAssetsAsync(string assetPath, System.Type assetType)
{
AssetProviderBase provider = TryGetAssetProvider(assetPath);
if (provider == null)
{
if (SimulationOnEditor)
provider = new DatabaseSubAssetsProvider(assetPath, assetType);
else
provider = new BundledSubAssetsProvider(assetPath, assetType);
_providers.Add(provider);
}
// 引用计数增加
provider.Reference();
return provider.Handle;
}
internal static BundleFileLoader CreateOwnerBundleLoader(string assetPath)
{
string bundleName = BundleServices.GetAssetBundleName(assetPath);
AssetBundleInfo bundleInfo = BundleServices.GetAssetBundleInfo(bundleName);
return CreateBundleFileLoaderInternal(bundleInfo);
}
internal static List<BundleFileLoader> CreateDependBundleLoaders(string assetPath)
{
List<BundleFileLoader> result = new List<BundleFileLoader>();
string[] depends = BundleServices.GetAllDependencies(assetPath);
if (depends != null)
{
foreach (var dependBundleName in depends)
{
AssetBundleInfo dependBundleInfo = BundleServices.GetAssetBundleInfo(dependBundleName);
BundleFileLoader dependLoader = CreateBundleFileLoaderInternal(dependBundleInfo);
result.Add(dependLoader);
}
}
return result;
}
internal static void RemoveBundleProviders(List<AssetProviderBase> providers)
{
foreach (var provider in providers)
{
_providers.Remove(provider);
}
}
private static BundleFileLoader CreateBundleFileLoaderInternal(AssetBundleInfo bundleInfo)
{
// 如果加载器已经存在
BundleFileLoader loader = TryGetBundleFileLoader(bundleInfo.BundleName);
if (loader != null)
return loader;
// 新增下载需求
loader = new BundleFileLoader(bundleInfo);
_loaders.Add(loader);
return loader;
}
private static BundleFileLoader TryGetBundleFileLoader(string bundleName)
{
BundleFileLoader loader = null;
for (int i = 0; i < _loaders.Count; i++)
{
BundleFileLoader temp = _loaders[i];
if (temp.BundleInfo.BundleName.Equals(bundleName))
{
loader = temp;
break;
}
}
return loader;
}
private static AssetProviderBase TryGetAssetProvider(string assetPath)
{
AssetProviderBase provider = null;
for (int i = 0; i < _providers.Count; i++)
{
AssetProviderBase temp = _providers[i];
if (temp.AssetPath.Equals(assetPath))
{
provider = temp;
break;
}
}
return provider;
}
#region 调试专属方法
internal static void GetDebugSummy(DebugSummy summy)
{
summy.ClearAll();
summy.BundleCount = _loaders.Count;
summy.AssetCount = _providers.Count;
foreach (var provider in _providers)
{
DebugSummy.ProviderInfo providerInfo = new DebugSummy.ProviderInfo();
providerInfo.AssetPath = provider.AssetPath;
providerInfo.RefCount = provider.RefCount;
providerInfo.States = provider.States;
providerInfo.BundleInfos.Clear();
summy.ProviderInfos.Add(providerInfo);
if (provider is BundledProvider)
{
BundledProvider temp = provider as BundledProvider;
temp.GetBundleDebugInfos(providerInfo.BundleInfos);
}
}
// 重新排序
summy.ProviderInfos.Sort();
}
#endregion
}
}

View File

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

View File

@ -0,0 +1,19 @@

namespace YooAsset
{
/// <summary>
/// 解密方法
/// </summary>
public enum EDecryptMethod
{
/// <summary>
/// 获取解密的数据偏移
/// </summary>
GetDecryptOffset,
/// <summary>
/// 获取解密的字节数据
/// </summary>
GetDecryptBinary,
}
}

View File

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

View File

@ -0,0 +1,21 @@

namespace YooAsset
{
public interface IBundleServices
{
/// <summary>
/// 获取AssetBundle的信息
/// </summary>
AssetBundleInfo GetAssetBundleInfo(string bundleName);
/// <summary>
/// 获取资源所属的资源包名称
/// </summary>
string GetAssetBundleName(string assetPath);
/// <summary>
/// 获取资源依赖的所有AssetBundle列表
/// </summary>
string[] GetAllDependencies(string assetPath);
}
}

View File

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

Some files were not shown because too many files have changed in this diff Show More