iOS-Unity2018.4.24f1使用Jenkins自動化構(gòu)建
Unity2019開始逐步將作為iOS framework形式接入工程,相比之前版本極大的方便了工程的維護(hù)。
本文腳本是從Unity生成xcode工程之后開始執(zhí)行,不一定完全適用,請按照自己需求進(jìn)行修改。
注意修改腳本中“xxxxxx”的地方。
一.安裝xcodeproj
#安裝ruby xcodeproj庫
$gem install xcodeproj
#如果提示沒有權(quán)限運行下面這條命令:
$sudo gem install xcodeproj
二、配置xcodeproj工程
1、Unity調(diào)用native的協(xié)議類
NativeCallProxy.h
// [!] important set UnityFramework in Target Membership for this file
// [!] and set Public header visibility
#import <UIKit/UIKit.h>
// NativeCallsProtocol defines protocol with methods you want to be called from managed
@protocol NativeCallsProtocol
@required
- (void)ShowNativeWindow;
- (void)NativeSupportService:(NSString *)json;
@end
__attribute__ ((visibility("default")))
@interface UnityFrameworkLibAPI : NSObject
// call it any time after UnityFrameworkLoad to set object implementing NativeCallsProtocol methods
+ (void)RegisterAPIforNativeCalls:(id<NativeCallsProtocol>)aApi;
// 原生發(fā)送消息給Unity
+ (void)SendMessageToUnity:(NSString *)obj method:(NSString *)method msg:(NSString *)msg;
+ (void)OutOfAshes;
@end
NativeCallProxy.mm
#include "NativeCallProxy.h"
#include "UnityInterface.h"
#include "UnityAppController.h"
#include "UnityAppController+OutOfAshes.h"
@implementation UnityFrameworkLibAPI
id<NativeCallsProtocol> api = NULL;
+ (void)load {
extern const char* AppControllerClassName;
AppControllerClassName = "AppDelegate";
}
+ (void)RegisterAPIforNativeCalls:(id<NativeCallsProtocol>)aApi {
api = aApi;
}
+ (void)SendMessageToUnity:(NSString *)obj method:(NSString *)method msg:(NSString *)msg {
UnitySendMessage([obj UTF8String], [method UTF8String], [msg UTF8String]);
}
+ (void)OutOfAshes {
[GetAppController() outOfAshes];
}
@end
extern "C" void ShowNativeWindow()
{
return [api ShowNativeWindow];
}
/// Core Methods
extern "C" void NativeSupportService(const char *JSON)
{
return [api NativeSupportService:[NSString stringWithUTF8String:JSON]];
}
/// Displays unity views after the startup diagram ends
extern "C" void UnityOutOfAshes()
{
[GetAppController() outOfAshes];
}
2、控制啟動圖消失分類
UnityAppController+OutOfAshes.h
//
// UnityAppController+OutOfAshes.h
// xxxxxx
//
// Created by xxxxxx on 2021/5/12.
//
#include "UnityAppController.h"
NS_ASSUME_NONNULL_BEGIN
@interface UnityAppController (OutOfAshes)
- (void)outOfAshes;
@end
NS_ASSUME_NONNULL_END
UnityAppController+OutOfAshes.mm
//
// UnityAppController+OutOfAshes.m
// xxxxxx
//
// Created by xxxxxx on 2021/5/12.
//
#include "UnityAppController+OutOfAshes.h"
#include "UnityAppController+Rendering.h"
#include "UI/OrientationSupport.h"
#include "UI/UnityView.h"
#include "UI/UnityViewControllerBase.h"
#include "UI/ActivityIndicator.h"
extern bool _skipPresent;
extern bool _unityAppReady;
@interface UnityAppController ()
- (void)transitionToViewController:(UIViewController*)vc;
- (void)updateAppOrientation:(UIInterfaceOrientation)orientation;
- (UIViewController*)createUnityViewControllerDefault;
- (void)orientInterface:(UIInterfaceOrientation)orient;
@end
@implementation UnityAppController (OutOfAshes)
- (void)outOfAshes
{
if (_rootController != _viewControllerForOrientation[0])
[self transitionToViewController: _viewControllerForOrientation[0]];
}
- (void)checkOrientationRequest
{
if (!UnityHasOrientationRequest() && !UnityShouldChangeAllowedOrientations())
return;
// normally we want to call attemptRotationToDeviceOrientation to tell iOS that we changed orientation constraints
// but if the current orientation is disabled we need special processing, as iOS will simply ignore us
// the only good/robust way is to simply recreate "autorotating" view controller and transition to it if needed
// please note that we want to trigger "orientation request" code path if we recreate autorotating view controller
bool changeOrient = UnityHasOrientationRequest();
// first we check if we need to update orientations enabled for autorotation
// this needs to be done *only* if we are to continue autorotating
// otherwise we will transition from this view controller
// and iOS will reread enabled orientations on next ViewController activation
const bool autorot = UnityShouldAutorotate();
if (UnityShouldChangeAllowedOrientations() && autorot)
{
NSUInteger rootOrient = 1 << UIViewControllerInterfaceOrientation(self.rootViewController);
if (_rootController == _viewControllerForOrientation[0] && (rootOrient & EnabledAutorotationInterfaceOrientations()))
{
// if we are currently autorotating AND changed allowed orientations while keeping current interface orientation allowed:
// we can simply trigger attemptRotationToDeviceOrientation and we are done
// please note that this can happen when current *device* orientation is disabled (and we want to enable it)
[UIViewController attemptRotationToDeviceOrientation];
}
else
{
// otherwise we recreate default autorotating view controller
// please note that below we will check if root controller still equals _viewControllerForOrientation[0]
// in that case (we update _viewControllerForOrientation[0]) the check will fail and will trigger transition (as expected)
// you may look at this check as "are we autorotating with same constraints"
_viewControllerForOrientation[0] = [self createUnityViewControllerDefault];
changeOrient = true;
}
}
if (changeOrient)
{
// on some devices like iPhone XS layoutSubview is not called when transitioning from different orientations with the same resolution
// therefore forcing layoutSubview on all orientation changes
[_unityView setNeedsLayout];
if (autorot)
{
if (_viewControllerForOrientation[0] == nil)
_viewControllerForOrientation[0] = [self createUnityViewControllerDefault];
// if (_rootController != _viewControllerForOrientation[0])
// [self transitionToViewController: _viewControllerForOrientation[0]];
[UIViewController attemptRotationToDeviceOrientation];
}
else
{
UIInterfaceOrientation requestedOrient = ConvertToIosScreenOrientation((ScreenOrientation)UnityRequestedScreenOrientation());
// on one hand orientInterface: should be perfectly fine "reorienting" to current orientation
// in reality, ios might be confused by transitionToViewController: shenanigans coupled with "nothing have changed actually"
// as an example: prior to ios12 that might result in status bar going "bad" (becoming transparent)
if (_rootController != _viewControllerForOrientation[requestedOrient])
[self orientInterface: requestedOrient];
}
}
UnityOrientationRequestWasCommitted();
}
- (void)showGameUI
{
HideActivityIndicator();
// HideSplashScreen();
// make sure that we start up with correctly created/inited rendering surface
// NB: recreateRenderingSurface won't go into rendering because _unityAppReady is false
#if UNITY_SUPPORT_ROTATION
[self checkOrientationRequest];
#endif
[_unityView recreateRenderingSurface];
// UI hierarchy
//[_window addSubview: _rootView];
//_window.rootViewController = _rootController;
//[_window bringSubviewToFront: _rootView];
#if UNITY_SUPPORT_ROTATION
// to be able to query orientation from view controller we should actually show it.
// at this point we finally started to show game view controller. Just in case update orientation again
[self updateAppOrientation: ConvertToIosScreenOrientation(UIViewControllerOrientation(_rootController))];
#endif
// why we set level ready only now:
// surface recreate will try to repaint if this var is set (poking unity to do it)
// but this frame now is actually the first one we want to process/draw
// so all the recreateSurface before now (triggered by reorientation) should simply change extents
_unityAppReady = true;
// why we skip present:
// this will be the first frame to draw, so Start methods will be called
// and we want to properly handle resolution request in Start (which might trigger surface recreate)
// NB: we want to draw right after showing window, to avoid black frame creeping in
_skipPresent = true;
if (!UnityIsPaused())
UnityRepaint();
_skipPresent = false;
[self repaint];
[UIView setAnimationsEnabled: YES];
}
@end
NativeCallProxy類主要是提供Unity和native交互功能,UnityAppController+OutOfAshes類控制啟動圖移除,滿足去除app啟動圖到顯示UnityUI中間白屏的需求。
這兩類作為實體文件放入Unity工程的iOS插件使用。
3、配置Xcode工程腳本
#執(zhí)行腳本示例:
$ruby '配置腳本文件路徑' 'Unity生成的xcode工程根目錄'
require "fileutils"
require 'xcodeproj'
unity_project_name="xxxxxx"
unity_iPhone_project_dir=$1
FileUtils.cd(unity_iPhone_project_dir) do
#給UnityFramework.h添加“#import "NativeCallProxy.h"”
def add_import_nativeCallProxy_h_to_unityFramework_h()
unityFramework_header_target_place = "#import \"UnityAppController.h\""
unityFramework_header_target_replace = "#import \"UnityAppController.h\"\n#import \"NativeCallProxy.h\""
File.open("./UnityFramework/UnityFramework.h","r:utf-8") do |lines|
read = lines.read.clone
if read.include?(unityFramework_header_target_replace)
puts "\nUnityFramework.h已包含“#import \"NativeCallProxy.h\"”"
return false
else
buffer = read.gsub(unityFramework_header_target_place,unityFramework_header_target_replace)
File.open("./UnityFramework/UnityFramework.h","w"){|l|
l.write(buffer)
}
puts "\033[32mUnityFramework.h添加“#import \"NativeCallProxy.h\"”\n\033[0m\n"
return true
end
end
end
#修改UNITY_TRAMPOLINE_IN_USE配置
def change_unity_use_remote_notifications_flag()
unity_use_remote_notifications_flag_close = "#define UNITY_USES_REMOTE_NOTIFICATIONS 0"
unity_use_remote_notifications_flag_open = "#define UNITY_USES_REMOTE_NOTIFICATIONS 1"
File.open("./Classes/Preprocessor.h","r:utf-8") do |lines|
read = lines.read.clone
if read.include?(unity_use_remote_notifications_flag_open)
puts "\nPreprocessor.h已修改過UNITY_USES_REMOTE_NOTIFICATIONS為1"
else
buffer = read.gsub(unity_use_remote_notifications_flag_close,unity_use_remote_notifications_flag_open)
File.open("./Classes/Preprocessor.h","w"){|l|
l.write(buffer)
}
end
end
puts "\033[32mPreprocessor.h修改UNITY_USES_REMOTE_NOTIFICATIONS為1\n\033[0m\n"
end
#置灰iPhone X橫條
def hide_iphone_home_indicator()
uirect_edge_old_config = "- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures\n{\n UIRectEdge res = UIRectEdgeNone;\n if (UnityGetDeferSystemGesturesTopEdge())\n res |= UIRectEdgeTop;\n if (UnityGetDeferSystemGesturesBottomEdge())\n res |= UIRectEdgeBottom;\n if (UnityGetDeferSystemGesturesLeftEdge())\n res |= UIRectEdgeLeft;\n if (UnityGetDeferSystemGesturesRightEdge())\n res |= UIRectEdgeRight;\n return res;\n}\n\n- (BOOL)prefersHomeIndicatorAutoHidden\n{\n return UnityGetHideHomeButton();\n}"
uirect_edge_new_config = "- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures\n{\n return UIRectEdgeAll;\n}"
File.open("./Classes/UI/UnityViewControllerBase+iOS.mm","r:utf-8") do |lines|
read = lines.read.clone
if read.include?(uirect_edge_new_config)
puts "已置灰過iPhoneX橫條"
else
buffer = read.gsub(uirect_edge_old_config,uirect_edge_new_config)
File.open("./Classes/UI/UnityViewControllerBase+iOS.mm","w"){|l|
l.write(buffer)
}
end
end
puts "\033[32m置灰iPhone X橫條完成\n\033[0m\n"
end
#***************************************** 配置xcodeproj選項 *****************************************#
#修改”NativeCallProxy.h“屬性為UnityFramework公共頭文件
def add_nativeCallProxy_h_to_unityFramework_public(target)
target.headers_build_phase.files.each do |file|
if file.display_name == 'NativeCallProxy.h'
file.settings = { "ATTRIBUTES" => ["Public"] }
puts "\033[32mNativeCallProxy.h設(shè)置為'Public'\n\033[0m\n"
end
end
end
#”Data“目錄Target Membership增加”UnitFramework“
def add_unityframework_target_membership_for_data_dir(target1, target2)
target1.resources_build_phase.files_references.each do |file_references|
if file_references.display_name == 'Data'
build_phase = target2.resources_build_phase
build_phase.add_file_reference(file_references, true)
puts "\033[32m”Data“目錄Target Membership增加”UnitFramework“\n\033[0m\n"
end
end
end
#配置Build Settings
def add_build_settings(target)
target.build_configurations.each do |config|
# 獲得build settings
build_settings = config.build_settings
build_settings["CURRENT_PROJECT_VERSION"] = 0
puts "CURRENT_PROJECT_VERSION ==> 0"
build_settings["ARCHS"] = "arm64";
puts "Build Settings: ARCHS ==> arm64"
build_settings["CODE_SIGN_STYLE"] = "Automatic"
puts "Build Settings: CODE_SIGN_STYLE ==> Automatic"
build_settings["CODE_SIGN_IDENTITY"] = "Apple Development"
puts "Build Settings: CODE_SIGN_IDENTITY ==> Apple Development"
build_settings["DEVELOPMENT_TEAM"] = "xxxxxx"
puts "Build Settings: DEVELOPMENT_TEAM ==> xxxxxx"
build_settings["PROVISIONING_PROFILE_SPECIFIER"] = ""
puts "Build Settings: PROVISIONING_PROFILE_SPECIFIER ==> "
build_settings["ENABLE_BITCODE"] = "NO";
puts "Build Settings: ENABLE_BITCODE ==> NO"
build_settings["GCC_C_LANGUAGE_STANDARD"] = "gnu99";
puts "Build Settings: GCC_C_LANGUAGE_STANDARD ==> gnu99"
build_settings["GCC_ENABLE_OBJC_EXCEPTIONS"] = "YES";
puts "Build Settings: GCC_ENABLE_OBJC_EXCEPTIONS ==> YES"
puts "\033[32m#{config.name}模式Build Settings 設(shè)置完成\n\033[0m\n"
end
end
#***************************************** 執(zhí)行配置 *****************************************#
#給UnityFramework.h添加“#import "NativeCallProxy.h"”
if add_import_nativeCallProxy_h_to_unityFramework_h == false
puts "\nxxxxxx.rb非首次執(zhí)行 \n\n"
puts "\033[1m結(jié)束配置#{unity_project_name}.xcodeproj\033[0m\n "
exit 0
end
#修改UNITY_TRAMPOLINE_IN_USE配置
change_unity_use_remote_notifications_flag
#置灰iPhone X橫條
hide_iphone_home_indicator
#打開項目工程.xcodeproj
unity_iPhone_project_file_path = "#{unity_project_name}.xcodeproj"
project = Xcodeproj::Project.open(unity_iPhone_project_file_path)
unity_iPhone_target = ''
unityFramework_target = ''
#找到需要操作的target
project.targets.each_with_index do |target,index|
if target.name == unity_project_name
unity_iPhone_target = project.targets[index]
end
if target.name == 'UnityFramework'
unityFramework_target = project.targets[index]
end
end
#修改”NativeCallProxy.h“屬性為UnityFramework公共頭文件
add_nativeCallProxy_h_to_unityFramework_public(unity_iPhone_target)
#”Data“目錄Target Membership增加”UnitFramework“
add_unityframework_target_membership_for_data_dir(unity_iPhone_target, unityFramework_target)
#配置Build Settings
add_build_settings(unity_iPhone_target)
project.save
puts "\033[1m結(jié)束配置#{unity_project_name}.xcodeproj\033[0m\n "
end
exit 0
三、編譯Xcode工程、導(dǎo)出ipa、發(fā)布ipa包到fir和AppStore
參考上一篇:iOS-Unity2018.4.24f1使用Jenkins自動化構(gòu)建
四、工程目錄示例圖

主工程main.mm代碼.png

主工程AppDelegate.h代碼.png
五、配置工程中出現(xiàn)的一些錯誤
[libil2cpp] ERROR: Could not open /var/containers/Bundle/Application/xxxxxx/xxxxxx.app/Frameworks/UnityFramework.framework/Data/Managed/Metadata/global-metadata.dat IL2CPP initialization failed
網(wǎng)上有其他解決方法,我集成時出現(xiàn)此錯誤是因為沒有給主工程main.mm添加[ufw setDataBundleId: "com.unity3d.framework"];