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)

相关推荐
码农君莫笑8 小时前
PowerBuilder中调用Excel OLE对象的方法
windows·excel·powerbuilder
罗汉松(山水白河)9 小时前
解除WPS登录限制
windows·经验分享·笔记·学习·wps
Nobita Chen9 小时前
Python实现windows自动关机
开发语言·windows·python
李一帆'11 小时前
Windows安装Docker Desktop
windows·docker·容器
C MIKE12 小时前
unity免费资源2025-1-10
unity·游戏引擎
恬静的小魔龙13 小时前
【Unity3D日常开发】Unity3D中打开Window文件对话框打开文件(PC版)
3d·unity·编辑器·游戏引擎·音视频
90后小陈老师13 小时前
Unity搭配VS Code使用
unity·游戏引擎
书生-w13 小时前
局域网共享文件夹实现两台Windows电脑之间传输文件
开发语言·windows·php
FatherOfCodingMan13 小时前
unity PrefabUtility 接口使用记录
unity·游戏引擎
林枫依依13 小时前
Unity 视频导入unity后,播放时颜色变得很暗很深,是什么原因导致?
unity·游戏引擎