AssetBundle打包和读取

发布时间 2023-09-04 11:56:58作者: 伟衙内

基本信息

  创建了项目名有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;
    }
}
  Assets/{0} 中 此处已经统一设置路径前缀为Assets,否则就需要在XML中的Path的Value属性中配置了,所有的资源都是要从Assets目录下开始。
  Assets\Resources\ItemPrefab\Item\Box\Cube.prefab
 

定制打包界面

  如图所示,编写一个如上所示的打包界面,用于打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);
}

 

  最终和同步一样,出现一个方块,