当然前面两篇的都是为这篇的代码服务的。
BuildAssetBundle.cs这个文件就是主要的打包assetbundle操作了。
首先是这些声明:
//assetbundle资源
static string assetFilePath = "/assetbundles";
static string buildCfgFilePath = Application.dataPath + "/Editor/Build/build.txt";
static string levelCfgFilePath = Application.dataPath + "/Editor/Build/level.txt";
static string buildResourceCfg = Application.dataPath + "/Editor/Build/Resource.txt";
//打包环境设置
static BuildAssetBundleOptions options = BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets | BuildAssetBundleOptions.DeterministicAssetBundle;
static BuildTarget buildPlatform = BuildTarget.StandaloneWindows;
//保存所有的scene信息
static List<string> mScenes = new List<string>();
//保存所有Resource信息
static List<string> mResources = new List<string>();
//保存所有的Asset信息 场景+Resource
//保存所有要用到的资源,并按依赖数量分好了level
//int:代表level
//string:文件的路径,包括路径和文件名.
//AssetUnit:string文件的各种信息存放在这里面
static Dictionary<int, Dictionary<string, AssetUnit>> allLevelAssets = new Dictionary<int, Dictionary<string, AssetUnit>>();
然后接下来的3个函数不言而喻:
这三个函数都是放到editor中的,
显示到Editor中就是这样子的。
//Andriod打包
[MenuItem("Build/BuildAndriod")]
public static void BuildAndriod()
{
buildPlatform = BuildTarget.Android;
BuildResourceFromUnityRule();
}
//IOS打包
[MenuItem("Build/BuildIOS")]
public static void BuildIOS()
{
buildPlatform = BuildTarget.iPhone;
BuildResourceFromUnityRule();
}
//Windows打包
[MenuItem("Build/BuildWindows")]
public static void BuildWindows()
{
buildPlatform = BuildTarget.StandaloneWindows;
BuildResourceFromUnityRule();
}
当然最主要的还是BuildResourceFromUnityRule()这个函数:
public static void BuildResourceFromUnityRule()
{
//清空数据
//assetbundleFilePath:Application.dataPath + "/assetbundles/"
if (Directory.Exists(ResourceCommon.assetbundleFilePath))
Directory.Delete(ResourceCommon.assetbundleFilePath, true);
//刷新数据
Caching.CleanCache();
AssetDatabase.Refresh();
//获取所有Scene信息
//通过自己定义的levelCfgFilePath目录的文件来获取所有Scene的路径,
//然后存到mScenes里面
GetBuildScenes();
//获取所有需要build的文件,
//然后保存到mResources中,当然保存的都是路径信息
GetBuildResources();
//get到所有mScenes和mResources中的
//资源的所有依赖,这样所有要用到的资源都get到了,
//然后按依赖个数把这些资源分等级。
//就是填充allLevelAssets这个Dictionary
GetAllAssets();
//保存Asset资源信息
//这个函数里面生成了所有文件(未打包)的XML信息,
//并保存到Assets/assetbundles/AssetInfo.bytes文件中
DumpAssetInfoFile();
//打包asset
//打包Assets/Resources下的所有资源(排除了无用资源)
//打包到Assets/assetbundles目录下
BuildResource();
//保存资源配置assetbundle
//所有打包好的资源的信息统一保存到
//Assets/assetbundles/Resource.bytes文件中(XML格式)
DumpResourceFile();
//根据所有assetbundle文件生成版本信息
//对每一个打包好的文件计算MD5值,
//并把文件基本信息存成XML文件version.bytes
//生成在Assets/assetbundles目录下,然后再复制到Assets/Resources目录下
//这里面也顺便把文件size存到了对应的AssetUnit里面
DumpVersionFile();
//根据生成的assetbundle文件大小填充 assetInfo.byte文件
//读取之前存到AssetUnit里面的的size,然后修改AssetInfo.bytes文件
AddAssetSizeToAssetInfoFile();
AssetDatabase.Refresh();
DebugEx.LogError("BuildResource finish", "BuildResource", true);
}
剩下的就是具体实现了,太长了,不过还是一下子贴出来好了:
//获取需要打包Resources目录下所有资源信息
//Assets/Resources目录下所有文件存到mResources里,除了meta文件和Version.bytes文件
[MenuItem("Build/Tools/GetBuildResources")]
public static void GetBuildResources()
{
mResources.Clear();
//Resource资源路径
string resourcePath = Application.dataPath + "/Resources/";
string[] files = Directory.GetFiles(resourcePath, "*.*", SearchOption.AllDirectories);
foreach (string file in files)
{
string suffix = BuildCommon.getFileSuffix(file);
if (suffix == "meta")
continue;
string realFile = file.Replace("\\", "/");
realFile = realFile.Replace(Application.dataPath, "Assets");
if (realFile == "Assets/Resources/Version.bytes")
continue;
mResources.Add(realFile);
}
}
//获取选中需要打包的Resources资源信息
[MenuItem("Build/Tools/GetSelectedBuildResources")]
public static void GetSelectedBuildResources()
{
mResources.Clear();
//Resource资源路径
//string resourcePath = Application.dataPath + "/Resources/";
//string[] files = Directory.GetFiles(resourcePath, "*.*", SearchOption.AllDirectories);
//foreach (string file in files)
//{
// string suffix = BuildCommon.getFileSuffix(file);
// if (suffix == "meta" || suffix == "cs")
// continue;
// string realFile = file.Replace("\\", "/");
// realFile = realFile.Replace(Application.dataPath, "Assets");
// mResources.Add(realFile);
//}
Object[] selection = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);
if (selection.Length == 0)
return;
foreach (Object obj in selection)
{
string assetPath = AssetDatabase.GetAssetPath(obj);
mResources.Add(assetPath);
Debug.Log("select:" + assetPath);
}
}
//获取需要打包的Scene目录下的所有资源信息
public static void GetBuildScenes()
{
mScenes.Clear();
//获取场景路径
FileStream fs = new FileStream(levelCfgFilePath, FileMode.Open);
StreamReader sr = new StreamReader(fs);
string path = "";
while (path != null)
{
if (path != "")
{
mScenes.Add(path);
}
path = sr.ReadLine();
}
sr.Close();
fs.Close();
}
[MenuItem("Build/Tools/TestSceneAssets")]
public static void TestSceneAssets()
{
string sceneName = "Assets/Scenes/1.unity";
string[] depends = AssetDatabase.GetDependencies(new string[] { sceneName });
foreach (string str in depends)
{
Debug.Log("depency: " + str);
}
}
//获取所有的Asset
[MenuItem("Build/Tools/GetAllAssets")]
public static void GetAllAssets()
{
//清空操作
allLevelAssets.Clear();
List<string> allAssetPath = new List<string>();
//1添加场景路径
foreach (string scene in mScenes)
{
allAssetPath.Add(scene);
}
//2添加Resource资源路径
foreach(string resrPath in mResources)
{
allAssetPath.Add(resrPath);
}
//获取场景以及预制件中所有的资源信息
//保存场景文件以及所有的resource文件的依赖
string[] allExportAssets = AssetDatabase.GetDependencies(allAssetPath.ToArray());
//这里allFiles好像声明了并没用过
Dictionary<string, AssetUnit> allFiles = new Dictionary<string, AssetUnit>();
foreach (string p in allExportAssets)
{
//if (p.Contains(".dll") || p.Contains(".cs"))
if (p.Contains(".dll"))
continue;
AssetUnit unit = new AssetUnit(p);
//Asset等级
//level最低是1.
int level = unit.mLevel;
//存在
if (allLevelAssets.ContainsKey(level))
{
allLevelAssets[level].Add(p, unit);
}
else
{
//添加等级
Dictionary<string, AssetUnit> levelAsset = new Dictionary<string, AssetUnit>();
allLevelAssets.Add(level,levelAsset);
//添加asset信息
allLevelAssets[level].Add(p, unit);
}
}
//创建Asset索引
BuildAssetUnitIndex();
}
[MenuItem("Build/Tools/CheckResources")]
public static void CheckResources()
{
AssetDatabase.Refresh();
//获取资源信息
GetBuildScenes();
GetBuildResources();
CheckAssetValid();
Debug.Log("Check finised");
}
[MenuItem("Build/Tools/BuildSelected")]
public static void BuildSelected()
{
//清空数据
if (Directory.Exists(ResourceCommon.assetbundleFilePath))
Directory.Delete(ResourceCommon.assetbundleFilePath, true);
//刷新数据
Caching.CleanCache();
AssetDatabase.Refresh();
//获取资源信息
GetBuildScenes();
GetSelectedBuildResources();
//获取所有的打包asset
GetAllAssets();
//保存Asset资源信息
DumpAssetInfoFile();
//打包asset
BuildResource();
//保存资源配置assetbundle
DumpResourceFile();
//根据所有assetbundle文件生成版本信息
DumpVersionFile();
//根据生成的assetbundle文件大小填充 assetInfo.byte文件
AddAssetSizeToAssetInfoFile();
AssetDatabase.Refresh();
DebugEx.LogError("BuildResource finish", "BuildResource", true);
}
public static void BuildResource()
{
AssetDatabase.Refresh();
//执行依赖性打包
//资源最大等级
int maxLevel = allLevelAssets.Count;
if (maxLevel == 0)
return;
//从最低等级开始打包
for (int level = 1; level <= maxLevel; level++)
{
//启用以下所有资源的交叉引用,
//直到调用PopAssetDependencies为止
//--这算是打包的必要步骤,
BuildPipeline.PushAssetDependencies();
//获取不同等级的aaset
Dictionary<string, AssetUnit> levelAssets = allLevelAssets[level];
//遍历该等级的所有asset打包
foreach(KeyValuePair<string, AssetUnit> pair in levelAssets)
{
//根据路径获取asset资源
Object asset = AssetDatabase.LoadMainAssetAtPath(pair.Value.mPath);
if (null == asset)
DebugEx.LogError("load " + pair.Value.mPath + " failed!!!", "BuildResource", true);
//生成打包保存路径
//assetbundleFileSuffix = ".bytes";
string savePath = pair.Value.mPath.Insert(6, assetFilePath) + ResourceCommon.assetbundleFileSuffix;
BuildCommon.CheckFolder(BuildCommon.getPath(savePath));
//打包名称去Asset
//场景资源(xxx.unity)和普通资源分开
//普通资源
//打包成xxx.bytes文件
//可以到Assets/assetbundles这个目录下去看看
if (pair.Value.mSuffix != "unity")//普通资源
{
string assetName = pair.Value.mPath.Replace("Assets/", "");
//资源打包
//这函数过时了
if (!BuildPipeline.BuildAssetBundleExplicitAssetNames(
new Object[] { asset }, new string[] { assetName }, savePath, options, buildPlatform))
DebugEx.LogError("build assetbundle " + savePath + " failed!!!", "BuildResource", true);
}
//场景资源,没有依赖场景的
else
{
AssetDatabase.Refresh();
BuildPipeline.PushAssetDependencies();
string error = BuildPipeline.BuildStreamedSceneAssetBundle(new string[]{pair.Value.mPath}, savePath, buildPlatform);
if (error != "")
DebugEx.LogError(error, "BuildResource", true);
BuildPipeline.PopAssetDependencies();
Debug.Log("scene path" + pair.Value.mPath);
//pair.Value.mPath
}
}
}
//popdepency依赖
for (int level = 1; level <= maxLevel; level++)
{
BuildPipeline.PopAssetDependencies();
}
}
//创建Asset资源信息
//把所有资源的信息保存到一个xml文件里面
public static void DumpAssetInfoFile()
{
if(allLevelAssets.Count ==0)
return;
////创建所有资源Asset列表
XmlDocument doc = new XmlDocument();
XmlElement root = doc.CreateElement("AllAssets");
//遍历所有Asset数据
for (int level = 1; level <= allLevelAssets.Count; level++)
{
Dictionary<string, AssetUnit> levelAssets = allLevelAssets[level];
foreach (KeyValuePair<string, AssetUnit> asset in levelAssets)
{
string assetName = asset.Key;
AssetUnit assetUnit = asset.Value;
XmlElement ele = doc.CreateElement("Asset");
//设置路径名称
assetName = assetName.Replace("Assets/", "");
ele.SetAttribute("name", assetName);
//设置asset索引
ele.SetAttribute("index", assetUnit.mIndex.ToString());
//设置等级
ele.SetAttribute("level", level.ToString());
List<AssetUnit> sortDepencys = new List<AssetUnit>();
//获取AssetUnit所有依赖,并排序
List<string> depencys = assetUnit.mAllDependencies;
foreach (string depency in depencys)
{
AssetUnit depencyUnit = GetAssetUnit(depency);
sortDepencys.Add(depencyUnit);
}
//排序
sortDepencys.Sort(SortAssetUnit);
//保存依赖索引
string depencystr = "";
for (int i = 0; i < sortDepencys.Count; i++)
{
AssetUnit unit = sortDepencys[i];
if (i != sortDepencys.Count - 1)
depencystr += unit.mIndex + ",";
else
depencystr += unit.mIndex;
}
ele.SetAttribute("depency", depencystr.ToString());
root.AppendChild(ele);
}
}
doc.AppendChild(root);
//这是assetbundleFilePath的声明:写在ResourceCommon里面
//public static string assetbundleFilePath = Application.dataPath + "/assetbundles/";
BuildCommon.CheckFolder(BuildCommon.getPath(ResourceCommon.assetbundleFilePath));
doc.Save(ResourceCommon.assetbundleFilePath + "AssetInfo.bytes");
Debug.Log("CreateAssetInfo success!!!");
}
//创建资源索引
public static void DumpResourceFile()
{
////创建所有资源Asset列表
XmlDocument doc = new XmlDocument();
XmlElement root = doc.CreateElement("AllResources");
XmlElement resource = doc.CreateElement("Resources");
root.AppendChild(resource);
foreach (string res in mResources)
{
string ex = BuildCommon.getFileSuffix(res);
string path = res.Replace("Assets/", "");
//去掉后缀
path = path.Replace("." + ex, "");
XmlElement ele = doc.CreateElement("file");
ele.SetAttribute("name", path);
ele.SetAttribute("type", ex);
resource.AppendChild(ele);
}
//创建所有需要打包的Level列表
XmlElement sceneRes = doc.CreateElement("Level");
root.AppendChild(sceneRes);
foreach (string scene in mScenes)
{
XmlElement ele = doc.CreateElement("file");
string path = scene.Replace("Assets/", "");
path = path.Replace(".unity", "");
ele.SetAttribute("name", path);
ele.SetAttribute("type", "unity");
sceneRes.AppendChild(ele);
}
doc.AppendChild(root);
BuildCommon.CheckFolder(BuildCommon.getPath(ResourceCommon.assetbundleFilePath));
doc.Save(ResourceCommon.assetbundleFilePath + "Resource.bytes");
Debug.Log("CreateResourceCfg success!!!");
}
[MenuItem("Build/Tools/DumpVersionFile")]
public static void DumpVersionFile()
{
List<string> allFiles = new List<string>();
BuildCommon.GetFiles(ResourceCommon.assetbundleFilePath, allFiles, true);
XmlDocument doc = new XmlDocument();
XmlElement root = doc.CreateElement("Version");
root.SetAttribute("Number", "1.0.0");
root.SetAttribute("Big", "false");
foreach (string element in allFiles)
{
int size = 0;
string md5 = GetFileMD5(element, ref size);
XmlElement ele = doc.CreateElement("file");
string relativePath = element.Replace(Application.dataPath + "/assetbundles/", "");
ele.SetAttribute("fpath", relativePath);
ele.SetAttribute("size", size.ToString());
ele.SetAttribute("md5", md5);
root.AppendChild(ele);
//保存大小信息到AssetUnit中
//一开始初始化AssetUnit的时候,size为0,
//在这里把size存进AssetUnit
string assetName = "Assets/" + relativePath;
assetName = assetName.Substring(0, assetName.Length - 6);
AssetUnit assetUnit = GetAssetUnit(assetName);
if(assetUnit != null)
assetUnit.mAssetSize = size;
}
doc.AppendChild(root);
string assetBundleVersionPath = ResourceCommon.assetbundleFilePath + "Version.bytes";
doc.Save(assetBundleVersionPath);
//拷贝到对应的Resource目录
string resourceVersionPath = Application.dataPath + "/Resources/" + "Version.bytes";
File.Copy(assetBundleVersionPath, resourceVersionPath, true);
Debug.Log("Dump Version Success!!!");
}
//添加asset信息
private static void AddAssetSizeToAssetInfoFile()
{
//添加asset信息
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(ResourceCommon.assetbundleFilePath + "AssetInfo.bytes");
XmlNodeList xnl = xmlDoc.SelectSingleNode("AllAssets").ChildNodes;
for (int i = 0; i < xnl.Count; i++)
{
XmlElement xe = (XmlElement)xnl.Item(i);
string assetName = xe.GetAttribute("name");
//获取对应的AssetUnit
AssetUnit unit = GetAssetUnit("Assets/" + assetName);
xe.SetAttribute("bundlesize", unit.mAssetSize.ToString());
}
xmlDoc.Save(ResourceCommon.assetbundleFilePath + "AssetInfo.bytes");
}
//计算文件MD5值,顺便获得文件size
//其实我觉得这函数写到BuildCommon里比较好
private static string GetFileMD5(string fpath, ref int size)
{
FileStream fs = new FileStream(fpath, FileMode.Open);
MD5 md5 = MD5.Create();
byte[] vals = md5.ComputeHash(fs);
string ret = BitConverter.ToString(vals);
ret = ret.Replace("-", "");
size = (int)fs.Length;
fs.Close();
return ret;
}
//按等级顺序创建AssetUnit的索引信息
//给allLevelAssets中的所有level中的所有AssetUnit的mIndex赋上值
//从level0的第一个AssetUnit开始,第一个AssetUnit的index为0,然后后面的AssetUnit依次index++
private static void BuildAssetUnitIndex()
{
if (allLevelAssets.Count == 0)
return;
int index = 0;
for (int level = 1; level <= allLevelAssets.Count; level++)
{
Dictionary<string, AssetUnit> levelAssets = allLevelAssets[level];
foreach (KeyValuePair<string, AssetUnit> pair in levelAssets)
{
AssetUnit unit = pair.Value;
unit.mIndex = index;
index++;
}
}
}
//根据Asset名称获取对应的AssetUnit
private static AssetUnit GetAssetUnit(string name)
{
if (allLevelAssets.Count == 0)
return null;
for (int level = 1; level <= allLevelAssets.Count; level++)
{
Dictionary<string, AssetUnit> assetUnit = allLevelAssets[level];
if (assetUnit.ContainsKey(name))
return assetUnit[name];
}
return null;
}
//检查所有的打包资源
private static void CheckAssetValid()
{
//清空操作
allLevelAssets.Clear();
List<string> allAssetPath = new List<string>();
//1添加场景路径
foreach (string scene in mScenes)
{
allAssetPath.Add(scene);
}
//2添加Resource资源路径
foreach(string resrPath in mResources)
{
allAssetPath.Add(resrPath);
}
//获取场景以及预制件中所有的资源信息
string[] allExportAssets = AssetDatabase.GetDependencies(allAssetPath.ToArray());
List<string> checkAssets = new List<string>();
foreach (string p in allExportAssets)
{
if (p.Contains(".dll"))
continue;
string assetName = Path.GetFileName(p);
assetName = assetName.ToLower();
if(checkAssets.Contains(assetName))
{
DebugEx.Log("the asset " + assetName + "has already exsited");
}
else
{
checkAssets.Add(assetName);
}
}
}
//对AssetUnit进行排序
private static int SortAssetUnit(AssetUnit unit1, AssetUnit unit2)
{
int res = 0;
if (unit1.mLevel > unit2.mLevel)
{
res = 1;
}
else if (unit1.mLevel < unit2.mLevel)
{
res = -1;
}
return res;
}
}
太长了,不过文章开头应该能理清大概步骤了,然后后面很长的代码就是具体的实现代码,这样应该会好理解一些。