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 (texImporter.textureType == 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) { //這是帶加密的步驟,先放着 } 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 (texImporter.textureType == 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); }
然后一個資源打包流程就完成了