前提说明:
我用的是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 assetInfoDict = new Dictionary();
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 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();
}
///
/// 递归分析每个所被依赖到的资源
///
///
///
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 childSet = new HashSet();
private HashSet parentSet = new HashSet();
public AssetInfo(string assetPath, bool isRootAsset = false)
{
this.assetPath = assetPath;
}
public Object GetAsset()
{
Object asset = AssetDatabase.LoadMainAssetAtPath(assetPath);
return asset;
}
///
/// 从这里开始分析构建资源依赖树
///
///
public void AddParent(AssetInfo parent)
{
if (parent == this || IsParentEarlyDep(parent) || parent == null)
return;
parentSet.Add(parent);
parent.AddChild(this);
parent.RemoveRepeatChildDep(this);
RemoveRepeatParentDep(parent);
}
///
/// 清除我父节点对我子节点的重复引用,保证树形结构
///
///
private void RemoveRepeatChildDep(AssetInfo targetChild)
{
List infolist = new List(parentSet);
for (int i = 0; i < infolist.Count; i++)
{
AssetInfo pinfo = infolist[i];
pinfo.RemoveChild(targetChild);
pinfo.RemoveRepeatChildDep(targetChild);
}
}
///
/// 清除我子节点被我父节点的重复引用,保证树形结构
///
///
private void RemoveRepeatParentDep(AssetInfo targetParent)
{
List infolist = new List(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);
}
///
/// 如果父节点早已当此父节点为父节点
///
///
///
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;
}
///
/// 打包碎片粒度
///
///
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("" + "设置ab,Image资源: " + this.assetPath + " ");
}
else
{
string abname = this.assetPath.Replace("/", "_") + ".ab";
//不是图集,而且大于阀值
if (this.parentSet.Count >= pieceThreshold)
{
ai.SetAssetBundleNameAndVariant(abname, string.Empty);
Debug.Log("" + "设置ab,有多个引用: " + this.assetPath+" ");
}
//根节点
else if (this.parentSet.Count == 0 || this.isRootAsset)
{
ai.SetAssetBundleNameAndVariant(abname, string.Empty);
Debug.Log("" + "设置ab,根资源ab: " + this.assetPath + " ");
}
else
{
//其余的子资源
ai.SetAssetBundleNameAndVariant(string.Empty, string.Empty);
Debug.Log("" + "清除ab, 仅有1个引用: " + this.assetPath + " ");
}
}
}
}
用法:
1,选择Unity内我们所要打包的资源所在的文件夹;
2,菜单栏“AssetBundleMgr->SetAssetbundleName”,完成!看一下资源的AssetBundleName。
如:我选择了Prefabs文件夹,然后菜单栏“AssetBundleMgr->SetAssetbundleName”,资源都被正确设置了AssetBundleName。