UnityAssetBundle资源打包
unity资源打包可以分为⼀下⼏个过程:
1、先把图⽚批量⽣成图集
2、把其他路径下的资源,⽐如逻辑lua脚本拷贝到资源⽂件夹下,⽅便后⾯资源打包
3、⾃动给资源⽂件夹下所有资源设置AssetBundle的Name和variant
4、利⽤unity提供的api进⾏资源打包
5、创建XML资源列表
⼀、图集打包
图集打包使⽤的是unity新版的SpriteAtlas,舍弃了旧版的SpritePackage,主要是旧版的图集⽆法从图集获取图⽚资源public static string[] textureExtensions = new[] {".png"};
[MenuItem("BuildAssetBundle/ScanSpriteAtlas")]
public static void ScanSpriteAtlas()
{
var path = Path.Combine(Application.dataPath, "ResourcesAssets");
var dirInfo = new DirectoryInfo(path);
ScanSprites(dirInfo);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
public static void ScanSprites(DirectoryInfo dirInfo)
{
var dirArr = dirInfo.GetDirectories();
if (dirArr.Length > 0)
{
for (int i = 0; i < dirArr.Length; i++)
{
ScanSprites(dirArr[i]);
}
}
var fileArr = dirInfo.GetFiles();
if (fileArr.Length > 0)
{
var dirPath = dirInfo.FullName.Replace("\\", "/");
var subDirPath = dirPath.Replace(Application.dataPath, "Assets");
for (int i = 0; i < fileArr.Length; i++)
{
var label = "";
var variant = "";
if (textureExtensions.Contains(fileArr[i].Extension))
{
var texImporter = AssetImporter.GetAtPath(Path.Combine(subDirPath, fileArr[i].Name)) as TextureImporter;
if (ureType == TextureImporterType.Sprite)
{
GetSpriteAtlas(dirInfo);
break;
}
}
}
}
}
public static SpriteAtlas GetSpriteAtlas(DirectoryInfo dirInfo)
{
//⽂件夹路径
var assetDataPath = dirInfo.FullName.Replace("\\", "/").Replace(Application.dataPath, "Assets");
//图集名称
var atlasName = assetDataPath.Replace("Assets/ResourcesAssets/", "").Replace("/", "-");
/
/图集路径
var assetPath = $"{assetDataPath}/{atlasName}.spriteatlas";
//加载图集
var sa = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(assetPath);
if (sa == null)
{
Debug.Log($"Creat SpriteAtlas at path : {assetDataPath}");
sa = new SpriteAtlas();
AssetDatabase.CreateAsset(sa, assetPath);
Object texture = AssetDatabase.LoadMainAssetAtPath(assetDataPath);
SpriteAtlasPackingSettings packset = new SpriteAtlasPackingSettings()
{
blockOffset = 1,
enableRotation = false,
enableTightPacking = false,
padding = 4
};
sa.SetPackingSettings(packset);
SpriteAtlasTextureSettings texSet = new SpriteAtlasTextureSettings()
{
readable = true,
filterMode = FilterMode.Bilinear,
sRGB = true,
generateMipMaps = true
};
sa.SetTextureSettings(texSet);
sa.SetIncludeInBuild(false);
sa.SetIsVariant(false);
sa.Add(new Object[] { texture });
}
return sa;
}
⼆、资源拷贝
资源拷贝也是为了加密做准备,在把我们的lua脚本拷贝到资源⽂件夹下的时候可以进⼀步加密,把我们的lua脚本变为加密之后的脚本,可以防⽌软件被反编译之后拿到我们的lua脚本
public static void CopyLuaToSources(bool encode)
{
string outPath = Application.dataPath + "/ResourcesAssets/Lua";
try
{
if (!Directory.Exists(outPath))
{
Directory.CreateDirectory(outPath);
}
}
catch (System.UnauthorizedAccessException ex)
{
if (Debug.unityLogger.logEnabled)
{
Debug.Log(ex.Message);
}
}
var hasErr = false;
var strErr = new StringBuilder();
CopyLuaBytesFiles(ref hasErr, Application.dataPath+"/Lua/", outPath, encode, strErr);
if (hasErr)
{
Exception ex = new Exception(strErr.ToString());
throw (ex);
}
}
public static void CopyLuaBytesFiles(ref bool hasErr, string targetPath, string outPath, bool isencode = false, StringBuilder strErr = null)
{
if (!Directory.Exists(targetPath))
{
return;
}
string[] files = Directory.GetFiles(targetPath, "*.lua", SearchOption.AllDirectories);
int len = targetPath.Length;
if (targetPath[len - 1] == '/' || targetPath[len - 1] == '\\')
{
--len;
}
LuaHashComparer hashComparer = new LuaHashComparer();
for (int i = 0; i < files.Length; i++)
{
//获取Assets/Lua之后的lua⽂件路径
string str = files[i].Remove(0, len);
/
/lua⽂件copy到的⽬标⽂件
string desc = $"{outPath}{str}.bytes";
string dir = Path.GetDirectoryName(desc);
Directory.CreateDirectory(dir);
string src = files[i];
if (isencode)
{
//这是带加密的步骤,先放着
editor bar
}
else
{
/
/正常的copy,不带加密的流程
if (hashComparer.IsChanged(src, src.Replace(targetPath, "").ToLower()))
{
var _outPath = desc.ToLower();
if (File.Exists(_outPath))
{
File.Delete(_outPath);
}
File.Copy(src, _outPath, true);
File.SetAttributes(_outPath, FileAttributes.Normal);
}
}
}
EditorUtility.ClearProgressBar();
AssetDatabase.Refresh();
}
三、⾃动设置AssetBundle的Name和Variant
这⼀步其实做的就是资源分类,每个资源都有其对应的资源路径,包名其实就是它的路径
public static void AutoSetBundleName(bool clear)
{
string path = Path.Combine(Application.dataPath, "ResourcesAssets");
DirectoryInfo info = new DirectoryInfo(path);
ScanDir(info, clear);
}
public static string[] IgnoreExtensions = new[] {".meta", ".DS_Store"};
public static string[] VideoExtensions = new[] { ".mp4"};
public static List<FileInfo> videoFileList = new List<FileInfo>();
public static void ScanDir(DirectoryInfo info, bool clear, bool smartSet = true)
{
DirectoryInfo[] dirArr = info.GetDirectories();
//递归遍历所有⽂件夹
for (int i = 0; i < dirArr.Length; i++)
{
ScanDir(dirArr[i], clear);
//显⽰进度条
EditorUtility.DisplayProgressBar("扫描资源⽂件夹", $"资源⽂件夹名:{dirArr[i].Name}", i*1.0f/dirArr.Length);
}
FileInfo[] fileArr = info.GetFiles();
if (fileArr.Length > 0)
{
string dirPath = info.FullName.Replace("\\", "/");
string subDirPath = dirPath.Replace(Application.dataPath, "Assets");
string assetBundleLabel = subDirPath.Replace("Assets/ResourcesAssets/", "").ToLower();
//根据⽂件夹添加标签,⾃动打包
for (int i = 0; i < fileArr.Length; i++)
{
if (IgnoreExtensions.Contains(fileArr[i].Extension))
{
continue;
}
string label = "None";
string variant = "None";
if (!clear)
{
if (textureExtensions.Contains(fileArr[i].Extension))
{
//根据路径获取指定资源
var texImporter = AssetImporter.GetAtPath(Path.Combine(subDirPath, fileArr[i].Name)) as TextureImporter;
if (ureType == TextureImporterType.Sprite)
{
continue;
}
}
label = assetBundleLabel;
variant = "variant";
}
string assetPath = $"{subDirPath}/{fileArr[i].Name}";
AssetImporter asset = AssetImporter.GetAtPath(assetPath);
Debug.Log($"FileName:{fileArr[i].Name},Extension:{fileArr[i].Extension}");
if (asset.assetBundleName != label || !smartSet)
{
try
{
asset.SetAssetBundleNameAndVariant(label, variant);
}
catch (Exception ex)
{
Debug.Log(ex.Message);
EditorUtility.ClearProgressBar();
}
}
EditorUtility.DisplayProgressBar("设置AB包名", $"包名:{label}", i*1.0f/fileArr.Length);
}
}
EditorUtility.ClearProgressBar();
}
四、资源打包
使⽤unity提供的api进⾏资源打包,资源包构建选项为BuildAssetBundleOptions.ChunkBasedCompression
BuildAssetBundleOptions.ChunkBasedCompression采⽤LZ4的压缩格式,相⽐于LZMA⽽⾔⽂件体积更⼤,但是不要求在使⽤之前整个bundle都被解压。
LZ4使⽤chunk based 算法,这就运⾏⽂件以chunk或者piece的⽅式加载,只解压⼀个chunk⽂件,⽽⽆需解压bundle中其余不相关的chunk public static void BuildAllAssetBundleToPersistent(BuildAssetBundleOptions bundleOptions)
{
string packPath = OriginPath;
FileHelper.CreatDirectory(packPath);
if (packPath.Length <= 0)
return;
Debug.Log($"OutPath:{packPath}");
BuildPipeline.BuildAssetBundles(packPath, bundleOptions, GetCurrBuildTarget());
AssetDatabase.Refresh();
}
五、创建资源列表XML
创建资源列表,⽤xml⽂件来存储,存储资源的名字、key值(即资源路径)和资源对呀ab包的名字(即MD5),⽤来进⾏资源加载的时候进⾏索引
public static void CreatVersionResXML()
{
string packagePath = hotfixPath;
if (packagePath.Length <= 0 || !Directory.Exists(packagePath))
{
return;
}
List<string> fileList = new List<string>();
fileList = FileHelper.GetFiles(OriginPath, fileList, ".variant");
XmlDocument xml = new XmlDocument();
xml.AppendChild(xml.CreateXmlDeclaration("1.0", "UTF-8", null));
//⽣成根节点
xml.AppendChild(xml.CreateElement("Root"));
//⽣成⼤版本号
XmlNode root = xml.SelectSingleNode("Root");
//版本号赋值
XmlElement verNode = xml.CreateElement("version");
verNode.InnerText = $"{Application.version}.{GitCommitDataId}";
root.AppendChild(verNode);
//是否加密
XmlElement encNode = xml.CreateElement("encryption");
encNode.InnerText = "true";
root.AppendChild(encNode);
//添加资源加点
XmlElement resNode = xml.CreateElement("res");
root.AppendChild(resNode);
string copyToPath = hotfixPath;
FileHelper.RebuildDic(copyToPath);
for (int i = 0; i < fileList.Count; i++)
{
FileInfo info = new FileInfo(fileList[i]);
string md5 = FileHelper.BuildFileMD5(fileList[i]);
string fileName = fileList[i].Substring(OriginPath.Length + 1).Replace("\\", "/").Replace(".variant", "");
File.Copy(fileList[i], Path.Combine(copyToPath, md5));
string asset = FileHelper.LoadFile($"{fileList[i]}.manifest");
int idx = asset.IndexOf("Dependencies:");
string subStr = Regex.Unescape(asset.Substring(idx + "Dependencies:".Length));
List<string> depsArr = new List<string>();
//判断是否拥有依赖资源⽂件
if (!subStr.Contains("[]"))
{
//去掉字符串⾸尾空格
subStr = subStr.Trim();
string[] deps = subStr.Split('-');
for (int j = 0; i < deps.Length; j++)
{
string key = deps[j].Trim().Replace(OriginPath + "/", "").Replace(".variant", "");
depsArr.Add(key);
}
}
AddNodeToXML(xml, fileName, md5, info.Length, depsArr);
EditorUtility.DisplayProgressBar("⽣成资源⽂件", $"添加资源信息:{fileName},md5为:{md5}", i*1.0f/fileList.Count);        }
string dirPath = resPath.Replace("/version", "");
FileHelper.CreatDirectory(dirPath);
xml.Save(resPath + @".xml");
xml.Save(packagePath + @".xml");
}
public static void AddNodeToXML(XmlDocument xml, string name, string md5str, long size, List<string> desp)
{
//创建根节点
XmlNode resNode = xml.SelectSingleNode("Root/res");
//添加元素
XmlElement element = xml.CreateElement("res");
element.SetAttribute("size", size.ToString());
element.SetAttribute("name", name);
element.InnerText = md5str;
if (desp.Count > 0)
{
XmlElement depEle = xml.CreateElement("dep");
for (int i = 0; i < desp.Count; i++)
{
XmlElement dep = xml.CreateElement("dep");
dep.InnerText = desp[i];
depEle.AppendChild(dep);
}
element.AppendChild(depEle);
}
resNode.AppendChild(element);
}
然后⼀个资源打包流程就完成了
新⼿个⼈理解,如果有误,敬请谅解
如果您有更好的⽅式,欢迎分享给我,谢谢了您嘞!