一、原理流程
1、在编辑器里统一设置AB包名及路径管理。
2、根据依赖关系生成不冗余的AB包。
3、根据基于Asset的全路径生成的依赖关系表。
4、根据自己的依赖关系表加载ab包。如果是编辑器下,则直接加载资源。
二、制作打包配置表
按照前一篇的分包策略,表里的设置主要分为两种:
1、基于文件夹下所有单个文件进行打包。(Prefab)
2、基于文件夹进行打包。
[CreateAssetMenu(fileName = "ABConfig", menuName = "CreatABConfig", order = 0)]
public class ABConfig : ScriptableObject
{
//单个文件所在文件夹路径,会遍历这个文件夹下面所有Prefab,所有的Prefab的名字不能重复,必须保证名字的唯一性
public List<string> m_AllPrefabPath = new List<string>();
public List<FileDirABName> m_AllFileDirAB = new List<FileDirABName>();
[System.Serializable]
public struct FileDirABName
{
public string ABName;
public string Path;
}
}
三、生成AB包过程
1、根据单个文件和文件夹设置AB包。
2、剔除冗余AB包。
3、生成AB包配置表。
4、重要方法:
-
(1)string[] allStr = AssetDatabase.FindAssets("t:Prefab", abConfig.m_AllPrefabPath.ToArray());
=>得到整个工程的预制体的GUID -
(2)string[] allBundlePath = AssetDatabase.GetAssetPathsFromAssetBundle(ab包名字);
=>得到所有ab包的路径 -
(3)string path = AssetDatabase.GUIDToAssetPath(allStr[i]);
=>通过GUID得到资源的Asset路径 -
(4)GameObject obj = AssetDatabase.LoadAssetAtPath<GameObject>(path);
=>编辑器下根据路径加载一个资源 -
(5)string[] allDepend = AssetDatabase.GetDependencies(path);
=>根据路径获取这个路径的资源所有的依赖,包括材质,纹理,预制体,脚本等 -
(6)AssetImporter assetImporter = AssetImporter.GetAtPath(path);
assetImporter.assetBundleName = name;
=>通过这种方式可以设置一个资源的ab包名字 -
(7)string[] allBundles = AssetDatabase.GetAllAssetBundleNames();
=>得到所有的ab包名称
四、详细代码分析过程
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Xml.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine.Profiling;
public class BundleEditor
{
private static string m_BunleTargetPath = Application.dataPath+"/../AssetBundle/" + EditorUserBuildSettings.activeBuildTarget.ToString();
private static string ABCONFIGPATH = "Assets/RealFram/Editor/Resource/ABConfig.asset";
private static string ABBYTEPATH = RealConfig.GetRealFram().m_ABBytePath;
//key是ab包名,value是路径,所有文件夹ab包dic
private static Dictionary<string, string> m_AllFile_AbDir_Dic = new Dictionary<string, string>();
//过滤的总list
private static List<string> m_AllFilterAbPath_Lst = new List<string>();
//单个prefab的ab包。key是prefab的名字,也是ab包名,value是ab包资源所对应的所有依赖的路径(剔除已包含的路径)
private static Dictionary<string, List<string>> m_AllPrefab_AbDir_Dic = new Dictionary<string, List<string>>();
//储存所有有效路径
private static List<string> m_ConfigFile_Lst = new List<string>();
[MenuItem("Tools/打包")]
public static void Build()
{
DataEditor.AllXmlToBinary();
m_ConfigFile_Lst.Clear();
m_AllFilterAbPath_Lst.Clear();
m_AllFile_AbDir_Dic.Clear();
m_AllPrefab_AbDir_Dic.Clear();
//------------------------先处理文件夹的资源。从配置文件中得到要打包的资源。
//根据配置文件数据全部读取出来(只有编辑器模式可用)
ABConfig abConfig = AssetDatabase.LoadAssetAtPath<ABConfig>(ABCONFIGPATH);
foreach (ABConfig.FileDirABName fileDir in abConfig.m_AllFileDirAB)
{
if (m_AllFile_AbDir_Dic.ContainsKey(fileDir.ABName))
{
Debug.LogError("AB包配置名字重复,请检查!");
}
else
{
m_AllFile_AbDir_Dic.Add(fileDir.ABName, fileDir.Path);
m_AllFilterAbPath_Lst.Add(fileDir.Path);
m_ConfigFile_Lst.Add(fileDir.Path);
}
}
//-----------------------再处理单个预制体的资源。从配置文件中得到要打包的资源。
//得到整个工程的预制体的GUID
string[] allStr = AssetDatabase.FindAssets("t:Prefab", abConfig.m_AllPrefabPath.ToArray());
for (int i = 0; i < allStr.Length; i++)
{
//通过GUID得到资源的Asset路径
string path = AssetDatabase.GUIDToAssetPath(allStr[i]);
//显示进度条
EditorUtility.DisplayProgressBar("查找Prefab", "Prefab:" + path, i * 1.0f / allStr.Length);
m_ConfigFile_Lst.Add(path);
if (!ContainAllFileAB(path))
{
GameObject obj = AssetDatabase.LoadAssetAtPath<GameObject>(path);
//获取指定资源所直接或间接依赖的其他资源的GUID,例如材质、纹理、预制体。
//用文件夹的方式打包的资源之所以不需要对依赖做处理,是因为文件夹那边都是单一类型资源,没有重复,互相之间也没有依赖关系。
string[] allDepend = AssetDatabase.GetDependencies(path);
//该预制体所有依赖资源的路径
List<string> allDependPath = new List<string>();
for (int j = 0; j < allDepend.Length; j++)
{
//排除已经被包含的资源,以及脚本类型资源
if (!ContainAllFileAB(allDepend[j]) && !allDepend[j].EndsWith(".cs"))
{
m_AllFilterAbPath_Lst.Add(allDepend[j]);
allDependPath.Add(allDepend[j]);
}
}
if (m_AllPrefab_AbDir_Dic.ContainsKey(obj.name))
{
Debug.LogError("存在相同名字的Prefab!名字:" + obj.name);
}
else
{
m_AllPrefab_AbDir_Dic.Add(obj.name, allDependPath);
}
}
}
//这部分对ab资源设置ab包名字
foreach (string name in m_AllFile_AbDir_Dic.Keys)
{
SetABNameByString(name, m_AllFile_AbDir_Dic[name]);
}
foreach (string name in m_AllPrefab_AbDir_Dic.Keys)
{
SetABNameByList(name, m_AllPrefab_AbDir_Dic[name]);
}
//最重要的打包方法
BunildAssetBundle();
//把之前设置过的文件(包括各个文件夹,prefab)的ab包名,全部复原回来
string[] oldABNames = AssetDatabase.GetAllAssetBundleNames();
for (int i = 0; i < oldABNames.Length; i++)
{
AssetDatabase.RemoveAssetBundleName(oldABNames[i], true);
EditorUtility.DisplayProgressBar("清除AB包名", "名字:" + oldABNames[i], i * 1.0f / oldABNames.Length);
}
//对名字保存
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
//清空进度条
EditorUtility.ClearProgressBar();
}
static void SetABNameByString(string name, string path)
{
//使用这个方式,根据路径设置这个资源的ab包名
AssetImporter assetImporter = AssetImporter.GetAtPath(path);
if (assetImporter == null)
{
Debug.LogError("不存在此路径文件:" + path);
}
else
{
assetImporter.assetBundleName = name;
}
}
static void SetABNameByList(string name, List<string> paths)
{
for (int i = 0; i < paths.Count; i++)
{
SetABNameByString(name, paths[i]);
}
}
static void BunildAssetBundle()
{
//在编辑器环境,得到所有的ab包名称
string[] allBundles = AssetDatabase.GetAllAssetBundleNames();
//key为全路径,value为包名
Dictionary<string, string> resPathDic = new Dictionary<string, string>();
for (int i = 0; i < allBundles.Length; i++)
{
//通过ab包名字,得到这个ab包所有资源的路径。注意和AssetDatabase.GetDependencies的区别,输入参数以及返回结果都不相同。
string[] allBundlePath = AssetDatabase.GetAssetPathsFromAssetBundle(allBundles[i]);
for (int j = 0; j < allBundlePath.Length; j++)
{
if (allBundlePath[j].EndsWith(".cs"))
continue;
Debug.Log("此AB包:" + allBundles[i] + "下面包含的资源文件路径:" + allBundlePath[j]);
resPathDic.Add(allBundlePath[j], allBundles[i]);
}
}
if (!Directory.Exists(m_BunleTargetPath))
{
Directory.CreateDirectory(m_BunleTargetPath);
}
DeleteAB();
//生成自己的配置表
WriteData(resPathDic);
//最终打包代码
AssetBundleManifest manifest = BuildPipeline.BuildAssetBundles(m_BunleTargetPath, BuildAssetBundleOptions.ChunkBasedCompression, EditorUserBuildSettings.activeBuildTarget);
if (manifest == null)
{
Debug.LogError("AssetBundle 打包失败!");
}
else
{
Debug.Log("AssetBundle 打包完毕");
}
}
static void WriteData(Dictionary<string ,string> resPathDic)
{
AssetBundleConfig config = new AssetBundleConfig();
config.ABList = new List<ABBase>();
foreach (string path in resPathDic.Keys)
{
if (!ValidPath(path))
continue;
ABBase abBase = new ABBase();
abBase.Path = path;
abBase.Crc = Crc32.GetCrc32(path);
abBase.ABName = resPathDic[path];
abBase.AssetName = path.Remove(0, path.LastIndexOf("/") + 1);
abBase.ABDependce = new List<string>();
string[] resDependce = AssetDatabase.GetDependencies(path);
for (int i = 0; i < resDependce.Length; i++)
{
string tempPath = resDependce[i];
if (tempPath == path || path.EndsWith(".cs"))
continue;
string abName = "";
if (resPathDic.TryGetValue(tempPath, out abName))
{
if (abName == resPathDic[path])
continue;
if (!abBase.ABDependce.Contains(abName))
{
abBase.ABDependce.Add(abName);
}
}
}
config.ABList.Add(abBase);
}
//写入xml
string xmlPath = Application.dataPath + "/AssetbundleConfig.xml";
if (File.Exists(xmlPath)) File.Delete(xmlPath);
FileStream fileStream = new FileStream(xmlPath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
StreamWriter sw = new StreamWriter(fileStream, System.Text.Encoding.UTF8);
XmlSerializer xs = new XmlSerializer(config.GetType());
xs.Serialize(sw, config);
sw.Close();
fileStream.Close();
//写入二进制
foreach (ABBase abBase in config.ABList)
{
abBase.Path = "";
}
FileStream fs = new FileStream(ABBYTEPATH, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
fs.Seek(0, SeekOrigin.Begin);
fs.SetLength(0);
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(fs, config);
fs.Close();
AssetDatabase.Refresh();
SetABNameByString("assetbundleconfig", ABBYTEPATH);
}
/// <summary>
/// 删除无用AB包
/// </summary>
static void DeleteAB()
{
string[] allBundlesName = AssetDatabase.GetAllAssetBundleNames();
DirectoryInfo direction = new DirectoryInfo(m_BunleTargetPath);
FileInfo[] files = direction.GetFiles("*", SearchOption.AllDirectories);
for (int i = 0; i < files.Length; i++)
{
Debug.Log(files[i].Name);
//如果这个ab包已经打过了不用再打。不删除.meta文件。不删除.manifest 和 assetbundleconfig
if (ConatinABName(files[i].Name, allBundlesName) || files[i].Name.EndsWith(".meta")|| files[i].Name.EndsWith(".manifest") || files[i].Name.EndsWith("assetbundleconfig"))
{
continue;
}
else
{
Debug.Log("此AB包已经被删或者改名了:" + files[i].Name);
if (File.Exists(files[i].FullName))
{
File.Delete(files[i].FullName);
}
//删除ab包的同时,连同它的manifest也一起删除掉
if (File.Exists(files[i].FullName + ".manifest"))
{
File.Delete(files[i].FullName + ".manifest");
}
}
}
}
/// <summary>
/// 遍历文件夹里的文件名与设置的所有AB包进行检查判断
/// </summary>
/// <param name="name"></param>
/// <param name="strs"></param>
/// <returns></returns>
static bool ConatinABName(string name, string[] strs)
{
for (int i = 0; i < strs.Length; i++)
{
if (name == strs[i])
return true;
}
return false;
}
/// <summary>
/// 是否包含在已经有的AB包里,做来做AB包冗余剔除。
/// 过滤的思路是,把所有的资源的路径记录下来,然后在对单个预制体做资源打包时进行过滤
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
static bool ContainAllFileAB(string path)
{
for (int i = 0; i < m_AllFilterAbPath_Lst.Count; i++)
{
//把所有的情况考虑在内。比如完全相等则认为是结果是true。或者路径有包含关系,且同时满足替换了之后,是以'/'开头的情况,那么也认为是true。
if (path == m_AllFilterAbPath_Lst[i] || (path.Contains(m_AllFilterAbPath_Lst[i]) && (path.Replace(m_AllFilterAbPath_Lst[i],"")[0] == '/')))
return true;
}
return false;
}
/// <summary>
/// 是否有效路径
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
static bool ValidPath(string path)
{
for (int i = 0; i < m_ConfigFile_Lst.Count; i++)
{
if (path.Contains(m_ConfigFile_Lst[i]))
{
return true;
}
}
return false;
}
}
网友评论