Unity 自定义批量打包工具

打包配置项

csharp 复制代码
using UnityEngine;
using System.Collections.Generic;

namespace MYTOOL.Build
{
    /// <summary>
    /// 批量打包配置文件
    /// </summary>
    [CreateAssetMenu]
    public class BatchBuildProfile : ScriptableObject
    {
        public List<BuildTask> tasks = new List<BuildTask>(0);
    }
}

打包功能

csharp 复制代码
using UnityEditor;
using UnityEngine;
using System;
using System.IO;
using System.Collections.Generic;

namespace MYTOOL.Build
{
    public class LogMessage
    {
        public LogType type;
        public string message;

        public LogMessage(LogType type, string message)
        {
            this.type = type;
            this.message = message;
        }
    }

    [CustomEditor(typeof(BatchBuildProfile))]
    public class BatchBuildProfileInspector : Editor
    {
        //配置文件
        private BatchBuildProfile profile;
        //折叠栏
        private Dictionary<BuildTask, bool> foldoutMap;
        //记录日志
        private List<LogMessage> logsList;

        private void OnEnable()
        {
            profile = target as BatchBuildProfile;
            foldoutMap = new Dictionary<BuildTask, bool>();
            logsList = new List<LogMessage>();
        }

        public override void OnInspectorGUI()
        {
            OnMenuGUI();
            OnListGUI();
            serializedObject.ApplyModifiedProperties();
            if (GUI.changed)
                EditorUtility.SetDirty(profile);
        }

        /// <summary>
        /// 菜单项
        /// </summary>
        private void OnMenuGUI()
        {
            EditorGUILayout.HelpBox($"已有打包工作项:{profile.tasks.Count}个", MessageType.Info);
            EditorGUILayout.HelpBox($"打包时会先进行排序, 优先打包当前平台【{EditorUserBuildSettings.activeBuildTarget}】", MessageType.Info);
            //限制20个
            if (profile.tasks.Count < 20)
            {
                //新建工作项
                if (GUILayout.Button("新建工作项", GUILayout.Height(30)))
                {
                    Undo.RecordObject(profile, "Create");
                    string buildPath = Path.Combine(Directory.GetParent(Application.dataPath).FullName, ".Build");
                    if (Directory.Exists(buildPath) == false)
                    {
                        Directory.CreateDirectory(buildPath);
                        Debug.LogFormat("创建构建目录:{0}", buildPath);
                    }

                    var task = new BuildTask(PlayerSettings.productName, (MyBuildTarget)EditorUserBuildSettings.activeBuildTarget, buildPath);
                    profile.tasks.Add(task);
                }
            }
            else
            {
                EditorGUILayout.HelpBox($"无法新建打包工作项", MessageType.Warning);
            }

            if (profile.tasks.Count > 0)
            {
                //清空
                GUI.color = Color.yellow;
                if (GUILayout.Button("清空工作项", GUILayout.Height(30)))
                {
                    Undo.RecordObject(profile, "Clear");
                    if (EditorUtility.DisplayDialog("提醒", "是否确认清理所有打包工作项?", "确定", "取消"))
                    {
                        Debug.LogWarningFormat("清理{0}个打包工作项", profile.tasks.Count);
                        profile.tasks.Clear();
                        foldoutMap.Clear();
                    }
                }

                //开始打包
                GUI.color = Color.cyan;
                if (GUILayout.Button("开始打包", GUILayout.Height(30)))
                {
                    if (EditorUtility.DisplayDialog("确认操作", "即将开始打包过程,这可能需要一些时间。您希望继续吗?", "继续", "取消"))
                    {
                        logsList.Clear();
                        OnBuild(false);
                    }
                    return;
                }

                //清理并打包
                GUI.color = Color.yellow;
                if (GUILayout.Button("清理并打包", GUILayout.Height(30)))
                {
                    if (EditorUtility.DisplayDialog("确认操作", "即将进行清理并开始打包过程,这可能需要一些时间。您希望继续吗?", "继续", "取消"))
                    {
                        if (EditorUtility.DisplayDialog("重要提醒", "清理操作将移除当前构建平台的所有文件,请确保已备份重要数据。是否要继续?此操作不可逆。", "确定继续", "取消"))
                        {
                            logsList.Clear();
                            OnBuild(true);
                        }
                    }
                    return;
                }
            }

            GUI.color = Color.white;
            //排序
            if (profile.tasks.Count > 1)
            {
                if (GUILayout.Button("排序工作项", GUILayout.Height(30)))
                {
                    Debug.Log("排序打包工作项");
                    profile.tasks.Sort(new BuildTaskComparer());
                    return;
                }
            }
        }

        /// <summary>
        /// 任务项
        /// </summary>
        private void OnListGUI()
        {
            for (int i = 0; i < profile.tasks.Count; i++)
            {
                var task = profile.tasks[i];
                if (foldoutMap.ContainsKey(task) == false)
                {
                    foldoutMap.Add(task, true);
                }

                GUILayout.Space(20);
                GUILayout.BeginHorizontal("Badge");
                GUILayout.Space(20);
                foldoutMap[task] = EditorGUILayout.Foldout(foldoutMap[task], task.ToString(), true);
                if (GUILayout.Button(EditorGUIUtility.IconContent("TreeEditor.Trash"), "IconButton", GUILayout.Width(20)))
                {
                    Undo.RecordObject(profile, "Delete Task");
                    foldoutMap.Remove(task);
                    profile.tasks.Remove(task);
                    break;
                }
                GUILayout.EndHorizontal();

                //折叠栏
                if (foldoutMap[task])
                {
                    GUILayout.BeginVertical("Box");

                    //是否激活
                    GUILayout.BeginHorizontal();
                    GUILayout.Label("是否激活:", GUILayout.Width(70));
                    task.enableTask = GUILayout.Toggle(task.enableTask, "");
                    GUILayout.EndHorizontal();

                    //打包场景
                    GUILayout.BeginHorizontal();
                    GUILayout.Label("打包场景:", GUILayout.Width(70));
                    if (GUILayout.Button("+", GUILayout.Width(20f)))
                    {
                        task.sceneAssets.Add(null);
                    }
                    GUILayout.EndHorizontal();

                    //场景列表
                    if (task.sceneAssets.Count > 0)
                    {
                        OnSceneAssetsList(task);
                    }

                    //产品名称
                    GUILayout.BeginHorizontal();
                    GUILayout.Label("产品名称:", GUILayout.Width(70));
                    var newPN = GUILayout.TextField(task.productName);
                    if (task.productName != newPN)
                    {
                        Undo.RecordObject(profile, "Product Name");
                        task.productName = newPN;
                    }
                    GUILayout.EndHorizontal();

                    //打包平台
                    GUILayout.BeginHorizontal();
                    GUILayout.Label("打包平台:", GUILayout.Width(70));
                    var newBT = (MyBuildTarget)EditorGUILayout.EnumPopup(task.buildTarget);
                    if (task.buildTarget != newBT)
                    {
                        Undo.RecordObject(profile, "Build Target");
                        task.buildTarget = newBT;
                        //这些平台只能使用IL2CPP
                        if (task.buildTarget == MyBuildTarget.iOS || task.buildTarget == MyBuildTarget.WebGL || task.buildTarget == MyBuildTarget.WeixinMiniGame)
                        {
                            task.scriptMode = ScriptingImplementation.IL2CPP;
                        }
                        //其它平台默认切换到Player
                        if (task.buildTarget != MyBuildTarget.StandaloneWindows64 && task.buildTarget != MyBuildTarget.StandaloneLinux64 && task.buildTarget != MyBuildTarget.NoTarget)
                        {
                            task.buildSubtarget = StandaloneBuildSubtarget.Player;
                        }
                    }
                    GUILayout.EndHorizontal();

                    //Windows Linux添加打包子平台
                    if (task.buildTarget == MyBuildTarget.StandaloneWindows64 || task.buildTarget == MyBuildTarget.StandaloneLinux64)
                    {
                        GUILayout.BeginHorizontal();
                        GUILayout.Label("打包子平台:", GUILayout.Width(70));
                        var newBS = (StandaloneBuildSubtarget)EditorGUILayout.EnumPopup(task.buildSubtarget);
                        if (task.buildSubtarget != newBS)
                        {
                            Undo.RecordObject(profile, "Build Subtarget");
                            task.buildSubtarget = newBS;
                        }
                        GUILayout.EndHorizontal();
                    }

                    //打包选项
                    GUILayout.BeginHorizontal();
                    GUILayout.Label("打包选项:", GUILayout.Width(70));
                    var newBO = (BuildOptions)EditorGUILayout.EnumFlagsField(task.buildOptions);
                    if (task.buildOptions != newBO)
                    {
                        Undo.RecordObject(profile, "Build Options");
                        task.buildOptions = newBO;
                    }
                    GUILayout.EndHorizontal();

                    //脚本模式
                    GUILayout.BeginHorizontal();
                    GUILayout.Label("脚本模式:", GUILayout.Width(70));
                    var newSM = (ScriptingImplementation)EditorGUILayout.EnumPopup(task.scriptMode);
                    if (task.scriptMode != newSM)
                    {
                        Undo.RecordObject(profile, "Script Mode");
                        task.scriptMode = newSM;
                    }
                    GUILayout.EndHorizontal();

                    //打包路径
                    GUILayout.BeginHorizontal();
                    GUILayout.Label("打包路径:", GUILayout.Width(70));
                    GUILayout.TextField(task.buildPath);
                    if (GUILayout.Button("浏览", GUILayout.Width(40f)))
                    {
                        string path = EditorUtility.SaveFolderPanel("Build Path", task.buildPath, "");
                        if (!string.IsNullOrWhiteSpace(path))
                        {
                            task.buildPath = path;
                        }

                        GUIUtility.ExitGUI();
                    }
                    GUILayout.EndHorizontal();

                    //安卓平台添加其它选项
                    if (task.buildTarget == MyBuildTarget.Android)
                    {
                        GUILayout.BeginHorizontal();
                        GUILayout.Label("keystore:", GUILayout.Width(70));
                        PlayerSettings.Android.keystorePass = EditorGUILayout.PasswordField(PlayerSettings.Android.keystorePass);
                        GUILayout.EndHorizontal();

                        GUILayout.BeginHorizontal();
                        GUILayout.Label("keyalias:", GUILayout.Width(70));
                        PlayerSettings.Android.keyaliasPass = EditorGUILayout.PasswordField(PlayerSettings.Android.keyaliasPass);
                        GUILayout.EndHorizontal();

                        //导出工程
                        GUILayout.BeginHorizontal();
                        GUILayout.Label("导出工程:", GUILayout.Width(70));
                        EditorUserBuildSettings.exportAsGoogleAndroidProject = GUILayout.Toggle(EditorUserBuildSettings.exportAsGoogleAndroidProject, "");
                        GUILayout.EndHorizontal();
                    }

                    GUILayout.EndVertical();
                }
            }
        }

        private void OnSceneAssetsList(BuildTask task)
        {
            GUILayout.BeginHorizontal();
            GUILayout.Space(75);
            GUILayout.BeginVertical("Badge");
            for (int j = 0; j < task.sceneAssets.Count; j++)
            {
                var sceneAsset = task.sceneAssets[j];
                GUILayout.BeginHorizontal();
                GUILayout.Label($"{j + 1}.", GUILayout.Width(20));
                task.sceneAssets[j] = EditorGUILayout.ObjectField(sceneAsset, typeof(SceneAsset), false) as SceneAsset;
                if (GUILayout.Button("↑", "MiniButtonLeft", GUILayout.Width(20)))
                {
                    if (j > 0)
                    {
                        Undo.RecordObject(profile, "Move Up Scene Assets");
                        var temp = task.sceneAssets[j - 1];
                        task.sceneAssets[j - 1] = sceneAsset;
                        task.sceneAssets[j] = temp;
                    }
                }
                if (GUILayout.Button("↓", "MiniButtonMid", GUILayout.Width(20)))
                {
                    if (j < task.sceneAssets.Count - 1)
                    {
                        Undo.RecordObject(profile, "Move Down Scene Assets");
                        var temp = task.sceneAssets[j + 1];
                        task.sceneAssets[j + 1] = sceneAsset;
                        task.sceneAssets[j] = temp;
                    }
                }
                if (GUILayout.Button("+", "MiniButtonMid", GUILayout.Width(20)))
                {
                    Undo.RecordObject(profile, "Add Scene Assets");
                    task.sceneAssets.Insert(j + 1, null);
                    break;
                }
                if (GUILayout.Button("-", "MiniButtonMid", GUILayout.Width(20)))
                {
                    Undo.RecordObject(profile, "Delete Scene Assets");
                    task.sceneAssets.RemoveAt(j);
                    break;
                }
                GUILayout.EndHorizontal();
            }
            GUILayout.EndVertical();
            GUILayout.EndHorizontal();
        }

        /// <summary>
        /// 开始打包
        /// </summary>
        /// <param name="clearBuild">清理旧的构建</param>
        private void OnBuild(bool clearBuild)
        {
            //排序,优先当前平台的任务
            profile.tasks.Sort(new BuildTaskComparer());
            //旧版本号
            string oldVersion = PlayerSettings.bundleVersion;
            //新版本号
            string newVersion = GetNewVersion();
            try
            {
                for (int i = 0; i < profile.tasks.Count; i++)
                {
                    var task = profile.tasks[i];
                    if (task.enableTask == false || task.buildTarget == MyBuildTarget.NoTarget)
                    {
                        logsList.Add(new LogMessage(LogType.Log, $"跳过: {task}"));
                        continue;
                    }
                    BuildTarget buildTarget = (BuildTarget)task.buildTarget;
                    BuildTargetGroup targetGroup = BuildPipeline.GetBuildTargetGroup(buildTarget);
                    BuildPlayerOptions buildPlayerOptions = SetBuildParams(targetGroup, task);
                    EditorUtility.DisplayProgressBar("正在打包", profile.tasks[i].ToString(), (float)i + 1 / profile.tasks.Count);

                    if (string.IsNullOrEmpty(buildPlayerOptions.locationPathName))
                    {
                        throw new Exception(($"无法打包 {task},产品名称可能为空"));
                    }
                    if (buildPlayerOptions.scenes.Length == 0)
                    {
                        throw new Exception($"无法打包 {task},打包场景为空");
                    }

                    //切换平台
                    if (buildTarget != EditorUserBuildSettings.activeBuildTarget)
                    {
                        EditorUserBuildSettings.SwitchActiveBuildTarget(targetGroup, buildTarget);
                    }

                    //设置新版本号
                    PlayerSettings.bundleVersion = newVersion;
                    PlayerSettings.SetScriptingBackend(targetGroup, task.scriptMode);

                    string path = Path.GetDirectoryName(buildPlayerOptions.locationPathName);
                    if (clearBuild && Directory.Exists(path))
                    {
                        Directory.Delete(path, true);
                    }
                    if (Directory.Exists(path) == false)
                    {
                        Directory.CreateDirectory(path);
                    }

                    //开始打包
                    var report = BuildPipeline.BuildPlayer(buildPlayerOptions);
                    switch (report.summary.result)
                    {
                        case UnityEditor.Build.Reporting.BuildResult.Unknown:
                            logsList.Add(new LogMessage(LogType.Error, $"{task} 出现未知错误"));
                            break;

                        case UnityEditor.Build.Reporting.BuildResult.Succeeded:
                            logsList.Add(new LogMessage(LogType.Log, $"{task} 打包耗时: {(report.summary.buildEndedAt - report.summary.buildStartedAt).TotalSeconds}秒"));
                            break;

                        case UnityEditor.Build.Reporting.BuildResult.Failed:
                            string errorMsg = "";
                            foreach (var file in report.GetFiles())
                            {
                                errorMsg += file.path + "\n";
                            }
                            foreach (var step in report.steps)
                            {
                                foreach (var stepmsg in step.messages)
                                {
                                    errorMsg += "\n" + stepmsg.content;
                                }

                                errorMsg += "\n";
                            }
                            logsList.Add(new LogMessage(LogType.Error, $"{task} 打包失败: {errorMsg}"));
                            break;

                        case UnityEditor.Build.Reporting.BuildResult.Cancelled:
                            logsList.Add(new LogMessage(LogType.Log, $"{task} 取消打包"));
                            break;
                    }

                    //打包成功,打开目录并记录版本号
                    if (report.summary.result == UnityEditor.Build.Reporting.BuildResult.Succeeded)
                    {
                        File.WriteAllText(string.Format("{0}/version.txt", path), newVersion);
                        Application.OpenURL(path);
                    }
                }
            }
            catch (Exception ex)
            {
                //异常情况下还原版本号
                PlayerSettings.bundleVersion = oldVersion;
                Debug.LogFormat("还原打包版本号:{0}", oldVersion);
                Debug.LogException(ex);
            }
            finally
            {
                EditorUtility.ClearProgressBar();
                Debug.LogFormat("当前打包版本号:{0}", newVersion);
                foreach (var log in logsList)
                {
                    Debug.unityLogger.Log(log.type, log.message);
                }
                logsList.Clear();
            }
        }

        /// <summary>
        /// 获取新版本号
        /// </summary>
        /// <returns></returns>
        private string GetNewVersion()
        {
            try
            {
                Version version = new Version(PlayerSettings.bundleVersion);
                int major = version.Major;              //主版本
                int minor = version.Minor;              //次版本
                int build = version.Build;              //构建版本
                int revision = version.Revision + 1;    //修订版本

                if (revision >= 100)
                {
                    build += 1;
                    revision = 0;
                }
                if (build >= 100)
                {
                    minor += 1;
                    build = 0;
                }
                if (minor >= 100)
                {
                    major += 1;
                    minor = 0;
                }

                return $"{major}.{minor}.{build}.{revision}";
            }
            catch (Exception)
            {
                return "1.0.0.0";
            }
        }

        /// <summary>
        /// 设置构建参数
        /// </summary>
        /// <param name="targetGroup"></param>
        /// <param name="task"></param>
        /// <returns></returns>
        private BuildPlayerOptions SetBuildParams(BuildTargetGroup targetGroup, BuildTask task)
        {
            BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions();

            List<string> levels = new List<string>();
            string[] activeLevels = EditorBuildSettingsScene.GetActiveSceneList(EditorBuildSettings.scenes);
            if (activeLevels.Length > 0)
            {
                levels.AddRange(activeLevels);
            }
            for (int i = 0; i < task.sceneAssets.Count; i++)
            {
                var scenePath = AssetDatabase.GetAssetPath(task.sceneAssets[i]);
                if (!string.IsNullOrEmpty(scenePath) && !levels.Contains(scenePath))
                {
                    levels.Add(scenePath);
                }
            }

            buildPlayerOptions.scenes = levels.ToArray();
            buildPlayerOptions.target = (BuildTarget)task.buildTarget;
            buildPlayerOptions.subtarget = (int)task.buildSubtarget;
            buildPlayerOptions.targetGroup = targetGroup;
            buildPlayerOptions.options = task.buildOptions;
            buildPlayerOptions.locationPathName = GetBuildTargetPath(task.buildTarget, task.buildSubtarget, task.buildOptions, task.buildPath, task.productName);
            return buildPlayerOptions;
        }

        /// <summary>
        /// 获取构建路径
        /// </summary>
        /// <param name="buildTarget"></param>
        /// <param name="buildOptions"></param>
        /// <param name="buildPath"></param>
        /// <param name="productName"></param>
        /// <returns></returns>
        private string GetBuildTargetPath(MyBuildTarget buildTarget, StandaloneBuildSubtarget buildSubtarget, BuildOptions buildOptions, string buildPath, string productName)
        {
            if (string.IsNullOrEmpty(productName))
            {
                return string.Empty;
            }

            bool isDevelopment = buildOptions.HasFlag(BuildOptions.Development);
            string currentDate = DateTime.Now.ToString("yyMMdd");
            string locationPathName = Path.Combine(buildPath, buildTarget.ToString(), buildSubtarget.ToString(), currentDate, productName);
            switch (buildTarget)
            {
                case MyBuildTarget.StandaloneOSX:
                    {
                        if (isDevelopment)
                            locationPathName += "_dev.app";
                        else
                            locationPathName += ".app";
                    }
                    break;

                case MyBuildTarget.StandaloneWindows64:
                    {
                        if (isDevelopment)
                            locationPathName += "_dev.exe";
                        else
                            locationPathName += ".exe";
                    }
                    break;

                case MyBuildTarget.StandaloneLinux64:
                    {
                        if (isDevelopment)
                            locationPathName += "_dev.x86_64";
                        else
                            locationPathName += ".x86_64";
                    }
                    break;

                case MyBuildTarget.Android:
                    {
                        if (isDevelopment)
                            locationPathName += $"_{currentDate}_dev";
                        else
                            locationPathName += $"_{currentDate}";

                        if (EditorUserBuildSettings.exportAsGoogleAndroidProject == false)
                            locationPathName += ".APK";
                    }
                    break;

                case MyBuildTarget.iOS:
                    {
                        if (isDevelopment)
                            locationPathName += $"_{currentDate}_dev";
                        else
                            locationPathName += $"_{currentDate}";
                    }
                    break;
            }

            return locationPathName;
        }
    }
}

任务配置项

csharp 复制代码
using System;
using System.Collections.Generic;
using UnityEditor;

namespace MYTOOL.Build
{
    /// <summary>
    /// 打包的目标平台
    /// </summary>
    public enum MyBuildTarget
    {
        NoTarget = -2,
        //
        // 摘要:
        //     Build a macOS standalone (Intel 64-bit).
        StandaloneOSX = 2,
        //
        // 摘要:
        //     Build a Windows 64-bit standalone.
        StandaloneWindows64 = 19,
        //
        // 摘要:
        //     Build a Linux 64-bit standalone.
        StandaloneLinux64 = 24,
        //
        // 摘要:
        //     Build an iOS player.
        iOS = 9,
        //
        // 摘要:
        //     Build an Android .apk standalone app.
        Android = 13,
        //
        // 摘要:
        //     Build to WebGL platform.
        WebGL = 20,
        //
        // 摘要:
        //     Build to WeixinMiniGame platform.
        WeixinMiniGame = 47,
        //
        // 摘要:
        //     Build an OpenHarmony .hap standalone app.
        OpenHarmony = 48,
    }

    /// <summary>
    /// 打包工作项
    /// </summary>
    [Serializable]
    public class BuildTask
    {
        /// <summary>
        /// 是否激活
        /// </summary>
        public bool enableTask;
        /// <summary>
        /// 打包的产品名称
        /// </summary>
        public string productName;
        /// <summary>
        /// 打包的目标平台
        /// </summary>
        public MyBuildTarget buildTarget;
        /// <summary>
        /// 打包的目标子平台
        /// </summary>
        public StandaloneBuildSubtarget buildSubtarget;
        /// <summary>
        /// 打包的选项
        /// </summary>
        public BuildOptions buildOptions;
        /// <summary>
        /// 脚本模式
        /// </summary>
        public ScriptingImplementation scriptMode;
        /// <summary>
        /// 打包的保存路径
        /// </summary>
        public string buildPath;
        /// <summary>
        /// 打包的场景列表
        /// </summary>
        public List<SceneAsset> sceneAssets;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="productName">产品名称</param>
        /// <param name="buildTarget">目标平台</param>
        /// <param name="buildPath">保存路径</param>
        public BuildTask(string productName, MyBuildTarget buildTarget, string buildPath)
        {
            this.productName = productName;
            this.buildTarget = buildTarget;
            this.buildPath = buildPath;

            enableTask = true;
            buildSubtarget = StandaloneBuildSubtarget.Player;
            buildOptions = BuildOptions.CleanBuildCache;
            scriptMode = ScriptingImplementation.IL2CPP;
            sceneAssets = new List<SceneAsset>();
        }

        public override string ToString()
        {
            return string.Format("{0}【{1}】", productName, buildTarget);
        }
    }


    class BuildTaskComparer : IComparer<BuildTask>
    {
        public int Compare(BuildTask x, BuildTask y)
        {
            if ((BuildTarget)x.buildTarget == EditorUserBuildSettings.activeBuildTarget && (BuildTarget)y.buildTarget != EditorUserBuildSettings.activeBuildTarget)
            {
                return -1; // x排在前
            }
            else if ((BuildTarget)x.buildTarget != EditorUserBuildSettings.activeBuildTarget && (BuildTarget)y.buildTarget == EditorUserBuildSettings.activeBuildTarget)
            {
                return 1; // y排在前
            }
            else
            {
                return x.buildTarget.ToString().CompareTo(y.buildTarget.ToString());
            }
        }
    }
}

效果图,可以将它锁定在这里,方便后面使用

使用也很简单,选择打包的平台,并设置一些参数。点击开始打包或清理并打包。

注意:打包场景字段是额外添加, 每次打包都会先获取Build Settings里激活的场景,并添加上打包场景中的设置

其它解释:
有些字段是直接使用Unity的,所以数据是共享的,比如安卓特有的选项,一个地方修改,相对于的位置也发生改变。
构建目录格式:打包路径+打包平台+打包子平台+日期(yyMMdd)
为什么添加打包子平台字段,因为我的项目中需要打包服务端(Dedicated Server)

相关推荐
cpsvps_net11 小时前
美国服务器环境下Windows容器工作负载智能弹性伸缩
windows
甄超锋12 小时前
Java ArrayList的介绍及用法
java·windows·spring boot·python·spring·spring cloud·tomcat
NRatel12 小时前
亚马逊S3的使用简记(游戏资源发布更新)
游戏·unity·amazon s3
cpsvps14 小时前
美国服务器环境下Windows容器工作负载基于指标的自动扩缩
windows
网硕互联的小客服17 小时前
Apache 如何支持SHTML(SSI)的配置方法
运维·服务器·网络·windows·php
etcix17 小时前
implement copy file content to clipboard on Windows
windows·stm32·单片机
许泽宇的技术分享17 小时前
Windows MCP.Net:基于.NET的Windows桌面自动化MCP服务器深度解析
windows·自动化·.net
SmalBox18 小时前
【渲染流水线】[几何阶段]-[屏幕映射]以UnityURP为例
unity·渲染
非凡ghost19 小时前
AMS PhotoMaster:全方位提升你的照片编辑体验
windows·学习·信息可视化·软件需求
mortimer20 小时前
一次与“顽固”外部程序的艰难交锋:subprocess 调用exe踩坑实录
windows·python·ai编程