diff --git a/README.md b/README.md
index e2b63c6..507667d 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
[![Watch on GitHub][github-watch-badge]][github-watch]
[![Star on GitHub][github-star-badge]][github-star]
-Flutter unity 3D widget for embedding unity in flutter. Add a Flutter widget to show unity. Works on Android, iOS in works.
+Flutter unity 3D widget for embedding unity in flutter. Add a Flutter widget to show unity. Works on Android and iOS.
## Installation
First depend on the library by adding this to your packages `pubspec.yaml`:
@@ -27,9 +27,10 @@ import 'package:flutter_unity_widget/flutter_unity_widget.dart';
## Preview
-Android (30 fps gif, showcasing communication between Flutter and Unity):
+30 fps gifs, showcasing communication between Flutter and Unity:
![gif](https://github.com/snowballdigital/flutter-unity-view-widget/blob/master/preview_android.gif?raw=true)
+![gif](https://github.com/snowballdigital/flutter-unity-view-widget/blob/master/preview_ios.gif?raw=true)
@@ -60,9 +61,11 @@ Now your project files should look like this.
1. First Open Unity Project.
-2. Click Menu: File => Build Settings => Player Settings
+2. Click Menu: File => Build Settings
-3. Change `Product Name` to Name of the Xcode project, You can find it follow `ios/${XcodeProjectName}.xcodeproj`.
+Be sure you have at least one scene added to your build.
+
+3. => Player Settings
**Android Platform**:
1. Make sure your `Graphics APIs` are set to OpenGLES3 with a fallback to OpenGLES2 (no Vulkan)
@@ -74,20 +77,22 @@ Now your project files should look like this.
- ARM64 ✅
- x86 ✅
+
- **IOS Platform**:
- 1. Other Settings find the Rendering part, uncheck the `Auto Graphics API` and select only `OpenGLES2`.
- 2. Depending on where you want to test or run your app, (simulator or physical device), you should select the appropriate SDK on `Target SDK`.
+ **iOS Platform**:
+ 1. This only works with Unity version >=2019.3 because uses Unity as a library!
+ 2. Other Settings find the Rendering part, uncheck the `Auto Graphics API` and select only `OpenGLES3`.
+ 3. Depending on where you want to test or run your app, (simulator or physical device), you should select the appropriate SDK on `Target SDK`.
-
+
### Add Unity Build Scripts and Export
-Copy [`Build.cs`](https://github.com/snowballdigital/flutter-unity-view-widget/tree/master/scripts/Editor/Build.cs) and [`XCodePostBuild.cs`](https://github.com/f111fei/react-native-unity-demo/blob/master/unity/Cube/Assets/Scripts/Editor/XCodePostBuild.cs) to `unity//Assets/Scripts/Editor/`
+Copy [`Build.cs`](https://github.com/snowballdigital/flutter-unity-view-widget/tree/master/scripts/Editor/Build.cs) and [`XCodePostBuild.cs`](https://github.com/snowballdigital/flutter-unity-view-widget/tree/master/scripts/Editor/XCodePostBuild.cs) to `unity//Assets/Scripts/Editor/`
Open your unity project in Unity Editor. Now you can export the Unity project with `Flutter/Export Android` (for Unity versions up to 2019.2), `Flutter/Export Android (Unity 2019.3.*)` (for Unity versions 2019.3 and up, which uses the new [Unity as a Library](https://blogs.unity3d.com/2019/06/17/add-features-powered-by-unity-to-native-mobile-apps/) export format), or `Flutter/Export IOS` menu.
@@ -123,6 +128,24 @@ IOS will export unity project to `ios/UnityExport`.
}
```
+**iOS Platform Only**
+
+ 1. open your ios/Runner.xcworkspace (workspace!, not the project) in Xcode and add the exported project in the workspace root (with a right click in the Navigator, not on an item -> Add Files to “Runner” -> add the UnityExport/Unity-Iphone.xcodeproj file
+
+ 2. Select the Unity-iPhone/Data folder and change the Target Membership for Data folder to UnityFramework
+
+ 3. Add this to your Runner/Runner/Runner-Bridging-Header.h
+
+```c
+#import "UnityUtils.h"
+```
+ 4. Add to AppDelegate.swift before the GeneratePluginRegistrant call:
+
+```swift
+InitArgs(CommandLine.argc, CommandLine.unsafeArgv)
+```
+ 5. Opt-in to the embedded views preview by adding a boolean property to the app's `Info.plist` file with the key `io.flutter.embedded_views_preview` and the value `YES`.
+
### AR Foundation (ANDROID only at the moment)
@@ -274,7 +297,6 @@ class _UnityDemoScreenState extends State{
- `resume()` (Use this to resume unity player)
## Known issues
- - no iOS support yet
- Android Export requires several manual changes
- Using AR will make the activity run in full screen (hiding status and navigation bar).
diff --git a/change_target_membership_data_folder.png b/change_target_membership_data_folder.png
new file mode 100644
index 0000000..1373a57
Binary files /dev/null and b/change_target_membership_data_folder.png differ
diff --git a/ios/Classes/FlutterUnity.h b/ios/Classes/FlutterUnity.h
deleted file mode 100644
index ca66ad7..0000000
--- a/ios/Classes/FlutterUnity.h
+++ /dev/null
@@ -1,25 +0,0 @@
-//
-// Created by rex on 19/03/2019.
-//
-
-#ifndef FLUTTER_UNITY_WIDGET_FLUTTERUNITY_H
-#define FLUTTER_UNITY_WIDGET_FLUTTERUNITY_H
-
-
-#import
-
-@interface FlutterUnityController : NSObject
-
-- (instancetype)initWithWithFrame:(CGRect)frame
- viewIdentifier:(int64_t)viewId
- arguments:(id _Nullable)args
- binaryMessenger:(NSObject*)messenger;
-
-- (UIView*)view;
-@end
-
-@interface FlutterUnityFactory : NSObject
-- (instancetype)initWithMessenger:(NSObject*)messenger;
-@end
-
-#endif //FLUTTER_UNITY_WIDGET_FLUTTERUNITY_H
diff --git a/ios/Classes/FlutterUnity.m b/ios/Classes/FlutterUnity.m
deleted file mode 100644
index 2eff0b8..0000000
--- a/ios/Classes/FlutterUnity.m
+++ /dev/null
@@ -1,87 +0,0 @@
-//
-// Created by rex on 19/03/2019.
-//
-
-#include "FlutterUnity.h"
-
-@implementation FlutterUnityFactory {
- NSObject* _messenger;
-}
-
-- (instancetype)initWithMessenger:(NSObject*)messenger {
- self = [super init];
- if (self) {
- _messenger = messenger;
- }
- return self;
-}
-
-
-@implementation FlutterUnityController {
- WKWebView* _webView;
- int64_t _viewId;
- FlutterMethodChannel* _channel;
-}
-
-- (instancetype)initWithWithFrame:(CGRect)frame
- viewIdentifier:(int64_t)viewId
- arguments:(id _Nullable)args
- binaryMessenger:(NSObject*)messenger {
- if ([super init]) {
- _viewId = viewId;
- _webView = [[WKWebView alloc] initWithFrame:frame];
- NSString* channelName = [NSString stringWithFormat:@"nativeweb_%lld", viewId];
- _channel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:messenger];
- __weak __typeof__(self) weakSelf = self;
- [_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
- [weakSelf onMethodCall:call result:result];
- }];
-
- }
- return self;
-}
-
-- (UIView*)view {
- return _webView;
-}
-
-- (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
- if ([[call method] isEqualToString:@"postMessage"]) {
- [self postMessage:call result:result];
- } else if ([[call method] isEqualToString:@"isReady"]) {
- [self postMessage:call result:result];
- } else if ([[call method] isEqualToString:@"createUnity"]) {
- [self postMessage:call result:result];
- } else if ([[call method] isEqualToString:@"pause"]) {
- [self postMessage:call result:result];
- } else if ([[call method] isEqualToString:@"resume"]) {
- [self postMessage:call result:result];
- } else {
- result(FlutterMethodNotImplemented);
- }
-}
-
-- (void)postMessage:(FlutterMethodCall*)call result:(FlutterResult)result {
- NSString* url = [call arguments];
- if (![self postMessage:url]) {
- result([FlutterError errorWithCode:@"loadUrl_failed"
- message:@"Failed parsing the URL"
- details:[NSString stringWithFormat:@"URL was: '%@'", url]]);
- } else {
- result(nil);
- }
-}
-
-- (bool)onPostMessage:(NSString*)url {
- NSURL* nsUrl = [NSURL URLWithString:url];
- if (!nsUrl) {
- return false;
- }
- NSURLRequest* req = [NSURLRequest requestWithURL:nsUrl];
- [_webView loadRequest:req];
- return true;
-}
-
-@end
-
-
diff --git a/ios/Classes/FlutterUnityView.h b/ios/Classes/FlutterUnityView.h
new file mode 100644
index 0000000..2b1bad0
--- /dev/null
+++ b/ios/Classes/FlutterUnityView.h
@@ -0,0 +1,18 @@
+//
+// FlutterUnityView.h
+// FlutterUnityView
+//
+// Created by krispypen on 8/1/2019
+//
+
+#import
+
+#import "UnityUtils.h"
+
+@interface FlutterUnityView : UIView
+
+@property (nonatomic, strong) UIView* uView;
+
+- (void)setUnityView:(UIView *)view;
+
+@end
diff --git a/ios/Classes/FlutterUnityView.m b/ios/Classes/FlutterUnityView.m
new file mode 100644
index 0000000..ea5753e
--- /dev/null
+++ b/ios/Classes/FlutterUnityView.m
@@ -0,0 +1,37 @@
+//
+// FlutterUnityView.m
+// FlutterUnityView
+//
+// Created by krispypen on 8/1/2019
+//
+
+#import "FlutterUnityView.h"
+
+@implementation FlutterUnityView
+
+- (id)initWithFrame:(CGRect)frame
+{
+ self = [super initWithFrame:frame];
+ return self;
+}
+
+- (void)dealloc
+{
+}
+
+- (void)setUnityView:(UIView *)view
+{
+ self.uView = view;
+ [self setNeedsLayout];
+}
+
+- (void)layoutSubviews
+{
+ [super layoutSubviews];
+ [(UIView *)self.uView removeFromSuperview];
+ [self insertSubview:(UIView *)self.uView atIndex:0];
+ ((UIView *)self.uView).frame = self.bounds;
+ [(UIView *)self.uView setNeedsLayout];
+}
+
+@end
diff --git a/ios/Classes/FlutterUnityWidgetPlugin.h b/ios/Classes/FlutterUnityWidgetPlugin.h
index 0de4a00..013da00 100644
--- a/ios/Classes/FlutterUnityWidgetPlugin.h
+++ b/ios/Classes/FlutterUnityWidgetPlugin.h
@@ -1,4 +1,25 @@
+//
+// FlutterUnityWidgetPlugin.h
+// FlutterUnityWidgetPlugin
+//
+// Created by Kris Pypen on 8/1/19.
+//
+
#import
@interface FlutterUnityWidgetPlugin : NSObject
@end
+
+@interface FUController : NSObject
+
+- (instancetype)initWithFrame:(CGRect)frame
+ viewIdentifier:(int64_t)viewId
+ arguments:(id _Nullable)args
+ registrar:(NSObject *)registrar;
+
+- (UIView*)view;
+@end
+
+@interface FUViewFactory : NSObject
+- (instancetype)initWithRegistrar:(NSObject *)registrar;
+@end
diff --git a/ios/Classes/FlutterUnityWidgetPlugin.m b/ios/Classes/FlutterUnityWidgetPlugin.m
index 2a96730..4febb23 100644
--- a/ios/Classes/FlutterUnityWidgetPlugin.m
+++ b/ios/Classes/FlutterUnityWidgetPlugin.m
@@ -1,20 +1,106 @@
+//
+// FlutterUnityWidgetPlugin.m
+// FlutterUnityWidgetPlugin
+//
+// Created by Kris Pypen on 8/1/19.
+//
+
#import "FlutterUnityWidgetPlugin.h"
-#import
+#import "UnityUtils.h"
+#import "FlutterUnityView.h"
-+ (void)registerWithRegistrar:(NSObject*)registrar {
- FlutterNativeWebFactory* webviewFactory =
- [[FlutterNativeWebFactory alloc] initWithMessenger:registrar.messenger];
- [registrar registerViewFactory:webviewFactory withId:@"unity_view"];
-}
-
-
-/*
-#import "FlutterUnityWidgetPlugin.h"
-#import
+#include
@implementation FlutterUnityWidgetPlugin
+ (void)registerWithRegistrar:(NSObject*)registrar {
- [SwiftFlutterUnityWidgetPlugin registerWithRegistrar:registrar];
+ FUViewFactory* fuviewFactory = [[FUViewFactory alloc] initWithRegistrar:registrar];
+ [registrar registerViewFactory:fuviewFactory withId:@"unity_view"];
}
@end
-*/
\ No newline at end of file
+
+@implementation FUViewFactory {
+ NSObject* _registrar;
+}
+- (instancetype)initWithRegistrar:(NSObject*)registrar {
+ self = [super init];
+ if (self) {
+ _registrar = registrar;
+ }
+ return self;
+}
+- (NSObject*)createArgsCodec {
+ return [FlutterStandardMessageCodec sharedInstance];
+}
+
+- (NSObject*)createWithFrame:(CGRect)frame
+ viewIdentifier:(int64_t)viewId
+ arguments:(id _Nullable)args {
+ FUController* controller = [[FUController alloc] initWithFrame:frame
+ viewIdentifier:viewId
+ arguments:args
+ registrar:_registrar];
+ return controller;
+}
+
+@end
+
+@implementation FUController {
+ FlutterUnityView* _uView;
+ int64_t _viewId;
+ FlutterMethodChannel* _channel;
+}
+
+- (instancetype)initWithFrame:(CGRect)frame
+ viewIdentifier:(int64_t)viewId
+ arguments:(id _Nullable)args
+ registrar:(NSObject*)registrar {
+ if ([super init]) {
+ _viewId = viewId;
+
+ NSString* channelName = [NSString stringWithFormat:@"unity_view_%lld", viewId];
+ _channel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:registrar.messenger];
+ __weak __typeof__(self) weakSelf = self;
+ [_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
+ [weakSelf onMethodCall:call result:result];
+ }];
+
+ }
+ return self;
+}
+
+- (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
+ if ([[call method] isEqualToString:@"postMessage"]) {
+ [self postMessage:call result:result];
+ } else {
+ result(FlutterMethodNotImplemented);
+ }
+}
+
+- (void)postMessage:(FlutterMethodCall*)call result:(FlutterResult)result {
+ NSString* object = [call arguments][@"gameObject"];
+ NSString* method = [call arguments][@"methodName"];
+ NSString* message = [call arguments][@"message"];
+
+ UnityPostMessage(object, method, message);
+
+ result(nil);
+}
+
+- (UIView*)view {
+ _uView = [[FlutterUnityView alloc] init];
+ if ([UnityUtils isUnityReady]) {
+ [_uView setUnityView: (UIView*)[GetAppController() unityView]];
+ } else {
+ [UnityUtils createPlayer:^{
+ [_uView setUnityView: (UIView*)[GetAppController() unityView]];
+ }];
+ }
+ return _uView;
+}
+
+@end
+
+
+
+
+
diff --git a/ios/Classes/UnityUtils.h b/ios/Classes/UnityUtils.h
index a0513cd..31fdca5 100644
--- a/ios/Classes/UnityUtils.h
+++ b/ios/Classes/UnityUtils.h
@@ -1,27 +1,23 @@
-//
-// Created by rex on 15/03/2019.
-//
-
-#ifndef FLUTTER_UNITY_WIDGET_UNITYUTILS_H
-#define FLUTTER_UNITY_WIDGET_UNITYUTILS_H
-
#import
+#ifndef UnityUtils_h
+#define UnityUtils_h
+
#ifdef __cplusplus
extern "C" {
#endif
-void InitArgs(int argc, char* argv[]);
+ void InitArgs(int argc, char* argv[]);
-bool UnityIsInited(void);
+ bool UnityIsInited(void);
-void InitUnity();
+ void InitUnity();
-void UnityPostMessage(NSString* gameObject, NSString* methodName, NSString* message);
+ void UnityPostMessage(NSString* gameObject, NSString* methodName, NSString* message);
-void UnityPauseCommand();
+ void UnityPauseCommand();
-void UnityResumeCommand();
+ void UnityResumeCommand();
#ifdef __cplusplus
} // extern "C"
@@ -40,5 +36,4 @@ void UnityResumeCommand();
@end
-#endif //FLUTTER_UNITY_WIDGET_UNITYUTILS_H
-
+#endif /* UnityUtils_h */
diff --git a/ios/Classes/UnityUtils.mm b/ios/Classes/UnityUtils.mm
index 8c152ee..7d59064 100644
--- a/ios/Classes/UnityUtils.mm
+++ b/ios/Classes/UnityUtils.mm
@@ -1,10 +1,8 @@
-#include "RegisterMonoModules.h"
-#include "RegisterFeatures.h"
#include
#import
-#import "UnityInterface.h"
#import "UnityUtils.h"
-#import "UnityAppController.h"
+
+#include
// Hack to work around iOS SDK 4.3 linker problem
// we need at least one __TEXT, __const section entry in main application .o files
@@ -18,6 +16,8 @@ char** g_argv;
void UnityInitTrampoline();
+UnityFramework* ufw;
+
extern "C" void InitArgs(int argc, char* argv[])
{
g_argc = argc;
@@ -29,6 +29,19 @@ extern "C" bool UnityIsInited()
return unity_inited;
}
+UnityFramework* UnityFrameworkLoad()
+{
+ NSString* bundlePath = nil;
+ bundlePath = [[NSBundle mainBundle] bundlePath];
+ bundlePath = [bundlePath stringByAppendingString: @"/Frameworks/UnityFramework.framework"];
+
+ NSBundle* bundle = [NSBundle bundleWithPath: bundlePath];
+ if ([bundle isLoaded] == false) [bundle load];
+
+ UnityFramework* ufw = [bundle.principalClass getInstance];
+ return ufw;
+}
+
extern "C" void InitUnity()
{
if (unity_inited) {
@@ -36,41 +49,30 @@ extern "C" void InitUnity()
}
unity_inited = true;
- UnityInitStartupTime();
+ ufw = UnityFrameworkLoad();
- @autoreleasepool
- {
- UnityInitTrampoline();
- UnityInitRuntime(g_argc, g_argv);
-
- 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);
- }
+ [ufw setDataBundleId: "com.unity3d.framework"];
+ [ufw frameworkWarmup: g_argc argv: g_argv];
}
extern "C" void UnityPostMessage(NSString* gameObject, NSString* methodName, NSString* message)
{
- UnitySendMessage([gameObject UTF8String], [methodName UTF8String], [message UTF8String]);
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [ufw sendMessageToGOWithName:[gameObject UTF8String] functionName:[methodName UTF8String] message:[message UTF8String]];
+ });
}
extern "C" void UnityPauseCommand()
{
dispatch_async(dispatch_get_main_queue(), ^{
- UnityPause(1);
+ [ufw pause:true];
});
}
extern "C" void UnityResumeCommand()
{
dispatch_async(dispatch_get_main_queue(), ^{
- UnityPause(0);
+ [ufw pause:false];
});
}
diff --git a/ios/flutter_unity_widget.podspec b/ios/flutter_unity_widget.podspec
index 910e54c..ea1a834 100644
--- a/ios/flutter_unity_widget.podspec
+++ b/ios/flutter_unity_widget.podspec
@@ -15,7 +15,11 @@ Flutter unity 3D widget for embedding unity in flutter
s.source_files = 'Classes/**/*'
s.public_header_files = 'Classes/**/*.h'
s.dependency 'Flutter'
+ s.frameworks = 'UnityFramework'
s.ios.deployment_target = '8.0'
+ s.xcconfig = {
+ 'FRAMEWORK_SEARCH_PATHS' => '"${PODS_ROOT}/../UnityExport" "${PODS_ROOT}/../.symlinks/flutter/ios-release" "${PODS_CONFIGURATION_BUILD_DIR}"',
+ 'OTHER_LDFLAGS' => '$(inherited) -framework UnityFramework ${PODS_LIBRARIES}'
+ }
end
-
diff --git a/preview_ios.gif b/preview_ios.gif
new file mode 100644
index 0000000..e28e08a
Binary files /dev/null and b/preview_ios.gif differ
diff --git a/pubspec.yaml b/pubspec.yaml
index 4c29349..2828df2 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,6 +3,8 @@ description: Flutter unity 3D widget for embedding unity in flutter
version: 0.1.5
authors:
- Rex Raphael
+ - Thomas Stockx
+ - Kris Pypen
homepage: https://github.com/snowballdigital/flutter-unity-view-widget/tree/master
environment:
diff --git a/scripts/Editor/XCodePostBuild.cs b/scripts/Editor/XCodePostBuild.cs
new file mode 100644
index 0000000..9ae503a
--- /dev/null
+++ b/scripts/Editor/XCodePostBuild.cs
@@ -0,0 +1,295 @@
+/*
+MIT License
+Copyright (c) 2017 Jiulong Wang
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#if UNITY_IOS
+
+using System;
+
+using System.Collections.Generic;
+using System.IO;
+
+using UnityEditor;
+using UnityEditor.Callbacks;
+using UnityEditor.iOS.Xcode;
+
+///
+/// Adding this post build script to Unity project enables the flutter-unity-widget to access it
+///
+public static class XcodePostBuild
+{
+
+ ///
+ /// The identifier added to touched file to avoid double edits when building to existing directory without
+ /// replace existing content.
+ ///
+ private const string TouchedMarker = "https://github.com/snowballdigital/flutter-unity-view-widget";
+
+ [PostProcessBuild]
+ public static void OnPostBuild(BuildTarget target, string pathToBuiltProject)
+ {
+ if (target != BuildTarget.iOS)
+ {
+ return;
+ }
+
+ PatchUnityNativeCode(pathToBuiltProject);
+
+ UpdateUnityProjectFiles(pathToBuiltProject);
+ }
+
+ ///
+ /// We need to add the Data folder to the UnityFramework framework
+ ///
+ private static void UpdateUnityProjectFiles(string pathToBuiltProject)
+ {
+ var pbx = new PBXProject();
+ var pbxPath = Path.Combine(pathToBuiltProject, "Unity-iPhone.xcodeproj/project.pbxproj");
+ pbx.ReadFromFile(pbxPath);
+
+ // Add UnityExport/Data
+ var targetGuid = pbx.TargetGuidByName("UnityFramework");
+ var fileGuid = pbx.AddFolderReference(Path.Combine(pathToBuiltProject, "Data"), "Data");
+ pbx.AddFileToBuild(targetGuid, fileGuid);
+
+ pbx.WriteToFile(pbxPath);
+ }
+
+ ///
+ /// Make necessary changes to Unity build output that enables it to be embedded into existing Xcode project.
+ ///
+ private static void PatchUnityNativeCode(string pathToBuiltProject)
+ {
+ EditUnityFrameworkH(Path.Combine(pathToBuiltProject, "UnityFramework/UnityFramework.h"));
+ EditUnityAppControllerH(Path.Combine(pathToBuiltProject, "Classes/UnityAppController.h"));
+ EditUnityAppControllerMM(Path.Combine(pathToBuiltProject, "Classes/UnityAppController.mm"));
+ }
+
+
+ ///
+ /// Edit 'UnityFramework.h': add 'frameworkWarmup'
+ ///
+ private static void EditUnityFrameworkH(string path)
+ {
+ var inScope = false;
+
+ // Add frameworkWarmup method
+ EditCodeFile(path, line =>
+ {
+ inScope |= line.Contains("- (void)runUIApplicationMainWithArgc:");
+
+ if (inScope)
+ {
+ if (line.Trim() == "")
+ {
+ inScope = false;
+
+ return new string[]
+ {
+ "",
+ "// Added by " + TouchedMarker,
+ "- (void)frameworkWarmup:(int)argc argv:(char*[])argv;",
+ ""
+ };
+ }
+ }
+
+ return new string[] { line };
+ });
+ }
+
+ ///
+ /// Edit 'UnityAppController.h': returns 'UnityAppController' from 'AppDelegate' class.
+ ///
+ private static void EditUnityAppControllerH(string path)
+ {
+ var inScope = false;
+ var markerDetected = false;
+
+ // Add static GetAppController
+ EditCodeFile(path, line =>
+ {
+ inScope |= line.Contains("- (void)startUnity:");
+
+ if (inScope)
+ {
+ if (line.Trim() == "")
+ {
+ inScope = false;
+
+ return new string[]
+ {
+ "",
+ "// Added by " + TouchedMarker,
+ "+ (UnityAppController*)GetAppController;",
+ ""
+ };
+ }
+ }
+
+ return new string[] { line };
+ });
+
+ inScope = false;
+ markerDetected = false;
+
+ // Modify inline GetAppController
+ EditCodeFile(path, line =>
+ {
+ inScope |= line.Contains("extern UnityAppController* GetAppController");
+
+ if (inScope && !markerDetected)
+ {
+ if (line.Trim() == "")
+ {
+ inScope = false;
+ markerDetected = true;
+
+ return new string[]
+ {
+ "// }",
+ "",
+ "static inline UnityAppController* GetAppController()",
+ "{",
+ " return [UnityAppController GetAppController];",
+ "}",
+ };
+ }
+
+ return new string[] { "// " + line };
+ }
+
+ return new string[] { line };
+ });
+
+
+ }
+
+ ///
+ /// Edit 'UnityAppController.mm': triggers 'UnityReady' notification after Unity is actually started.
+ ///
+ private static void EditUnityAppControllerMM(string path)
+ {
+ var inScope = false;
+ var markerDetected = false;
+
+ EditCodeFile(path, line =>
+ {
+ if (line.Trim() == "@end")
+ {
+ return new string[]
+ {
+ "",
+ "// Added by " + TouchedMarker,
+ "static UnityAppController *unityAppController = nil;",
+ "",
+ @"+ (UnityAppController*)GetAppController",
+ "{",
+ " static dispatch_once_t onceToken;",
+ " dispatch_once(&onceToken, ^{",
+ " unityAppController = [[self alloc] init];",
+ " });",
+ " return unityAppController;",
+ "}",
+ "",
+ line,
+ };
+ }
+
+ inScope |= line.Contains("- (void)startUnity:");
+ markerDetected |= inScope && line.Contains(TouchedMarker);
+
+ if (inScope && line.Trim() == "}")
+ {
+ inScope = false;
+
+ if (markerDetected)
+ {
+ return new string[] { line };
+ }
+ else
+ {
+ return new string[]
+ {
+ " // Modified by " + TouchedMarker,
+ @" [[NSNotificationCenter defaultCenter] postNotificationName: @""UnityReady"" object:self];",
+ "}",
+ };
+ }
+ }
+
+ return new string[] { line };
+ });
+
+ inScope = false;
+ markerDetected = false;
+
+ // Modify inline GetAppController
+ EditCodeFile(path, line =>
+ {
+ inScope |= line.Contains("UnityAppController* GetAppController()");
+
+ if (inScope && !markerDetected)
+ {
+ if (line.Trim() == "}")
+ {
+ inScope = false;
+ markerDetected = true;
+
+ return new string[]
+ {
+ "",
+ };
+ }
+
+ return new string[] { "// " + line };
+ }
+
+ return new string[] { line };
+ });
+ }
+
+ private static void EditCodeFile(string path, Func> lineHandler)
+ {
+ var bakPath = path + ".bak";
+ if (File.Exists(bakPath))
+ {
+ File.Delete(bakPath);
+ }
+
+ File.Move(path, bakPath);
+
+ using (var reader = File.OpenText(bakPath))
+ using (var stream = File.Create(path))
+ using (var writer = new StreamWriter(stream))
+ {
+ string line;
+ while ((line = reader.ReadLine()) != null)
+ {
+ var outputs = lineHandler(line);
+ foreach (var o in outputs)
+ {
+ writer.WriteLine(o);
+ }
+ }
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/workspace.png b/workspace.png
new file mode 100644
index 0000000..4115386
Binary files /dev/null and b/workspace.png differ