Unity + Jenkins自动打包 (二)构建Jenkins项目以及编写Python、Unity脚本

article/2025/10/22 20:36:50

1、新建Jenkens项目

在上一篇中,完成了Jenkins的安装和初始化,以及权限设置。
查看上一篇:Jenkins安装 点此
现在打开浏览器,输入http://localhost:8081,当然,需要改成你自己设置的Jenkins端口号,然后就会出现一下界面
点击“新建Item”
在这里插入图片描述

输入一个名称,比如现在要构建一个自动打包项目,就叫Build_Apk,然后点击“Freestyle project”
构建一个自由风格的项目。在这里插入图片描述

点击确定,就会生成一个空项目。
在这里插入图片描述

2、添加构建参数

在新建的项目界面中
在这里插入图片描述
勾选第一个“Disard old builds”,它会帮你删除旧的构建记录。
而第二个“This project is parameterized”,意思是这是一个参数化构建的项目,勾选后就可以添加各种参数,如下图:
在这里插入图片描述
比如,我们在Unity打包时,项目上线时会构建一个整包Apk,在上线后,会经常构建热更包,那么就需要添加一个选择参数来区分,如下图:
在这里插入图片描述
然后点击“保存”
在这里插入图片描述
再点击"Build with Parameters",就可以看到刚才添加的参数
在这里插入图片描述
在这里插入图片描述

再点击“配置”,来添加一个Bool 型参数
在这里插入图片描述
在这里插入图片描述
其中“Set by Default”,勾选后这个参数会默认勾选上。
那么这个参数就可以用来选择是否构建AssetBundle,比如第一次出包的时候,肯定就要勾选上,而当我们出包后,测试发现一些配置上的Bug,那么这个时候只需要修改配置表,重新构建Apk即可,无需重新构建AssetBundle,那这时候就不用勾选这个参数。

当然,我们打包时一般来说都会有很多步骤,一般都是一些老前辈写好的静态工具方法,打包的同志按照步骤挨个点一遍,那当然也会出现像上面说到的情况,有时候只是改动一小部分东西,不用全部执行,那就可以添加各种参数来跳过不必要的操作。

3、添加命令、脚本

一般来说,用于打包的Unity工程都在一个专用来打包的主机上,而且一般有两个工程,在其中一个工程里构建AB,再拷贝到另一个不包含各种资源的工程里,然后构建Apk。

那么首先便要将用于构建AB的工程更新,找到“构建”,增加构建步骤,选择“Execute windows batch command”,增加命令行命令。
在这里插入图片描述
以SVN为例,如果是Git,查一下命令就行了
首先找到需要更新的项目目录:
在这里插入图片描述
然后添加命令:
在这里插入图片描述
后面的参数 --accept theirs-conflict,这是用来解决冲突的,以服务器的版本为准。

更新完成后就可以启动Unity工程,等待Unity编译完成后,执行打包的各个步骤。
那么接下来就要编写Unity的静态方法供外部调用:

public class GameBuild {public static void StartBuild() {// 开始执行各个打包步骤// 。。。。。。。。。。。。// 。。。。。。。。。。。。}
}

那怎么判断Unity已经编译完成了,之后再调用我们写的打包方法呢?

public class GameBuild {public static void WaitCompilingToBuild() {EditorApplication.update += CanStartBuild;}static void CanStartBuild() {if ( !EditorApplication.isCompiling ) {EditorApplication.update -= CanStartBuild;Debug.Log( "=====编译完成=====" );EditorApplication.delayCall += StartBuild;}}static void StartBuild() {// 开始执行各个打包步骤// 。。。。。。。。。。。。// 。。。。。。。。。。。。}
}

现在我们只需要调用 GameBuild.WaitCompilingToBuild,就可以实现在Unity编译完成后调用我们的打包方法。
EditorApplication.delayCall += StartBuild
这一句的意思是:将其执行延迟到检视面板更新完成之后。每个函数在添加后仅执行一次。

现在,在Jenkins里面添加命令
start C:\Progra~1\Unity\Editor\Unity.exe -disable-assembly-updater -projectPath D:\Test_Project -executeMethod GameBuild.WaitCompilingToBuild --Build_AB:%Build_AB%
最后面的参数 --Build_AB:%Build_AB% ,就是用来将我们上面添加的Bool型参数传入Unity,那么Unity如何接收,请往下看
在这里插入图片描述

其中 taskkill /F /IM Unity.exe 是用来关闭Unity进程,因为Unity只能同时存在一个实例

其中start 后面跟Unity.exe的路径;
需要注意 :命令行在执行非CD命令时,无法识别空格,
而Unity一般安装在C:\Program Files下,需要将路径写为:C:\Progra~1

或者先执行 cd 命令进入Unity安装目录,再调用,如图:
在这里插入图片描述

假设打包步骤如我上面所说,在构建AB的工程执行完成后,再将各种资源都拷到构建Apk的工程,那么怎么判断调用的静态方法是否完成。因为调用Unity的方法,命令行不会一直持有Unity,在调用Unity方法后,不会等Unity的方法执行完后再执行下一句命令,所以需要用另外的脚本来判断。

我一般喜欢用Python脚本来判断,关于Python的安装请自行百度。
首先要在打包方法里面增加日志打印,完整代码为:

using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;public class GameBuild {private static string logFilePath = "E:\\Log_Build.txt";private static bool Build_AB;public static void WaitCompilingToBuild() {// 接收外部传入的参数string[] args = System.Environment.GetCommandLineArgs();foreach ( var s in args ) {if ( s.Contains( "--Build_AB:" ) ) {Build_AB = s.Split( ':' )[ 1 ] == "true";}}EditorApplication.update += CanStartBuild;}static void CanStartBuild() {if ( !EditorApplication.isCompiling ) {EditorApplication.update -= CanStartBuild;Debug.Log( "=====编译完成=====" );EditorApplication.delayCall += StartBuild;}}static void StartBuild() {// 开始执行各个打包步骤if ( Build_AB ) {// 开始构建AssetBundle}else {// 跳过构建AssetBundle}WriteLog("Build AB finished");}public static void WriteLog( string Logstring ) {if ( !File.Exists( logFilePath ) ) {FileStream stream = File.Create( logFilePath );stream.Close();stream.Dispose();}using ( StreamWriter writer = new StreamWriter( logFilePath, true ) ) {writer.WriteLine( Logstring );writer.Close();}}
}

那么现在,就只需要监听 Log_Build.txt,这个文件里面是否有日志“Build AB finished”,如果检测到,那么就跳出循环,命令行就可以执行下一步命令。
你可能会问,我为什么不直接使用unity的Debug.log,因为在打包的时候会输出大量的日志,直接检测Unity的日志会消耗比较大。
接下加上Python脚本:

# -*- coding:gb18030 -*-
import os
import time
import sys# 实时监测unity的log, 参数target_log是我们要监测的目标log, 如果检测到了, 则跳出while循环
def Listen_Log(target_log):pos = 0while True:if os.path.exists(log_file):print("监测到日志文件,开始监测打包步骤")breakelse:print("未监测到日志文件,等待")time.sleep(20)while True:fd = open(log_file, 'r')if pos != 0:fd.seek(pos, 0)while True:line = fd.readline()pos = pos + len(line)if target_log in line:print('监测到unity输出了目标log: ' + target_log + '  继续执行下一步==========')fd.close()returnif line.strip():print(line)else:breakfd.close()if __name__ == '__main__':log_file = 'E:/Log_Build.txt'print("开始监测日志")Listen_Log("Build AB finished")

将脚本命名为Listen_Log.py,然后在Jenkins里面添加命令
在这里插入图片描述

加下来将资源拷到构建Apk的工程里面,直接使用cmd命令:copy pathA pathB
将A文件,拷到B目录下

在这里插入图片描述

接下来在构建Apk的工程里面添加静态方法,同样也需要在Unity编译完成后开始构建Apk
那么,构建Apk就不必使用日志检测的方式判断是否完成,只需要检测目标Apk是否存在即可
那么就需要一个确定的Apk名字,而且外部需要能拿到,那么接下来就用额外参数的形式来调用unity的静态方法
也就是说,Apk的名字由Python脚本确定,然后传给Unity脚本
然后在Apk构建完成后,将Apk复制到大家都能访问到的共享文件夹里面,然后在QQ或者钉钉群里面通知所有人
如果使用QQ的话,打包的主机上需要登录QQ,并且将需要发送消息的QQ群窗口打开
Python脚本,Buid_Apk.py如下

# -*- coding:gb18030 -*-
import os
import time
import win32gui
import win32con
import win32clipboard as wdef call_unity_static_func(func):cmd = 'start %s -disable-assembly-updater -projectPath %s -executeMethod %s --pathName:%s'% (unity_exe, project_path, func, timeStr)print('run cmd:  ' + cmd)os.system(cmd)print("开始调用打包方法,构建Apk")# 实时监测Apk是否存在
def Check_Unity_Apk():while True:if os.path.exists(Apk_file):print("监测到Apk,构建Apk成功,开始复制到共享文件夹")breakelse:print("未监测到Apk,等待一段时间")time.sleep(20)def CopyFile():if os.path.exists(targetPath):print(" ")else:print("路径不存在,创建路径: " + targetPath)os.mkdir(targetPath)while True:if os.path.exists(targetPath):print("路径存在,开始复制")breakelse:time.sleep(2)cmd = "copy {0} {1}".format(Apk_file, targetPath)os.system(cmd)time.sleep(5)while True:if os.path.exists(targetApkPath):print("复制到共享文件夹完成,开始通知QQ群")breakelse:time.sleep(5)def SendQQ():theTime = time.strftime('%Y-%m-%d %H:%M', time.localtime())# 发送的消息msg = theTime + "\n打包机器人: 构建Apk完成\n最新包位置:" + showApkPath# 窗口名字name = "XXX项目组"# 将测试消息复制到剪切板中w.OpenClipboard()w.EmptyClipboard()w.SetClipboardData(win32con.CF_UNICODETEXT, msg)w.CloseClipboard()# 获取窗口句柄handle = win32gui.FindWindow(None, name)# 填充消息win32gui.SendMessage(handle, 770, 0, 0)# 回车发送消息win32gui.SendMessage(handle, win32con.WM_KEYDOWN, win32con.VK_RETURN, 0)if __name__ == '__main__':unity_exe = r'C:\Progra~1\Unity\Editor\Unity.exe'project_path = 'D:\Build_Apk_Project'# 执行unity静态方法timeStr = time.strftime('%Y_%m_%d_%H_%M', time.localtime())Apk_file = "E:\\Apk\\" + timeStr + ".apk"targetTimeStr = time.strftime('%Y_%m_%d', time.localtime())targetPath = "共享文件:\\APK\\" + targetTimeStrtargetApkPath = targetPath + "\\" + timeStr + ".apk"showApkPath = "共享文件/APK/" + targetTimeStr + "/" + timeStr + ".apk"static_func = 'GameBuild.WaitCompilingToStartBuildApk'call_unity_static_func(static_func)print("开始监测日志")Check_Unity_Apk()time.sleep(2)CopyFile()time.sleep(2)SendQQ()time.sleep(2)print("构建 Apk 结束")

Unity脚本如下

using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;public class BuildApk
{private static string PathName = "";public static void WaitCompilingToStartBuildApk() {// 接收外部传入的参数string[] args = System.Environment.GetCommandLineArgs();foreach ( var s in args ) {if ( s.Contains( "--pathName:" ) ) {PathName = s.Split( ':' )[ 1 ];Debug.LogError( "收到传进来的Apk名: " + PathName );}}EditorApplication.update += CanBuildApk;}public static void CanBuildApk() {if ( !EditorApplication.isCompiling ) {EditorApplication.update -= CanBuildApk;EditorApplication.delayCall += StartBuildApk;}}public static void StartBuildApk() {//版本号PlayerSettings.bundleVersion = "1.0.0";//API 兼容性等级PlayerSettings.SetApiCompatibilityLevel( BuildTargetGroup.Android, ApiCompatibilityLevel.NET_2_0_Subset );//最低版本PlayerSettings.Android.minSdkVersion = AndroidSdkVersions.AndroidApiLevel21;//目标版本PlayerSettings.Android.targetSdkVersion = AndroidSdkVersions.AndroidApiLevel26;//安装位置 自动 autoPlayerSettings.Android.preferredInstallLocation = AndroidPreferredInstallLocation.Auto;//使用Gradle进行构建EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Gradle;//设置包名PlayerSettings.applicationIdentifier = "com.公司名.游戏名";//产品名字PlayerSettings.productName = "啦啦啦";PlayerSettings.Android.keystoreName = @"D:\XXXXXXXX\XXX.keystore";PlayerSettings.Android.keystorePass = "xxxxxx";PlayerSettings.Android.keyaliasName = "android.keystore";PlayerSettings.Android.keyaliasPass = "xxxxxx";//定义符,添加宏PlayerSettings.SetScriptingDefineSymbolsForGroup( BuildTargetGroup.Android, "XXXXXXXXXXXXXX" );BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions();buildPlayerOptions.scenes = GetBuildScenes();buildPlayerOptions.locationPathName = "D:/Apk/" + PathName + ".apk";buildPlayerOptions.target = BuildTarget.Android;buildPlayerOptions.options = BuildOptions.None;//执行打包 场景名字,打包路径BuildPipeline.BuildPlayer( buildPlayerOptions );Debug.Log( "构建Apk完成" );}static string[] GetBuildScenes() {List<string> names = new List<string>();foreach ( EditorBuildSettingsScene e in EditorBuildSettings.scenes ) {if ( e == null )continue;if ( e.enabled )names.Add( e.path );}return names.ToArray();}
}

代码逻辑就是:由Python去调用Unity的静态方法,并且在调用时传入一个字符串参数作为Apk的名字,然后在Python脚本里面监测Apk是否存在,然后拷到共享文件夹内,然后通知QQ群。

接下来在Jenkins里面添加命令
在这里插入图片描述

4、完结

那么关于Unity + Jenkins自动打包的东西就讲到这儿,有什么不对的地方欢迎各位大佬在评论区指出!
有什么问题也可以在CSDN上私信问我,博客名:水星程序店!


http://chatgpt.dhexx.cn/article/i1uRiBou.shtml

相关文章

unity python服务器_Unity如何连接服务器: 一个简单的例子

Unity3D本身是用来做客户端的通用游戏引擎, 要建立网络连接的话, 其实需要使用的是C#本身的网络和线程模块, 即System.Net.Sockets & System.Threading. 本文中我做了一个简单的例子, 适合那些需要做Unity客户端连接服务器功能的人入门. 整体项目 客户端: 我做的项目主要是…

unity + python socket通信,自定义数据包

unity和python相互之间通过socket通信来发送自定义数据包是一个利用unity构建场景和通过python来做数据处理的方式&#xff0c;能够有效的利用两种不同语言的优势。 我已经将对应的操作封装为对应的一个模块&#xff0c;SocketTools.cs&#xff0c;我们先来看一下具体的代码用…

假设电话收费标准为: (1)国际长途1.00元/分钟,(2) 国内长途0.60元/分钟,(3)市话前3分钟0.20元,3分钟以后0.10元/分钟。现假设某话单文件中每条话单包含如下信息:通话日期(10

你好&#xff0c;帮我做个C语言题目 你好&#xff0c;我是Assistant。很高兴为您做题。 请提供题目的完整题面&#xff0c;并提供任何相关的输入、输出、约束条件等信息。 如果您有任何其他问题&#xff0c;请随时与我联系。我将竭诚为您服务。 897354667qq.com 假设电话收…

电商用户行为数据分析实战(MySQL +PowerBI)

目录 一、项目概况二、数据源三、数据清洗3.1 选择子集导入&#xff0c;匹配适合的数据类型3.2 列重命名3.3重复值处理3.4 缺失值处理3.5 异常值处理从timestamps字段中提取日期数据列查看日期列数据异常情况 四、数据分析4.1 整体购物情况&#xff0c;基于AARRR模型4.1.1 各字…

【业务数据分析】——十大常用数据分析方法

&#x1f935;‍♂️ 个人主页&#xff1a;Lingxw_w的个人主页 ✍&#x1f3fb;作者简介&#xff1a;计算机科学与技术研究生在读 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4a…

基于订单的数据分析

目录 项目背景 数据理解 指标维度 指标梳理 维度梳理 导入数据 数据预处理 数据格式整理 规范字段名 增加字段 简化地址 缺失值处理 异常值分析​ 重复值处理 数据分析 描述性统计 总体销售情况 周趋势、日趋势分析 产品价格分析 地区分析 转化率分析 总结…

大数据培训 | 电商用户行为分析之订单支付实时监控

在电商网站中&#xff0c;订单的支付作为直接与营销收入挂钩的一环&#xff0c;在业务流程中非常重要。对于订单而言&#xff0c;为了正确控制业务流程&#xff0c;也为了增加用户的支付意愿&#xff0c;网站一般会设置一个支付失效时间&#xff0c;超过一段时间不支付的订单就…

订单数据分析

订单背景 订单&#xff1a;对订单的预测不仅为了企业更好的制定物料采购计划、控制库存、提升生产效率、控制生产进度&#xff0c;还为了帮助企业更好的把控市场潜在需求&#xff0c;分析目前经营状态和未来发展趋势。 宽厚板材市场价格&#xff08;只能查询到近三个月的&…

关于订单功能的处理和分析

这两天看了一下RABC的权限管理处理&#xff0c;梳理了一下订单功能的表创建&#xff0c;界面&#xff0c;功能分析。 目录 RABC RBAC0模型 那么对于RABC模型我们怎么创建数据库表&#xff1f; 订单模块的梳理 RABC RABC说的是在用户和权限之间多一个角色&#xff0c;用户与…

订单数据分析-实战

1. 京东订单数据准备 1.1 京东订单数据介绍 2020年5月25日10%抽样数据大家电-家用电器-冰箱70K 1.2 数据清洗 缺失值处理 用户城市和省份信息有部分缺失&#xff0c;部分订单的订单中支付时间为空值数据逻辑错误格式内容一致性 import pandas as pd import numpy as np im…

话单数据完整流程

原始数据__解析_____>>>解析后的数据___入库____>>>汇总的数据 1.原始数据 上游中兴的原始数据&#xff0c;在远程桌面Winscp软件中查看。丢失了下游也没法补充采集。得等上游补充采集后下游才能解析。当原始数据存在&#xff0c;而话单数据显示红点&…

话单分析账单分析行踪分析三合一数据分析

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

Office Tool Plus(安装visio)

说明&#xff1a;需要提前卸载原先的Office&#xff08;Word、PPT、Excel等&#xff09; 一、Office Tool Plus官网 https://otp.landian.vip/zh-cn/download.html 二、下载Office Tool Plus 百度网盘链接&#xff08;Office Tool Plus安装包&#xff09; 链接&#xff1a;…

FFmpeg音频解码-音频可视化

最近在做一个音频可视化的业务&#xff0c;网上有Java层的实现方法&#xff0c;但是业务需要用C实现&#xff0c;从原理出发其实很简单&#xff0c;先对音频进行解码&#xff0c;再计算分贝。这比把大象放进冰箱还简单。本文从音频可视化的业务为依托&#xff0c;以FFmpeg为基础…

基于FFmpeg的视频播放器之七:音频解码

一.流程 音频解码的流程和视频解码几乎一样,最大的区别是解码后需要进行重采样。因为解码出的AVSampleFormat格式是AV_SAMPLE_FMT_FLTP(float, planar),该格式无法直接使用SDL进行播放,需要转换成SDL支持的AV_SAMPLE_FMT_S16(signed 16 bits)格式。关于重采样,详见下篇…

2020手机音频解码芯片_2020杰理音频芯片全解析,14款音频产品代表作拆解汇总...

珠海市杰理科技股份有限公司,成立于2010年。杰理科技主要从事射频智能终端、多媒体智能终端等系统级芯片(SoC)的研究、开发和销售。 杰理科技的芯片产品主要应用于AI智能音箱、蓝牙音箱、蓝牙耳机、智能语音玩具等物联网智能终端产品,下游应用产品市场十分广泛和巨大。 杰理科…

音频编解码原理

实例说明 音频编解码常用的实现方案有三 种。 第一种就是采用专用的音频芯片对 语音信号进行采集和处理&#xff0c;音频编解码算法集成在硬件内部&#xff0c;如 MP3 编解码芯片、语音合成 分析芯片等。使用这种方案的优点就是处理速度块&#xff0c;设计周期短&#xff1b;缺…

基于STM32音频解码MP3——vs1053

基于正点原子教程 VS1053简介&#xff1a; 1.该模块采用VS1053B 作为主芯片 2.支持&#xff1a;MP3/WMA/OGG/WAV/FLAC/MIDI/AAC 等音频格式的解码 3.支持&#xff1a;OGG/WAV 音频格式的录音&#xff0c;支持高低音调节以及 EarSpeaker 空间效果设置 模块如图所示正点原子 AL…

ijkplayer音频解码播放架构分析

ijkplayer是一款跨平台播放器&#xff0c;支持Android与iOS播放&#xff0c;音频解码默认使用FFmpeg的avcodec软解。Android端播放音频可以用OpenSL ES和AudioTrack&#xff0c;而iOS端播放音频默认使用AudioQueue。 一、iOS音频解码播放 采用pipeline形式创建音频播放组件&a…

HIFI音频解码芯片ES9023

现在的HiFi播放器、解码耳放设备越来越多&#xff0c;推陈出新的速度也越来越快。各家厂商也都对产品进行了卖点细分&#xff0c;把新款旗舰级解码芯片拎出来宣传。美国ESS公司推出的ES9038Pro芯片大家都早已耳熟能详。 美国ESS系列芯片拥有行业高标准的信噪比 DNR&#xff08;…