【狂云歌之unity_vr】unity项目持续集成dailybuild以及多平台打包管理

简介: # 【狂云歌之unity_vr】unity项目持续集成dailybuild以及多平台打包管理 ![unityvr](http://upload-images.jianshu.io/upload_images/2990112-ccf04f1f24821870.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ## 前言 &e

【狂云歌之unity_vr】unity项目持续集成dailybuild以及多平台打包管理

unityvr

前言

 持续集成的意义就不多说了。unity通常打包一般就直接build&run,但是在实际项目中,往往直接在服务器build包,所以命令行打包必不可少,这里一方面分享unity打包做持续集成,一方面分享使用unity管理多平台打包,例如一个vrapp需要支持gear版本,支持小米版本,支持cardboard版本等等~懂的人就知道这里具有一定的管理维护成本。

 我们做vr相关的app,需要支持gear、cardboard、小米、vivo、大朋、暴风、Idealens、pico、nibiru、酷开等一大堆平台,曾经还有lg和htcvive、oculus平台,未来还会有更多的平台,所以关于unity项目的多平台管理是很重要的,在这方面我们也在探索,积累了一点经验。这里介绍的主要是基于unity中c#写的打包和多平台管理,如果将其中一部分功能使用python和其他配置文件来实现也是可以的,只是用c#直接做会方便许多。

jenkins

jenkins

https://jenkins.io/index.html

 jenkins不用多说,懂的人都了解是干什么的,来源于hudson,可以比较容易的搭起一个持续集成服务器,支持svn和git等版本管理。支持bash,所以可以用bash、python等大部分脚本来写打包脚本和前后的处理。

unity命令行打包

 unity如何进行命令行打包呢,其实unity是支持以命令行方式启动的,但是需要关闭editor支持执行命令,如下:

${unity可执行文件路径} -projectPath ${项目路径} 
-executeMethod CloudBuild.PerformBuildAndroidCloudAlphaRelease 
-batchmode -quit -logFile ${放log的路径} 
-ForceExitEditor

 整体比较容易理解,其中CloudBuild.PerformBuildAndroidCloudAlphaRelease是一个类的静态方法,然后在这个方法中写打包相关逻辑即可。

EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTarget.Android);
string[] scenes = { "Assets/Scenes/Init.unity", "Assets/Scenes/Main.unity" };
BuildPipeline.BuildPlayer(scenes, buildpath, BuildTarget.Android, BuildOptions.None);

 核心逻辑就这么多,就会开始打一个android包并且生成到buildpath下面

多平台打包管理

准备工作

 对unity插件不熟悉的可以看下 开发unity插件——一次搞定unity编辑器常用功能

 我这里准备了一个全局的配置文件,当然这个可以使用外部配置文件来管理配置,是一个道理的。这里定义了两个平台版本一个是alpha一个是beta,使用宏来区分不同平台的版本号。

using UnityEngine;
using System.Collections;

public class GlobalConfig {
#if CLOUD_ALPHA
    public const int ClientVersionCode = 1;
    public const string ClientVersion = "1.0";
#elif CLOUD_BETA
    public const int ClientVersionCode = 2;
    public const string ClientVersion = "1.1";
#endif
}

 文件目录组织大概如下,其中CloudBuild是编辑器工具,包含菜单项和打包功能,两个平台分别依赖不同的so文件和manifest文件。

dir

 制作了一个menu,主要包含的功能是可以在alpha和beta平台之间切换,可以打alpha平台的apk包,当然想打beta平台的包只需要简单修改。

menu

manifest管理

 写个简单的脚本进行manifest替换就好,对于alpha平台和beta平台各有自己的manifest文件,在切换平台的时候将对应的manifest复制替换。

/// <summary>
///  使用相应的androidmanifest
/// </summary>
static void UseAndroidManifest(string filename)
{
    string src_filename = string.Format("AndroidManifest-{0}.xml", filename);
    string dst_filename = "AndroidManifest.xml";
    string path = Application.dataPath + "/Plugins/Android/";
    File.Copy(path + src_filename, path + dst_filename, true);
    AssetDatabase.Refresh();//因为修改了manifest文件,所以刷新unity的assets
}

依赖包管理

 为什么要做依赖包管理呢?因为在使用不同平台sdk的时候,可能会引入很多sdk,每个sdk里包含自己的so、jar、aar包等,如果什么都不管理,直接打包的话,那么这些依赖的文件都会打进所有的apk包,简单来说就会增加包的体积,更严重的情况下,这些不同平台sdk里的依赖库可能还会有冲突,如果打进同一个apk包,后果不堪设想~

 做依赖包管理主要依赖unity自己的assetimport管理如下图,那么只要在需要的时候勾选不需要的时候取消勾选就好了,我们要做的就是用代码来自动实现这个功能。

assetsimport

 先准备好各个平台的依赖包路径

static string[] Plugins_Alpha = new string[] {
    "Assets/Plugins/Android/libs/armeabi-v7a/alpha.so",
};

static string[] Plugins_Beta = new string[] {
    "Assets/Plugins/Android/libs/armeabi-v7a/beta.so",
};

 然后在切换不同平台的时候对这些依赖包的import做处理,这块Asset属于plugin,所以使用pluginimporter来管理勾选的问题。

static void ChangePluginToAlpha()
{
    SetEnablePluginImport(Plugins_Alpha, true);
    SetEnablePluginImport(Plugins_Beta, false);
}

static void ChangePluginToBeta()
{
    SetEnablePluginImport(Plugins_Alpha, false);
    SetEnablePluginImport(Plugins_Beta, true);
}

static void SetEnablePluginImport(string[] plugins, bool enable = true)
{
    foreach(var path in plugins)
    {
        PluginImporter vrlib = AssetImporter.GetAtPath(path) as PluginImporter;
        vrlib.SetCompatibleWithPlatform(BuildTarget.Android, enable);
    }
}

 非常简单一看就可以懂,然后试一下就明白了。

平台切换

 平台切换功能主要是在editor里调试各个平台功能的时候使用的菜单项,功能也很简单,就做了下面几件事情

  • 切平台和宏定义
  • 切playersetting参数
  • 切buildscene配置
  • 切manifest和依赖包
  • 保存及打开对应平台的场景(如果场景不是复用的)

 代码示例如下,因为我们做vr相关的app,所以在gear平台时vrsupport为true,其他平台时为false,如果不同平台的scene不一样,那么在最后问用户是否保存当前场景,然后打开对应平台的场景。

/// <summary>
/// 切换alpha平台
/// </summary>
[UnityEditor.MenuItem("CloudBuild/SwitchToAlpha", priority = 50)]
static void SwitchToAlpha()
{
    PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, DEFINES_ALPHA);
    PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.iOS, DEFINES_ALPHA);
    PlayerSettings.virtualRealitySupported = true;
    EditorBuildSettings.scenes = new EditorBuildSettingsScene[] {
        new EditorBuildSettingsScene("Assets/Scenes/Init.unity", true),
        new EditorBuildSettingsScene("Assets/Scenes/Main.unity", true)
    };//场景
    UseAndroidManifest("Alpha");
    ChangePluginToAlpha();
    EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo();
    EditorSceneManager.OpenScene("Assets/Scenes/Main.unity");
}

打包

 那么最后打包脚本如下,粗看信息量可能比较大,实际只做了几件事情

  • 准备好要打包的scene
  • 将当前editor的状态保存一下,以便打完包恢复,这是为了开发使用方便而已,否则在开发机上打个包就发现editor的很多属性变了有时很尴尬
  • 处理android的签名问题
  • 打包
  • 最后如果是windows,一般是开发机,直接打开build好的apk所在文件夹,方便使用
/// <summary>
/// </summary>
[UnityEditor.MenuItem("CloudBuild/CloudAlpha-Release")]
static void PerformBuildAndroidCloudAlphaRelease()
{
    EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTarget.Android);
    string[] scenes = { "Assets/Scenes/Init.unity", "Assets/Scenes/Main.unity" };
    string path = GetBuildPathAndroid();
    if (scenes == null || scenes.Length == 0 || path == null)
    {
        Debug.LogError("error scene is null");
        return;
    }
    string tempid = PlayerSettings.bundleIdentifier;
    string name = PlayerSettings.productName;
    bool virtualRealitySupported = PlayerSettings.virtualRealitySupported;
    PlayerSettings.virtualRealitySupported = true;
    PlayerSettings.bundleIdentifier = "net.itsong.vralpha";
    PlayerSettings.productName = PRODUCT_NAME;
    PlayerSettings.Android.keystoreName = "";
    PlayerSettings.Android.keyaliasName = "";
    PlayerSettings.Android.keyaliasPass = "";
    PlayerSettings.Android.keystorePass = "";
    PlayerSettings.Android.bundleVersionCode = GlobalConfig.ClientVersionCode;
    PlayerSettings.bundleVersion = GlobalConfig.ClientVersion;
    string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android);
    UseAndroidManifest("Alpha");
    ChangePluginToAlpha();
    PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, DEFINES_ALPHA + DEFINES_RELEASE);
    string buildpath = path + string.Format("cloudvr_alpha_release_{0}.apk", DateTime.Now.ToString("MMddHHmm", DateTimeFormatInfo.InvariantInfo));
    BuildPipeline.BuildPlayer(scenes, buildpath, BuildTarget.Android, BuildOptions.None);
    PlayerSettings.virtualRealitySupported = virtualRealitySupported;
    PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, defines);
    PlayerSettings.bundleIdentifier = tempid;
    PlayerSettings.productName = name;
    PlayerSettings.Android.keystoreName = "";
    PlayerSettings.Android.keyaliasName = "";
    PlayerSettings.Android.keyaliasPass = "";
    PlayerSettings.Android.keystorePass = "";
    string dir = path.Replace('/', '\\');
#if UNITY_EDITOR_WIN
    System.Diagnostics.Process.Start("explorer.exe", "\"" + dir + "\"");
#endif
}

 完整的CloudBuild文件如下:

using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
using System;
using System.Globalization;
using UnityEditor.SceneManagement;
/// <summary>
/// </summary>
partial class CloudBuild
{
    const string PRODUCT_NAME = "狂云歌VR";
    const string DEFINES_ALPHA  = "CROSS_PLATFORM_INPUT;MOBILE_INPUT;CLOUD_ALPHA;";
    const string DEFINES_BETA = "CROSS_PLATFORM_INPUT;MOBILE_INPUT;CLOUD_BETA;";
    const string DEFINES_RELEASE = "CLOUD_RELEASE";

    static string[] Plugins_Alpha = new string[] {
        "Assets/Plugins/Android/libs/armeabi-v7a/alpha.so",
    };

    static string[] Plugins_Beta = new string[] {
        "Assets/Plugins/Android/libs/armeabi-v7a/beta.so",
    };

    // Build the Android APK and place into main project folder
    static string GetBuildPathAndroid()
    {
        string dirPath = Application.dataPath + "/../build/android/";
        if (!System.IO.Directory.Exists(dirPath))
        {
            System.IO.Directory.CreateDirectory(dirPath);
        }
        return dirPath;
    }

    /// <summary>
    /// </summary>
    [UnityEditor.MenuItem("CloudBuild/CloudAlpha-Release")]
    static void PerformBuildAndroidCloudAlphaRelease()
    {
        EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTarget.Android);
        string[] scenes = { "Assets/Scenes/Init.unity", "Assets/Scenes/Main.unity" };
        string path = GetBuildPathAndroid();
        if (scenes == null || scenes.Length == 0 || path == null)
        {
            Debug.LogError("error scene is null");
            return;
        }
        string tempid = PlayerSettings.bundleIdentifier;
        string name = PlayerSettings.productName;
        bool virtualRealitySupported = PlayerSettings.virtualRealitySupported;
        PlayerSettings.virtualRealitySupported = true;
        PlayerSettings.bundleIdentifier = "net.itsong.vralpha";
        PlayerSettings.productName = PRODUCT_NAME;
        PlayerSettings.Android.keystoreName = "";
        PlayerSettings.Android.keyaliasName = "";
        PlayerSettings.Android.keyaliasPass = "";
        PlayerSettings.Android.keystorePass = "";
        PlayerSettings.Android.bundleVersionCode = GlobalConfig.ClientVersionCode;
        PlayerSettings.bundleVersion = GlobalConfig.ClientVersion;
        string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android);
        UseAndroidManifest("Alpha");
        ChangePluginToAlpha();
        PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, DEFINES_ALPHA + DEFINES_RELEASE);
        string buildpath = path + string.Format("cloudvr_alpha_release_{0}.apk", DateTime.Now.ToString("MMddHHmm", DateTimeFormatInfo.InvariantInfo));
        BuildPipeline.BuildPlayer(scenes, buildpath, BuildTarget.Android, BuildOptions.None);
        PlayerSettings.virtualRealitySupported = virtualRealitySupported;
        PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, defines);
        PlayerSettings.bundleIdentifier = tempid;
        PlayerSettings.productName = name;
        PlayerSettings.Android.keystoreName = "";
        PlayerSettings.Android.keyaliasName = "";
        PlayerSettings.Android.keyaliasPass = "";
        PlayerSettings.Android.keystorePass = "";
        string dir = path.Replace('/', '\\');
#if UNITY_EDITOR_WIN
        System.Diagnostics.Process.Start("explorer.exe", "\"" + dir + "\"");
#endif
    }

    /// <summary>
    /// 切换alpha平台
    /// </summary>
    [UnityEditor.MenuItem("CloudBuild/SwitchToAlpha", priority = 50)]
    static void SwitchToAlpha()
    {
        PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, DEFINES_ALPHA);
        PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.iOS, DEFINES_ALPHA);
        PlayerSettings.virtualRealitySupported = true;
        EditorBuildSettings.scenes = new EditorBuildSettingsScene[] {
            new EditorBuildSettingsScene("Assets/Scenes/Init.unity", true),
            new EditorBuildSettingsScene("Assets/Scenes/Main.unity", true)
        };//场景
        UseAndroidManifest("Alpha");
        ChangePluginToAlpha();
        EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo();
        EditorSceneManager.OpenScene("Assets/Scenes/Main.unity");
    }

    /// <summary>
    /// 切换beta平台
    /// </summary>
    [UnityEditor.MenuItem("CloudBuild/SwitchToBeta", priority = 50)]
    static void SwitchToBeta()
    {
        PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, DEFINES_BETA);
        PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.iOS, DEFINES_BETA);
        PlayerSettings.virtualRealitySupported = true;
        EditorBuildSettings.scenes = new EditorBuildSettingsScene[] {
            new EditorBuildSettingsScene("Assets/Scenes/Init.unity", true),
            new EditorBuildSettingsScene("Assets/Scenes/Main.unity", true)
        };//场景
        UseAndroidManifest("Beta");
        ChangePluginToBeta();
        EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo();
        EditorSceneManager.OpenScene("Assets/Scenes/Main.unity");
    }

    /// <summary>
    ///  使用相应的androidmanifest
    /// </summary>
    static void UseAndroidManifest(string filename)
    {
        string src_filename = string.Format("AndroidManifest-{0}.xml", filename);
        string dst_filename = "AndroidManifest.xml";
        string path = Application.dataPath + "/Plugins/Android/";
        File.Copy(path + src_filename, path + dst_filename, true);

        PlayerSettings.Android.bundleVersionCode = GlobalConfig.ClientVersionCode;
        PlayerSettings.bundleVersion = GlobalConfig.ClientVersion;
        AssetDatabase.Refresh();
    }

    static void ChangePluginToAlpha()
    {
        SetEnablePluginImport(Plugins_Alpha, true);
        SetEnablePluginImport(Plugins_Beta, false);
    }

    static void ChangePluginToBeta()
    {
        SetEnablePluginImport(Plugins_Alpha, false);
        SetEnablePluginImport(Plugins_Beta, true);
    }

    static void SetEnablePluginImport(string[] plugins, bool enable = true)
    {
        foreach(var path in plugins)
        {
            PluginImporter vrlib = AssetImporter.GetAtPath(path) as PluginImporter;
            vrlib.SetCompatibleWithPlatform(BuildTarget.Android, enable);
        }
    }
}

后续

 这里没写build ios ipa包的过程,ios的build过程会稍微长一些,要先build好xcode project然后再通过xcode的命令行去打包,所以前半部分与android是可以复用的,只要稍加修改就可以支持ios的build。另外我们现在做vr相关的app,大部分都是android版本,所以apk的管理比较实用。

目录
相关文章
|
3月前
|
Java Maven
2022最新版超详细的Maven下载配置教程、IDEA中集成maven(包含图解过程)、以及导入项目时jar包下载不成功的问题解决
这篇文章是一份关于Maven的安装和配置指南,包括下载、环境变量设置、配置文件修改、IDEA集成Maven以及解决jar包下载问题的方法。
2022最新版超详细的Maven下载配置教程、IDEA中集成maven(包含图解过程)、以及导入项目时jar包下载不成功的问题解决
|
1月前
|
存储 JavaScript 数据库
ToB项目身份认证AD集成(一):基于目录的用户管理、LDAP和Active Directory简述
本文介绍了基于目录的用户管理及其在企业中的应用,重点解析了LDAP协议和Active Directory服务的概念、关系及差异。通过具体的账号密码认证时序图,展示了利用LDAP协议与AD域进行用户认证的过程。总结了目录服务在现代网络环境中的重要性,并预告了后续的深入文章。
|
1月前
|
人工智能 JavaScript 网络安全
ToB项目身份认证AD集成(三完):利用ldap.js实现与windows AD对接实现用户搜索、认证、密码修改等功能 - 以及针对中文转义问题的补丁方法
本文详细介绍了如何使用 `ldapjs` 库在 Node.js 中实现与 Windows AD 的交互,包括用户搜索、身份验证、密码修改和重置等功能。通过创建 `LdapService` 类,提供了与 AD 服务器通信的完整解决方案,同时解决了中文字段在 LDAP 操作中被转义的问题。
|
1月前
|
jenkins Shell 持续交付
Jenkins持续集成GitLab项目 GitLab提交分支后触发Jenkis任务 持续集成 CI/CD 超级详细 超多图(二)
Jenkins持续集成GitLab项目 GitLab提交分支后触发Jenkis任务 持续集成 CI/CD 超级详细 超多图(二)
69 0
|
1月前
|
安全 Java 测试技术
ToB项目身份认证AD集成(二):快速搞定window server 2003部署AD域服务并支持ssl
本文详细介绍了如何搭建本地AD域控测试环境,包括安装AD域服务、测试LDAP接口及配置LDAPS的过程。通过运行自签名证书生成脚本和手动部署证书,实现安全的SSL连接,适用于ToB项目的身份认证集成。文中还提供了相关系列文章链接,便于读者深入了解AD和LDAP的基础知识。
|
3月前
|
编译器 vr&ar 图形学
从零开始的unity3d入门教程(五)---- 基于Vuforia的AR项目
这是一篇Unity3D结合Vuforia实现增强现实(AR)项目的入门教程,涵盖了环境配置、Vuforia账户注册、Target数据集创建、Unity项目设置、AR程序配置、Android环境配置以及最终在手机上测试运行的全过程。
从零开始的unity3d入门教程(五)---- 基于Vuforia的AR项目
|
1月前
|
Java Shell 开发工具
git集成IDEA,托管项目实现版本管理
git集成IDEA,托管项目实现版本管理
33 0
|
1月前
|
jenkins Shell 持续交付
Jenkins持续集成GitLab项目 GitLab提交分支后触发Jenkis任务 持续集成 CI/CD 超级详细 超多图(一)
Jenkins持续集成GitLab项目 GitLab提交分支后触发Jenkis任务 持续集成 CI/CD 超级详细 超多图(一)
149 0
|
2月前
|
存储 NoSQL 数据处理
组合和继承怎么集成一个性能较好的项目
组合与继承是面向对象编程的核心概念,前者通过对象间关联实现高效解耦,后者则重用代码以节省空间和内存。组合常用于现代项目,利用代理与依赖注入简化代码管理;而继承简化了子模块对父模块资源的应用,但修改会影响整体。随着分层解耦及微服务架构如SpringCloud的出现,这些技术进一步优化了数据处理效率和服务响应性能,尤其在分布式存储与高并发场景下。同步异步调用、Redis分布式应用等也广泛运用组合与继承,实现代码和内存空间的有效复用。
|
3月前
|
jenkins 测试技术 持续交付
解锁.NET项目高效秘籍:从理论迷雾到实践巅峰,持续集成与自动化测试如何悄然改变游戏规则?
【8月更文挑战第28天】在软件开发领域,持续集成(CI)与自动化测试已成为提升效率和质量的关键工具。尤其在.NET项目中,二者的结合能显著提高开发速度并保证软件稳定性。本文将从理论到实践,详细介绍CI与自动化测试的重要性,并以ASP.NET Core Web API项目为例,演示如何使用Jenkins和NUnit实现自动化构建与测试。每次代码提交后,Jenkins自动触发构建流程,通过编译和运行NUnit测试确保代码质量。这种方式不仅节省了时间,还能快速发现并解决问题,推动.NET项目开发迈向更高水平。
51 8
下一篇
无影云桌面