前提说明:
- 本文只是针对Unity5.x及以上版本打包AssetBundle。Unity5.x虽然说打包时会处理好资源的依赖关系,但前提依然要我们设置好目标资源的AssetBundleName,如果设置资源AssetBundleName时忽略了资源之间的依赖关系,那么打包AssetBundle时,依然会产生重复打包的资源,所以我写了一套脚本来自动分析资源的依赖关系,并根据资源的依赖关系来设置AssetBundleName,从而避免不必要的资源重复打包。
我也了解过,
Unity的AssetStore也有对应的图形界面工具,查看和处理打包AssetBundle时遇到的资源重复打包问题,但觉得图形化界面工具还得人工查看和修改,如果项目大了,一千多个ab甚至几千个ab,那么每次更新资源时去查看哪里存在依赖那也挺累得,效率不高,并且手动修正难说说没有看漏改漏。所以,不如来套脚本搞它一把,提高工作效率和质量。
我用的是Unity5.3.6。
前提有点特殊的是Unity的UGUI图集打包,Unity的官网有介绍如何将UGUI图集正确打成AssetBundle(但我现在翻回去找不到链接了= =。),简单说下:
首先,UGUI是有图集的。
我的UGUI图集打包AssetBundle方式是,每张图集打成一个AssetBundle,所以我这里保证了每张UI图片的PackingTag和自己的AssetBundleName一样。原因,如果AssetBundleName不一样,那么这张UI图片后面所打成的AssetBundle包将会扯带了整张图集的内容,这张图集别的UI图片也在用啊,却被无辜包含进了这个ab包。所以通过保证UI图片的PackingTag==AssetBundleName来保证每张图集只存在一个AssetBundle,保证图集不被重复打包。
怎么分析资源的依赖关系呢,并设置AssetBundleName呢?
我们检测资源之间的依赖关系,遍历每一个有引用的资源进行分析,对于非UGUI的图集资源(UGUI图集上面说了),如果此资源A被其他地方资源B引用仅仅1次,那么就将此资源A的AssetBundleName置空不设置,这样打包时,此资源就会自动被和资源B打到一起合成一个AssetBundle包,如此减少打包的碎片。如果资源A被引用超过2次及以上,那么就为资源独立设置AssetBundleName,从而避免被重复打包到几个依赖它的资源包。
这里所说的资源被依赖超过2次就独立打包,如果觉得碎片化太严重,产生太多AssetBundle文件,也可以设置成n(n>=1)次才独立打包,开心就好。
资源依赖处理的代码构建思路:
其实资源之间的依赖关系,就是一个树形依赖关系,只要能构建出资源之间的依赖树,那么就能了解到某个资源被多少颗树引用,也就是被多少个资源引用,从而对症下药,自然能合理设置AssetBundleName。
然后有代码LoaderManager.cs和ABInfo.cs。
注意:一定要放在Editor文件夹下!!
LoaderManager.cs
using UnityEngine;
using System.Collections;
using System.IO;
using UnityEditor;
using System.Collections.Generic;
public class LoaderManager {
static Dictionary<string, AssetInfo> assetInfoDict = new Dictionary<string, AssetInfo>();
private static string curRootAsset = string.Empty;
private static float curProgress = 0f;
[MenuItem("AssetBundleMgr / SetAssetbundleName")]
static void SetABNames()
{
string path = GetSelectedAssetPath();
if (path == null)
{
Debug.LogWarning("请先选择目标文件夹");
return;
}
LoaderManager.GetAllAssets(path);
}
[MenuItem("AssetBundleMgr / ClearAllAssetbundelname")]
static void CleaarAllABNames()
{
string[] abnames = AssetDatabase.GetAllAssetBundleNames();
foreach (var n in abnames)
{
AssetDatabase.RemoveAssetBundleName(n, true);
}
}
public static void GetAllAssets(string rootDir) {
assetInfoDict.Clear();
DirectoryInfo dirinfo = new DirectoryInfo(rootDir);
FileInfo[] fs = dirinfo.GetFiles("*.*", SearchOption.AllDirectories);
int ind = 0;
foreach (var f in fs)
{
curProgress = (float)ind / (float)fs.Length;
curRootAsset = "正在分析依赖:"+f.Name;
EditorUtility.DisplayProgressBar(curRootAsset, curRootAsset, curProgress);
ind++;
int index = f.FullName.IndexOf("Assets");
if (index != -1)
{
string assetPath = f.FullName.Substring(index);
Object asset = AssetDatabase.LoadMainAssetAtPath(assetPath);
string upath = AssetDatabase.GetAssetPath(asset);
if (assetInfoDict.ContainsKey(assetPath) == false
&& assetPath.StartsWith("Assets")
&& !(asset is MonoScript)
&& !(asset is LightingDataAsset)
&& asset != null
) {
AssetInfo info = new AssetInfo(upath, true);
//标记一下是文件夹下根资源
CreateDeps(info);
}
EditorUtility.UnloadUnusedAssetsImmediate();
}
EditorUtility.UnloadUnusedAssetsImmediate();
}
EditorUtility.ClearProgressBar();
int setIndex = 0;
foreach (KeyValuePair<string, AssetInfo> kv in assetInfoDict) {
EditorUtility.DisplayProgressBar("正在设置ABName", kv.Key, (float)setIndex/(float)assetInfoDict.Count);
setIndex++;
AssetInfo a = kv.Value;
a.SetAssetBundleName(2);
}
EditorUtility.ClearProgressBar();
EditorUtility.UnloadUnusedAssetsImmediate();
AssetDatabase.SaveAssets();
}
/// <summary>
/// 递归分析每个所被依赖到的资源
/// </summary>
/// <param name="self"></param>
/// <param name="parent"></param>
static void CreateDeps(AssetInfo self, AssetInfo parent = null) {
if (self.HasParent(parent))
return;
if (assetInfoDict.ContainsKey(self.assetPath) == false) {
assetInfoDict.Add(self.assetPath, self);
}
self.AddParent(parent);
Object[] deps = EditorUtility.CollectDependencies(new Object[] { self.GetAsset() });
for (int i = 0; i < deps.Length; i++) {
Object o = deps[i];
if (o is MonoScript || o is LightingDataAsset)
continue;
string path = AssetDatabase.GetAssetPath(o);
if (path == self.assetPath)
continue;
if (path.StartsWith("Assets") == false)
continue;
AssetInfo info = null;
if (assetInfoDict.ContainsKey(path))
{
info = assetInfoDict[path];
}
else {
info = new AssetInfo(path);
assetInfoDict.Add(path, info);
}
EditorUtility.DisplayProgressBar(curRootAsset, path, curProgress);
CreateDeps(info, self);
}
EditorUtility.UnloadUnusedAssetsImmediate();
}
static string GetSelectedAssetPath()
{
var selected = Selection.activeObject;
if (selected == null)
{
return null;
}
Debug.Log(selected.GetType());
if (selected is DefaultAsset)
{
string path = AssetDatabase.GetAssetPath(selected);
Debug.Log("选中路径: " + path);
return path;
}
else
{
return null;
}
}
}
ABInfo.cs
using UnityEngine;
using System.Collections;
using UnityEditor;
using System.Collections.Generic;
public class AssetInfo
{
//是不是被打包文件夹下的直接资源
private bool isRootAsset = false;
public string assetPath { get; private set; }
private HashSet<AssetInfo> childSet = new HashSet<AssetInfo>();
private HashSet<AssetInfo> parentSet = new HashSet<AssetInfo>();
public AssetInfo(string assetPath, bool isRootAsset = false)
{
this.assetPath = assetPath;
}
public Object GetAsset()
{
Object asset = AssetDatabase.LoadMainAssetAtPath(assetPath);
return asset;
}
/// <summary>
/// 从这里开始分析构建资源依赖树
/// </summary>
/// <param name="parent"></param>
public void AddParent(AssetInfo parent)
{
if (parent == this || IsParentEarlyDep(parent) || parent == null)
return;
parentSet.Add(parent);
parent.AddChild(this);
parent.RemoveRepeatChildDep(this);
RemoveRepeatParentDep(parent);
}
/// <summary>
/// 清除我父节点对我子节点的重复引用,保证树形结构
/// </summary>
/// <param name="targetParent"></param>
private void RemoveRepeatChildDep(AssetInfo targetChild)
{
List<AssetInfo> infolist = new List<AssetInfo>(parentSet);
for (int i = 0; i < infolist.Count; i++)
{
AssetInfo pinfo = infolist[i];
pinfo.RemoveChild(targetChild);
pinfo.RemoveRepeatChildDep(targetChild);
}
}
/// <summary>
/// 清除我子节点被我父节点的重复引用,保证树形结构
/// </summary>
/// <param name="targetChild"></param>
private void RemoveRepeatParentDep(AssetInfo targetParent)
{
List<AssetInfo> infolist = new List<AssetInfo>(childSet);
for (int i = 0; i < infolist.Count; i++)
{
AssetInfo cinfo = infolist[i];
cinfo.RemoveParent(targetParent);
cinfo.RemoveRepeatParentDep(targetParent);
}
}
private void RemoveChild(AssetInfo targetChild)
{
childSet.Remove(targetChild);
targetChild.parentSet.Remove(this);
}
private void RemoveParent(AssetInfo parent)
{
parent.childSet.Remove(this);
parentSet.Remove(parent);
}
private void AddChild(AssetInfo child)
{
childSet.Add(child);
}
/// <summary>
/// 如果父节点早已当此父节点为父节点
/// </summary>
/// <param name="targetParent"></param>
/// <returns></returns>
private bool IsParentEarlyDep(AssetInfo targetParent)
{
if (parentSet.Contains(targetParent))
{
return true;
}
var e = parentSet.GetEnumerator();
while (e.MoveNext())
{
if (e.Current.IsParentEarlyDep(targetParent))
{
return true;
}
}
return false;
}
public bool HasParent(AssetInfo p)
{
if (parentSet.Contains(p))
return true;
return false;
}
/// <summary>
/// 打包碎片粒度
/// </summary>
/// <param name="pieceThreshold"></param>
public void SetAssetBundleName(int pieceThreshold)
{
AssetImporter ai = AssetImporter.GetAtPath(this.assetPath);
//针对UGUI图集的处理,图集以文件夹为单位打包ab
if (ai is TextureImporter)
{
TextureImporter tai = ai as TextureImporter;
string filePath = System.IO.Path.GetDirectoryName(this.assetPath);
tai.spritePackingTag = filePath.ToLower().Replace("\\", "_").Replace(".png",string.Empty).Replace(".jpg", string.Empty).Replace(" ", string.Empty);
//AssetBundleName和spritePackingTag保持一致
tai.SetAssetBundleNameAndVariant(tai.spritePackingTag + ".ab", null);
Debug.Log("<color=#2E8A00>" + "设置ab,Image资源: " + this.assetPath + "</color>");
}
else
{
string abname = this.assetPath.Replace("/", "_") + ".ab";
//不是图集,而且大于阀值
if (this.parentSet.Count >= pieceThreshold)
{
ai.SetAssetBundleNameAndVariant(abname, string.Empty);
Debug.Log("<color=#6501AB>" + "设置ab,有多个引用: " + this.assetPath+"</color>");
}
//根节点
else if (this.parentSet.Count == 0 || this.isRootAsset)
{
ai.SetAssetBundleNameAndVariant(abname, string.Empty);
Debug.Log("<color=#025082>" + "设置ab,根资源ab: " + this.assetPath + "</color>");
}
else
{
//其余的子资源
ai.SetAssetBundleNameAndVariant(string.Empty, string.Empty);
Debug.Log("<color=#DBAF00>" + "清除ab, 仅有1个引用: " + this.assetPath + "</color>");
}
}
}
}
用法:
1,选择Unity内我们所要打包的资源所在的文件夹;
2,菜单栏“AssetBundleMgr->SetAssetbundleName”,完成!看一下资源的AssetBundleName。
如:我选择了Prefabs文件夹,然后菜单栏“AssetBundleMgr->SetAssetbundleName”,资源都被正确设置了AssetBundleName。