前言
Unity发布到iOS平台得到的是一个Xcode工程,这个Xcode工程可以直接编译运行,但是我们现在已经有个iOS项目了,Unity是作为项目的一部分,所以需要在已有的Xcode工程中集成Unity。
环境:Xcode 9 + Unity 2017。
Unity导出时设置图形API不要用Metal,因为我发现按我的集成方法使用Metal会崩,不知道什么原因。

下图为Unity发布得到的Xcode工程,红框中是我们要添加到自己的项目的文件夹。

步骤
1. 将Unity Xcode工程中的Classes和Libraries文件夹添加到工程中,注意勾选Copy items if needed
,并选择Create groups
。
(这里我把Unity相关的文件都放入了Unity逻辑文件夹下,便于管理,话说Xcode 9终于默认创建物理文件夹了,老版本默认创建的是逻辑文件夹,逻辑文件夹只在Xcode中显示,在Finder中是看不到的)

2. 将Unity Xcode工程中的Data文件夹添加到工程中,注意勾选Copy items if needed
,并选择Create folder references
。

3. 删除Libraries下的libil2cpp,注意删除选项选择Remove References
。

4. 删除Classes下的main.mm,直接Move To Trash
,因为我们使用自己的main文件,main.mm中有一些Unity初始化的代码,我将其放入了UnityController.mm中,下文会说到。
5. 接下来修改项目的Build Settings,建议从Unity导出的Xcode工程的Build Settings中复制,不容易出错。
- Other Linker Flags:
$(inherited) -weak_framework CoreMotion -weak-lSystem

- Header Search Paths:
$(inherited) "$(SRCROOT)/Classes" "$(SRCROOT)" $(SRCROOT)/Classes/Native $(SRCROOT)/Libraries/bdwgc/include $(SRCROOT)/Libraries/libil2cpp/include
(如果你在第一步中建立的是物理文件夹,就把物理文件夹路径加进去)

- Other C Flags:
$(inherited) -DINIT_SCRIPTING_BACKEND=1 -fno-strict-overflow -DRUNTIME_IL2CPP=1

- Precompile Prefix Header:YES
Prefix Header:Classes/Prefix.pch

- 在User-Defined添加以下属性,注意版本号换成自己的Unity版本号。
GCC_THUMB_SUPPORT:NO
GCC_USE_INDIRECT_FUNCTION_CALLS:NO
UNITY_RUNTIME_VERSION:2017.1.0p5
UNITY_SCRIPTING_BACKEND:il2cpp


- 添加框架,建议按自己导出的Unity-Xcode工程来,不同人的可能不一样,注意Optional(弱引用)。

6. 创建类UnityController.mm继承自UnityAppController.mm
Classes下的UnityAppController类就是用于启动、显示Unity界面的类,不过它创建的是全屏界面,而我们需要的是让Unity只占整个界面的一部分,所以需要修改;我的方法是自己建一个类继承它,从而自定义自己的方法,代码如下:
//
// UnityController.h
//
#import "UnityAppController.h"
@interface UnityController : UnityAppController
@property (nonatomic, readonly, weak) UIView *playView; /* 展示Unity的view */
+ (instancetype)instance;
- (void)initUnity;
- (void)pauseUnity;
- (void)startUnity;
- (BOOL)isPaused;
@end
//
// UnityController.mm
//
#import "UnityController.h"
#import "UnityAppController.h"
#import "UnityAppController+ViewHandling.h"
#import "UnityAppController+Rendering.h"
#import "DisplayManager.h"
#import "UnityView.h"
#include "RegisterMonoModules.h"
#include "RegisterFeatures.h"
#include <csignal>
@interface UnityController()
@property (nonatomic, assign) BOOL isInitUnity;
@end
@implementation UnityController
+ (instancetype)instance {
return (UnityController *)[[UIApplication sharedApplication] valueForKeyPath:@"delegate.unityController"];
}
- (instancetype)init
{
self = [super init];
if (self) {
self.isInitUnity = NO;
// 注册Unity的事件
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillTerminate:) name:UIApplicationWillTerminateNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
return self;
}
- (UIView *)playView {
return self.unityView;
}
static const int constsection = 0;
void UnityInitTrampoline();
void initMain() {
@autoreleasepool
{
UnityInitStartupTime();
UnityInitTrampoline();
UnityInitRuntime(0, NULL);
RegisterMonoModules();
NSLog(@"-> registered mono modules %p\n", &constsection);
RegisterFeatures();
// iOS terminates open sockets when an application enters background mode.
// The next write to any of such socket causes SIGPIPE signal being raised,
// even if the request has been done from scripting side. This disables the
// signal and allows Mono to throw a proper C# exception.
std::signal(SIGPIPE, SIG_IGN);
}
}
- (void)initUnity {
if (!self.isInitUnity) {
initMain();
if ([UIDevice currentDevice].generatesDeviceOrientationNotifications == NO)
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
UnityInitApplicationNoGraphics([[[NSBundle mainBundle] bundlePath] UTF8String]);
[self selectRenderingAPI];
[UnityRenderingView InitializeForAPI: self.renderingAPI];
_window = nil;
_unityView = [self createUnityView];
[DisplayManager Initialize];
_mainDisplay = [DisplayManager Instance].mainDisplay;
[_mainDisplay createWithWindow: _window andView: _unityView];
[super applicationDidBecomeActive:[UIApplication sharedApplication]];
self.isInitUnity = YES;
}
}
- (void)pauseUnity {
//[self applicationWillResignActive:[UIApplication sharedApplication]];
UnityPause(1);
}
- (void)startUnity {
//[self applicationDidBecomeActive:[UIApplication sharedApplication]];
UnityPause(0);
}
- (BOOL)isPaused {
if (UnityIsPaused() == 1) {
return YES;
}
else {
return NO;
}
}
- (void)appWillEnterForeground:(NSNotification *)notification {
[self applicationWillEnterForeground:[UIApplication sharedApplication]];
}
- (void)appDidBecomeActive:(NSNotification *)notification {
if (nil == self.unityView) {
return;
}
[self applicationDidBecomeActive:[UIApplication sharedApplication]];
}
- (void)appWillResignActive:(NSNotification *)notification {
[self applicationWillResignActive:[UIApplication sharedApplication]];
}
- (void)appWillTerminate:(NSNotification *)notification {
[self applicationWillTerminate:[UIApplication sharedApplication]];
}
- (void)appDidReceiveMemoryWarning:(NSNotification *)notification {
[self applicationDidReceiveMemoryWarning:[UIApplication sharedApplication]];
}
@end
7. 打开AppDelegate.m文件,添加UnityController属性,并在应用程序加载完成后创建对象。

8. 打开Classes下的UnityAppController.h文件,修改GetAppController()函数。
inline UnityAppController* GetAppController()
{
// return (UnityAppController*)[UIApplication sharedApplication].delegate;
return (UnityAppController *)[[UIApplication sharedApplication] valueForKeyPath:@"delegate.unityController"];
}
9. 为ViewController.m添加代码。
//
// ViewController.m
//
#import "ViewController.h"
#import "UnityController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectMake(60, 60, 80, 40);
[button setTitle:@"开启Unity" forState:UIControlStateNormal];
[self.view addSubview:button];
[button addTarget:self action:@selector(clickHandler:) forControlEvents:UIControlEventTouchUpInside];
UIButton *button1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button1.frame = CGRectMake(160, 60, 80, 40);
[button1 setTitle:@"暂停Unity" forState:UIControlStateNormal];
[self.view addSubview:button1];
[button1 addTarget:self action:@selector(clickHandler1:) forControlEvents:UIControlEventTouchUpInside];
// 供Unity显示的View
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(10, 150, 300, 300)];
[view setBackgroundColor:[UIColor grayColor]];
[view setTag:22];
[self.view addSubview:view];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void) clickHandler:(id)sender
{
[[UnityController instance] initUnity];
[UnityController instance].playView.frame = [self.view viewWithTag:22].bounds;
[[self.view viewWithTag:22] addSubview:[UnityController instance].playView];
}
- (void) clickHandler1:(id)sender
{
if ([[UnityController instance] isPaused]) {
[[UnityController instance] startUnity];
}
else {
[[UnityController instance] pauseUnity];
}
}
@end
好了,大功告成!Demo的运行效果如下:

后记
- 网上有些教程说还要添加MapFileParser.sh文件,我看了这个文件是用于Unity托管堆栈跟踪的,暂时用不到,就不添加了。
- 启动Unity后只能暂停/运行Unity,没有找到让Unity完全销毁的方法,这样的话内存占用肯定是不会减少了,CPU在暂停的时候还是占用很小的,如果有人有让Unity完全销毁的办法,欢迎指教。
- Demo工程下载链接:
https://download.csdn.net/download/suwk1009/10351477
网友评论