一:AssetBundle打包
unity5提供AssetBundle打包API:
public static AssetBundleManifest BuildAssetBundles(string outputPath, BuildAssetBundleOptions assetBundleOptions, BuildTarget targetPlatform)
在Unity中给资源文件添加标签,在UnityEditor里执行上述代码,OutPutPath文件路径下会自动生成Assetbundle包。生成文件结构如下:
.Manifest 文件中存储ab包的加载路径以及对应的依赖关系。
打包代码:
public class AssetBundleWindow : EditorWindow
{
private string[] arrTag = { "All", "Scene", "Role", "Effect", "Audio", "UI", "None" };
/// <summary>
/// 标记索引
/// </summary>
private int tagIndex = 0;
/// <summary>
/// 选择标记的索引
/// </summary>
private int selectTagIndex = -1;
/// <summary>
/// 打包平台Target
/// </summary>
private string[] arrBuildTarget = { "Windows", "Android", "iOS" };
/// <summary>
/// 选择打包平台Target的索引
/// </summary>
private int selectBuildTargetIndex = -1;
#if UNITY_STANDALONE_WIN
private BuildTarget target = BuildTarget.StandaloneWindows;
private int buildTargetIndex = 0;
#elif UNITY_ANDROID
private int buildTargetIndex = 1;
private BuildTarget target = BuildTarget.Android;
#elif UNITY_IPHONE
private int buildTargetIndex = 2;
private BuildTarget target = BuildTarget.iOS;
#endif
/// <summary>
/// 绘制窗口
/// </summary>
private void OnGUI()
{
if (m_lst == null) return;
#region 按钮行
GUILayout.BeginHorizontal("box");
selectTagIndex = EditorGUILayout.Popup(tagIndex, arrTag, GUILayout.Width(100));
if (selectTagIndex != tagIndex)
{
tagIndex = selectTagIndex;
EditorApplication.delayCall = OnSelectTagCallBack;
}
selectBuildTargetIndex = EditorGUILayout.Popup(buildTargetIndex, arrBuildTarget, GUILayout.Width(100));
if (selectBuildTargetIndex != buildTargetIndex)
{
buildTargetIndex = selectBuildTargetIndex;
EditorApplication.delayCall = OnSelectTargetCallBack;
}
if (GUILayout.Button("保存设置", GUILayout.Width(200)))
{
EditorApplication.delayCall = OnSaveAssetBundleCallBack;
}
if (GUILayout.Button("打AssetBundle包", GUILayout.Width(200)))
{
EditorApplication.delayCall = OnAssetBundleCallBack;
}
if (GUILayout.Button("清空AssetBundle包", GUILayout.Width(200)))
{
EditorApplication.delayCall = OnClaerAssetBundleCallBack;
}
if (GUILayout.Button("拷贝数据表", GUILayout.Width(200)))
{
EditorApplication.delayCall = OnCopyDataTableCallBack;
}
if (GUILayout.Button("生成版本文件", GUILayout.Width(200)))
{
EditorApplication.delayCall = OnCreateVersionTextCallBack;
}
EditorGUILayout.Space();
GUILayout.EndHorizontal();
#endregion
GUILayout.BeginHorizontal("box");
GUILayout.Label("包名");
GUILayout.Label("标记", GUILayout.Width(100));
GUILayout.Label("文件夹", GUILayout.Width(200));
GUILayout.Label("初始资源", GUILayout.Width(200));
GUILayout.EndHorizontal();
GUILayout.BeginVertical();
pos = EditorGUILayout.BeginScrollView(pos);
for (int i = 0; i < m_lst.Count; i++)
{
AssetBundleEntity entity = m_lst[i];
GUILayout.BeginHorizontal("box");
m_Dic[entity.Key] = GUILayout.Toggle(m_Dic[entity.Key], "", GUILayout.Width(20));
GUILayout.Label(entity.Name);
GUILayout.Label(entity.Tag, GUILayout.Width(100));
GUILayout.Label(entity.IsFolder.ToString(), GUILayout.Width(200));
GUILayout.Label(entity.IsFirstData.ToString(), GUILayout.Width(200));
//GUILayout.Label(entity.Size.ToString(), GUILayout.Width(100));
GUILayout.EndHorizontal();
foreach (string path in entity.PathList)
{
GUILayout.BeginHorizontal("box");
GUILayout.Space(40);
GUILayout.Label(path);
GUILayout.EndHorizontal();
}
}
EditorGUILayout.EndScrollView();
GUILayout.EndVertical();
}
}
UnityEditor模式下执行下述代码,调用并显示窗口
[MenuItem("Tools/资源管理/资源打包管理")]
public static void AssetsBundleCreate()
{
AssetBundleWindow win = EditorWindow.GetWindow<AssetBundleWindow>();
win.titleContent = new GUIContent("资源打包");
win.Show();
}
资源文件可以按照功能模块或者场景分组,也可以利用XML文件显式管理。
通过代码实现自动打标签
找到需要打成Assetbundle包的资源文件夹,递归查询资源文件路径,并通过AssetImporter改变资源文件.meta 文件中的标签信息。
private void SaveFolderSettings(string[] folderArr, bool isSetNull)
{
foreach (string folderPath in folderArr)
{
//1.先看这个文件夹下的文件
string[] arrFile = Directory.GetFiles(folderPath);
//2.对文件进行设置
foreach (var filePath in arrFile)
{
//进行设置
SaveFileSetting(filePath, isSetNull);
}
//3.看这个文件夹下的子文件夹
string[] arrFolder = Directory.GetDirectories(folderPath);
SaveFolderSettings(arrFolder, isSetNull);
}
}
private void SaveFileSetting(string filePath, bool isSetNull)
{
FileInfo file = new FileInfo(filePath);
if (!file.Extension.Equals(".meta", StringComparison.CurrentCultureIgnoreCase))
{
int index = filePath.IndexOf("Assets/", StringComparison.CurrentCultureIgnoreCase);
//路径
string newPath = filePath.Substring(index);
//文件名
string fileName = newPath.Replace("Assets/", "").Replace(file.Extension, "");
//后缀
string variant = file.Extension.Equals(".unity", StringComparison.CurrentCultureIgnoreCase) ? "unity3d" : "assetbundle";
AssetImporter import = AssetImporter.GetAtPath(newPath);
import.SetAssetBundleNameAndVariant(fileName, variant);
if (isSetNull)
{
import.SetAssetBundleNameAndVariant(null, null);
}
import.SaveAndReimport();
}
}
二:AssetBundle加载
AssetBundle通过网络加载到本地后,文件夹的层级结构类似上面的AssetBundle包资源结构图。加载过程为 加载依赖文件配置 --> 加载依赖项开始 --> 加载资源文件
首先先封装一下AssetBundleLoader
public class AssetBundleLoader : IDisposable
{
private AssetBundle bundle;
public AssetBundleLoader(string path, bool isFullPath = false)
{
string fullPath = isFullPath ? path : LocalFileMgr.Instance.LocalFilePath + path;
bundle = AssetBundle.LoadFromMemory(LocalFileMgr.Instance.GetBuffer(fullPath));
}
public T LoadAsset<T>(string name) where T : UnityEngine.Object
{
if (bundle == null) return default(T);
return bundle.LoadAsset(name) as T;
}
public UnityEngine.Object LoadAsset(string name)
{
return bundle.LoadAsset(name);
}
public UnityEngine.Object[] LoadAllAssets()
{
return bundle.LoadAllAssets();
}
public void Dispose()
{
if (bundle != null)
{
bundle.Unload(false);
}
}
}
一般在资源文件下载完成或者游戏刚开始的时候去加载依赖文件配置,只需要执行一次即可。
private AssetBundleManifest m_Manifest; //依赖文件配置
/// <summary>
/// 加载依赖文件配置
/// </summary>
private void LoadManifestBundle()
{
if (m_Manifest != null)
{
return;
}
string assetName = string.Empty;
#if UNITY_STANDALONE_WIN
assetName = "Windows";
#elif UNITY_ANDROID
assetName = "Android";
#elif UNITY_IPHONE
assetName = "iOS";
#endif
using (AssetBundleLoader loader = new AssetBundleLoader(assetName))
{
m_Manifest = loader.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
}
}
需要加载某个资源文件时:
/// <summary>
/// 所有加载的Asset资源镜像
/// </summary>
private Dictionary<string, Object> m_AssetDic = new Dictionary<string, Object>();
/// <summary>
/// 依赖项的列表
/// </summary>
private Dictionary<string, AssetBundleLoader> m_AssetBundleDic = new Dictionary<string, AssetBundleLoader>();
public void LoadOrDownload<T>(string path, string name, System.Action<T> onComplete, byte type = 0) where T : Object
{
lock (this)
{
//1.加载依赖文件配置
LoadManifestBundle();
//2.加载依赖项开始
string[] arrDps = m_Manifest.GetAllDependencies(path);
//先检查所有依赖项 是否已经下载 没下载的就下载
CheckDps(0, arrDps, () =>
{
//=============下载主资源开始===================
string fullPath = (LocalFileMgr.Instance.LocalFilePath + path).ToLower();
#region 下载或者加载主资源
if (!File.Exists(fullPath))
{
#region 如果文件不存在 需要下载
DownloadDataEntity entity = DownloadMgr.Instance.GetServerData(path);
if (entity != null)
{
AssetBundleDownload.Instance.StartCoroutine(AssetBundleDownload.Instance.DownloadData(entity,
(bool isSuccess) =>
{
if (isSuccess)
{
if (m_AssetDic.ContainsKey(fullPath))
{
if (onComplete != null)
{
onComplete(m_AssetDic[fullPath] as T);
}
return;
}
for (int i = 0; i < arrDps.Length; i++)
{
if (!m_AssetDic.ContainsKey((LocalFileMgr.Instance.LocalFilePath + arrDps[i]).ToLower()))
{
AssetBundleLoader loader = new AssetBundleLoader(arrDps[i]);
Object obj = loader.LoadAsset(GameUtil.GetFileName(arrDps[i]));
m_AssetBundleDic[(LocalFileMgr.Instance.LocalFilePath + arrDps[i]).ToLower()] = loader;
m_AssetDic[(LocalFileMgr.Instance.LocalFilePath + arrDps[i]).ToLower()] = obj;
}
}
//直接加载
using (AssetBundleLoader loader = new AssetBundleLoader(fullPath, isFullPath: true))
{
if (onComplete != null)
{
Object obj = loader.LoadAsset<T>(name);
m_AssetDic[fullPath] = obj;
//进行回调
onComplete(obj as T);
}
}
}
}));
}
#endregion
}
else
{
if (m_AssetDic.ContainsKey(fullPath))
{
if (onComplete != null)
{
onComplete(m_AssetDic[fullPath] as T);
}
//if (OnCreate != null)
//{
// OnCreate(m_AssetDic[fullPath] as GameObject);
//}
return;
}
//===================================
for (int i = 0; i < arrDps.Length; i++)
{
if (!m_AssetDic.ContainsKey((LocalFileMgr.Instance.LocalFilePath + arrDps[i]).ToLower()))
{
AssetBundleLoader loader = new AssetBundleLoader(arrDps[i]);
Object obj = loader.LoadAsset(GameUtil.GetFileName(arrDps[i]));
m_AssetBundleDic[(LocalFileMgr.Instance.LocalFilePath + arrDps[i]).ToLower()] = loader;
m_AssetDic[(LocalFileMgr.Instance.LocalFilePath + arrDps[i]).ToLower()] = obj;
}
}
//===================================
//直接加载
using (AssetBundleLoader loader = new AssetBundleLoader(fullPath, isFullPath: true))
{
if (onComplete != null)
{
Object obj = loader.LoadAsset<T>(name);
m_AssetDic[fullPath] = obj;
//进行回调
onComplete(obj as T);
}
//if (OnCreate != null)
//{
// Object obj = loader.LoadAsset<T>(name);
// m_AssetDic[fullPath] = obj;
// //进行回调
// OnCreate(obj as GameObject);
//}
}
}
#endregion
//=============下载主资源结束===================
});
}
}
//检查依赖关系
private void CheckDps(int index, string[] arrDps, System.Action onComplete)
{
lock (this)
{
if (arrDps == null || arrDps.Length == 0)
{
if (onComplete != null) onComplete();
return;
}
string fullPath = LocalFileMgr.Instance.LocalFilePath + arrDps[index];
if (!File.Exists(fullPath))
{
//如果文件不存在 需要下载
DownloadDataEntity entity = DownloadMgr.Instance.GetServerData(arrDps[index]);
if (entity != null)
{
AssetBundleDownload.Instance.StartCoroutine(AssetBundleDownload.Instance.DownloadData(entity,
(bool isSuccess) =>
{
index++;
if (index == arrDps.Length)
{
if (onComplete != null) onComplete();
return;
}
CheckDps(index, arrDps, onComplete);
}));
}
}
else
{
index++;
if (index == arrDps.Length)
{
if (onComplete != null) onComplete();
return;
}
CheckDps(index, arrDps, onComplete);
}
}
}
加载的时候先去加载依赖关系,如果依赖文件本地文件夹中不存在需要使用DownloadMgr分配下载器去下载对应的资源文件,然后加载依赖,最后加载资源文件本身。
三:AssetBundle卸载
在场景切换或者Loading界面的时候可以去手动卸载资源。
public void UnloadDpsAssetBundle()
{
foreach (var item in m_AssetBundleDic)
{
item.Value.Dispose();
}
m_AssetBundleDic.Clear();
m_AssetDic.Clear();
}
卸载单个资源文件的时候,需要先卸载资源本身再去卸载依赖关系