A Flutter plugin for Unity 3D integration. Embed Unity content in Flutter apps with typed communication, lifecycle management, asset streaming, and support for Unity 2022.3 LTS through Unity 6.
Flutter handles UI (screens, buttons, navigation). Unity handles 3D (rendering, game logic). They run in the same app but are separate worlds -- they talk by sending JSON messages through a native bridge.
Flutter (Dart) Unity (C#)
UI, screens, JSON messages 3D rendering,
business logic <=========================> game logic
native bridge (iOS/Android)
There are no direct method calls. Everything goes through serialized JSON over the platform bridge. Flutter never calls C# directly, and Unity never calls Dart directly.
.
├── unity_kit/ # Flutter plugin package (published to pub.dev)
│ ├── lib/ # Dart source code
│ ├── android/ # Android native layer (Kotlin)
│ ├── ios/ # iOS native layer (Swift)
│ ├── unity/ # C# scripts for Unity side
│ ├── example/ # Example Flutter app
│ ├── test/ # Test suite (35 files, 640+ tests)
│ └── doc/ # API reference & asset streaming guide
├── doc/ # Detailed guides
│ ├── unity-export.md # Step-by-step Unity export guide
│ ├── unity_integrations.md # Content loading methods (scenes, Addressables, etc.)
│ ├── android-integration.md # Android native architecture & known issues
│ ├── ios-integration.md # iOS native architecture & known issues
│ └── architecture.md # Overall architecture overview
└── .github/workflows/ # CI/CD (auto-tag + pub.dev publish)
| Feature | What it does |
|---|---|
| Typed Bridge | UnityMessage objects instead of raw strings. Structured, type-safe communication. |
| Lifecycle Management | State machine: uninitialized -> ready -> paused -> disposed. Invalid transitions throw exceptions. |
| Readiness Guard | Messages sent before Unity is ready get queued and auto-flushed when the engine starts. Nothing is silently dropped. |
| Message Batching | Groups rapid-fire messages into single native calls (~16ms windows). |
| Message Throttling | Rate-limits outgoing messages with configurable strategies (drop / keepLatest / keepFirst). |
| Asset Streaming | Download Unity bundles from CDN, cache locally with SHA-256 verification, load via Addressables. |
| Unity 6 Support | Reflection-based player creation handles both legacy UnityPlayer and Unity 6's UnityPlayerForActivityOrService. |
| Platform Views | Android (Virtual Display) and iOS (UiKitView composition). |
# pubspec.yaml
dependencies:
unity_kit: ^0.9.2import 'package:unity_kit/unity_kit.dart';
final bridge = UnityBridgeImpl(platform: UnityKitPlatform.instance);
await bridge.initialize();
UnityView(
bridge: bridge,
config: const UnityConfig(sceneName: 'MainScene'),
onReady: (bridge) => bridge.send(UnityMessage.command('StartGame')),
onMessage: (msg) => print('Unity says: ${msg.type}'),
);Copy unity_kit/unity/Assets/Scripts/UnityKit/ into your Unity project. A FlutterBridge GameObject auto-creates on scene load and handles all communication.
// Receive messages from Flutter
public class MyHandler : FlutterMonoBehaviour
{
protected override void OnFlutterMessage(string method, string data)
{
switch (method)
{
case "StartGame":
// do something
NativeAPI.SendToFlutter("{\"type\":\"game_started\"}");
break;
}
}
}Flutter -> Unity (routed to a handler):
{"target": "MyHandler", "method": "StartGame", "data": "params"}Unity -> Flutter (typed event):
{"type": "game_started", "data": {"score": 100}}| Direction | Casing | Example |
|---|---|---|
| Flutter -> Unity (commands) | PascalCase |
LoadToy, StartGame |
| Unity -> Flutter (responses) | snake_case |
toy_loaded, game_started |
Unity side:
- Add a
casein your handler (or create a newFlutterMonoBehaviour) - Send responses via
NativeAPI.SendToFlutter(json)
Flutter side:
- Send:
bridge.send(UnityMessage.to('HandlerName', 'MethodName', data)) - Listen:
bridge.messageStream.listen((msg) => ...)
The bridge never changes. You define a JSON contract (command name + payload), implement on both sides, done.
Unity project and Flutter project are separate. Unity exports a library module, which gets copied to the Flutter project.
Unity Project Flutter Project
Assets/, Scenes/ android/unityLibrary/ <-- artifact
Scripts/UnityKit/ ios/UnityLibrary/ <-- artifact
Builds/ (export artifacts) -- copy --> lib/, pubspec.yaml
- Open Unity Editor with your project
- Flutter > Export Android (Debug) or Flutter > Export iOS (Debug)
- Artifacts appear in
Builds/ - Set Flutter project path in Flutter > Settings for auto-deploy, or copy manually
Unity -quit -batchmode -projectPath /path/to/project \
-executeMethod UnityKit.Editor.Build.ExportAndroidReleaseSee Unity Export Guide for full details.
For downloading 3D content from a server at runtime:
1. Flutter downloads content_manifest.json from CDN
2. User selects a toy/asset in the UI
3. Flutter downloads the .bundle file to local cache (SHA-256 verified)
4. Flutter tells Unity: "cache is at /path" (SetCachePath)
5. Flutter tells Unity: "load asset with key 'alphie'" (LoadToyRemote)
6. Unity Addressables loads from local cache (not from network again)
7. Unity sends "toy_loaded" back to Flutter
- Setup (one-time):
Flutter > Setup Addressables - Add asset: place prefab in Addressables group, set address key
- Build:
Flutter > Build Addressables(generates bundles +content_manifest.json) - Upload: push
ServerData/[BuildTarget]/to CDN
See Asset Streaming Guide for manifest format and configuration.
cd unity_kit && dart pub get # Install dependencies
cd unity_kit && flutter test # Run tests
cd unity_kit && dart analyze # Analyze
cd unity_kit && dart format . # Format
cd unity_kit && dart pub publish --dry-run # Publish check| Guide | What's in it |
|---|---|
| Unity Export | Step-by-step: install scripts, configure build, export for Android/iOS/WebGL |
| Content Loading | All methods: scenes, prefabs, AssetBundles, Addressables, glTF, AR |
| Android Integration | Android native layer: Unity 6 patterns, reflection, touch, rendering activation |
| iOS Integration | iOS native layer: UnityFramework, .mm bridge, Metal crash prevention |
| Architecture | Overall design: bridge, lifecycle, streams, exceptions |
| Asset Streaming | Addressables vs AssetBundles, manifest format, cache management |
| API Reference | Class signatures, parameters, code examples |
| FAQ | Common problems and solutions |
See doc/faq.md for the full list. Here are the top issues:
Unity needs an explicit "refocus" after attaching to the view: windowFocusChanged(true) + pause() + resume(). This is handled by unity_kit automatically. If you see a black screen, check that you're using the latest version.
Unity's New Input System requires InputDevice.SOURCE_TOUCHSCREEN on touch events. The UnityContainerLayout in unity_kit fixes this. If touch doesn't work, make sure you're not overriding the touch handling.
Usually a timing issue: Unity's Metal renderer needs a non-zero view frame before initialization. unity_kit polls with waitForNonZeroFrame(). If it persists, ensure the UnityView widget has a non-zero size in the layout.
ProGuard/R8 can strip classes used via reflection or JNI. unity_kit's Build.cs auto-adds ProGuard keep rules during export. If you see silent failures in release, verify proguard-unity.txt contains keep rules for com.unity_kit.** and com.unity3d.player.**.
- Check
FlutterBridgeGameObject exists in the first scene - Check your handler's target name matches what Flutter sends
- Check Unity Console for
[UnityKit] No handler registered for target: xxx - On iOS: verify
UnityKitNativeBridge.mmis inAssets/Plugins/iOS/
unity_kit uses reflection to detect Unity 6's UnityPlayerForActivityOrService class automatically. No manual configuration needed. If you're using Unity 6 and see issues, check the Android Integration guide for constructor signature details.
- 3D model loading utilities and prefab management helpers
- Full step-by-step tutorial covering the complete integration flow
- Additional example scenes and sample Unity project
MIT License -- see unity_kit/LICENSE for details.
Created by Eryk Kruk