unity_kit
Flutter plugin for Unity 3D integration — typed bridge, binary protocol, performance + AR, on Android, iOS, web & desktop.
Flutter plugin for embedding Unity 3D scenes (Unity as a Library) — bridge messaging, lifecycle management, asset streaming. On pub.dev as unity_kit.
English project snapshot. Visit GitHub for the latest content.
pub package pub points License: MIT
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.
Full hosted documentation — a complete zero-to-running walkthrough, native setup guides, API reference and troubleshooting — lives at codigee.com/open-source/unity-kit.
| Guide | What's in it |
|---|---|
| Step-by-step walkthrough | Zero-to-running: every file, path, and edit |
| Unity export | Configure & export for Android, iOS, WebGL |
| Android setup | Native layer, Gradle, rendering |
| iOS setup | UnityFramework embedding, Metal crash prevention |
| Content loading | Scenes, prefabs, AssetBundles at runtime |
| API reference | Class signatures & parameters |
| Asset streaming | CDN manifest + verified cache |
| Architecture | Bridge, lifecycle, streams |
| FAQ | Troubleshooting common issues |
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. |
| Environment Preflight | environment() reports which runtime the build actually ships and whether its native libraries pass Google Play's 16 KB page size rule. |
| Platform Views | Android (Virtual Display) and iOS (UiKitView composition). |
# pubspec.yaml
dependencies:
unity_kit: ^2.1.0
import '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:
case in your handler (or create a new FlutterMonoBehaviour)NativeAPI.SendToFlutter(json)Flutter side:
bridge.send(UnityMessage.to('HandlerName', 'MethodName', data))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
Builds/Unity -quit -batchmode -projectPath /path/to/project \
-executeMethod UnityKit.Editor.Build.ExportAndroidRelease
See 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
Flutter > Setup AddressablesFlutter > Build Addressables (generates bundles + content_manifest.json)ServerData/[BuildTarget]/ to CDNSee 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
Hosted documentation: a step-by-step integration guide with troubleshooting is also available at codigee.com/open-source/unity-kit.
| 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.**.
FlutterBridge GameObject exists in the first scene[UnityKit] No handler registered for target: xxxUnityKitNativeBridge.mm is in Assets/Plugins/iOS/Before mounting a view, ask what the build actually carries:
final env = await UnityKitPlatform.instance.environment();
if (!env.isReadyForUnity) {
// No Unity runtime in this build: a UnityView would come up blank.
debugPrint(env.summary);
}
if (env.failsPageSizeRequirement) {
// Google Play rejects updates targeting Android 15+ whose native
// libraries are aligned to less than 16 KB.
debugPrint('Not 16 KB aligned: '
'${env.unalignedLibraries.map((l) => l.name).join(', ')}');
}
env.summary prints a single line for logs, for example:
Unity runtime: unity6 (com.unity3d.player.UnityPlayerForActivityOrService),
abi arm64-v8a, device page size 16384 B, 16 KB alignment: aligned
Two Unity upgrade traps this catches:
env.runtime says which player class was
found, so a build that silently shipped without the Unity libraries is
obvious instead of showing up as an empty surface..so. The check
reads the p_align of each PT_LOAD segment out of the shipped files, so
it reports the binary's truth rather than the Unity version's claim.iOS reports the runtime and page size but no alignment verdict: the 16 KB rule is an Android packaging requirement.
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.
MIT License -- see unity_kit/LICENSE for details.
Created by Eryk Kruk