分组策略 【Unity|Unity通用框架搭建(五)——基于Addressable一键打包工具】游戏的资源类型大致分为:
- 全局资源:图集、贴图、配置表、字体、音频等。使用频繁,多处使用。
- 关卡资源:指定场景下使用的资源。特定关卡下使用的资源。使用频率低,甚至需要游戏等级达到一定等级之后才能使用。
- Local:本地资源,资源随安装包发布。
- Remoted:远程资源,当用户需要使用的时候在下载使用。(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文件
文章图片
推荐阅读
- Unity3D|Unity Addressables 深入浅出(一)
- Unity3D|【游戏开发高阶】从零到一教你Unity使用ToLua实现热更新(含Demo工程 | LuaFramework | 增量 | HotUpdate)
- Unity3D|《学Unity的猫》——第十三章(Unity使用Animator控制动画播放,皮皮猫打字机游戏)
- Unity3D|《学Unity的猫》——第十六集(Unity动画使用混合树BlendTree实现动画过渡控制)
- Unity3D|【游戏开发实战】Unity UGUI实现循环复用列表,显示巨量列表信息,含Demo工程源码
- Unity3D|(完结)Unity游戏开发——新发教你做游戏(七)(Animator控制角色动画播放)
- Unity常见的优化性能设置
- 游戏|元宇宙密室逃脱游戏攻略来啦!
- Unity3d|Unity中的层级以及渲染顺序