iOS-Unity2019.4.24f1c1使用Jenkins自动化构建

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 <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;

// 原生发送消息给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工程脚本

#执行脚本示例:
$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自动化构建

四、工程目录示例图
主工程main.mm代码.png

主工程AppDelegate.h代码.png
五、配置工程中出现的一些错误

[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"];

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335