当前位置: 首页 > news >正文

Unity 自定义批量打包工具

打包配置项

using UnityEngine;
using System.Collections.Generic;namespace MYTOOL.Build
{[System.Flags]public enum VersionOptions{None = 0,Major = 1,Minor = 4,Build = 8,Revision = 0x10,}/// <summary>/// 批量打包配置文件/// </summary>[CreateAssetMenu]public class BatchBuildProfile : ScriptableObject{public VersionOptions versionOptions = VersionOptions.Revision;public List<BuildTask> tasks = new List<BuildTask>(0);}
}

打包功能

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(){//新旧版本号if (profile.tasks.Count > 0){GUILayout.Space(10);//版本选项GUILayout.BeginHorizontal();GUILayout.Label("版本选项:", GUILayout.Width(70));var newVO = (VersionOptions)EditorGUILayout.EnumFlagsField(profile.versionOptions);if (profile.versionOptions != newVO){Undo.RecordObject(profile, "Version Options");profile.versionOptions = newVO;}GUILayout.EndHorizontal();GUILayout.BeginHorizontal();GUILayout.Label("旧版本号:", GUILayout.Width(70));GUILayout.Label(PlayerSettings.bundleVersion);GUILayout.EndHorizontal();GUILayout.BeginHorizontal();GUILayout.Label("新版本号:", GUILayout.Width(70));GUILayout.Label(GetNewVersion(profile.versionOptions));GUILayout.EndHorizontal();}for (int i = 0; i < profile.tasks.Count; i++){var task = profile.tasks[i];if (foldoutMap.ContainsKey(task) == false){foldoutMap.Add(task, true);}GUI.contentColor = task.enableTask ? Color.green : Color.white;GUILayout.Space(10);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]){GUI.contentColor = Color.white;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;//这些平台只能使用IL2CPPif (task.buildTarget == MyBuildTarget.iOS || task.buildTarget == MyBuildTarget.WebGL || task.buildTarget == MyBuildTarget.WeixinMiniGame){task.scriptMode = ScriptingImplementation.IL2CPP;}//其它平台默认切换到Playerif (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(profile.versionOptions);//设置新版本号PlayerSettings.bundleVersion = newVersion;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.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 = "\n";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} 取消打包"));return;}//打包成功,打开目录并记录版本号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(VersionOptions options){try{Version version = new Version(PlayerSettings.bundleVersion);int major = version.Major;              //主版本int minor = version.Minor;              //次版本int build = version.Build;              //构建版本int revision = version.Revision;        //修订版本//默认不处理if (options == VersionOptions.None){//revision += 1;}else{major += options.HasFlag(VersionOptions.Major) ? 1 : 0;minor += options.HasFlag(VersionOptions.Minor) ? 1 : 0;build += options.HasFlag(VersionOptions.Build) ? 1 : 0;revision += options.HasFlag(VersionOptions.Revision) ? 1 : 0;}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";elselocationPathName += ".app";}break;case MyBuildTarget.StandaloneWindows64:{if (isDevelopment)locationPathName += "_dev.exe";elselocationPathName += ".exe";}break;case MyBuildTarget.StandaloneLinux64:{if (isDevelopment)locationPathName += "_dev.x86_64";elselocationPathName += ".x86_64";}break;case MyBuildTarget.Android:{if (isDevelopment)locationPathName += $"_{currentDate}_dev";elselocationPathName += $"_{currentDate}";if (EditorUserBuildSettings.exportAsGoogleAndroidProject == false)locationPathName += ".APK";}break;case MyBuildTarget.iOS:{if (isDevelopment)locationPathName += $"_{currentDate}_dev";elselocationPathName += $"_{currentDate}";}break;}return locationPathName;}}
}

任务配置项

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 standalone.StandaloneWindows = 5,//// 摘要://     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}-{2}】", productName, buildTarget, buildSubtarget);}}/// <summary>
/// BuildTask比较
/// </summary>
public class BuildTaskComparer : IComparer<BuildTask>
{public int Compare(BuildTask x, BuildTask y){if (x.enableTask && (BuildTarget)x.buildTarget == EditorUserBuildSettings.activeBuildTarget && (BuildTarget)y.buildTarget != EditorUserBuildSettings.activeBuildTarget){return -1; // x排在前}else if (y.enableTask && (BuildTarget)x.buildTarget != EditorUserBuildSettings.activeBuildTarget && (BuildTarget)y.buildTarget == EditorUserBuildSettings.activeBuildTarget){return 1; // y排在前}else if (x.enableTask && y.enableTask && x.buildTarget == y.buildTarget){return 0; //保持当前}else if (x.enableTask && x.buildTarget == y.buildTarget){return -1; // x排在前}else if (y.enableTask && x.buildTarget == y.buildTarget){return 1; // y排在前}else{return x.buildTarget.ToString().CompareTo(y.buildTarget.ToString());}}
}

效果图,可以将它锁定在这里,方便后面使用
使用也很简单,选择打包的平台,并设置一些参数。点击开始打包或清理并打包。
注意:打包场景字段是额外添加, 每次打包都会先获取Build Settings里激活的场景,并添加上打包场景中的设置
在这里插入图片描述

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

相关文章:

Unity 自定义批量打包工具

打包配置项 using UnityEngine; using System.Collections.Generic;namespace MYTOOL.Build {[System.Flags]public enum VersionOptions{None 0,Major 1,Minor 4,Build 8,Revision 0x10,}/// <summary>/// 批量打包配置文件/// </summary>[CreateAssetMenu]…...

WebGL性能检测

WebGL性能检测系统说明 检测维度 1. WebGL版本支持检测(20分) WebGL 1.0 和 WebGL 2.0 版本检测WebGL 2.0 支持得20分仅支持WebGL 1.0 得12分主要影响高级特性和性能优化的可用性2. GPU性能评估(25分) 通过WEBGL_debug_renderer_info获取显卡信息根据GPU品牌和型号进行评…...

C#,图论与图算法,输出无向图“欧拉路径”的弗勒里(Fleury Algorithm)算法和源程序

1 欧拉路径 欧拉路径是图中每一条边只访问一次的路径。欧拉回路是在同一顶点上开始和结束的欧拉路径。 这里展示一种输出欧拉路径或回路的算法。 以下是Fleury用于打印欧拉轨迹或循环的算法&#xff08;源&#xff09;。 1、确保图形有0个或2个奇数顶点。2、如果有0个奇数顶…...

Jenkins触发器--在其他项目执行后构建

前言&#xff1a; jenkins中有多种触发器可用&#xff0c;可以方便的控制构建的启动 这里简单介绍下项目后构建的配置方法 1. 解释&#xff1a; Build after other projects are built Set up a trigger so that when some other projects finish building, a new build is…...

UE5.4运行报错解决(关于osg使用-无法解决的外部命令)(未解决)

报错如下&#xff1a; 09:38:06:665 4>EpicGames.Core -> E:\AppInstall\EpicGames\UE_5.4\Engine\Source\Programs\Shared\EpicGames.Core\bin\Development\net6.0\EpicGames.Core.dll 09:38:06:668 5>------ 已启动全部重新生成: 项目: EpicGames.MsBuild, 配…...

Swift语言的软件工程

Swift语言的软件工程 引言 随着科技的不断进步&#xff0c;软件开发行业正在经历着前所未有的变化。在这场变革中&#xff0c;Swift语言作为苹果公司推出的一种新型编程语言&#xff0c;凭借其简洁、高效及安全的特性&#xff0c;正在快速崛起&#xff0c;成为现代软件工程中…...

国内外网络安全政策动态(2024年12月)

▶︎ 1.2项网络安全国家标准获批发布 2024年12月6日&#xff0c;根据2024年11月28日国家市场监督管理总局、国家标准化管理委员会发布的中华人民共和国国家标准公告&#xff08;2024年第29号&#xff09;&#xff0c;全国网络安全标准化技术委员会归口的2项网络安全国家标准正…...

DELTA并联机械手视觉方案荣获2024年度机器人应用典型案例奖

直击现场 2025年1月9日晚&#xff0c;2024深圳市机器人年度评选颁奖典礼在深圳市南山区圣淘沙酒店正式拉开帷幕。本次颁奖活动由中国科学院深圳先进技术研究院指导&#xff0c;深圳市机器人协会与《机器人与智能系统》杂志组织承办。 正运动公司受邀参与此次典礼&#xff0c;…...

python 3个线程轮流打印A、B、C

要实现 Python 中三个线程轮流打印 A、B、C 的效果&#xff0c;可以使用 threading 模块和 Condition 或 Lock 来同步线程。以下是使用 Condition 的解决方案&#xff1a; 代码实现 import threading# 初始化条件变量 condition threading.Condition() current 0 # 共享变…...

Http 响应状态码 前后端联调

http 响应状态码 &#xff1a;是服务器在处理HTTP请求时返回的状态信息&#xff0c;用于表示请求的处理结果 1xx : 信息性状态码 100 Continue: 服务器已收到请求头部&#xff0c;客户端应继续发送请求体。 101 Switching Protocols : 切换协议。服务器已理解客户端的请求&a…...

AI Agent:软件测试自动化的新纪元

在信息技术日新月异的今天&#xff0c;人工智能&#xff08;AI&#xff09;技术的蓬勃发展正引领着各个行业的深刻变革&#xff0c;软件测试领域同样迎来了前所未有的机遇与挑战。AI Agent&#xff0c;这一融合了先进机器学习与自然语言处理技术的智能实体&#xff0c;正悄然成…...

C#结构体,枚举,泛型,事件,委托--10

目录 一.结构体 二.特殊的结构体(ref struct): 三.枚举 四.泛型 泛型的使用: 1.泛型类:定义一个泛型类,使用类型参数T 2.泛型方法:在方法定义中使用类型参数 3.泛型接口 五.委托及泛型委托 委托 泛型委托 六.事件 事件: 泛型事件:使用泛型委托&#xff08;如Event…...

MATLAB语言的语法糖

MATLAB语言的语法糖 在现代编程语言中&#xff0c;语法糖&#xff08;Syntactic Sugar&#xff09;是一个常见的概念&#xff0c;它指的是某种编程语言提供的语法&#xff0c;使得代码更加简洁易读&#xff0c;而不改变语言本身的功能。MATLAB作为一种广泛应用于科学计算、工程…...

前端开发:HTML常见标签

1.注释标签 注释不会显示在界面上 . 目的是提高代码的可读性 . ctrl / 快捷键可以快速进行注释 / 取消注释 . <!-- 我是注释 --> 2.标题标签 有六个 , 从 h1 - h6. 数字越大 , 则字体越小 <h1> hello </h1> //我们所写的csdn的格式中的标题一…...

《AI发展的三个关键视角:基础设施、开源趋势与社会影响》

基础设施在AI研究中至关重要&#xff0c;但往往被忽视 基础设施在AI研究中至关重要&#xff0c;但往往被忽视&#xff0c;这一观点可以从多个方面进行深入探讨&#xff1a; 1. 基础设施的定义 基础设施在AI研究中通常指的是支持研究和开发的硬件、软件、网络和数据管理系统。…...

eslint.config.js和.eslintrc.js有什么区别

eslint.config.js 和 .eslintrc.js 的主要区别在于它们所对应的 ESLint 版本和配置方法&#xff1a; 1. .eslintrc.js&#xff1a; 这是 ESLint v8 及更早版本使用的配置文件格式。 它使用层级式的配置系统。 现在被称为"旧版"配置格式 。 2. eslint.config.js&am…...

Open FPV VTX开源之默认MAVLink设置

Open FPV VTX开源之默认MAVLink设置 1. 源由2. 准备3. 连接4. 安装5. 配置6. 测试6.1 启动wfb-ng服务6.2 启动wfb-ng监测6.3 启动QGroundControl6.4 观察测试结果 7. 总结8. 参考资料9. 补充9.1 telemetry_tx异常9.2 DEBUG串口部分乱码9.3 PixelPilot软件问题 1. 源由 飞控图传…...

一学就废|Python基础碎片,OS模块

Python 中的操作系统模块提供了与操作系统交互的功能。操作系统属于 Python 的标准实用程序模块。该模块提供了一种使用依赖于操作系统的功能的可移植方式。os和os. path模块包括许多与文件系统交互的函数。 Python-OS 模块函数 我们将讨论 Python os 模块的一些重要功能&…...

新版本的IDEA如何解决Git分支显示为警告⚠<unknown>的问题

目录 问题再现 解决思路 首先我们要想到 这个分支有没有从远程代码仓库拉去下来 复习一下 git 命令 其次思考 最后思考 问题再现 这边我使用的是 IDEA 2024.3.3.1 Jetbrains 官网的最新版 同时也是官方账号登录 的 今天上 github 去拉项目到 本地 出现了分支不显示的问…...

HTML和CSS相关的问题,为什么页面加载速度慢?

页面加载速度慢是网站优化中一个常见的问题&#xff0c;可能由于多种原因&#xff0c;包括HTML和CSS的代码编写方式、资源的加载顺序、页面渲染的复杂性等。以下是一些常见的原因和优化方法&#xff0c;结合实际项目代码示例进行讲解。 1. 过多的资源请求 如果页面包含大量的…...

使用葡萄城+vue实现Excel

最终实现效果如下 包含增加复选框 设置公式 设置背景颜色等&#xff0c;代码实在太多 有需要可留言 第一步&#xff1a;创建表头 请使用官网提供的网址&#xff1a;在线 Excel 编辑器 | SpreadJS 在线表格编辑器 1.点击下方号&#xff0c;创建一个新的sheet页 默认新创建的she…...

【漫话机器学习系列】042.提前停止训练的优势(Early Stopping Advantages)

提前停止训练&#xff08;Early Stopping&#xff09;的优势 提前停止是一种有效的正则化技术&#xff0c;在训练模型时通过监控验证集的性能来决定训练的结束点&#xff0c;从而避免过拟合。以下是提前停止的主要优势&#xff1a; 1. 防止过拟合 提前停止通过在验证集性能开…...

【2024遥感应用组一等奖】基于风云四号卫星的小时级大气气溶胶和颗粒物监测

作品介绍‍ 01 研究背景‍ 人口和产业的增长和集聚,增加了污染物的排放,改变了污染物的扩散条件,导致中国区域空气污染严重,是目前可持续发展面临的重大挑战之一。其中细颗粒物(PM2.5)是国内主要的空气污染物,威胁居民的身心健康,影响城市生态系统中社会、经济和自然…...

Cesium加载地形

Cesium的地形来源大致可以分为两种&#xff0c;一种是由Cesium官方提供的数据源&#xff0c;一种是第三方的数据源&#xff0c;官方源依赖于Cesium Assets&#xff0c;如果设置了AccessToken后&#xff0c;就可以直接使用Cesium的地形静态构造方法来获取数据源CesiumTerrainPro…...

Spring-Cloud-Gateway-Samples,nacos为注册中心,负载均衡

背景&#xff1a;本想找个简单例子看下&#xff0c;无奈版本依赖太过复杂&#xff0c;花了点时间。记录下吧 使用Spring Cloud Gateway作为网关服务&#xff0c;Nacos作为注册中心&#xff0c;实现对子服务的负载均衡访问。简单例子。 环境要求&#xff1a;JDK1.8、nacos 1.3…...

Centos9 + Docker 安装 MySQL8.4.0 + 定时备份数据库到本地

Centos9 Docker 安装 MySQL8.4.0 定时备份数据库到本地 创建目录&#xff0c;创建配置文件启动容器命令定时备份MySQL执行脚本Linux每日定时任务命令文件内参数其他时间参数 AT一次性定时任务 创建目录&#xff0c;创建配置文件 $ mkdir -p /opt/mysql/conf$ vim /opt/mysql/…...

【开源免费】基于Vue和SpringBoot的英语知识应用网站(附论文)

本文项目编号 T 138 &#xff0c;文末自助获取源码 \color{red}{T138&#xff0c;文末自助获取源码} T138&#xff0c;文末自助获取源码 目录 一、系统介绍二、数据库设计三、配套教程3.1 启动教程3.2 讲解视频3.3 二次开发教程 四、功能截图五、文案资料5.1 选题背景5.2 国内…...

aws(学习笔记第二十四课) 使用sam开发step functions

aws(学习笔记第二十四课) 使用sam开发step functions 学习内容&#xff1a; 生成sam的step functions实例程序什么是SAM amazon Serverless Application ModelSAM程序结构SAM执行程序 1. 生成sam的step functions实例程序 参照文档 这里参照AWS的官方文档SAM amazon Serverl…...

RabbitMQ介绍与使用

RabbitMQ官网 RabbitMQ 介绍 RabbitMQ 是一个开源的消息代理和队列服务器&#xff0c;基于 AMQP&#xff08;高级消息队列协议&#xff09;标准&#xff0c;使用 Erlang 编程语言构建。它是消息队列&#xff08;MQ&#xff09;的一种&#xff0c;广泛应用于分布式系统中&#x…...

LeetCode 热题 100_腐烂的橘子(52_994_中等_C++)(图;广度优先遍历(队列))

LeetCode 热题 100_腐烂的橘子&#xff08;52_994&#xff09; 题目描述&#xff1a;输入输出样例&#xff1a;题解&#xff1a;解题思路&#xff1a;思路一&#xff08;广度优先遍历&#xff08;队列&#xff09;&#xff09;&#xff1a; 代码实现代码实现&#xff08;思路一…...

【数学】概率论与数理统计(五)

文章目录 [toc] 二维随机向量及其分布随机向量离散型随机向量的概率分布律性质示例问题解答 连续型随机向量的概率密度函数随机向量的分布函数性质连续型随机向量均匀分布 边缘分布边缘概率分布律边缘概率密度函数二维正态分布示例问题解答 边缘分布函数 二维随机向量及其分布 …...

《AI模型格局演变:Claude、Gemini Flash与OpenAI的技术角力》

Anthropic的Claude模型在通用聊天模型中表现优异&#xff0c;但在市场份额上仍落后于OpenAI Anthropic的Claude模型在通用聊天模型中的表现优异&#xff0c;主要体现在以下几个方面&#xff1a; 1. 技术优势 性能表现: Claude模型在处理复杂任务&#xff08;如编程、自然语言…...

[0405].第05节:搭建Redis主从架构

Redis学习大纲 一、3主3从的集群配置&#xff1a; 1.1.集群规划 1.分片集群需要的节点数量较多&#xff0c;这里我们搭建一个最小的分片集群&#xff0c;包含3个master节点&#xff0c;每个master包含一个slave节点&#xff0c;结构如下&#xff1a; 2.每组是一主一从&#x…...

01、kafka知识点综合

kafka是一个优秀大吞吐消息队列&#xff0c;下面我就从实用的角度来讲讲kafka中&#xff0c;“kafka为何有大吞吐的机制”&#xff0c;“数据不丢失问题”&#xff0c;“精准一次消费问题” 01、kafka的架构组织和运行原理 kafka集群各个节点的名称叫broker&#xff0c;因为kaf…...

K8S集群常用命令

1&#xff0c;查看pod kubectl get pods -A 查看所有的pod kubectl get pods 这个只查看namespace为default下的pod&#xff0c;也就是只查看默认命名空间下的pod kubectl get pod -A -o wide 查看所有的pod&#xff0c;并且放出的信息更全&#xff08;包含了pod的ip&#xff0…...

数据集-目标检测系列- 石榴 检测数据集 pomegranate >> DataBall

数据集-目标检测系列- 石榴 检测数据集 pomegranate >> DataBall DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;会员享有 百种数据集&#xff0c;持续增加中。 需要更多数据资源和技术解决方案&#xff0c;知识星球&#xff1a; “DataBall - X 数据球(fre…...

【ubuntu24.04】配置ssh以root登录

ubuntu默认不能以root登录 SSH 访问 Ubuntu 时,root 用户登录提示 Access denied 的问题,通常是因为 Ubuntu 默认禁用了 root 用户通过 SSH 登录。这是出于安全性考虑的默认配置。原因分析 默认禁用 root 登录: 在 /etc/ssh/sshd_config 配置文件中,PermitRootLogin 默认…...

python学opencv|读取图像(二十九)使用cv2.getRotationMatrix2D()函数旋转缩放图像

【1】引言 前序已经学习了如何平移图像&#xff0c;相关文章链接为&#xff1a; python学opencv|读取图像&#xff08;二十七&#xff09;使用cv2.warpAffine&#xff08;&#xff09;函数平移图像-CSDN博客 在此基础上&#xff0c;我们尝试旋转图像的同时缩放图像。 【2】…...

Python语言的编程范式

Python语言的编程范式探讨 引言 在当今的编程世界中&#xff0c;Python语言以其简洁、易读和强大的功能深受开发者的喜爱。作为一种多范式编程语言&#xff0c;Python支持多种编程风格&#xff0c;包括面向对象编程&#xff08;OOP&#xff09;、函数式编程和命令式编程等。每…...

JAVA多线程学习

文章目录 线程相关概念线程创建继承Thread类Runnable接口多个线程同时操作同一个对象测试&#xff1a;实现callable接口(了解)静态代理lamda表达式 线程状态线程停止线程休眠线程礼让 线程相关概念 线程&#xff1a;是进程的一部分&#xff0c;一个进程之内的线程之间共享进程的…...

【c语言】指针 (完结)

一、sizeof和strlen的对比 1、sizeof 前面我们在学习操作符的时候&#xff0c;我们学习了sizeof&#xff0c;知道其是计算变量所占内存的大小的&#xff0c;单 位是字节&#xff0c;如果操作数是数据类型的话&#xff0c;计算的就是这个类型的变量所占的内存空间的大…...

vue使用自动化导入api插件unplugin-auto-import,避免频繁手动导入

‌unplugin-auto-import‌是一个现代的自动导入插件&#xff0c;旨在简化前端开发中的导入过程&#xff0c;减少手动导入的繁琐工作&#xff0c;提升开发效率。它支持多种构建工具&#xff0c;包括Vite、Webpack、Rollup和esbuild&#xff0c;并且可以与TypeScript配合使用&…...

matlab函数讲解——randsample

在MATLAB中&#xff0c;randsample函数用于从一个给定的集合中随机选择样本。函数的基本用法是从指定范围内随机选择元素&#xff0c;具体用法如下&#xff1a; 用法 y randsample(n, k, true, w)参数说明 n: 整数&#xff0c;表示从1到n的集合中进行抽样。例如&#xff0c;…...

50_Lua垃圾回收

1.Lua垃圾回收机制概述 Lua采用了一种自动内存管理机制,称为垃圾回收(Garbage Collection, GC)。垃圾回收的主要目的是回收程序中不再被使用的内存,从而避免内存泄漏。Lua的垃圾回收器负责回收动态分配的对象,如函数、用户数据、表、字符串、线程、内部结构等。Lua的垃圾…...

【Python】数据容器:列表,元组,字符串,集合字典及通用操作

文章目录 一.序列1.1list列表定义常用操作列表的遍历 1.2tuple元组定义常见操作元组的遍历 1.3str字符串定义常见操作字符串的遍历 1.4序列常用操作——切片 二.set集合定义常见操作集合的遍历 三.dict字典定义常用操作字典的嵌套 *数据容器对比总结四.数据容器的通用操作4.1通…...

Wi-Fi Direct (P2P)原理及功能介绍

目录 Wi-Fi Direct &#xff08;P2P&#xff09;介绍Wi-Fi Direct P2P 概述P2P-GO&#xff08;P2P Group Owner&#xff09;工作流程 wifi-Direct使用windows11 wifi-directOpenwrtwifi的concurrent mode Linux环境下的配置工具必联wifi芯片P2P支持REF Wi-Fi Direct &#xff…...

系统看门狗配置--以ubuntu为例

linux系统配置看门狗 以 ubuntu 系统配置看门狗为例 配置看门狗使用的脚本文件&#xff0c;需要使用管理员权限来执行&#xff1a; 配置是&#xff1a;系统每 30S 喂一次狗&#xff0c;超过 60S 不进行投喂&#xff0c;就会自动重启。 1. 系统脚本内容&#xff1a; #!/bin/b…...

Mysql--基础篇--多表查询(JOIN,笛卡尔积)

在MySQL中&#xff0c;多表查询&#xff08;也称为联表查询或JOIN操作&#xff09;是数据库操作中非常常见的需求。通过多表查询&#xff0c;你可以从多个表中获取相关数据&#xff0c;并根据一定的条件将它们组合在一起。MySQL支持多种类型的JOIN操作&#xff0c;每种JOIN都有…...

44_Lua迭代器

在Lua中,迭代器是一种用于遍历集合元素的重要工具。掌握迭代器的使用方法,对于提高Lua编程的效率和代码的可读性具有重要意义。 1.迭代器概述 1.1 迭代器介绍 迭代器是一种设计模式,它提供了一种访问集合元素的方法,而不需要暴露其底层结构。在Lua中,迭代器通常以一个函…...

网络原理(三)—— 传输层 之 UDP 和 TCP协议

传输层 在传输层两大关键的协议就是UDP和TCP协议了&#xff0c;除此之外&#xff0c;还有别的传输层协议&#xff0c;本文章将介绍UDP和TCP协议&#xff0c;重点介绍TCP协议。 首先回顾TCP和UDP 的特点&#xff1a; UDP&#xff1a;不可靠传输&#xff0c;面向数据包&#xf…...