Unity自动打包——Shell交互

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

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

#!/bin/sh

#Unity安装路径
UnityPath="C:\Program Files\Unity\Hub\Editor\2020.3.22f1c1\Editor\Unity.exe"
#工程路径
ProjectPath="E:\ARWork\AutoBuildDemo"
#UnityLog输出路径
UnityLogPath="C:\Users\Administrator\Desktop\ShellTest\unitylog.txt"
#需要调用Unity的静态方法
UnityFunc=S.Utility.Editor.ShellHelper.OnReceive

#版本号
Version=1.0.0
#目标平台 Android、iOS、WebGL、StandaloneWindows、StandaloneWindows64
BuildTarget=iOS
#获取当前时间
Time=$(date "+%Y-%m-%d_%H-%M-%S")
#输出的app名字或文件夹名字
AppName="OutRoot/$BuildTarget/$Time"
#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
do
    echo $line
done < $UnityLogPath

echo 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=$Version BuildTarget=$BuildTarget AppName=$AppName Do=$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
                    : (BuildTarget)Enum.Parse(typeof(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);
            }
            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);
            }
            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);
            }
            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联动就可以实现自动打包的功能了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

搭建react项目

#pic_center 400x 参考文章&#xff1a; react开发环境搭建 系列文章&#xff1a; 文章目录 create-react-app安装reactnpm版本管理npm 镜像安装 create-react-app安装react 查看是否安装过create-react-app npm list create-react-app -- create-react-app5.0.1 # 输出版本…

Leetcode 两数之和 Ⅱ - 输入有序数组

这段代码实现了在一个非递减排序的数组中找到两个数&#xff0c;使它们的和等于目标值的算法。算法使用了双指针技术&#xff0c;具体思想如下&#xff1a; 算法思想&#xff1a; 初始化指针&#xff1a;定义两个指针 left 和 right&#xff0c;分别指向数组的起始位置和末尾位…

论文略读:GRAG:GraphRetrieval-Augmented Generation

202404 arxiv 1 motivation 在许多应用场景中&#xff0c;如科学文献网络、推荐系统和知识图谱&#xff0c;文档之间存在复杂的关联&#xff0c;这些关联在传统的RAG模型中常常被忽略 例如&#xff0c;在处理科学文献时&#xff0c;RAG仅基于文本相似性的检索方法无法充分利用…

103 - Lecture 1

Introduction to Database 一、Introduction to Database Systems 1. 数据的定义 What is Data? EX: data could be a docx file storing your project status report; data could be a spreadsheet containing information • 数据只有在设计的场景中才有意义。&#xff…

【论文复现】MSA+抑郁症模型总结(二)

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀MSA抑郁症模型 情感分析的应用1. 概述2. 论文地址3. 研究背景4. 主要贡献5. 模型结构和代码6. 数据集介绍7. 性能展示8. 复现过程9. 运行过程…

JavaScript 实现文本转语音功能

全篇大概2000 字&#xff08;含代码&#xff09;&#xff0c;建议阅读时间10分钟。 引言 我将向大家展示如何使用 JavaScript 和 Web Speech API 快速实现一个“文本转语音”的 Web 应用。通过这个教程&#xff0c;你将了解如何让浏览器将输入的文本朗读出来。 预览效果 一、…

GitLab基于Drone搭建持续集成(CI/CD)

本文介绍了如何为 Gitee 安装 Drone 服务器。服务器打包为在 DockerHub 上分发的最小 Docker 映像。 1. 准备工作 创建OAuth应用 创建 GitLab OAuth 应用。Consumer Key 和 Consumer Secret 用于授权访问极狐GitLab 资源。 ps:授权回调 URL 必须与以下格式和路径匹配&…

RHCE 第四次作业

一.搭建dns服务器能够对自定义的正向或者反向域完成数据解析查询。 1.配置环境 [rootlocalhost ~]# yum install bind [rootlocalhost ~]#systemctl stop firewalld [rootlocalhost ~]#setenforce 0 2.配置DNS主服务器 [rootlocalhost ~]# vim /etc/named.conf options { …

【ArcGIS】绘制各省碳排放分布的中国地图

首先&#xff0c;准备好各省、自治区、直辖市及特别行政区&#xff08;包括九段线&#xff09;的shp文件&#xff1a; 通过百度网盘分享的文件&#xff1a;GS&#xff08;2022&#xff09;1873 链接&#xff1a;https://pan.baidu.com/s/1wq8-XM99LXG_P8q-jNgPJA 提取码&#…

关于CountDownLatch失效问题

一、项目背景 这几天要开发一个类似支付宝那种年度账单统计的功能&#xff0c;就是到元旦后支付完会把用户这一年的消费情况从各个维度&#xff08;我们把这一个维度称作一个指标&#xff09;统计分析形成一张报告展示给用户。 这个功能实现用到了CountDownLatch。假如统计分析…

【专题】2024年全球生物医药交易报告汇总PDF洞察(附原数据表)

原文链接&#xff1a;https://tecdat.cn/?p38191 在当今复杂多变的全球经济环境下&#xff0c;医药行业正面临着诸多挑战与机遇。2024 年&#xff0c;医药行业的发展态势备受关注。 一方面&#xff0c;全球生物医药交易活跃&#xff0c;2021 - 2023 年的交易中&#xff0c;已…

鸿蒙5.0时代:原生鸿蒙应用市场引领开发者服务新篇章

前言 10月22日原生鸿蒙之夜发布会宣布HarmonyOS NEXT正式发布&#xff0c;首个版本号&#xff1a;鸿蒙5.0。这次“纯血鸿蒙”脱离了底层安卓架构成为纯国产的独立系统&#xff0c;仅凭这一点就有很多想象空间。 目前鸿蒙生态设备已超10亿&#xff0c;原生鸿蒙操作系统在中国市…

3.PyCharm工具

第三方IDE&#xff0c;集成开发工具&#xff0c;官网下载。 社区版本&#xff0c;免费使用。 创建项目 配置解释器&#xff0c;创建python文件&#xff0c;编写代码&#xff0c;运行&#xff1a;

arkUI:Flex弹性布局的各个属性

arkUI&#xff1a;Flex弹性布局的简单使用 1 主要内容说明2 相关内容2.1 Flex弹性布局的方向2.1.1 源码1的简答说明2.1.2 源码1 &#xff08;Flex弹性布局的方向&#xff09;2.1.3 源码1运行效果2.1.3.1 当direction: FlexDirection.RowReverse2.1.3.2 当direction: FlexDirect…

串口接收,不定长数据接收

###1.CUBE-MX配置串口 2.我采用串口中断接收&#xff0c;打开中断接口 3.时钟同样8倍频&#xff0c;1分频&#xff0c;使用内部时钟 打开串口中断 main() { __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 启用空闲中断__HAL_UART_ENABLE_IT(&huart1, UART_IT_R…

TikTok本土店vs跨境店:解读TikTok小店差异

TikTok小店的两种主要的店铺类型&#xff1a;本土店和跨境店&#xff0c;虽然这两种店铺在功能上有相似之处&#xff0c;但它们在运营模式、市场定位、目标受众和面临的挑战等方面存在显著的区别。 一、定义与基本特征 1. TikTok本土店 本土店指的是在特定国家或地区内经营的…

深度学习——优化算法、激活函数、归一化、正则化

文章目录 &#x1f33a;深度学习面试八股汇总&#x1f33a;优化算法方法梯度下降 (Gradient Descent, GD)动量法 (Momentum)AdaGrad (Adaptive Gradient Algorithm)RMSProp (Root Mean Square Propagation)Adam (Adaptive Moment Estimation)AdamW 优化算法总结 经验和实践建议…

用 Python搭建一个微型的HTTP服务器用于传输 2024/11/9

使用内置的 http.server 模块,来搭建微型服务器。 快速启动服务器http.server --- HTTP 服务器Python 3.13.0 文档 声明:文章代码部分 由 ai 生成 创建一个简单的文件共享服务器 进入 需要共享的目录 再打开cmd 输入以下代码 python -m http.server 8000 打开服务器 设置主…

虚拟机linux7.9下安装mysql

1.MySQL官网下载安装包&#xff1a; MySQL :: Download MySQL Community Server https://cdn.mysql.com/archives/mysql-5.7/mysql-5.7.39-linux-glibc2.12-x86_64.tar.gz 2.解压文件&#xff1a; #tar xvzf mysql-5.7.39-linux-glibc2.12-x86_64.tar.gz 3.移动文件&#…

Turtlebot3 buger 硬件与操作平台详细介绍

引言 TurtleBot3 有三个版本&#xff0c;分别是紧凑型的 Burger、功能更强的 Waffle和性能提升的 Waffle Pi&#xff0c;分别适用于不同的应用需求。使用 Raspberry Pi 作为主控单板计算机&#xff08;SBC&#xff09;&#xff0c;而 Waffle Pi 可以使用更强大的 NVIDIA Jetson…