Unity|Unity通用框架搭建(五)——基于Addressable一键打包工具

分组策略 【Unity|Unity通用框架搭建(五)——基于Addressable一键打包工具】游戏的资源类型大致分为:

  • 全局资源:图集、贴图、配置表、字体、音频等。使用频繁,多处使用。
  • 关卡资源:指定场景下使用的资源。特定关卡下使用的资源。使用频率低,甚至需要游戏等级达到一定等级之后才能使用。
根据Addressable系统的特性,我们大致可以将游戏的资源文件分两类:
  • Local:本地资源,资源随安装包发布。
  • Remoted:远程资源,当用户需要使用的时候在下载使用。(Addressable会自动检查,如果本地已有最新资源,则不会下载)
    Unity|Unity通用框架搭建(五)——基于Addressable一键打包工具
    文章图片
    Unity|Unity通用框架搭建(五)——基于Addressable一键打包工具
    文章图片

    本文主要记录,如果自动化分组标记资源,一键打包。
自动标记 根据文件夹路径,我们将对资源进行自动标记地址,标签以及分组类型(本地或远程)。
  • 图集标记:Unity2017之后,Unity提供了SpriteAtlas组件,让开发人员自行规划图集,因此在对资源进行自动标记时,我们需要对图集进行单独的处理,自动化创建SpriteAtlas图集。所以设计时将所有图集统一固定到Local/Atlas下分文件夹存储,打出的图集文件则存放至Local/SpriteAtlas路径下:
/// /// 自动创建图集 /// /// 路径 /// 文件夹 private static void addSpriteAtlas(string path, DirectoryInfo dir) { var dirs = dir.GetDirectories(); if (dirs == null || dirs.Length == 0) { string name = path.Replace(AtlasRoot + "/", string.Empty).Replace("/", "_"); string filePath = SpriteAtlas + "/" + name + ".spriteatlas"; if (File.Exists(filePath)) { int assetIndex = filePath.IndexOf("Assets"); string guidPath = filePath.Remove(0, assetIndex); var guid = AssetDatabase.AssetPathToGUID(guidPath); var group = setting.FindGroup("Local_SpriteAtlas"); var entry = setting.CreateOrMoveEntry(guid, group); var label = name + ".spriteatlas"; if (entry.address != name) { entry.SetAddress(name); addAddressInfo("Local_SpriteAtlas", name); } List oldLabels = new List(); foreach (var item in entry.labels) { if (item != label) oldLabels.Add(item); } for (int i = 0; i < oldLabels.Count; i++) { entry.SetLabel(oldLabels[i], false); setting.RemoveLabel(oldLabels[i]); } if (!setting.GetLabels().Contains("SpriteAtlas")) { setting.AddLabel("SpriteAtlas"); } entry.SetLabel("SpriteAtlas", true); if (!setting.GetLabels().Contains(label)) { setting.AddLabel(label); } entry.SetLabel(label, true); return; } else { SpriteAtlas atlas = new SpriteAtlas(); //设置打包参数 SpriteAtlasPackingSettings packSetting = new SpriteAtlasPackingSettings() { blockOffset = 1, enableRotation = true, enableTightPacking = false, padding = 2, }; atlas.SetPackingSettings(packSetting); //设置打包后Texture图集信息 SpriteAtlasTextureSettings textureSettings = new SpriteAtlasTextureSettings() { readable = false, generateMipMaps = false, sRGB = true, filterMode = FilterMode.Bilinear, }; atlas.SetTextureSettings(textureSettings); //设置平台图集大小压缩等信息 TextureImporterPlatformSettings platformSettings = new TextureImporterPlatformSettings() { maxTextureSize = 4096, format = TextureImporterFormat.Automatic, crunchedCompression = true, textureCompression = TextureImporterCompression.Compressed, compressionQuality = 50, }; atlas.SetPlatformSettings(platformSettings); int index = filePath.IndexOf("Assets"); string atlasPath = filePath.Remove(0, index); AssetDatabase.CreateAsset(atlas, atlasPath); index = path.IndexOf("Assets"); string spritePath = path.Remove(0, index); Object obj = AssetDatabase.LoadAssetAtPath(spritePath, typeof(Object)); atlas.Add(new[] { obj }); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); int assetIndex = filePath.IndexOf("Assets"); string guidPath = filePath.Remove(0, assetIndex); var guid = AssetDatabase.AssetPathToGUID(guidPath); var group = setting.FindGroup("Local_SpriteAtlas"); var entry = setting.CreateOrMoveEntry(guid, group); var label = name + ".spriteatlas"; if (entry.address != name) { entry.SetAddress(name); addAddressInfo("Local_SpriteAtlas", name); } List oldLabels = new List(); foreach (var item in entry.labels) { if (item != label) oldLabels.Add(item); } for (int i = 0; i < oldLabels.Count; i++) { entry.SetLabel(oldLabels[i], false); setting.RemoveLabel(oldLabels[i]); } if (!setting.GetLabels().Contains(label)) { setting.AddLabel(label); } entry.SetLabel(label, true); if (!setting.GetLabels().Contains("SpriteAtlas")) { setting.AddLabel("SpriteAtlas"); } entry.SetLabel("SpriteAtlas", true); AssetDatabase.Refresh(); } } else { if (dirs.Length > 0) { foreach (var info in dirs) { addSpriteAtlas(path + "/" + info.Name, info); } }} }

  • 创建分组:按照分组根据Local/Remoted下文件夹来进行Addressable资源分组
addressDic.Clear(); /*var groupList = setting.groups; for (int i = 0; i < groupList.Count; i++) { if (!groupList[i].name.Contains("Built In Data") && !groupList[i].name.Contains("Default Local Group")) setting.RemoveGroup(groupList[i]); }*/ ///创建分组 string loaclRoot = Application.dataPath + "/AddressableAssets/Local"; string remotedRoot = Application.dataPath + "/AddressableAssets/Remoted"; DirectoryInfo[] dirs = new DirectoryInfo(loaclRoot).GetDirectories(); foreach (var info in dirs) { string group_name = "Local_" + info.Name; var group = setting.FindGroup(group_name); if (group == null) { group = setting.CreateGroup(group_name, false, false, false, new List { setting.DefaultGroup.Schemas[0], setting.DefaultGroup.Schemas[1] }); } AutoMarkRootAddress("Local", info); if (info.Name != "SpriteAtlas") AutoMark(info.Name); } dirs = new DirectoryInfo(remotedRoot).GetDirectories(); foreach (var info in dirs) {string group_name = "Remoted_" + info.Name; var group = setting.FindGroup(group_name); if (group == null) { group = setting.CreateGroup(group_name, false, false, false, new List { setting.DefaultGroup.Schemas[0], setting.DefaultGroup.Schemas[1] }); } AutoMarkRootAddress("Remoted", info); AutoMark(info.Name, false); } ///自动创建图集 AutoCreateSpriteAtlas(); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); Debug.Log("MarkAsset Successful");

  • 具体资源则根据文件夹路径信息标记到指定的分组当中即可:
private static void markFiles(string path, string name, DirectoryInfo dir, bool local = true) { var files = dir.GetFiles(); if (files != null && files.Length > 0) { foreach (var file in files) { if (file.Extension != ".meta") { int index = file.Name.IndexOf("."); string address = file.Name.Remove(index, file.Name.Length - index); string group_name = local ? "Local_" + name : "Remoted_" + name; string assetPath = path + "/" + dir.Name + "/" + file.Name; List label = new List(); string[] allDirs; if (local) { allDirs = (path + "/" + dir.Name).Replace("Assets/AddressableAssets/Local/" + name + "/", string.Empty).Split('/'); } else { allDirs = (path + "/" + dir.Name).Replace("Assets/AddressableAssets/Remoted/" + name + "/", string.Empty).Split('/'); } label.Add(name); for (int i = 0; i < allDirs.Length; i++) { label.Add(allDirs[i]); } var guid = AssetDatabase.AssetPathToGUID(assetPath); var group = setting.FindGroup(group_name); if (group != null) { var entry = setting.CreateOrMoveEntry(guid, group); if (entry.address != address) { entry.SetAddress(address); addAddressInfo(group_name, address + file.Extension); List oldLabels = new List(); foreach (var item in entry.labels) { if (!label.Contains(item)) oldLabels.Add(item); } for (int i = 0; i < oldLabels.Count; i++) { entry.SetLabel(oldLabels[i], false); setting.RemoveLabel(oldLabels[i]); } for (int i = 0; i < label.Count; i++) { var _label = label[i]; if (!setting.GetLabels().Contains(_label)) { setting.AddLabel(_label); } entry.SetLabel(_label, true); } } } else { Debug.LogError("分组 = " + group_name + "不存在"); } } } } }

一键打包 自动化打包需要以下几个步骤:
  • 标记资源
  • 设置资源存放位置:markStatus 方法将根据不同参数设置为不同的路径
/// /// 标记为资源分组 /// 0 小包,所有资源存放资源服务器 /// 1 分包 ,Local资源存本地,Remoted资源存资源服务器 /// 2 整包,所有资源存本地 /// private void markStatus(int status) { List deleteList = new List(); for (int i = 0; i < setting.groups.Count; i++) { var group = setting.groups[i]; if (group.name != "Default Local Group" && group.name != "Built In Data") { if (group.entries.Count <= 0) { ///删除没有资源的分组 deleteList.Add(group); } else { foreach (var schema in group.Schemas) { if (schema is UnityEditor.AddressableAssets.Settings.GroupSchemas .BundledAssetGroupSchema) { bool bundleCrc = true; string buildPath = AddressableAssetSettings.kLocalBuildPath; string loadPath = AddressableAssetSettings.kLocalLoadPath; if (group.name.Contains("Local_")) { bundleCrc = status == 0; buildPath = status == 0 ? AddressableAssetSettings.kRemoteBuildPath : AddressableAssetSettings.kLocalBuildPath; loadPath = status == 0 ? AddressableAssetSettings.kRemoteLoadPath : AddressableAssetSettings.kLocalLoadPath; } else if (group.name.Contains("Remoted_")) { bundleCrc = !(status == 2); buildPath = status == 2 ? AddressableAssetSettings.kLocalBuildPath : AddressableAssetSettings.kRemoteBuildPath; loadPath = status == 2 ? AddressableAssetSettings.kLocalLoadPath : AddressableAssetSettings.kRemoteLoadPath; } else if (group.name.Contains("UpdateGroup_")) { bundleCrc = true; buildPath = AddressableAssetSettings.kRemoteBuildPath; loadPath = AddressableAssetSettings.kRemoteLoadPath; } var bundledAssetGroupSchema = (schema as UnityEditor.AddressableAssets.Settings.GroupSchemas.BundledAssetGroupSchema); bundledAssetGroupSchema.BuildPath.SetVariableByName(group.Settings, buildPath); bundledAssetGroupSchema.LoadPath.SetVariableByName(group.Settings, loadPath); bundledAssetGroupSchema.UseAssetBundleCrc = bundleCrc; bundledAssetGroupSchema.BundleNaming = UnityEditor.AddressableAssets.Settings.GroupSchemas.BundledAssetGroupSchema.BundleNamingStyle.NoHash; bundledAssetGroupSchema.BundleMode = UnityEditor.AddressableAssets.Settings.GroupSchemas.BundledAssetGroupSchema.BundlePackingMode.PackTogetherByLabel; } else if (schema is UnityEditor.AddressableAssets.Settings.GroupSchemas.ContentUpdateGroupSchema) { var updateGroupSchema = (schema as UnityEditor.AddressableAssets.Settings.GroupSchemas.ContentUpdateGroupSchema); if (group.name.Contains("Local_")) { updateGroupSchema.StaticContent = !(status == 0); } else if (group.name.Contains("Remoted_")) { updateGroupSchema.StaticContent = (status == 2); } else if (group.name.Contains("UpdateGroup_")) { updateGroupSchema.StaticContent = false; }} } } } } for (int i = 0; i < deleteList.Count; i++) { setting.RemoveGroup(deleteList[i]); } }

  • 设置当前所属环境: 因为开发、测试、商务、正式几个阶段可能同时存在,因为我们要为每个阶段设置相应的环境(主要是要区分资源服务器的位置):
public enum BuildEnvironment { Local,//内网测试整包 Debug, Beta, Release }private void setActiveProfileId() { var names = setting.profileSettings.GetAllProfileNames(); if (!names.Contains(environment.ToString())) { setting.profileSettings.AddProfile(environment.ToString(), setting.activeProfileId); } var id = setting.profileSettings.GetProfileId(environment.ToString()); if (setting.activeProfileId != id) setting.activeProfileId = id; if (environment == BuildEnvironment.Local) { setting.profileSettings.SetValue(setting.activeProfileId, "RemoteBuildPath", "[UnityEngine.AddressableAssets.Addressables.BuildPath]/[BuildTarget]"); setting.profileSettings.SetValue(setting.activeProfileId, "RemoteLoadPath", "{UnityEngine.AddressableAssets.Addressables.RuntimePath}/[BuildTarget]"); setting.BuildRemoteCatalog = false; } else { setting.BuildRemoteCatalog = true; string[] ver = version.Split('.'); string name = setting.profileSettings.GetProfileName(setting.activeProfileId); string buildPath = "ServerData" + "/[BuildTarget]/" + ver[0] + "." + ver[1] + "/" + name; if (setting.profileSettings.GetValueByName(setting.activeProfileId, "RemoteBuildPath") != buildPath) setting.profileSettings.SetValue(setting.activeProfileId, "RemoteBuildPath", buildPath); string loadPath = url + "/poetry/" + buildPath; if (setting.profileSettings.GetValueByName(setting.activeProfileId, "RemoteLoadPath") != loadPath) setting.profileSettings.SetValue(setting.activeProfileId, "RemoteLoadPath", loadPath); } }

  • 打包Addressable资源,一键打包APP应用:根据前面的设置,打包相应的Addressable资源(本质上是AssetBundle资源,Addressable系统的本质是要AssetBundle系统进行封装升级)
private void buildByStatus(int status, bool buildApp = true) { isBuildSuccess = true; BuildTools.ClearConsole(); Application.logMessageReceived += onLogMessage; if (buildApp) { /*Generator.ClearAll(); Generator.GenAll(); */ } AssetDatabase.Refresh(); setActiveProfileId(); AssetDatabase.Refresh(); markStatus(status); SetMD5Info(); AddressableAssetSettings.BuildPlayerContent(); //Addressable打包资源API AssetDatabase.Refresh(); CopyBuildData(); AssetDatabase.Refresh(); SetMD5Info(false); if (buildApp) build(); Application.logMessageReceived -= onLogMessage; if (isBuildSuccess) { string showMessage = string.Empty; if (status == 0) { showMessage = buildApp ? "打包小包成功" : "打包小包资源完成"; } else if (status == 1) { showMessage = buildApp ? "打包分包成功" : "打包分包资源完成"; } else if (status == 2) { showMessage = buildApp ? "打包整包成功" : "打包整包资源完成"; } if (EditorUtility.DisplayDialog(buildApp ? "打包完成" : "打包资源", showMessage, "确定")) { if (buildApp) { EditorUtility.RevealInFinder(BuildTools.OutPath); BuildTools.OutPath = string.Empty; }}} else { if (EditorUtility.DisplayDialog("打包失败", "请检测报错信息", "确定")) { EditorUtility.RevealInFinder(BuildTools.OutPath); BuildTools.OutPath = string.Empty; } } }

详细的源码已上传,直接导入Unity(开发时使用的版是Unity2019.4)可以使用,注意要从PackageManager中下载相应Addressable系统相关组件,并创建好AddressableSetting文件
Unity|Unity通用框架搭建(五)——基于Addressable一键打包工具
文章图片

    推荐阅读