资源打包策略
正常的打包策略:
- 在编辑器下设置文件夹或者文件AB包名
- 根据manifest进行依赖加载
优点:上手简单,方便操作。
缺点:容易产生冗余AB包;文件夹或文件等AB包名设置混乱,难以管理。
我的打包策略:
- 编写编辑器工具统一设置AB包名称及路径管理
- 根据依赖关系生成不冗余AB包
- 生成自己的依赖关系表
- 根据自己的依赖关系表加载AB包
优点:不会生成冗余的AB包,文件AB包名设置简单方便,容易管理
缺点:对于新手理解又一定难度。
实现:
1、自定义打包配置表
创建ABConfig脚本继承ScripttableObject
using System;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;
namespace Editor.Loader
{
// 创建资源AB配置菜单
[CreateAssetMenu(fileName = "ABConfig",menuName = "CreateABConfig")]
public class ABConfig:ScriptableObject
{
public List<ABDirInfo> abDirInfo = new List<ABDirInfo>();
[Serializable]
// 需要打AB包的文件夹信息
public struct ABDirInfo
{
[LabelText("报名")]
public string ABName;
[LabelText("包路径")]
public string ABPath;
}
}
}
效果:
2、生成AB包
- 根据文件夹设置AB包
- 剔除冗余AB包
- 生成AB包配置表(二进制文件和xml文件,xml文件用来直观的看出打包结果)
- 清空资源的AB包配置
- 删除旧的AB包(AB包配置发生改动后下一次打包需要清理掉旧的AB文件)
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Xml.Serialization;
using CS.Core.Loader;
using CS.Core.Utlis;
using UnityEditor;
using UnityEngine;
namespace Editor.Loader
{
public class AssetEditor
{
private static bool needExtension = false; // 是否需要扩展名
private static string ABOutPutPath = "Assets/AssetBundles"; // Application.streamingAssetsPath;
private static ABConfig abConfig;
// key 包名 value 包路径
private static Dictionary<string,string> abConfigDic = new Dictionary<string, string>();
[MenuItem("My菜单/打包AB包")]
private static void Pack()
{
abConfig = AssetDatabase.LoadAssetAtPath<ABConfig>("Assets/Resources/GameConfigData/ABConfig.asset");
GenerateAbConfigData();
SetBundleName();
GenerateXmlConfig();
BuildAssetBundle();
ClearAssetBundleName();
DeleteAssetBundle();
}
// 生成ab配置数据
private static void GenerateAbConfigData()
{
abConfigDic.Clear();
for (int i = 0; i < abConfig.abDirInfo.Count; i++)
{
var abInfo = abConfig.abDirInfo[i];
if (abConfigDic.ContainsKey(abInfo.ABName))
{
Debug.LogError($"配置错误:配置了相同的包名 name:{abInfo.ABName} path: {abInfo.ABPath}");
continue;
}
abConfigDic.Add(abInfo.ABName,abInfo.ABPath);
// Debug.Log($"生成包数据成功name:{abInfo.ABName} path:{abInfo.ABPath}");
}
}
private static void SetBundleName()
{
foreach (var abName in abConfigDic.Keys)
{
string[] guids = AssetDatabase.FindAssets("", new[] {abConfigDic[abName]});
for (int i = 0; i < guids.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
if(path.EndsWith(".cs")) continue;
SetBundleName(path,abName);
}
}
}
// 设置包
private static void SetBundleName(string path, string abName)
{
AssetImporter assetImporter = AssetImporter.GetAtPath(path);
if (assetImporter)
{
assetImporter.assetBundleName = abName;
}
}
// 清空资源的bundle名配置
private static void ClearAssetBundleName()
{
string[] abNames = AssetDatabase.GetAllAssetBundleNames();
for (int i = 0; i < abNames.Length; i++)
{
string[] allPaths = AssetDatabase.GetAssetPathsFromAssetBundle(abNames[i]);
for (int j = 0; j < allPaths.Length; j++)
{
SetBundleName(allPaths[j],null);
}
AssetDatabase.RemoveAssetBundleName(abNames[i],true);
}
}
// 生成xml配置文件
private static void GenerateXmlConfig()
{
// key 资源路径 value abName
Dictionary<string,string> resDic = new Dictionary<string, string>();
foreach (string abName in abConfigDic.Keys)
{
string[] assetPath = AssetDatabase.GetAssetPathsFromAssetBundle(abName);
for (int i = 0; i < assetPath.Length; i++)
{
string path = assetPath[i];
if(!resDic.ContainsKey(path))
resDic.Add(path, abName);
if (path.EndsWith(".prefab"))
{
// 预制体的依赖
string[] depends = AssetDatabase.GetDependencies(path);
for (int j = 0; j < depends.Length; j++)
{
if (!depends[j].EndsWith(".cs") && depends[j] != path)
{
AssetImporter assetImporter = AssetImporter.GetAtPath(depends[j]);
if(!resDic.ContainsKey(path))
resDic.Add(depends[j], assetImporter.assetBundleName);
}
}
}
}
}
AssetBundleConfig assetBundleConfig = new AssetBundleConfig();
foreach (var path in resDic.Keys)
{
ABInfo info = new ABInfo();
info.AseetName = Path.GetFileNameWithoutExtension(path);
info.AseetPath = path;
info.ABName = resDic[path];
info.Crc = CrcUtlis.GetCRC32(path);
info.Depends = new List<string>();
if (path.EndsWith(".prefab"))
{
// 预制体的依赖
string[] depends = AssetDatabase.GetDependencies(path);
for (int j = 0; j < depends.Length; j++)
{
if (!depends[j].EndsWith(".cs") && depends[j] != path)
{
AssetImporter assetImporter = AssetImporter.GetAtPath(depends[j]);
if (assetImporter.assetBundleName != info.ABName &&
!info.Depends.Contains(assetImporter.assetBundleName))
info.Depends.Add(assetImporter.assetBundleName);
}
}
}
assetBundleConfig.abInfoList.Add(info);
}
// 移除扩展名
if (!needExtension)
{
for (int i = 0; i < assetBundleConfig.abInfoList.Count; i++)
{
var abInfo = assetBundleConfig.abInfoList[i];
abInfo.AseetPath = GetFilePath(abInfo.AseetPath);
abInfo.Crc = CrcUtlis.GetCRC32(abInfo.AseetPath);
assetBundleConfig.abInfoList[i] = abInfo;
}
}
// 保存xml
FileStream fs = new FileStream("Assets/Resources/GameConfigData/AssetBundleConfig.xml",FileMode.Create,FileAccess.ReadWrite,FileShare.ReadWrite);
StreamWriter streamWriter = new StreamWriter(fs,Encoding.UTF8);
XmlSerializer xmlSerializer = new XmlSerializer(assetBundleConfig.GetType());
xmlSerializer.Serialize(streamWriter,assetBundleConfig);
streamWriter.Close();
fs.Close();
// 保存二进制文件
FileStream bfs = new FileStream(AssetBundleManager.AssetBundleConfigPath,FileMode.Create,FileAccess.ReadWrite,FileShare.ReadWrite);
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(bfs,assetBundleConfig);
bfs.Close();
}
private static string GetFilePath(string path)
{
if (!needExtension)
{
path = path.Replace(Path.GetExtension(path), String.Empty);
}
return path;
}
// 获取AB包资源配置信息
public static AssetBundleConfig GetAssetBundleConfig()
{
// FileStream fs = new FileStream("Assets/Resources/GameConfigData/AssetBundleConfig.xml", FileMode.Open,
// FileAccess.ReadWrite, FileShare.ReadWrite);
// 序列化文件
// XmlSerializer xmlSerializer = new XmlSerializer(typeof(AssetBundleConfig));
// AssetBundleConfig assetBundleConfig = xmlSerializer.Deserialize(fs) as AssetBundleConfig;
FileStream fs = new FileStream("Assets/Resources/GameConfigData/AssetBundleConfig.bin", FileMode.Open,
FileAccess.ReadWrite, FileShare.ReadWrite);
BinaryFormatter bf = new BinaryFormatter();
AssetBundleConfig assetBundleConfig = bf.Deserialize(fs) as AssetBundleConfig;
fs.Close();
return assetBundleConfig;
}
// 构建AB包
private static void BuildAssetBundle()
{
if (!Directory.Exists(ABOutPutPath))
{
Directory.CreateDirectory(ABOutPutPath);
}
// BuildAssetBundleOptions.None LZ4 BuildAssetBundleOptions.ChunkBasedCompression LZMA
BuildPipeline.BuildAssetBundles(ABOutPutPath, BuildAssetBundleOptions.ChunkBasedCompression, BuildTarget.StandaloneWindows);
}
// 删除旧的AB文件
private static void DeleteAssetBundle()
{
string[] guids = AssetDatabase.FindAssets("",new []{ABOutPutPath});
string rootFileName = Path.GetFileNameWithoutExtension(ABOutPutPath);
for (int i = 0; i < guids.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
string abName = Path.GetFileNameWithoutExtension(path);
if (rootFileName != abName && !abConfigDic.ContainsKey(abName))
{
AssetDatabase.DeleteAsset(path);
}
}
}
}
}
效果:
导出的xml文件里可以直观的看出 Assets/Resources/UI/Prefab/Win/Hero/Win_Hero 文件属于prefabAB包,并且依赖了image AB包