AssetBundle打包和读取
基本信息
创建了项目名有YoyoProject工程,是一个3D模板的工程,使用的是unity 2021版本,windows11系统。
打包
打包路径
string dataPath = Application.dataPath;
string persistentDataPath = Application.persistentDataPath;
string streamingAssetsPath = Application.streamingAssetsPath;
string temporaryCachePath = Application.temporaryCachePath;
Debug.Log("dataPath" + dataPath);
Debug.Log("persistentDataPath" + persistentDataPath);
Debug.Log("streamingAssetsPath" + streamingAssetsPath);
Debug.Log("temporaryCachePath" + temporaryCachePath);
打包配置
定义打包配置,在Editor目录下新建AssetBundle目录,同时创建AssetBundleConfig.xml,内容如下,
<?xml version="1.0" encoding="utf-8" ?>
<Root>
<AssetBundle>
<Item Name="Cube" Tag="Wuti" Version="1" Size="0" ToPath="/Wuti/">
<Path Value="Resources\ItemPrefab\Item\Box\Cube.prefab" >
</Path>
</Item>
<Item Name="SampleScene" Tag="Scene" Version="1" Size="0" ToPath="/Scene/">
<Path Value="Scenes\SampleScene.unity" >
</Path>
</Item>
</AssetBundle>
</Root>
ToPath是表示打包目标路径,
Path中属性Value是需要打包的资源。
新建一个AssetBundleEntity.cs,和Xml中内容对应,代码如下,
public class AssetBundleEntity
{
public string Key;
public string Name;
public string Tag;
public int Version;
public long Size;
public string ToPath;//打包保存的路径
private List<string> m_PathList = new List<string>();
public List<string> PathList
{
get
{
return m_PathList;
}
}
}
下面是解析XML,将内容放入到AssetBundleEntity类上,GetList()方法就是解析XML的方法,
public class AssetBundleDAL
{
/// <summary>
/// XML的路径
/// </summary>
private string m_Path;
/// <summary>
/// 返回的数据集合
/// </summary>
private List<AssetBundleEntity> m_List = null;
public AssetBundleDAL(string path)
{
m_Path = path;
m_List = new List<AssetBundleEntity>();
}
public List<AssetBundleEntity> GetList()
{
m_List.Clear();
//读取XML,把数据添加到m_List中
XDocument xDoc = XDocument.Load(m_Path);
XElement root = xDoc.Root;
XElement assetBundleNode = root.Element("AssetBundle");
int index = 0;
IEnumerable<XElement> lst = assetBundleNode.Elements("Item");
foreach (XElement item in lst)
{
AssetBundleEntity entity = new AssetBundleEntity();
entity.Key = "Key:"+ ++index;
//解析 AssetBundle->Item 节点
entity.Name = item.Attribute("Name").Value;
entity.Tag = item.Attribute("Tag").Value;
entity.Version = Int32.Parse(item.Attribute("Version").Value);
entity.Size = Int64.Parse(item.Attribute("Size").Value);
entity.ToPath = item.Attribute("ToPath").Value;
//解析 AssetBundle->Item->Path 节点
IEnumerable<XElement> pathList = item.Elements("Path");
foreach (XElement path in pathList)
{
entity.PathList.Add(string.Format("Assets/{0}",path.Attribute("Value").Value));
}
m_List.Add(entity);
}
return m_List;
}
}
定制打包界面
如图所示,编写一个如上所示的打包界面,用于打AssetBundle包。
新建一个AssetBundleWindow.cs类,基础UnityEditor,代码如下,主要是在OnGUI中绘制界面,
/// <summary>
/// AssetBundle管理窗口
/// </summary>
public class AssetBundleWindow : UnityEditor.EditorWindow
{
private AssetBundleDAL dal;
private List<AssetBundleEntity> list;
private Dictionary<string, bool> dic = new Dictionary<string, bool>();
private string[] arrTag = { "All", "Scene", "Role", "Effect", "Audio", "None" };
private int tagIndex = 0;
private string[] arrBuildTarget = { "Windows", "Android", "IOS" };
#if UNITY_STANDALONE_WIN
private BuildTarget target = BuildTarget.StandaloneWindows;//默认平台
private int buildTargetIndex = 0;
#elif UNITY_ANDROID
private BuildTarget target = BuildTarget.Android;
private int buildTargetIndex = 1;
#elif UNITY_IPHONE
private BuildTarget target = BuildTarget.iOS;
private int buildTargetIndex = 2;
#endif
private string xmlPath;
private Vector2 pos;
/// <summary>
/// 在构造函数AssetBundleWindow后执行
/// Application.dataPath 不允许在构造函数中执行
/// </summary>
private void OnEnable()
{
xmlPath = Application.dataPath + @"\Editor\AssetBundle\AssetBundleConfig.xml";
dal = new AssetBundleDAL(xmlPath);
list = dal.GetList();
for (int i = 0; i < list.Count; i++)
{
dic[list[i].Key] = true;
}
}
public AssetBundleWindow()
{
}
/// <summary>
/// 绘制窗口
/// </summary>
private void OnGUI()
{
if (list == null) return;
#region 按钮行
GUILayout.BeginHorizontal("box");
tagIndex = EditorGUILayout.Popup(tagIndex, arrTag,GUILayout.Width(100));
if(GUILayout.Button("选定Tag",GUILayout.Width(100)))
{
EditorApplication.delayCall = OnSelctTagCallBack;
}
buildTargetIndex = EditorGUILayout.Popup(buildTargetIndex, arrBuildTarget, GUILayout.Width(100));
if (GUILayout.Button("选定Target", GUILayout.Width(100)))
{
EditorApplication.delayCall = OnSelctTargetCallBack;
}
if (GUILayout.Button("打AssetBundle包", GUILayout.Width(200)))
{
EditorApplication.delayCall = OnAssetBundleCallBack;
}
if (GUILayout.Button("清空AssetBundle包", GUILayout.Width(200)))
{
//删除打包的资源
EditorApplication.delayCall = OnClearAssetBundleCallBack;
}
GUILayout.EndHorizontal();
#endregion
#region 标题行
GUILayout.BeginHorizontal("box");
GUILayout.Label("包名");
GUILayout.Label("标记", GUILayout.Width(100));
GUILayout.Label("保存路径", GUILayout.Width(100));
GUILayout.Label("版本", GUILayout.Width(100));
GUILayout.Label("大小", GUILayout.Width(100));
GUILayout.EndHorizontal();
#endregion
#region 内容区域
GUILayout.BeginVertical();
pos = EditorGUILayout.BeginScrollView(pos);//添加滚动区域
for(int i = 0; i < list.Count; i++)
{
AssetBundleEntity entity = list[i];
GUILayout.BeginHorizontal("box");
dic[entity.Key] = GUILayout.Toggle(dic[entity.Key], "",GUILayout.Width(20));//复选框
GUILayout.Label(entity.Name);
GUILayout.Label(entity.Tag, GUILayout.Width(100));
GUILayout.Label(entity.ToPath, GUILayout.Width(100));
GUILayout.Label(entity.Version.ToString(), GUILayout.Width(100));
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();
#endregion
}
/// <summary>
/// 选定Tag回调
/// </summary>
/// <exception cref="NotImplementedException"></exception>
private void OnSelctTagCallBack()
{
}
private void OnSelctTargetCallBack()
{
}
private void OnAssetBundleCallBack() { }
private void OnClearAssetBundleCallBack() { }
}
在Menu.cs中,让窗口展示出来,
public class Menu : MonoBehaviour
{
[MenuItem("CustomTools/AssetBundleCreate")]
public static void AssetBundleCreate()
{
AssetBundleWindow win = EditorWindow.GetWindow<AssetBundleWindow>();
win.titleContent = new GUIContent("AssetBundle打包");
win.Show();
}
}
回调代码
/// <summary>
/// 选定Tag回调
/// </summary>
/// <exception cref="NotImplementedException"></exception>
private void OnSelctTagCallBack()
{
switch (tagIndex)
{
case 0://All
foreach (AssetBundleEntity entity in list)
{
dic[entity.Key] = true;
}
break;
case 1://Scene
foreach (AssetBundleEntity entity in list)
{
dic[entity.Key] = entity.Tag.Equals("Scene", StringComparison.CurrentCultureIgnoreCase);
}
break;
//其他同Scene
case 5://None
foreach (AssetBundleEntity entity in list)
{
dic[entity.Key] = false;
}
break;
}
}
private void OnSelctTargetCallBack()
{
switch (buildTargetIndex)
{
case 0://Windows
target = BuildTarget.StandaloneWindows;
break;
case 1://Android
target = BuildTarget.Android;
break;
case 2://IOS
target = BuildTarget.iOS;
break;
}
}
打包代码(仅参考)
主要是调用 BuildPipeline.BuildAssetBundles,
/// <summary>
/// 打AssetBundle包的回调
/// </summary>
private void OnAssetBundleCallBack() {
//被选中需要打包的资源
List<AssetBundleEntity> listNeedBuild = new List<AssetBundleEntity>();
foreach (AssetBundleEntity entity in list)
{
if (dic[entity.Key])
{
listNeedBuild.Add(entity);
}
}
//循环打包
for(int i=0; i<listNeedBuild.Count; i++)
{
BuildAssetBundle(listNeedBuild[i]);
}
Debug.Log("打包完成");
}
private void BuildAssetBundle(AssetBundleEntity entity)
{
AssetBundleBuild[] arrBuild = new AssetBundleBuild[1];
AssetBundleBuild build = new AssetBundleBuild();
//后缀
string suffix = (entity.Tag.Equals("Scene", StringComparison.CurrentCultureIgnoreCase) ? "unity3d" : "assetbundle");
//包名
build.assetBundleName = string.Format("{0}.{1}",entity.Name,suffix);
//资源路径
build.assetNames = entity.PathList.ToArray();
arrBuild[0] = build;
//包存放路径
//dataPath路径为: E:\workspace\unity_workspace\YoyoProject\Assets 想放到,E:\workspace\unity_workspace\YoyoProject\AssetBundles目录下
//AssetBundles目录是自己建的
string toPath = Application.dataPath + "/../AssetBundles/" + arrBuildTarget[buildTargetIndex]+entity.ToPath;
if (!System.IO.Directory.Exists(toPath))//创建目录
{
Directory.CreateDirectory(toPath);
}
//打包
BuildPipeline.BuildAssetBundles(toPath, arrBuild, BuildAssetBundleOptions.None, target);
}
上面打包代码执行报错,
报错原因是我在Camera挂载的脚本下引用了UnityEditor,这两个是无法被打包进可执行程序的,
using UnityEditor;
using UnityEditor.Build;
如下图打包成功后,
清空包
/// <summary>
/// 清空打的AssetBundle包
/// </summary>
private void OnClearAssetBundleCallBack() {
string path = Application.dataPath + "/../AssetBundles/" + arrBuildTarget[buildTargetIndex];
if(Directory.Exists(path))
{
Directory.Delete(path, true);//第二个参数表示,级联删除数据
}
}
读取
读取AssetBundle
新建LocalFileMgr文件在Scripts目录下,代码如下,主要是读取本地资源文件,
/// <summary>
/// 本地文件管理
/// </summary>
public class LocalFileMgr : Singleton<LocalFileMgr>
{
#if UNITY_STANDALONE_WIN
private static string platform = "Windows";
#elif UNITY_IPHONE
private static string platform = "IOS";
#elif UNITY_ANDROID
private static string platform = "Android";
#endif
#if UNITY_EDITOR
public readonly string LocalFilePath = Application.dataPath + "/../AssetBundles/"+ platform;
#elif UNITY_ANDROID || UNITY_IPHONE || UNITY_STANDALONE_WIN
public readonly string LocalFilePath = Application.persistentDataPath + "/";
#endif
public byte[] GetBuffer(string path)
{
byte[] buffer = null;
using(FileStream fs = new FileStream(path, FileMode.Open))
{
buffer = new byte[fs.Length];
fs.Read(buffer, 0, buffer.Length);
}
return buffer;
}
}
同步加载
当要获取实例时,如下,这样就从资源包里面获取到预制体等资源,
运行后会在界面展示一个方块,
byte[] buffer = LocalFileMgr.Instance.GetBuffer(@"E:\workspace\unity_workspace\YoyoProject\AssetBundles\Windows\Wuti\cube.assetbundle");
AssetBundle bundle = AssetBundle.LoadFromMemory(buffer);
//加载镜像
GameObject obj = bundle.LoadAsset("Cube") as GameObject;
//卸载bundler
bundle.Unload(false);
Instantiate(obj);
上述加载方式是同步加载,如果这个资源很大,那么就需要异步加载。
异步加载
编写异步加载的类AssetBundleLoaderAsync,代码如下,其中有一个回调方法——OnLoadComplete,当加载完毕后执行。
异步加载主要是用到协程和AssetBundle.LoadFromMemoryAsync。
public class AssetBundleLoaderAsync : MonoBehaviour
{
private string m_FullPath;
private string m_Name;
private AssetBundle m_Bundle;
private AssetBundleCreateRequest request;
public Action<UnityEngine.Object> OnLoadComplete;
public void init(string path,string name)
{
m_FullPath = LocalFileMgr.Instance.LocalFilePath+"/"+ path;
m_Name = name;
}
// Start is called before the first frame update
void Start()
{
StartCoroutine(Load());
}
private IEnumerator Load()
{
request = AssetBundle.LoadFromMemoryAsync(LocalFileMgr.Instance.GetBuffer(m_FullPath));
yield return request;
m_Bundle = request.assetBundle;
if(OnLoadComplete != null)
{
OnLoadComplete(m_Bundle.LoadAsset(m_Name));
Destroy(gameObject);
}
}
private void OnDestroy()
{
if (m_Bundle != null) m_Bundle.Unload(false);
m_FullPath =null;
m_Name =null;
}
}
测试异步加载,
public AssetBundleLoaderAsync LoadAsync(string path,string name)
{
GameObject obj = new GameObject("AssetBundleCreate");
AssetBundleLoaderAsync async = obj.GetOrAddComponent<AssetBundleLoaderAsync>();
async.init(path, name);
return async;
}
void Start()
{
AssetBundleLoaderAsync async = LoadAsync(@"Wuti\cube.assetbundle", "cube");
async.OnLoadComplete = OnLoadComplete;
}
private void OnLoadComplete(UnityEngine.Object obj)
{
Instantiate(obj);
}
最终和同步一样,出现一个方块,