iOS-Unity2018.4.24f1使用Jenkins自动化构建
Unity2019开始逐步将作为iOS framework形式接入工程,相比之前版本极大的方便了工程的维护。
本文脚本是从Unity生成xcode工程之后开始执行,不一定完全适用,请按照自己需求进行修改。
注意修改脚本中“xxxxxx”的地方。
一.安装xcodeproj
#安装ruby xcodeproj库
$gem install xcodeproj
#如果提示没有权限运行下面这条命令:
$sudo gem install xcodeproj
二、配置xcodeproj工程
1、Unity调用native的协议类
NativeCallProxy.h
// [!] important set UnityFramework in Target Membership for this file
// [!] and set Public header visibility
#import
// 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)aApi;
// 原生发送消息给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 api = NULL;
+ (void)load {
extern const char* AppControllerClassName;
AppControllerClassName = "AppDelegate";
}
+ (void)RegisterAPIforNativeCalls:(id)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工程脚本
#执行脚本示例:
$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设置为'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 设置完成\n\033[0m\n"
end
end
#***************************************** 执行配置 *****************************************#
#给UnityFramework.h添加“#import "NativeCallProxy.h"”
if add_import_nativeCallProxy_h_to_unityFramework_h == false
puts "\nxxxxxx.rb非首次执行 \n\n"
puts "\033[1m结束配置#{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结束配置#{unity_project_name}.xcodeproj\033[0m\n "
end
exit 0
三、编译Xcode工程、导出ipa、发布ipa包到fir和AppStore
参考上一篇:iOS-Unity2018.4.24f1使用Jenkins自动化构建
四、工程目录示例图
五、配置工程中出现的一些错误
[libil2cpp] ERROR: Could not open /var/containers/Bundle/Application/xxxxxx/xxxxxx.app/Frameworks/UnityFramework.framework/Data/Managed/Metadata/global-metadata.dat IL2CPP initialization failed
网上有其他解决方法,我集成时出现此错误是因为没有给主工程main.mm
添加[ufw setDataBundleId: "com.unity3d.framework"];