持续集成的意义就很少说了。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
https://jenkins.io/index.htmlandroid
jenkins不用多说,懂的人都了解是干什么的,来源于hudson,能够比较容易的搭起一个持续集成服务器,支持svn和git等版本管理。支持bash,因此能够用bash、python等大部分脚原本写打包脚本和先后的处理。ios
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文件。服务器
制做了一个menu,主要包含的功能是能够在alpha和beta平台之间切换,能够打alpha平台的apk包,固然想打beta平台的包只须要简单修改。
写个简单的脚本进行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管理以下图,那么只要在须要的时候勾选不须要的时候取消勾选就行了,咱们要作的就是用代码来自动实现这个功能。
先准备好各个平台的依赖包路径
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...