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, iPad OS, iOS, Web
.
Windows support is a work in progress.
Need me to respond, tag me Rex Isaac Raphael.
This plugin expects you to atleast know how to use Unity Engine. If you have issues with how unity widget is presented, you can please modify your unity project build settings as you seem fit.
Moving forward, versioning of the package will change to match unity releases after proper test. Mind you this does not mean the package is not compatible with other versions, it just mean it's been tested to work with a unity version.
Windows coming soon.
First depend on the library by adding this to your packages pubspec.yaml
:
Flutter 3.0.0
dependencies:
flutter_unity_widget: ^2022.2.0
Pre Flutter 3.0.0 (This version will gradually be deprecated)
dependencies:
flutter_unity_widget: ^2022.1.0+7
Now inside your Dart code you can import it.
import 'package:flutter_unity_widget/flutter_unity_widget.dart';
You will need to open and export a Unity project, even for running the example. Your build will fail if you only include the widget in Flutter!
30 fps gifs, showcasing communication between Flutter and Unity:
In the tutorial below, there are steps specific to each platform, denoted by a :information_source: icon followed by the platform name (Android or iOS). You can click on its icon to expand it.
An existing Flutter project (if there is none, you can create a new one)
An existing Unity project (if there is none, you can create a new one).
A fuw-XXXX.unitypackage
file, found in the unitypackages folder.
Try to use the most recent unitypackage available.
These instructions assume you are using a new Unity project. If you open the example project from this repository, you can move on to the next section Unity Exporting.
Create a folder named unity in your Flutter project folder and move the Unity project into there.
The Unity export will modify some files in the /android
and /ios
folders of your flutter project. If your Unity project is in a different location the export might (partially) fail.
The expected path is
/unity/project-name/...
Make sure you have downloaded a fuw-XXXX.unitypackage file mentioned in prerequisites.
Using Unity (hub), open the Unity project. Go to Assets > Import Package > Custom Package and select the downloaded fuw-XXXX.unitypackage file. Click on Import.
Go to File > Build Settings > Player Settings and change the following under the Other settings > Configuration section:
In Scripting Backend, change to IL2CPP
(Android) Target Architectures, select ARMv7 and ARM64
(Android) For the best compatibility set Active Input Handling to Input Manager (Old)
or Both
.
(The new input system has some issues with touch input on Android)
(iOS) Select Target SDK depending on where you will run your app (simulator or physical device).
We recommend starting with a physical device and the Device SDK
setting, due to limited simulator support.
(Web) Set Publishing settings > Compression format to Brotli or Disabled.
Some users report that Unity gets stuck on the loading screen with the Gzip setting, due to MIME type errors.
In File > Build Settings, make sure to have at least 1 scene added to your build.
Some options in the Build settings window get overridden by the plugin's export script.
Attempting to change settings like Development Build
, Script Debugging
and Export project
in this window will not make a difference.
If you end up having to change a build setting that doesn't seem to respond, take a lookat the export script FlutterUnityIntegration\Editor\Build.cs
.
After importing the unitypackage, you should now see a Flutter option at the top of the Unity editor.
Click on Flutter and select the appropriate export option:
flutter_unity_cli
for larger projects.If you use git, you will probably want to add these unityLibrary folders to your gitignore file. These folders can get huge and are not guaranteed to work on another computer.
Proceed to the next section to handle iOS and Android specific setup after the export.
After exporting Unity, you will need to make some small changes in your iOS or Android project.
You will likely need to do this only once. These changes remain on future Unity exports.
The following setup for AR is done after making an export from Unity.
Warning: The XR Plugin Management
package version 4.3.1 - 4.3.3
has bug that breaks Android exports.
4.2.2
or 4.4
to avoid this. We recommend using a physical iOS or Android device, as emulator support is limited.
Below are the limited options to use an emulator.
On a UnityWidget
widget, get the UnityWidgetController
received by the onUnityCreated
callback.
Use the method postMessage
to send a string, using the GameObject name and the name of a behaviour method that should be called.
Create a new MonoBehaviour
subclass and add to the same GameObject as a script.
On this new behaviour, call GetComponent<UnityMessageManager>()
to get a UnityMessageManager
.
Use the method SendMessageToFlutter
to send a string. Receive this message using the onUnityMessage
callback of a UnityWidget
.
import 'package:flutter/material.dart';
import 'package:flutter_unity_widget/flutter_unity_widget.dart';
void main() {
runApp(
const MaterialApp(
home: UnityDemoScreen(),
),
);
}
class UnityDemoScreen extends StatefulWidget {
const UnityDemoScreen({Key? key}) : super(key: key);
@override
State<UnityDemoScreen> createState() => _UnityDemoScreenState();
}
class _UnityDemoScreenState extends State<UnityDemoScreen> {
static final GlobalKey<ScaffoldState> _scaffoldKey =
GlobalKey<ScaffoldState>();
UnityWidgetController? _unityWidgetController;
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: SafeArea(
bottom: false,
child: WillPopScope(
onWillPop: () async {
// Pop the category page if Android back button is pressed.
return true;
},
child: Container(
color: Colors.yellow,
child: UnityWidget(
onUnityCreated: onUnityCreated,
),
),
),
),
);
}
// Callback that connects the created controller to the unity controller
void onUnityCreated(controller) {
_unityWidgetController = controller;
}
}
import 'package:flutter/material.dart';
import 'package:flutter_unity_widget/flutter_unity_widget.dart';
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
static final GlobalKey<ScaffoldState> _scaffoldKey =
GlobalKey<ScaffoldState>();
UnityWidgetController? _unityWidgetController;
double _sliderValue = 0.0;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: const Text('Unity Flutter Demo'),
),
body: Card(
margin: const EdgeInsets.all(8),
clipBehavior: Clip.antiAlias,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
child: Stack(
children: <Widget>[
UnityWidget(
onUnityCreated: onUnityCreated,
onUnityMessage: onUnityMessage,
onUnitySceneLoaded: onUnitySceneLoaded,
fullscreen: false,
),
Positioned(
bottom: 20,
left: 20,
right: 20,
// <You need a PointerInterceptor here on web>
child: Card(
elevation: 10,
child: Column(
children: <Widget>[
const Padding(
padding: EdgeInsets.only(top: 20),
child: Text("Rotation speed:"),
),
Slider(
onChanged: (value) {
setState(() {
_sliderValue = value;
});
setRotationSpeed(value.toString());
},
value: _sliderValue,
min: 0,
max: 20,
),
],
),
),
),
],
),
),
),
);
}
// Communcation from Flutter to Unity
void setRotationSpeed(String speed) {
_unityWidgetController?.postMessage(
'Cube',
'SetRotationSpeed',
speed,
);
}
// Communication from Unity to Flutter
void onUnityMessage(message) {
print('Received message from unity: ${message.toString()}');
}
// Callback that connects the created controller to the unity controller
void onUnityCreated(controller) {
_unityWidgetController = controller;
}
// Communication from Unity when new scene is loaded to Flutter
void onUnitySceneLoaded(SceneLoaded? sceneInfo) {
if (sceneInfo != null) {
print('Received scene loaded from unity: ${sceneInfo.name}');
print(
'Received scene loaded from unity buildIndex: ${sceneInfo.buildIndex}');
}
}
}
fullscreen
(Enable or disable fullscreen mode on Android)pause()
(Use this to pause unity player)resume()
(Use this to resume unity player)unload()
(Use this to unload unity player) *Requires Unity 2019.4.3 or laterquit()
(Use this to quit unity player)postMessage(String gameObject, methodName, message)
(Allows you invoke commands in Unity from flutter)onUnityMessage(data)
(Unity to flutter binding and listener)onUnityUnloaded()
(Unity to flutter listener when unity is unloaded)onUnitySceneLoaded(String name, int buildIndex, bool isLoaded, bool isValid,)
(Unity to flutter binding and listener when new scene is loaded)
Location: Unity
Error:
Multiple precompiled assemblies with the same name Newtonsoft.Json.dll included on the current platform. Only one assembly with the same name is allowed per platform. (Assets/FlutterUnityIntegration/JsonDotNet/Assemblies/AOT/Newtonsoft.Json.dll)
PrecompiledAssemblyException: Multiple precompiled assemblies with the same name Newtonsoft.Json.dll included on the current platform. Only one assembly with the same name is allowed per platform.
Solution:
Locate the listed dll file, in this case:
Assets/FlutterUnityIntegration/JsonDotNet/Assemblies/AOT/Newtonsoft.Json.dll
.dll.txt
) to stop it from being imported.package-lock.json
Location: Unity
Error:
The type or namespace name 'Newtonsoft' could not be found (are you missing a using directive or an assembly reference?)
The type or namespace name 'JObject' could not be found (are you missing a using directive or an assembly reference?)
The type or namespace name 'JToken' could not be found (are you missing a using directive or an assembly reference?)
The type or namespace name 'JToken' could not be found (are you missing a using directive or an assembly reference?)
Solution:
Include the Newtonsoft JsonDotNet library.
It is likely already included in your project with a wrong file extension:
Assets/FlutterUnityIntegration/JsonDotNet/Assemblies/AOT/Newtonsoft.Json.dll.txt
Rename the .dll.txt
extension to .dll
in your file explorer and open Unity again.
Alternatively you can manually add the library from the Unity package manager.
Location: Unity
Error:
InvalidOperationException: The build target does not support build appending.
Solution:
1.1. On line 48, change the following:
- var options = BuildOptions.AcceptExternalModificationsToPlayer;
+ var options = BuildOptions.AllowDebugging;
+ EditorUserBuildSettings.exportAsGoogleAndroidProject = true;
1.2. On line 115, change the following:
- var options = BuildOptions.AcceptExternalModificationsToPlayer;
+ var options = BuildOptions.AllowDebugging;
Location: Android Studio
Error:
minSdkVersion XX cannot be smaller than version 19 declared in library
\ [:flutter_unity_widget] .../AndroidManifest.xml as the library might be using
\ APIs not available in XX
Solution:
- minSdkVersion XX
+ minSdkVersion 19
Location: Android Studio
Error:
e: .../FlutterUnityWidgetBuilder.kt: (15, 42): Expecting a parameter declaration
e: .../FlutterUnityWidgetBuilder.kt: (23, 25): Expecting an argument
e: .../FlutterUnityWidgetController.kt: (22, 44): Expecting a parameter declaration
e: .../FlutterUnityWidgetFactory.kt: (13, 58): Expecting a parameter declaration
Solution:
- ext.kotlin_version = '1.3.50'
+ ext.kotlin_version = '1.4.31'
Location: Android Studio
Error:
Unable to find a matching variant of project :unityLibrary:
Solution:
lintOptions {
disable 'InvalidPackage'
+ checkReleaseBuilds false
}
The easiest way to apply flavors for your app would be: flutter_flavorizr.
If you use flavors in your app you will notice that especially iOS crashes while running or building your app! Here are the necessary steps for flavored apps:
No changes needed. Flavors are applied without any additional setups.
For your Unity iOS-Build you have to add your flavors to your Unity iOS Configuration.
Runner
(your app) configurations. If you have for example the flavors:Your Runner
configurations are looking like this:
So you have the flavors:
Debug-dev
Profile-dev
Release-dev
Debug-prod
Profile-prod
Release-prod
These flavors needs to be added to your Unity-IPhone
project.
Unity-IPhone
project -> PROJECT Unity-IPhone
-> Info:Here you can see in the Configurations section only:
Release
ReleaseForProfiling
ReleaseForRunning
Debug
Debug
configuration twice and rename them to Debug-dev
and the second to Debug-prod
.You can do that by selecting +
and duplicate the configuration like this:
Repeat this with Release
to Release-dev
and Release-prod
.
Repeat this with Release
to Profile-dev
and Profile-prod
.
Your Unity-IPhone
configurations should now look like this:
Flutter on default doesn't support --flavor
for building web. But you can set your target main.dart
entrypoint (with -t main.dart
) while running and building. So if you setup your flavors properly there're also no changes needed for web to apply changes for your Flutter-Unity web App.
Build Setting - iOS - Other Settings - Configuration - Enable Custom Background Behaviors or iOS
commandLineArgs.add("--enable-debugger") commandLineArgs.add("--profiler-report") commandLineArgs.add("--profiler-output-file=" + workingDir + "/build/il2cpp"+ abi + "" + configuration + "/il2cpp_conv.traceevents")
Flutter widgets stacked on top of the UnityWidget will not register clicks or taps. This is a Flutter issue and can be solved by using the PointerInterceptor package.
Example usage:
Stack(
children: [
UnityWidget(
onUnityCreated: onUnityCreated,
onUnityMessage: onUnityMessage,
onUnitySceneLoaded: onUnitySceneLoaded,
),
Positioned(
bottom: 20,
left: 20,
right: 20,
child: PointerInterceptor(
child: ElevatedButton(
onPressed: () {
// do something
},
child: const Text('Example button'),
),
),
),
We already integrated this into our Examples in the /example
folder.
Support this project with your organization. Your donations will be used to help children first and then those in need. Your logo will show up here with a link to your website. [Contribute]
Thanks goes to these wonderful people (emoji key):
Rex Raphael 💻 📖 💬 🐛 👀 ✅ |
Thomas Stockx 💻 📖 💬 ✅ |
Kris Pypen 💻 📖 💬 ✅ |
Lorant Csonka 📖 📹 |
This project follows the all-contributors specification. Contributions of any kind welcome!