diff --git a/.all-contributorsrc b/.all-contributorsrc new file mode 100644 index 0000000..688a936 --- /dev/null +++ b/.all-contributorsrc @@ -0,0 +1,63 @@ +{ + "projectName": "flutter-unity-view-widget", + "projectOwner": "snowballdigital", + "repoType": "github", + "repoHost": "https://github.com", + "files": [ + "README.md" + ], + "imageSize": 100, + "commit": true, + "commitConvention": "eslint", + "contributors": [ + { + "login": "juicycleff", + "name": "Rex Raphael", + "avatar_url": "https://avatars2.githubusercontent.com/u/11243590?v=4", + "profile": "http://rexraphael.com", + "contributions": [ + "code", + "doc", + "question", + "bug", + "review", + "tutorial" + ] + }, + { + "login": "thomas-stockx", + "name": "Thomas Stockx", + "avatar_url": "https://avatars1.githubusercontent.com/u/1475368?v=4", + "profile": "https://stockxit.com", + "contributions": [ + "code", + "doc", + "question", + "tutorial" + ] + }, + { + "login": "krispypen", + "name": "Kris Pypen", + "avatar_url": "https://avatars1.githubusercontent.com/u/156955?v=4", + "profile": "http://krispypen.github.io/", + "contributions": [ + "code", + "doc", + "question", + "tutorial" + ] + }, + { + "login": "lorant-csonka-planorama", + "name": "Lorant Csonka", + "avatar_url": "https://avatars2.githubusercontent.com/u/48209860?v=4", + "profile": "https://github.com/lorant-csonka-planorama", + "contributions": [ + "doc", + "video" + ] + } + ], + "contributorsPerLine": 7 +} diff --git a/.gitignore b/.gitignore index d9ee87c..f6748aa 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,10 @@ *.iws .idea/ +node_modules/ +./package.json +.idea/ + # Visual Studio Code related .vscode/ diff --git a/2019_03_28_19_23_37.gif b/2019_03_28_19_23_37.gif deleted file mode 100644 index daf95e7..0000000 Binary files a/2019_03_28_19_23_37.gif and /dev/null differ diff --git a/CHANGELOG.md b/CHANGELOG.md index 54780dc..63f7d18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,23 @@ +## 0.1.6+1 + +* Adding Metal renderer support (on iOS) [@krispypen](https://github.com/krispypen) + +## 0.1.6 + +* iOS support for the Unity 2019.3 new export format Unity as a Library [@krispypen](https://github.com/krispypen) + +## 0.1.5 + +* Android support for the Unity 2019.3 new export format Unity as a Library [@thomas-stockx](https://github.com/thomas-stockx) + ## 0.1.4 -* Support for AR on Android thanks to @thomas-stockx - +* Support for AR on Android thanks to [@thomas-stockx](https://github.com/thomas-stockx) ## 0.1.3+4 -* Change input source of Flutter touch events so they work in Unity @thomas-stockx +* Change input source of Flutter touch events so they work in Unity [@thomas-stockx](https://github.com/thomas-stockx) * Instructions on how to implement Vuforia AR -* Fix postMessage throwing exceptions on Android @thomas-stockx -* Add video tutorial, replace `unity-player` with `unity-classes` in example -* Remove java and UnityPlayer changes to the windowmanager +* Fix postMessage throwing exceptions on Android [@thomas-stockx](https://github.com/thomas-stockx) +* Add video tutorial, replace `unity-player` with `unity-classes` in example [@lorant-csonka-planorama](https://github.com/lorant-csonka-planorama) +* Remove java and UnityPlayer changes to the windowmanager [@thomas-stockx](https://github.com/thomas-stockx) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..3de396f --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at rex@snowball.digital. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/README.md b/README.md index 2ce9de0..aa1c35c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # flutter_unity_widget +[![All Contributors](https://img.shields.io/badge/all_contributors-4-orange.svg?style=flat-square)](#contributors-) [![version][version-badge]][package] [![MIT License][license-badge]][license] @@ -7,14 +8,14 @@ [![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. Now you can make awesome gamified features of your app in Unity and get it rendered in a Flutter app both in fullscreen and embeddable mode. Works great on Android and iOS. ## Installation First depend on the library by adding this to your packages `pubspec.yaml`: ```yaml dependencies: - flutter_unity_widget: ^0.1.4 + flutter_unity_widget: ^0.1.6+1 ``` Now inside your Dart code you can import it. @@ -26,7 +27,10 @@ import 'package:flutter_unity_widget/flutter_unity_widget.dart'; ## Preview -![gif](https://github.com/snowballdigital/flutter-unity-view-widget/blob/master/2019_03_28_19_23_37.gif?raw=true) +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)
@@ -57,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) @@ -71,22 +77,20 @@ 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`. + **iOS Platform**: + 1. This only works with Unity version >=2019.3 because uses Unity as a library! 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`.
- - -
### Add Unity Build Scripts and Export -Copy [`Build.cs`](https://github.com/f111fei/react-native-unity-demo/blob/master/unity/Cube/Assets/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 unity project with `Flutter/Export Android` or `Flutter/Export IOS` menu. +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. @@ -99,20 +103,13 @@ IOS will export unity project to `ios/UnityExport`. **Android Platform Only** 1. After exporting the unity game, open Android Studio and and add the `Unity Classes` Java `.jar` file as a module to the unity project. You just need to do this once if you are exporting from the same version of Unity everytime. The `.jar` file is located in the ```/android/UnityExport/lib``` folder - 2. Next open `build.gradle` of `flutter_unity_widget` module and replace the dependencies with -```gradle - dependencies { - implementation project(':UnityExport') // The exported unity project - implementation project(':unity-classes') // the unity classes module you added from step 1 - } -``` - 3. Next open `build.gradle` of `UnityExport` module and replace the dependencies with + 2. If using Unity 2019.2 or older, open `build.gradle` of `UnityExport` module and replace the dependencies with ```gradle dependencies { implementation project(':unity-classes') // the unity classes module you added from step 1 } ``` - 4. Next open `build.gradle` of `UnityExport` module and remove these + 3. If using Unity 2019.2 or older, open `build.gradle` of `UnityExport` module and remove these ```gradle bundle { language { @@ -127,9 +124,31 @@ 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) +### AR Foundation (not compatible with Unity 2019.3) +https://github.com/Unity-Technologies/arfoundation-samples/issues/210 + +Android only as iOS requires Unity 2019.3. + If you want to use Unity for integrating Augmented Reality in your Flutter app, a few more changes are required: 1. Export the Unity Project as previously stated (using the Editor Build script). 2. Check if the exported project includes all required Unity libraries (.so) files (`lib/\/libUnityARCore.so` and `libarpresto_api.so`). There seems to be a bug where a Unity export does not include all lib files. If they are missing, use Unity to build a standalone .apk of your AR project, unzip the resulting apk, and copy over the missing .lib files to the `UnityExport` module. @@ -276,15 +295,16 @@ class _UnityDemoScreenState extends State{ ## API - `pause()` (Use this to pause unity player) - `resume()` (Use this to resume unity player) + - `postMessage(String gameObject, methodName, message)` (Allows you invoke commands in Unity from flutter) + - `onUnityMessage(data)` (Unity to flutter bindding and listener) ## 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). [version-badge]: https://img.shields.io/pub/v/flutter_unity_widget.svg?style=flat-square -[package]: https://pub.dartlang.org/packages/flutter_unity_widget/versions/0.1.2 +[package]: https://pub.dartlang.org/packages/flutter_unity_widget/ [license-badge]: https://img.shields.io/github/license/snowballdigital/flutter-unity-view-widget.svg?style=flat-square [license]: https://github.com/snowballdigital/flutter-unity-view-widget/blob/master/LICENSE [prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square @@ -293,3 +313,25 @@ class _UnityDemoScreenState extends State{ [github-watch]: https://github.com/snowballdigital/flutter-unity-view-widget/watchers [github-star-badge]: https://img.shields.io/github/stars/snowballdigital/flutter-unity-view-widget.svg?style=social [github-star]: https://github.com/snowballdigital/flutter-unity-view-widget/stargazers + +## Contributors ✨ + +Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + + + + + +
Rex Raphael
Rex Raphael

💻 📖 💬 🐛 👀
Thomas Stockx
Thomas Stockx

💻 📖 💬
Kris Pypen
Kris Pypen

💻 📖 💬
Lorant Csonka
Lorant Csonka

📖 📹
+ + + + + +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 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/example/unity/DemoApp/obj/Debug/Assembly-CSharp-Editor.csproj.CoreCompileInputs.cache b/example/unity/DemoApp/obj/Debug/Assembly-CSharp-Editor.csproj.CoreCompileInputs.cache index 3d80074..a4971f5 100644 --- a/example/unity/DemoApp/obj/Debug/Assembly-CSharp-Editor.csproj.CoreCompileInputs.cache +++ b/example/unity/DemoApp/obj/Debug/Assembly-CSharp-Editor.csproj.CoreCompileInputs.cache @@ -1 +1 @@ -eb7addd6604d08d1b14bdd2f0b7568eea6aaad77 +5c787ba36b071ef6ee6a4f60499357697d53c75b diff --git a/example/unity/DemoApp/obj/Debug/Assembly-CSharp-Editor.csprojAssemblyReference.cache b/example/unity/DemoApp/obj/Debug/Assembly-CSharp-Editor.csprojAssemblyReference.cache index 9d287cb..a9d7731 100644 Binary files a/example/unity/DemoApp/obj/Debug/Assembly-CSharp-Editor.csprojAssemblyReference.cache and b/example/unity/DemoApp/obj/Debug/Assembly-CSharp-Editor.csprojAssemblyReference.cache differ diff --git a/example/unity/DemoApp/obj/Debug/Assembly-CSharp.csproj.CoreCompileInputs.cache b/example/unity/DemoApp/obj/Debug/Assembly-CSharp.csproj.CoreCompileInputs.cache index 6a95d74..ee3ccf3 100644 --- a/example/unity/DemoApp/obj/Debug/Assembly-CSharp.csproj.CoreCompileInputs.cache +++ b/example/unity/DemoApp/obj/Debug/Assembly-CSharp.csproj.CoreCompileInputs.cache @@ -1 +1 @@ -1d70ee33aab6965c19c5636e539561c5c25ac66b +bd1065d7afc1a8da0fc454a1d7dede98f3aaa473 diff --git a/example/unity/DemoApp/obj/Debug/Assembly-CSharp.csprojAssemblyReference.cache b/example/unity/DemoApp/obj/Debug/Assembly-CSharp.csprojAssemblyReference.cache index 08da307..4521a16 100644 Binary files a/example/unity/DemoApp/obj/Debug/Assembly-CSharp.csprojAssemblyReference.cache and b/example/unity/DemoApp/obj/Debug/Assembly-CSharp.csprojAssemblyReference.cache 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_android.gif b/preview_android.gif new file mode 100644 index 0000000..36db7e1 Binary files /dev/null and b/preview_android.gif differ 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 aaecc04..cbc51f7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,8 +1,10 @@ name: flutter_unity_widget description: Flutter unity 3D widget for embedding unity in flutter -version: 0.1.4 +version: 0.1.6+1 authors: - Rex Raphael + - Thomas Stockx + - Kris Pypen homepage: https://github.com/snowballdigital/flutter-unity-view-widget/tree/master environment: diff --git a/scripts/Editor/Build.cs b/scripts/Editor/Build.cs new file mode 100644 index 0000000..281d5c8 --- /dev/null +++ b/scripts/Editor/Build.cs @@ -0,0 +1,119 @@ +using System; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using UnityEditor; +using UnityEngine; +using Application = UnityEngine.Application; +using BuildResult = UnityEditor.Build.Reporting.BuildResult; + +public class Build : MonoBehaviour +{ + static readonly string ProjectPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); + + static readonly string apkPath = Path.Combine(ProjectPath, "Builds/" + Application.productName + ".apk"); + + static readonly string androidExportPath = Path.GetFullPath(Path.Combine(ProjectPath, "../../android/UnityExport")); + static readonly string iosExportPath = Path.GetFullPath(Path.Combine(ProjectPath, "../../ios/UnityExport")); + + [MenuItem("Flutter/Export Android (Unity 2019.3.*) %&n", false, 1)] + public static void DoBuildAndroidLibrary() + { + DoBuildAndroid(Path.Combine(apkPath, "unityLibrary")); + + // Copy over resources from the launcher module that are used by the library + Copy(Path.Combine(apkPath + "/launcher/src/main/res"), Path.Combine(androidExportPath, "src/main/res")); + } + + [MenuItem("Flutter/Export Android %&a", false, 2)] + public static void DoBuildAndroidLegacy() + { + DoBuildAndroid(Path.Combine(apkPath, Application.productName)); + } + + public static void DoBuildAndroid(String buildPath) + { + if (Directory.Exists(apkPath)) + Directory.Delete(apkPath, true); + + if (Directory.Exists(androidExportPath)) + Directory.Delete(androidExportPath, true); + + EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Gradle; + + var options = BuildOptions.AcceptExternalModificationsToPlayer; + var report = BuildPipeline.BuildPlayer( + GetEnabledScenes(), + apkPath, + BuildTarget.Android, + options + ); + + if (report.summary.result != BuildResult.Succeeded) + throw new Exception("Build failed"); + + Copy(buildPath, androidExportPath); + + // Modify build.gradle + var build_file = Path.Combine(androidExportPath, "build.gradle"); + var build_text = File.ReadAllText(build_file); + build_text = build_text.Replace("com.android.application", "com.android.library"); + build_text = build_text.Replace("implementation fileTree(dir: 'libs', include: ['*.jar'])", "implementation project(':unity-classes')"); + build_text = Regex.Replace(build_text, @"\n.*applicationId '.+'.*\n", "\n"); + File.WriteAllText(build_file, build_text); + + // Modify AndroidManifest.xml + var manifest_file = Path.Combine(androidExportPath, "src/main/AndroidManifest.xml"); + var manifest_text = File.ReadAllText(manifest_file); + manifest_text = Regex.Replace(manifest_text, @"", ""); + Regex regex = new Regex(@"(\s|\S)+?", RegexOptions.Multiline); + manifest_text = regex.Replace(manifest_text, ""); + File.WriteAllText(manifest_file, manifest_text); + } + + [MenuItem("Flutter/Export IOS (Unity 2019.3.*) %&i", false, 3)] + public static void DoBuildIOS() + { + if (Directory.Exists(iosExportPath)) + Directory.Delete(iosExportPath, true); + + EditorUserBuildSettings.iOSBuildConfigType = iOSBuildType.Release; + + var options = BuildOptions.AcceptExternalModificationsToPlayer; + var report = BuildPipeline.BuildPlayer( + GetEnabledScenes(), + iosExportPath, + BuildTarget.iOS, + options + ); + + if (report.summary.result != BuildResult.Succeeded) + throw new Exception("Build failed"); + } + + static void Copy(string source, string destinationPath) + { + if (Directory.Exists(destinationPath)) + Directory.Delete(destinationPath, true); + + Directory.CreateDirectory(destinationPath); + + foreach (string dirPath in Directory.GetDirectories(source, "*", + SearchOption.AllDirectories)) + Directory.CreateDirectory(dirPath.Replace(source, destinationPath)); + + foreach (string newPath in Directory.GetFiles(source, "*.*", + SearchOption.AllDirectories)) + File.Copy(newPath, newPath.Replace(source, destinationPath), true); + } + + static string[] GetEnabledScenes() + { + var scenes = EditorBuildSettings.scenes + .Where(s => s.enabled) + .Select(s => s.path) + .ToArray(); + + return scenes; + } +} \ No newline at end of file diff --git a/scripts/Editor/XCodePostBuild.cs b/scripts/Editor/XCodePostBuild.cs new file mode 100644 index 0000000..d9176a2 --- /dev/null +++ b/scripts/Editor/XCodePostBuild.cs @@ -0,0 +1,333 @@ +/* +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")); + EditUnityViewMM(Path.Combine(pathToBuiltProject, "Classes/UI/UnityView.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 }; + }); + } + + /// + /// Edit 'UnityView.mm': fix the width and height needed for the Metal renderer + /// + private static void EditUnityViewMM(string path) + { + var inScope = false; + + // Add frameworkWarmup method + EditCodeFile(path, line => + { + inScope |= line.Contains("UnityGetRenderingResolution(&requestedW, &requestedH)"); + + if (inScope) + { + if (line.Trim() == "") + { + inScope = false; + + return new string[] + { + "", + "// Added by " + TouchedMarker, + " if (requestedW == 0) {", + " requestedW = _surfaceSize.width;", + " }", + " if (requestedH == 0) {", + " requestedH = _surfaceSize.height;", + " }", + "" + }; + } + } + + 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