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

分组策略

游戏的资源类型大致分为:

  • 全局资源:图集、贴图、配置表、字体、音频等。使用频繁,多处使用。
  • 关卡资源:指定场景下使用的资源。特定关卡下使用的资源。使用频率低,甚至需要游戏等级达到一定等级之后才能使用。

根据Addressable系统的特性,我们大致可以将游戏的资源文件分两类:

  • Local:本地资源,资源随安装包发布。
  • Remoted:远程资源,当用户需要使用的时候在下载使用。(Addressable会自动检查,如果本地已有最新资源,则不会下载)
    Unity通用框架搭建(五)——基于Addressable一键打包工具_第1张图片 Unity通用框架搭建(五)——基于Addressable一键打包工具_第2张图片
    本文主要记录,如果自动化分组标记资源,一键打包。

自动标记

根据文件夹路径,我们将对资源进行自动标记地址,标签以及分组类型(本地或远程)。

  • 图集标记: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<string> oldLabels = new List<string>();
                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<string> oldLabels = new List<string>();
                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<AddressableAssetGroupSchema> { 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<AddressableAssetGroupSchema> { 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<string> label = new List<string>();
                    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<string> oldLabels = new List<string>();
                            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<AddressableAssetGroup> deleteList = new List<AddressableAssetGroup>();
        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通用框架搭建(五)——基于Addressable一键打包工具_第3张图片

你可能感兴趣的:(Unity,游戏开发之路,unity3d,unity,Addressable,一键打包,资源打包)