1. 程式人生 > >使用UnityEditor製作視覺化打包AssetBundle(一)

使用UnityEditor製作視覺化打包AssetBundle(一)

基本流程

  • 建立xml檔案,將xml內容解析到實體類中
  • 繪製UnityEditor視窗,將xml內容繪製到視窗中
  • 打包AssetBundle
  • 載入AssetBundle(非同步和同步)

準備原材料:xml和entity實體類

<?xml version="1.0" encoding="utf-8"?>
<!-- AssetBundleConfig.xml -->
<Root>
  <AssetBundle>
    <Item Name="MainCity_Role_Cike" Tag="Role" Version="1" Size="0" ToPath="/Role/">
      <Path Value="Resources\RolePrefab\RolePlayer\MainCity_Role_Cike.prefab" />
    </Item>
    <Item Name="Scene_Main" Tag="Scene" Version="1" Size="0" ToPath="/Scene/">
      <Path Value="Scene\SceneMap\Scene_Main.unity" />
    </Item>
  </AssetBundle>
</Root>
// AssetBundleEntity.cs
using System.Collections.Generic;

public class AssetBundleEntity
{
    /// <summary>
    /// 打包指定專用Key(唯一性)
    /// </summary>
    public string Key;

    /// <summary>
    /// 名稱
    /// </summary>
    public string Name;

    /// <summary>
    /// 標記
    /// </summary>
    public string Tag;

    /// <summary>
    /// 版本號
    /// </summary>
    public int Version;

    /// <summary>
    /// 大小(K)
    /// </summary>
    public long Size;

    /// <summary>
    /// 將要存放的資料夾
    /// </summary>
    public string ToPath;

    /// <summary>
    /// 資源存放路徑集合
    /// </summary>
    private List<string> m_PathList = new List<string>();

    public List<string> PathList
    {
        get
        {
            return m_PathList;
        }
    }
}

PathList: 後面會使用到這個屬性,通過這個屬性將所有資源打包Assetbundle,儲存在自己定義的路徑/ToPath/下

XML讀取類的編寫

// AssetBundleDAL.cs
using System.Collections.Generic;
using System.Xml.Linq; 

public class AssetBundleDAL
{
    /// <summary>
    /// xml路徑
    /// </summary>
    private string m_XMLPath;

    /// <summary>
    /// 返回的AssetBundle實體集合
    /// </summary>
    private List<AssetBundleEntity> m_List = null;

    public AssetBundleDAL(string xmlPath)
    {
        this.m_XMLPath = xmlPath;
        m_List = new List<AssetBundleEntity>();
    }

    #region 讀取xml檔案 GetList
    /// <summary>
    /// 讀取xml檔案  儲存到實體集合中
    /// </summary> 
    public List<AssetBundleEntity> GetList()
    {
        m_List.Clear();

        //讀取xml  把資料新增到mlist中
        XDocument xDoc = XDocument.Load(m_XMLPath);
        XElement root = xDoc.Root;

        XElement assetBundleNode = root.Element("AssetBundle");

        IEnumerable<XElement> itemList = assetBundleNode.Elements("Item");
        int keyIndex = 0;
        foreach(XElement item in itemList)
        {
            AssetBundleEntity abEntity = new AssetBundleEntity();
            abEntity.Key = "key" + ++keyIndex;
            abEntity.Name = item.Attribute("Name").Value;
            abEntity.Tag = item.Attribute("Tag").Value;
            abEntity.ToPath = item.Attribute("ToPath").Value;
            abEntity.Version = item.Attribute("Version").Value.ToInt();
            abEntity.Size = item.Attribute("Size").Value.ToLong();

            IEnumerable<XElement> paths = item.Elements("Path");
            foreach(XElement path in paths)
            {
                abEntity.PathList.Add(string.Format(@"Assets\{0}", path.Attribute("Value").Value));
            }

            m_List.Add(abEntity);
        }

        return m_List;
    }
    #endregion
}

通過GetList()就可以直接獲取到所有的entity實體了

下面進行EditorWindow的編寫  -- 也就是視覺化的AssetBundle介面編寫

// AssetBundleWindow.cs
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;

public class AssetBundleWindow : EditorWindow
{ 
    private AssetBundleDAL abDAL = null;
    
    //AssetBundle實體列表
    private List<AssetBundleEntity> assetBundleEntities = null;

    //字典 Assetbundle名字 - 是否打包選中
    private Dictionary<string, bool> m_Dict;
    
    //型別陣列
    private string[] arrTag = { "All", "Scene", "Role", "Effect", "Audio", "None" };
    private int tagIndex = 0;

    //編譯平臺數組
    private string[] arrBuildTarget = { "Windows", "iOS", "Android" };

    string xmlpath = "";

    //ScrollView的Scroll位置
    private Vector2 pos;

#if UNITY_STANDALONE || UNITY_EDITOR
    private BuildTarget target = BuildTarget.StandaloneWindows;
    private int buildTargetIndex = 0;
#elif UNITY_IPHONE
    private BuildTarget target = BuildTarget.iOS;
    private int buildTargetIndex = 1;
#elif UNITY_ANDROID
    private BuildTarget target = BuildTarget.Android;
    private int buildTargetIndex = 2;
#endif

    void OnEnable()
    {
        // 自定義xmlpath 這裡我寫死了
        xmlpath = Application.dataPath + @"\Script\Editor\AssetBundleConfig.xml";

        m_Dict = new Dictionary<string, bool>();

        if (!string.IsNullOrEmpty(xmlpath))
        {
            abDAL = new AssetBundleDAL(xmlpath);
            assetBundleEntities = abDAL.GetList();
        }
        
        // 預設給所有的選項打勾
        for (int i = 0; i < assetBundleEntities.Count; i++)
        {
            m_Dict[assetBundleEntities[i].Key] = true;
        }
    }


    private void OnGUI()
    {
        if (assetBundleEntities == null) return;

        #region 按鈕行
        GUILayout.BeginHorizontal("box");

        tagIndex = EditorGUILayout.Popup(tagIndex, arrTag, GUILayout.Width(100));
        if (GUILayout.Button("選定Tag", GUILayout.Width(100)))
        {
            EditorApplication.delayCall = OnSelectTagCallback;
        }

        buildTargetIndex = EditorGUILayout.Popup(buildTargetIndex, arrBuildTarget, GUILayout.Width(100));
        if (GUILayout.Button("選定Target", GUILayout.Width(100)))
        {
            EditorApplication.delayCall = OnSelectTargetCallback;
        }
        if (GUILayout.Button("打包AB包", GUILayout.Width(100)))
        {
            EditorApplication.delayCall = OnBuildAllAB;
        }
        if (GUILayout.Button("清除AB包", GUILayout.Width(100)))
        {
            EditorApplication.delayCall = OnCleanAB;
        }
        EditorGUILayout.Space();
        GUILayout.EndHorizontal();
        #endregion

        GUILayout.BeginHorizontal("box");
        GUILayout.Label("包名");
        GUILayout.Label("標記", GUILayout.Width(100));
        GUILayout.Label("儲存路徑", GUILayout.Width(200));
        GUILayout.Label("版本", GUILayout.Width(100));
        GUILayout.Label("大小", GUILayout.Width(100));
        GUILayout.EndHorizontal();

        GUILayout.BeginVertical();

        pos = EditorGUILayout.BeginScrollView(pos);
        for (int i = 0; i < assetBundleEntities.Count; i++)
        {
            AssetBundleEntity abe = assetBundleEntities[i];

            GUILayout.BeginHorizontal("box");
            m_Dict[assetBundleEntities[i].Key] = GUILayout.Toggle(m_Dict[assetBundleEntities[i].Key], abe.Name);
            GUILayout.Label(abe.Tag, GUILayout.Width(100));
            GUILayout.Label(abe.ToPath, GUILayout.Width(200));
            GUILayout.Label(abe.Version.ToString(), GUILayout.Width(100));
            GUILayout.Label(abe.Size + "K", GUILayout.Width(100));
            GUILayout.EndHorizontal();

            foreach (string path in abe.PathList)
            {
                GUILayout.BeginHorizontal("box");
                GUILayout.Space(40);
                GUILayout.Label(path);
                GUILayout.EndHorizontal();
            }
        }

        EditorGUILayout.EndScrollView();

        GUILayout.EndVertical();
    }

    /// <summary>
    /// 清理AssetBundle
    /// </summary>
    private void OnCleanAB()
    {
        //開始建立
        string toPath = Application.dataPath + "/../AssetBundles/" + arrBuildTarget[buildTargetIndex];

        if(Directory.Exists(toPath))
        {
            Directory.Delete(toPath, true);
            Debug.Log("刪除成功");
        } 
    }

    /// <summary>
    /// 打包AssetBundle
    /// </summary>
    private void OnBuildAllAB()
    {
        for (int i = 0; i < assetBundleEntities.Count; i++)
        {
            if(m_Dict[assetBundleEntities[i].Key])
            { 
                BuildAssetBundle(assetBundleEntities[i]);
            }
        }
        Debug.Log("打包成功");
    }

    private void BuildAssetBundle(AssetBundleEntity entity)
    {
        AssetBundleBuild[] arrBuild = new AssetBundleBuild[1];
        AssetBundleBuild build = new AssetBundleBuild();

        //包名
        build.assetBundleName = entity.Name;
        //字尾
        if (entity.Tag.Equals("scene", StringComparison.CurrentCultureIgnoreCase))
        {
            build.assetBundleName += ".unity3d";
        }
        else
        {
            build.assetBundleName += ".assetbundle";
        }
        //資源路徑
        build.assetNames = entity.PathList.ToArray();

        arrBuild[0] = build;

        //開始建立
        string toPath = Application.dataPath + "/../AssetBundles/" + arrBuildTarget[buildTargetIndex] + entity.ToPath;

        if (!Directory.Exists(toPath))
            Directory.CreateDirectory(toPath);
        
        BuildPipeline.BuildAssetBundles(toPath, arrBuild, BuildAssetBundleOptions.None, target);
    }

    /// <summary>
    /// 選定Target回撥
    /// </summary>
    private void OnSelectTargetCallback()
    {
        //"Windows",  "iOS", "Android" 
        switch (buildTargetIndex)
        {
            case 0:
                target = BuildTarget.StandaloneWindows;
                buildTargetIndex = 0;
                break;
            case 1:
                target = BuildTarget.iOS;
                buildTargetIndex = 1;
                break;
            case 2:
                target = BuildTarget.Android;
                buildTargetIndex = 2;
                break;
        }
        Debug.Log("選定平臺為:" + target.ToString());
    }

    /// <summary>
    /// 選定Tag回撥
    /// </summary>
    private void OnSelectTagCallback()
    {
        //"All", "Scene", "Role", "Effect", "Audio", "None"
        switch (tagIndex)
        {
            case 0: //All
                foreach (AssetBundleEntity item in assetBundleEntities)
                {
                    m_Dict[item.Key] = true;
                }
                break;
            case 1: //Scene
                foreach (AssetBundleEntity item in assetBundleEntities)
                {
                    m_Dict[item.Key] = item.Tag.Equals("scene", StringComparison.CurrentCultureIgnoreCase);
                }
                break;
            case 2: //Role
                foreach (AssetBundleEntity item in assetBundleEntities)
                {
                    m_Dict[item.Key] = item.Tag.Equals("role", StringComparison.CurrentCultureIgnoreCase);
                }
                break;
            case 3: //Effect
                foreach (AssetBundleEntity item in assetBundleEntities)
                {
                    m_Dict[item.Key] = item.Tag.Equals("effect", StringComparison.CurrentCultureIgnoreCase);
                }
                break;
            case 4: //Audio
                foreach (AssetBundleEntity item in assetBundleEntities)
                {
                    m_Dict[item.Key] = item.Tag.Equals("audio", StringComparison.CurrentCultureIgnoreCase);
                }
                break;
            case 5: //None
                foreach (AssetBundleEntity item in assetBundleEntities)
                {
                    m_Dict[item.Key] = false;
                }
                break;
        }
    }
}

再新建一個類,把AssetBundleWindow例項化出來,就可以看到操作介面了

// CustomMenu.cs
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public class Menu
{ 
    [MenuItem("MyTools/CreateAssetBundle")]
    public static void CreateAssetBundle()
    {
        AssetBundleWindow abWin = new AssetBundleWindow();
        abWin.titleContent = new GUIContent("資源管理");
        abWin.Show(); 
    }
}

最終效果圖大概就是這樣:

各類路徑解釋:

PathList:存放的是各類資源在unity中的存放位置

"string toPath = Application.dataPath + "/../AssetBundles/" + arrBuildTarget[buildTargetIndex] + entity.ToPath;"

toPath這個是存放打包的assetbundle的,格式大概為:unity專案上一級目錄/AssetBundles/平臺名(win,Android,ios)/asset型別名/xxx.assetbundle

下一篇我會寫載入AssetBundle,有什麼錯誤或者建議的話,歡迎各位小夥伴留言。