Unity自动打包——Shell交互

Unity 无论是测试还是上线都需要打包,而每次打包我们还要打各种平台(安卓、Ios、WebGL、Windows…),有可能不同的打包时机还要有不同的配置项,这种工作枯燥、繁琐且易错,为了解决这一困扰就想到了能不能做一个工具来专门负责打包,甚至打完包能够自动的发送给测试人员或直接上传至服务器就更完美了。然后查了一些资料,发现Jenkins就是一种特别完美的工具,能够完美的解决我刚才提出的所有问题,至于Jenkins的搭建本章内容不会关注,这篇文章主要是解决Jenkins流水线配置的最后阶段,通过Jenkins调用Shell进而通知Unity自动打包的逻辑。

先看一下Unity官方文档对命令行参数的描写:命令行参数。
可以看出Unity官方其实是支持开发者们通过自动打包工具来提升效率的,并且也准备了大量的命令行参数来进行支持。
再通过对shell编程的了解,我编写了下面的shell脚本来通知Unity自动打包并设置指定配置,如下:

#!/bin/sh#Unity安装路径
UnityPath=D:\\UnityHub\\Editor\\2022.3.44f1c1\\Editor\\Unity.exe
#工程路径
ProjectPath=E:\\ARWork\\MarsWeb
#UnityLog输出路径
UnityLogPath=C:\\Users\\Administrator\\Desktop\\ShellTest\\unitylog.txt
#需要调用Unity的静态方法
UnityFunc=S.Utility.Editor.ShellHelper.OnReceive#版本号
Version=1.0.0
#目标平台
BuildTarget=WebGL
#输出的app名字或文件夹名字
AppName="TestApp"
#Unity要执行的行为
Do="Build"echo 开始启动Unity工程并执行方法命令$UnityPath -ProjectPath $ProjectPath -batchmode -quit -executeMethod $UnityFunc -logFile $UnityLogPath Version=$Version BuildTarget=$BuildTarget AppName=$AppName Do=$Do# 控制台输出Unity的Log信息
while read line
doecho $line
done < $UnityLogPathecho Unity处理完毕

我们只需要把UnityPath、ProjectPath配置成自己的路径,UnityLogPath可以写成固定的路径也可以接收Jenkins的参数,UnityFunc
是需要调用的Unity中的静态方法,这个在后面会讲到,至于Version、BuildTarget、AppName这几个参数动态可变我们可以通过$n 来接收Jenkins的参数来进行赋值,Do是通知Unity要做的操作,然后Unity会接收到这个操作标记并对应执行相关操作,这个也会在后面讲。
至于下面的while循环,其实就是读取了Unity执行后的log日志文件并逐行在控制台打印出来。

其实通过上面的解释,我们应该能知道,最重要的点就是UnityFunc和Do这两个字段的含义,UnityFunc是指向Unity工程里的一个静态方法,这个方法不可接受参数,这样就达成了Shell与Unity的通讯,虽然这个静态方法不支持接收参数,但是Unity确做了相关的处理能够拿到shell的参数,其实我们的参数就是Version= V e r s i o n B u i l d T a r g e t = Version BuildTarget= VersionBuildTarget=BuildTarget AppName= A p p N a m e D o = AppName Do= AppNameDo=Do 当然,这里不严谨,其实整段语句都是Unity可接受的参数,只不过我们只取我们关注的这几个信息而已。
那下面我们来看看Unity怎么接受这个shell指令。
ShellHelper

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEngine;namespace S.Utility.Editor
{/// <summary>/// Shell传递过来的参数集/// </summary>public interface IShellParams{/// <summary>/// 是否存在参数Key/// </summary>/// <param name="key">参数key</param>/// <returns></returns>bool HasKey(string key);/// <summary>/// 得到参数值/// </summary>/// <param name="key">参数key</param>/// <returns></returns>string GetValue(string key);/// <summary>/// 得到所有的参数key/// </summary>/// <returns></returns>string[] GetKeys();}/// <summary>/// Shell命令执行接口/// </summary>public interface IDo{/// <summary>/// 执行/// </summary>/// <param name="shellParams">Shell参数</param>void Do(IShellParams shellParams);}/// <summary>/// Shell解析辅助器/// </summary>public class ShellHelper{private static Dictionary<string, string> commandLineDict = new Dictionary<string, string>();public static IShellParams shellParams { get; private set; }private static DoConfig config = new DoConfig();/// <summary>/// 去解析doType的行为/// </summary>/// <param name="doType">doType</param>private static void Do(string doType){Debug.Log($"开始处理Do {doType} 行为!");config.GetDo(doType)?.Do(shellParams);}/// <summary>/// 接收到Shell消息/// </summary>public static void OnReceive(){if (shellParams == null) shellParams = new ShellParams();(shellParams as ShellParams).Refresh();string doType = shellParams.GetValue("Do");if (string.IsNullOrEmpty(doType)){Debug.LogError("Unity处理Shell消息失败,未找到Do指令!");}else{Do(doType);}}/// <summary>/// shell参数集的实现/// </summary>private class ShellParams : IShellParams{private Dictionary<string, string> commandLineDict = new Dictionary<string, string>();/// <summary>/// 参数key-value分割符/// </summary>public char splitChar = '=';public void Refresh(){if (commandLineDict == null) commandLineDict = new Dictionary<string, string>();else commandLineDict.Clear();string[] parameters = Environment.GetCommandLineArgs();int paramCount = parameters == null ? 0 : parameters.Length;if (paramCount == 0) return;string pattern = $"^(.*?){splitChar}(.*)";foreach (var p in parameters){Match match = Regex.Match(p, pattern);GroupCollection groups = match.Groups;if (groups == null || groups.Count != 3) commandLineDict[p] = null;else{commandLineDict[groups[1].Value] = groups[2].Value;}}}public bool HasKey(string key){if (commandLineDict == null || commandLineDict.Count == 0) return false;return commandLineDict.ContainsKey(key);}public string GetValue(string key){if (commandLineDict == null || commandLineDict.Count == 0) return null;string value;if (commandLineDict.TryGetValue(key, out value)){return value;}return null;}public string[] GetKeys(){if (commandLineDict == null || commandLineDict.Count == 0) return null;return commandLineDict.Keys.ToArray();}}}
}

DoConfig

namespace S.Utility.Editor
{/// <summary>/// Shell Do配置/// </summary>public class DoConfig{public IDo GetDo(string doType){switch (doType){case "Build":return new DoBuild();default:return null;}}}
}

DoBuild

using System;
using System.IO;
using UnityEditor;
using UnityEngine.SceneManagement;namespace S.Utility.Editor
{/// <summary>/// 打包操作/// </summary>public class DoBuild : IDo{public struct BuildData{public BuildTarget buildTarget;public string version;public string appName;public override string ToString(){return $"BuildTarget:{buildTarget} Version:{version} AppName:{appName}";}}public BuildData buildData { get; private set; }public void Do(IShellParams shellParams){string buildTarget = shellParams.GetValue("BuildTarget");string version = shellParams.GetValue("Version");string appName = shellParams.GetValue("AppName");buildData = new BuildData(){version = string.IsNullOrEmpty(version) ? PlayerSettings.bundleVersion : version,buildTarget = string.IsNullOrEmpty(buildTarget)? EditorUserBuildSettings.activeBuildTarget: Enum.Parse<BuildTarget>(buildTarget),appName = string.IsNullOrEmpty(appName) ? DateTime.Now.ToString() : appName};PlayerSettings.bundleVersion = buildData.version;if (EditorUserBuildSettings.activeBuildTarget != buildData.buildTarget) //当前平台不是目标平台{//切换至目标平台BuildTargetGroup buildTargetGroup = BuildPipeline.GetBuildTargetGroup(buildData.buildTarget);EditorUserBuildSettings.SwitchActiveBuildTarget(buildTargetGroup, buildData.buildTarget);}switch (buildData.buildTarget){case BuildTarget.Android:BuildAndroid();break;case BuildTarget.iOS:BuildIos();break;case BuildTarget.WebGL:BuildWebGL();break;case BuildTarget.StandaloneWindows:case BuildTarget.StandaloneWindows64:BuildWindows();break;}}/// <summary>/// 打安卓包/// </summary>void BuildAndroid(){string outPath = buildData.appName + ".apk";BuildPlayerOptions options = GetBaseBuildOptions();options.locationPathName = outPath;BuildPipeline.BuildPlayer(options);}/// <summary>/// 打Ios包/// </summary>void BuildIos(){string outPath = buildData.appName;if (Directory.Exists(outPath)){Directory.Delete(outPath);}Directory.CreateDirectory(outPath);BuildPlayerOptions options = new BuildPlayerOptions();options.locationPathName = outPath;options.target = buildData.buildTarget;options.targetGroup = BuildPipeline.GetBuildTargetGroup(buildData.buildTarget);BuildPipeline.BuildPlayer(options);}/// <summary>/// 打WebGL包/// </summary>void BuildWebGL(){string outPath = buildData.appName;if (Directory.Exists(outPath)){Directory.Delete(outPath);}Directory.CreateDirectory(outPath);BuildPlayerOptions options = GetBaseBuildOptions();options.locationPathName = outPath;BuildPipeline.BuildPlayer(options);}/// <summary>/// 打Windows包/// </summary>void BuildWindows(){string outPath = buildData.appName;if (Directory.Exists(outPath)){Directory.Delete(outPath);}Directory.CreateDirectory(outPath);BuildPlayerOptions options = GetBaseBuildOptions();options.locationPathName = outPath;BuildPipeline.BuildPlayer(options);}/// <summary>/// 得到基础的打包配置/// </summary>/// <returns></returns>private BuildPlayerOptions GetBaseBuildOptions(){BuildPlayerOptions options = new BuildPlayerOptions();options.target = buildData.buildTarget;options.targetGroup = BuildPipeline.GetBuildTargetGroup(buildData.buildTarget);options.scenes = GetAllScenePath();return options;}/// <summary>/// 获取PlayerSettings中所有场景的路径/// </summary>/// <returns></returns>private string[] GetAllScenePath(){int count = SceneManager.sceneCountInBuildSettings;if (count == 0) return null;string[] sceneArray = new string[count];for (int i = 0; i < count; i++){sceneArray[i] = SceneUtility.GetScenePathByBuildIndex(i);}return sceneArray;}}
}

分析一下C#这部分的代码:
首先最主要的就是ShellHelper对象的OnReceive方法,这个方法就是上面的shell中的UnityFunc字段,它负责响应Shell的指令。
然后我们通过(shellParams as ShellParams).Refresh();刷新了shell传递过来的参数,最后包装成IShellParams接口来传递这些参数。
然后我们在ShellHelper对象的Do(string doType) 方法通过DoConfig配置对象来获取对应的IDo执行对象,在执行对象中去具体实现指令逻辑。
所以后期如果我们想解析其它指令,例如BuildAB(打AB包)我们只需要在DoConfig的GetDo(string doType)方法中指定相应的解析IDo对象就可以完美的实现功能。

OK到这里我们的Unity就可以接收Shell的指令和参数了,后续再和Jenkins联动就可以实现自动打包的功能了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/3576.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

你适合哪种tiktok广告账户类型?

TikTok在广告营销方面的分类体系极为详尽。在开设广告账户时&#xff0c;根据不同的海外市场和商品类型&#xff0c;TikTok会有各自的开户标准。此外&#xff0c;广告主所开设的TikTok广告账户类型会直接影响其可投放的广告类型。在广告出价方面&#xff0c;广告主的营销目标不…

大规模语言模型:从理论到实践(1)

1、绪论 大规模语言模型&#xff08;Large Language Models&#xff0c;LLM&#xff09;是由包含数百亿以上参数的深度神经网络构建的语言模型&#xff0c;采用自监督学习方法通过大量无标注文本进行训练。自2018年以来&#xff0c;多个公司和研究机构相继发布了多种模型&#…

SpringBoot中@Validated或@Valid注解校验的使用

文章目录 SpringBoot中Validated或Valid注解校验的使用1. 添加依赖2. 使用示例准备2-1 测试示例用到的类2-2 实体Dto&#xff0c;加入校验注解2-2 Controller 3. 示例测试4. Valid 和 Validated注解详解4-1 常用规则注解4-2 分组验证4-2-1 示例准备4-2-2 Controller接口4-2-3 P…

HarmonyOS使用arkTS拉起指定第三方应用程序

HarmonyOS使用arkTS拉起指定第三方应用程序 前言代码及说明bundleName获取abilityName获取 前言 本篇只说采用startAbility方式拉起第三方应用&#xff0c;需要用到两个必备的参数bundleName&#xff0c;abilityName&#xff0c;本篇就介绍如何获取参数… 代码及说明 bundle…

04_CC2530+Uart串口通信

04_CC2530UART串口通信 串口通信基本概念 串行通信: 数据字节一位位地依次传送的通信方式, 串行通信的速度慢, 但用的传输线条数少, 成本低&#xff0c;适用于远距离的数据传送并行通信: 数据字节的各位同事传送的通信方式, 优点是数据传送速度快, 缺点是占用的传输线条数多,…

Speaker Recognition说话人识别(声纹识别)

说话人识别&#xff0c;又称声纹识别。从上世纪60年代开始到现在&#xff0c;声纹识别一直是生物识别技术研究的主题。从传统的基于模板匹配的方法&#xff0c;到早期基于统计学方法&#xff0c;直到基于深度学习的声纹识别技术成为主流。本项目给出一个从传统&#xff08;基于…

SpringBoot篇(简化操作的原理)

目录 一、代码位置 二、统一版本管理&#xff08;parent&#xff09; 三、提供 starter简化 Maven 配置 四、自动配置 Spring&#xff08;引导类&#xff09; 五、嵌入式 servlet 容器 一、代码位置 二、统一版本管理&#xff08;parent&#xff09; SpringBoot项目都会继…

华为HarmonyOS借助AR引擎帮助应用实现虚拟与现实交互的能力3-获取设备位姿

设备位姿描述了物体在真实世界中的位置和朝向。AR Engine提供了世界坐标下6自由度&#xff08;6DoF&#xff09;的位姿计算&#xff0c;包括物体的位置&#xff08;沿x、y、z轴方向位移&#xff09;和朝向&#xff08;绕x、y、z轴旋转&#xff09;。通过AR Engine&#xff0c;您…

【Git】Git常用命令

目录 1 前言2 git命令2.1 branch2.2 checkout2.3 pull and push2.4 config2.4.1 Proxy 2.5 tag2.6 rebase2.7 patch2.8 remote2.9 submodule2.10 rm2.10 gitignore2.11 某个commit更改了哪些文件2.12 clean 3 结束语 1 前言 本章记录总结在使用git过程中常用的一些命令&#x…

cgroup2版本下使用cgroups对内存/cpu进行控制

先查看cgroups的版本支持: cat /proc/filesystems | grep cgroup 运行结果: 如上表示支持cgroup2版本 一、对内存进行控制 cgroup版本对于内存控制是单独使用/sys/fs/cgroup/memory路径控制的,而在cgroup2版本中是统一管理,所以没有该路径,所以只需先进入该路径: cd /sys/…

安卓应用跳转回流的统一和复用

本文字数&#xff1a;6799字 预计阅读时间&#xff1a;35分钟 作为一个功能复杂的应用&#xff0c;无法避免地需要支持众多路径的回流&#xff0c;比如从Launcher、从Push通知、从端外H5、从合作第三方App以及从系统资源分享组件等。 我们知道&#xff0c;不同的回流路径会通过…

C3.【C++ Cont】名字空间、注释和变量

目录 1.回顾 2.名字空间(也称命名空间) 介绍 代码示例 3.注释 4.练习 B2003 输出第二个整数 方法1 方法2 1.回顾 在C1.【C Cont】准备中提到了名字空间(namespace)语句 using namespace std; 2.名字空间(也称命名空间) 介绍 1.处在在同一个空间内的,若有重名则会名…

常见自动化测试框架分层架构

作为一名专业的测试人员&#xff0c;搭建一个高级的自动化测试框架需要考虑多个因素。以下是一些步骤和指导&#xff0c;帮助你构建一个强大且灵活的自动化测试框架&#xff1a; 1. 理解框架的概念&#xff1a; - 首先&#xff0c;我们需要明确什么是“框架”。在自动化测试中…

103 - Lecture 2 Table and Data Part 1

SQL - Tables and Data Part 1 Relational Database Management System(RDBMS) 关系型数据库管理系统&#xff08;RDBMS&#xff09;是基于关系模型的数据库系统&#xff0c;它支持多种关系操作。关系模型是一种数据存储和检索的模型&#xff0c;它使用表格来组织数据&#x…

NestJS vs Fastify:Node.js框架的性能对决

在Node.js的世界中&#xff0c;框架的选择对于应用的性能和可维护性有着至关重要的影响。NestJS和Fastify是两个备受瞩目的框架&#xff0c;它们各自以其独特的优势在开发者社区中赢得了声誉。本文将深入探讨这两个框架的性能特点&#xff0c;并分析它们在不同场景下的适用性。…

【NOIP普及组】明明的随机数

【NOIP普及组】明明的随机数 C语言实现C实现Java实现Python实现 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 明明想在学校中请一些同学一起做一项问卷调查&#xff0c;为了实验的客观性&#xff0c;他先用计算机生成了N个1到1000之间的随…

python中t是什么意思

python中t是什么意思&#xff1f; python中t指的是“\r”&#xff1a;回车符&#xff0c;返回到这一行的开头&#xff0c;return的意思。 其他相关&#xff1a; \n&#xff1a;换行符&#xff0c;到下一行的同一位置&#xff0c;纵坐标相同&#xff0c;new line的意思。 \t…

OracleJDK与OpenJDK的区别(附带win11下多版本jdk安装)

OracleJDK与OpenJDK的区别&#xff08;附带win11下多版本jdk安装&#xff09; 在Java开发领域&#xff0c;OracleJDK与OpenJDK是两个常被提及的名词&#xff0c;它们都是Java开发工具包&#xff08;JDK&#xff09;的实现&#xff0c;但各自具有不同的特点和优势。在早期的jav…

代码随想录算法训练营第三十一天 | 56.合并区间 738.单调递增的数字 968.监控二叉树

LeetCode 56.合并区间&#xff1a; 文章链接 题目链接&#xff1a;56.合并区间 思路&#xff1a; ① 合并所有重叠的区间&#xff0c;合并后的区间数组不重叠&#xff0c;因此下面两种多区间重叠&#xff0c;其中的区间都要进行合并 ② 合并区间&#xff1a;因为情况2也算作…

[ComfyUI]FaceAging:太好玩啦!FaceAging终于装好了!负50到正100岁随心调整!超强又难装的节点安装教程来了! Comfyui教程

大家好&#xff01;今天我要向大家介绍一个超级有趣的话题——[ComfyUI]FaceAging&#xff01;这个工具能够让你轻松实现人脸年龄的调整&#xff0c;从负50岁到正100岁&#xff0c;让你的创作更加有趣和独特。 想象一下&#xff0c;你有一个强大的AI助手&#xff0c;它能够根据…