【狂雲歌之unity_vr】unity項目持續集成cibuild&dailybuild

【狂雲歌之unity_vr】unity項目持續集成dailybuild以及多平臺打包管理

unityvr

前言

 持續集成的意義就很少說了。unity一般打包通常就直接build&run,可是在實際項目中,每每直接在服務器build包,因此命令行打包必不可少,這裏一方面分享unity打包作持續集成,一方面分享使用unity管理多平臺打包,例如一個vrapp須要支持gear版本,支持小米版本,支持cardboard版本等等~懂的人就知道這裏具備必定的管理維護成本。html

 咱們作vr相關的app,須要支持gear、cardboard、小米、vivo、大朋、暴風、Idealens、pico、nibiru、酷開等一大堆平臺,曾經還有lg和htcvive、oculus平臺,將來還會有更多的平臺,因此關於unity項目的多平臺管理是很重要的,在這方面咱們也在探索,積累了一點經驗。這裏介紹的主要是基於unity中c#寫的打包和多平臺管理,若是將其中一部分功能使用python和其餘配置文件來實現也是能夠的,只是用c#直接作會方便許多。python

jenkins

jenkins

https://jenkins.io/index.htmlandroid

 jenkins不用多說,懂的人都瞭解是幹什麼的,來源於hudson,能夠比較容易的搭起一個持續集成服務器,支持svn和git等版本管理。支持bash,因此能夠用bash、python等大部分腳原本寫打包腳本和先後的處理。ios

unity命令行打包

 unity如何進行命令行打包呢,其實unity是支持以命令行方式啓動的,可是須要關閉editor支持執行命令,以下:git

${unity可執行文件路徑} -projectPath ${項目路徑} 
-executeMethod CloudBuild.PerformBuildAndroidCloudAlphaRelease 
-batchmode -quit -logFile ${放log的路徑} 
-ForceExitEditor

 總體比較容易理解,其中CloudBuild.PerformBuildAndroidCloudAlphaRelease是一個類的靜態方法,而後在這個方法中寫打包相關邏輯便可。c#

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

 核心邏輯就這麼多,就會開始打一個android包而且生成到buildpath下面windows

多平臺打包管理

準備工做

 對unity插件不熟悉的能夠看下 開發unity插件——一次搞定unity編輯器經常使用功能xcode

 我這裏準備了一個全局的配置文件,固然這個可使用外部配置文件來管理配置,是一個道理的。這裏定義了兩個平臺版本一個是alpha一個是beta,使用宏來區分不一樣平臺的版本號。bash

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的管理比較實用。

VR開發或者unity相關交流能夠郵件madcloudsong@qq .com
轉載請註明原文連接
http://blog.csdn.net/madcloud...

相關文章
相關標籤/搜索