一目录结构
增加两个类
二代码:
AssetUtility
/****************************************************
文件:AssetUtility.cs
作者:赵深圳
邮箱: 1329346609@qq.com
日期:2022/5/7 18:24:12
功能:资源管理相关的工具函数
*****************************************************/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Security.Cryptography;
using System.IO;
using System.Text;
using System;
public static class AssetUtility
{
private static readonly CRC32 crc32 = new CRC32();
/// <summary>
/// 计算一个Stream对象的CRC32散列码
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static string GetCRC32Hash(Stream input)
{
byte[] data = crc32.ComputeHash(input);
return ToHash(data);
}
private static string ToHash(byte[] data)
{
StringBuilder sb = new StringBuilder();
foreach (byte t in data)
{
sb.Append(t.ToString("x2"));
}
return sb.ToString();
}
internal sealed class CRC32 : HashAlgorithm
{
private const uint DefaultPolynomial = 0xedb88320u;
private const uint DefaultSeed = 0xffffffffu;
private static uint[] _defaultTable;
private readonly uint _seed;
private readonly uint[] _table;
private uint _hash;
public CRC32()
: this(DefaultPolynomial, DefaultSeed)
{
}
public CRC32(uint polynomial, uint seed)
{
if (!BitConverter.IsLittleEndian)
throw new PlatformNotSupportedException("Not supported on Big Endian processors");
_table = InitializeTable(polynomial);
_seed = _hash = seed;
}
public override int HashSize
{
get { return 32; }
}
public override void Initialize()
{
_hash = _seed;
}
protected override void HashCore(byte[] array, int ibStart, int cbSize)
{
_hash = CalculateHash(_table, _hash, array, ibStart, cbSize);
}
protected override byte[] HashFinal()
{
var hashBuffer = UInt32ToBigEndianBytes(~_hash);
HashValue = hashBuffer;
return hashBuffer;
}
public static uint Compute(byte[] buffer)
{
return Compute(DefaultSeed, buffer);
}
public static uint Compute(uint seed, byte[] buffer)
{
return Compute(DefaultPolynomial, seed, buffer);
}
public static uint Compute(uint polynomial, uint seed, byte[] buffer)
{
return ~CalculateHash(InitializeTable(polynomial), seed, buffer, 0, buffer.Length);
}
private static uint[] InitializeTable(uint polynomial)
{
if (polynomial == DefaultPolynomial && _defaultTable != null)
return _defaultTable;
var createTable = new uint[256];
for (var i = 0; i < 256; i++)
{
var entry = (uint)i;
for (var j = 0; j < 8; j++)
if ((entry & 1) == 1)
entry = (entry >> 1) ^ polynomial;
else
entry >>= 1;
createTable[i] = entry;
}
if (polynomial == DefaultPolynomial)
_defaultTable = createTable;
return createTable;
}
private static uint CalculateHash(uint[] table, uint seed, IList<byte> buffer, int start, int size)
{
var hash = seed;
for (var i = start; i < start + size; i++)
hash = (hash >> 8) ^ table[buffer[i] ^ (hash & 0xff)];
return hash;
}
private static byte[] UInt32ToBigEndianBytes(uint uint32)
{
var result = BitConverter.GetBytes(uint32);
if (BitConverter.IsLittleEndian)
Array.Reverse(result);
return result;
}
}
}
ModuleABConfig:
/****************************************************
文件:ModuleABConfig.cs
作者:赵深圳
邮箱: 1329346609@qq.com
日期:2022/5/7 18:15:20
功能: AB、Asset信息
*****************************************************/
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 一个Bundle数据 【用于序列化为json文件】
/// </summary>
public class BundleInfo
{
/// <summary>
/// bundle名字
/// </summary>
public string bundle_name;
/// <summary>
/// 这个bundle资源对应的crc
/// </summary>
public string crc;
/// <summary>
/// 这个bundle所包含资源的路径列表
/// </summary>
public List<string> assetLst;
}
/// <summary>
/// 一个Asset数据 【用于序列化为json文件】
/// </summary>
public class AssetInfo
{
/// <summary>
/// 这个资源的相对路径
/// </summary>
public string asset_path;
/// <summary>
/// 这个资源所属的AB包名字
/// </summary>
public string bundle_name;
/// <summary>
/// 这个资源所依赖的AB名字列表
/// </summary>
public List<string> dependenciesLst;
}
/// <summary>
/// ModuleABConfig对象 对应 整个单个模块的json文件
/// </summary>
public class ModuleABConfig
{
public ModuleABConfig(int assetCount)
{
BundleInfoDic = new Dictionary<string, BundleInfo>();
AssetInfoArr = new AssetInfo[assetCount];
}
public ModuleABConfig() { }
/// <summary>
/// 所有的assetbundle信息
/// key:AssetBundle名字
/// </summary>
public Dictionary<string, BundleInfo> BundleInfoDic;
/// <summary>
/// 所有的Asset资源
/// </summary>
public AssetInfo[] AssetInfoArr;
/// <summary>
/// 新增一个bundle
/// </summary>
/// <param name="bundleName">assetbundle名字</param>
/// <param name="info">assetbundle名字信息</param>
public void AddBundle(string bundleName,BundleInfo info)
{
BundleInfoDic[bundleName] = info;
}
/// <summary>
/// 新增一个资源记录
/// </summary>
/// <param name="index"></param>
/// <param name="assetInfo"></param>
public void AddAsset(int index, AssetInfo assetInfo)
{
AssetInfoArr[index] = assetInfo;
}
}
ABEditor
/****************************************************
文件:ABEditor.cs
作者:赵深圳
邮箱: 1329346609@qq.com
日期:2022/5/7 15:54:51
功能:AB包编辑器
*****************************************************/
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Collections.Generic;
using Newtonsoft.Json;
/// <summary>
/// 生成AB包编辑器工具
/// </summary>
public class ABEditor
{
/// <summary>
/// 需要打包的资源所在目标
/// </summary>
public static string rootPath = Application.dataPath + "/1SimpleABManager/Download";
/// <summary>
/// AB包输出路径
/// </summary>
public static string outABPath=Application.streamingAssetsPath;
/// <summary>
/// 记录每个资源所有在的AB包文件
/// key为路径
/// value为AB包名字
/// </summary>
public static Dictionary<string, string> m_Asset2BundleDic = new Dictionary<string, string>();
/// <summary>
/// 所有需要打包的资源对应的AssetBundleBuild
/// 一个文件对应一个AssetBundleBuild
/// </summary>
public static List<AssetBundleBuild> m_AssetBundleBuildLst = new List<AssetBundleBuild>();
/// <summary>
/// 记录每个Asset资源所依赖的AB包文件
/// key:Asset名字
/// Value:每个资源所依赖的AB包列表
/// </summary>
public static Dictionary<string, List<string>> m_Asset2DepenendciesDic = new Dictionary<string, List<string>>();
/// <summary>
/// 打包AB包
/// </summary>
[MenuItem("ZSTools/BuildABTools")]
public static void BuildAssetBundle()
{
Log.ZSZLog(LogCategory.Normal,"开始打AB包!!!");
if (Directory.Exists(outABPath))
{
Directory.Delete(outABPath,true);
}
//打包所有的文件夹
DirectoryInfo rootDir = new DirectoryInfo(rootPath);
//获取资源路径下所有的文件夹
//这里获取的文件夹,不包括自身,所有在资源所在文件(Application.dataPath + "/1SimpleABManager/Download")下不要放打包资源,会打包不到
//放在资源所在文件所在的下一级文件,如果需要放在资源文件夹中,请把自身加入到数组中
DirectoryInfo[] dirs = rootDir.GetDirectories();
foreach (DirectoryInfo moduleDir in dirs)
{
string moduleName = moduleDir.Name;
m_AssetBundleBuildLst.Clear();
m_Asset2BundleDic.Clear();
m_Asset2DepenendciesDic.Clear();
ScanChildDireations(moduleDir);
AssetDatabase.Refresh();
string moduleOutPath = outABPath + "/" + moduleName;
if (Directory.Exists(moduleOutPath))
{
Directory.Delete(moduleOutPath, true);
}
Directory.CreateDirectory(moduleOutPath);
//打包
BuildPipeline.BuildAssetBundles(moduleOutPath,m_AssetBundleBuildLst.ToArray(),BuildAssetBundleOptions.None,EditorUserBuildSettings.activeBuildTarget);
//计算依赖关系
CalAssetDependencies();
Log.ZSZLog(LogCategory.Normal, "计算依赖关系完毕!!!");
//生成配置文件
SaveModuleABConfig(moduleName);
Log.ZSZLog(LogCategory.Normal, "生成配置文件完毕!!!");
AssetDatabase.Refresh();
}
Log.ZSZLog(LogCategory.Normal,"打包AB包结束!!!");
}
/// <summary>
/// 根据指定的文件夹
/// 将这个文件夹下所有的一级子文件夹打成一个AB包
/// 递归这个文件夹下所有的子文件夹
/// </summary>
/// <param name="directoryInfo"></param>
private static void ScanChildDireations(DirectoryInfo directoryInfo)
{
if (directoryInfo.Name.EndsWith("CSProject")|| directoryInfo.Name.EndsWith(".cs"))
{
return;
}
ScanCurrDirectory(directoryInfo);
//递归当前路径下的所有文件夹
//使所有的一级文件夹为一个模块
DirectoryInfo[] dirs = directoryInfo.GetDirectories();
foreach (var item in dirs)
{
ScanChildDireations(item);
}
}
/// <summary>
/// 遍历当前路径下的文件,并打成一个AB包
/// </summary>
/// <param name="directoryInfo"></param>
private static void ScanCurrDirectory(DirectoryInfo directoryInfo)
{
List<string> assetNameLst = new List<string>();
//获取指定文件夹中所有的文件
FileInfo[] fileInfoArr = directoryInfo.GetFiles();
foreach (FileInfo fileInfo in fileInfoArr)
{
if (fileInfo.FullName.EndsWith(".meta"))
{
continue;
}
//assetName的格式类似 "Assets/GAssets/Launch/Sphere.prefab"
string assetName = fileInfo.FullName.Substring(Application.dataPath.Length-"Assets".Length).Replace('\\','/');
assetNameLst.Add(assetName);
}
if (assetNameLst.Count > 0)
{
string assetBundleName = directoryInfo.FullName.Substring(Application.dataPath.Length + 1).Replace('\\','_').ToLower();
AssetBundleBuild build = new AssetBundleBuild();
build.assetBundleName = assetBundleName;
build.assetNames = new string[assetNameLst.Count];
for (int i = 0; i < assetNameLst.Count; i++)
{
build.assetNames[i] = assetNameLst[i];
m_Asset2BundleDic.Add(assetNameLst[i],assetBundleName);
}
m_AssetBundleBuildLst.Add(build);
}
}
/// <summary>
/// 计算每个资源依赖的AB包列表
/// </summary>
private static void CalAssetDependencies()
{
foreach (string asset in m_Asset2BundleDic.Keys)
{
//当前资源所在的AB包
string assetBundle = m_Asset2BundleDic[asset];
//获取当前资源所有的依赖
string[] dependencies = AssetDatabase.GetDependencies(asset);
List<string> assetLst = new List<string>();
//获取当前所有依赖的资源的名字
if (dependencies != null && dependencies.Length > 0)
{
foreach (string oneAsset in dependencies)
{
if (oneAsset==asset||oneAsset.EndsWith(".cs"))
{
continue;
}
assetLst.Add(oneAsset);
}
}
if (assetLst.Count > 0)
{
List<string> abList = new List<string>();
//获取当前所有的依赖资源所在的ab包
foreach (string oneAsset in assetLst)
{
bool result= m_Asset2BundleDic.TryGetValue(oneAsset,out string bundle);
if (result==true)
{
if (bundle!= assetBundle)
{
abList.Add(bundle);
}
}
}
//存入依赖字典中
m_Asset2DepenendciesDic.Add(asset,abList);
}
}
}
/// <summary>
/// 将一个模块的资源依赖关系数据保存json文件
/// </summary>
/// <param name="moduleNmae">模块名字</param>
private static void SaveModuleABConfig(string moduleNmae)
{
ModuleABConfig config = new ModuleABConfig();
//记录AB包信息 遍历所有AssetBundleBuild
foreach (AssetBundleBuild build in m_AssetBundleBuildLst)
{
BundleInfo bundleInfo = new BundleInfo();
//获取当前AssetBundleBuild的名字(AB包名)
bundleInfo.bundle_name = build.assetBundleName;
bundleInfo.assetLst = new List<string>();
//遍历当前AB包中所有的资源信息
foreach (string item in build.assetNames)
{
bundleInfo.assetLst.Add(item);
}
//计算CRC
string abFilePath = outABPath + "/" + moduleNmae + "/" + bundleInfo.bundle_name;
using (FileStream stream=File.OpenRead(abFilePath))
{
bundleInfo.crc = AssetUtility.GetCRC32Hash(stream);
}
config.AddBundle(bundleInfo.bundle_name,bundleInfo);
}
//记录每个资源的依赖关系
int assetIndex = 0;
//获取所有的AB名字信息
foreach (var item in m_Asset2BundleDic)
{
AssetInfo assetInfo = new AssetInfo();
assetInfo.asset_path = item.Key;//设置路径
assetInfo.bundle_name = item.Value;//设置AB包名字
assetInfo.dependenciesLst = new List<string>();
//获取所有的依赖路径
bool result = m_Asset2DepenendciesDic.TryGetValue(item.Key,out List<string> dependencies);
if (result==true)
{
for (int i = 0; i < dependencies.Count; i++)
{
string bundleNmae = dependencies[i];
assetInfo.dependenciesLst.Add(bundleNmae);//加入到资源所依赖的资源列表中
}
}
config.AddAsset(assetIndex,assetInfo);
assetIndex++;
}
string moduleConfigName = moduleNmae.ToLower() + ".json";
string jsonPath = outABPath + "/" + moduleNmae + "/" + moduleConfigName;
if (File.Exists(jsonPath))
{
File.Delete(jsonPath);
}
File.Create(jsonPath).Dispose();
string jsonData = LitJson.JsonMapper.ToJson(config);
File.WriteAllText(jsonPath,ConvertJsonString( jsonData));
}
/// <summary>
/// 格式化json
/// </summary>
/// <param name="str">输入json字符串</param>
/// <returns>返回格式化后的字符串</returns>
private static string ConvertJsonString(string str)
{
JsonSerializer serializer = new JsonSerializer();
TextReader tr = new StringReader(str);
JsonTextReader jtr = new JsonTextReader(tr);
object obj = serializer.Deserialize(jtr);
if (obj != null)
{
StringWriter textWriter = new StringWriter();
JsonTextWriter jsonWriter = new JsonTextWriter(textWriter)
{
Formatting = Formatting.Indented,
Indentation = 4,
IndentChar = ' '
};
serializer.Serialize(jsonWriter, obj);
return textWriter.ToString();
}
else
{
return str;
}
}
}