Flutter package
unity_kit
Embed Unity 3D in Flutter with a typed, testable bridge — JSON or binary protocol, an enforced lifecycle, AR Foundation, and CDN asset streaming. Runs on Android and iOS (web/WebGL experimental).
API reference
Class signatures, parameters and code examples for the Dart API: UnityView, UnityBridge, UnityMessage, UnityConfig and friends.
source: unity_kit/doc/api.mdWidget
UnityView
UnityView is the widget entry point. It embeds the native Unity player as a platform view and surfaces the bridge streams as callbacks. The bridge parameter is optional: when omitted the widget creates and owns an internal UnityBridgeImpl; when you pass one, you own it and the widget will never dispose an external bridge (it survives the widget).
UnityView(
config: const UnityConfig(sceneName: 'GameScene'),
placeholder: const Center(child: CircularProgressIndicator()),
onReady: (bridge) {
bridge.send(UnityMessage.command('StartGame'));
},
onMessage: (message) {
if (message.type == 'score_updated') {
// handle score
}
},
)| Parameter | Type | Description |
|---|---|---|
bridge | UnityBridge? | External bridge. When null, one is created internally from UnityKitPlatform.instance; an external bridge is never disposed by the widget. |
config | UnityConfig | Configuration (defaults to const UnityConfig()). Controls scene, fullscreen, status bar, frame rate, view mode and AR. |
placeholder | Widget? | Overlay shown on top of the platform view until the bridge emits UnityLifecycleState.ready. |
onReady | void Function(UnityBridge bridge)? | Called once when the Unity player is ready to receive messages. |
onMessage | void Function(UnityMessage message)? | Called for every message received from Unity. |
onEvent | void Function(UnityEvent event)? | Called for every lifecycle event emitted by the Unity player. |
onSceneLoaded | void Function(SceneInfo scene)? | Called when a Unity scene finishes loading. |
gestureRecognizers | Set<Factory<OneSequenceGestureRecognizer>>? | Passed through to the underlying AndroidView / UiKitView so the host app can intercept touches before they reach Unity. |
Supported platforms
Production platforms are Android and iOS only. On desktop the view renders a “Platform not supported” placeholder, and while a web (WebGL) path exists via HtmlElementView it is not production-ready. iOS builds run on a physical device only — never the Simulator.
Bridge & messaging
UnityBridge
UnityBridge is the abstract Flutter ⟷ Unity interface — typed messaging, lifecycle control and event streams. UnityBridgeImpl is the default implementation created for you by UnityView when you do not supply your own.
abstract class UnityBridge {
UnityLifecycleState get currentState;
bool get isReady;
// Streams
Stream<UnityMessage> get messageStream;
Stream<UnityPerformanceStats> get performanceStream; // 2.0.0
Stream<UnityEvent> get eventStream;
Stream<SceneInfo> get sceneStream;
Stream<UnityLifecycleState> get lifecycleStream;
// Sending (JSON)
Future<void> send(UnityMessage message);
Future<void> sendWhenReady(UnityMessage message);
// Sending (binary, 2.0.0)
Future<void> sendBinary(UnityMessage message);
Future<void> sendBinaryWhenReady(UnityMessage message);
// Lifecycle
Future<void> initialize();
Future<void> pause();
Future<void> resume();
Future<void> unload();
Future<void> dispose();
}Sending
| Method | Behaviour |
|---|---|
send(message) | Sends immediately. Throws EngineNotReadyException if Unity is not ready. |
sendWhenReady(message) | Queues the message until Unity becomes ready; sends immediately if already ready. |
sendBinary(message) | Encodes with UnityBinaryCodec and delivers to Unity's ReceiveBinary entry point. Prefer this for high-frequency traffic. Throws EngineNotReadyException if not ready. |
sendBinaryWhenReady(message) | Queues a binary message until Unity becomes ready. |
Lifecycle & state
| Member | Behaviour |
|---|---|
currentState | Current UnityLifecycleState of the player (getter). |
isReady | Whether the player is ready to receive messages (getter). |
initialize() | Initialize the Unity player and begin listening for events. |
pause() | Pause the Unity player. |
resume() | Resume the Unity player from a paused state. |
unload() | Unload the Unity player while keeping the process alive. |
dispose() | Dispose all resources. The bridge cannot be reused after this. |
UnityBinaryCodec (2.0.0)
A compact, length-prefixed binary frame for UnityMessage, symmetric with the Unity-side UnityKitBinaryCodec. UnityBinaryWriter / UnityBinaryReader hand-pack typed payloads with writeInt32, writeInt64, writeFloat64, writeBool, writeString (and matching read*).
Uint8List bytes = UnityBinaryCodec.encode(UnityMessage.command('Move', {'x': 1}));
UnityMessage msg = UnityBinaryCodec.decode(bytes);
bool ok = UnityBinaryCodec.isBinaryFrame(bytes);Messaging
UnityMessage
A typed message exchanged between Flutter and Unity. By default a message targets FlutterBridge.ReceiveMessage; the factory constructors set the type, target and payload for the common cases.
const UnityMessage({
required this.type,
this.data,
this.gameObject = 'FlutterBridge',
this.method = 'ReceiveMessage',
});
factory UnityMessage.command(String action, [Map<String, dynamic>? data]);
factory UnityMessage.to(String gameObject, String method, [Map<String, dynamic>? data]);
factory UnityMessage.routed(String target, String method, [Map<String, dynamic>? data]);
factory UnityMessage.fromJson(String jsonString);Fields
| Field | Type | Default | Description |
|---|---|---|---|
type | String | required | Message type identifier (e.g. 'LoadScene', 'scene_loaded'). |
data | Map<String, dynamic>? | null | Optional payload data. |
gameObject | String | 'FlutterBridge' | Target Unity GameObject name. |
method | String | 'ReceiveMessage' | Target method name on the GameObject. |
nativeGameObject | String | — (getter) | Native target for UnitySendMessage; defaults to gameObject. Routed messages override it to 'FlutterBridge'. |
nativeMethod | String | — (getter) | Native method for UnitySendMessage; defaults to method. Routed messages override it to 'ReceiveMessage'. |
Factory constructors
| Constructor | Purpose |
|---|---|
UnityMessage.command(action, [data]) | A command to Unity. type is set to action, delivered to FlutterBridge.ReceiveMessage. |
UnityMessage.to(gameObject, method, [data]) | Targets a specific Unity GameObject and method directly via UnitySendMessage. |
UnityMessage.routed(target, method, [data]) | Routes through FlutterBridge's MessageRouter to a registered handler (e.g. FlutterAddressablesManager) instead of a standalone GameObject. |
UnityMessage.fromJson(jsonString) | Parses a JSON string received from Unity. Throws FormatException if the JSON is invalid or has no 'type' field. |
// Command to Unity
final msg = UnityMessage.command('LoadScene', {'name': 'Level1'});
// Event from Unity
final event = UnityMessage.fromJson('{"type":"scene_loaded","data":"Level1"}');Configuration
UnityConfig & AR
UnityConfig controls how the Unity player is created. toCreationParams() is the single source of truth for the Dart → native contract — it carries every option plus arMode.wireName to the Android, iOS, web and desktop hosts.
const UnityConfig({
this.sceneName = 'MainScene',
this.fullscreen = false,
this.unloadOnDispose = true,
this.hideStatusBar = false,
this.runImmediately = true,
this.targetFrameRate = 60,
this.platformViewMode = PlatformViewMode.hybridComposition,
this.transparentBackground = false,
this.arMode = UnityArMode.none,
});
factory UnityConfig.fullscreen({String sceneName = 'MainScene', bool transparentBackground = false});
factory UnityConfig.embedded({String sceneName = 'MainScene', bool transparentBackground = false});
factory UnityConfig.ar({String sceneName = 'MainScene', UnityArMode mode = UnityArMode.overlay});
enum UnityArMode { none, passthrough, overlay }Options
| Option | Type | Default | Description |
|---|---|---|---|
sceneName | String | 'MainScene' | Name of the Unity scene to load on initialization. |
fullscreen | bool | false | Whether Unity should render in fullscreen mode. |
unloadOnDispose | bool | true | Whether to unload Unity when the widget is disposed. |
hideStatusBar | bool | false | Whether to hide the system status bar. |
runImmediately | bool | true | Whether to start the Unity player immediately on creation. |
targetFrameRate | int | 60 | Target frame rate for Unity rendering. |
platformViewMode | PlatformViewMode | hybridComposition | Platform view rendering mode (Android only). |
transparentBackground | bool | false | Marks the native view non-opaque so Flutter content behind Unity shows through. iOS-only for now. |
arMode | UnityArMode | none | AR Foundation rendering mode. |
UnityArMode (2.0.0)
| Mode | Meaning |
|---|---|
none | No AR. Unity renders normally against its own background. |
passthrough | The device camera feed is rendered by Unity behind the scene — for fully Unity-driven AR. |
overlay | AR session active, Unity clears to a transparent background so the camera feed (or Flutter content) shows through. Pair with transparentBackground. |
transparentBackground is iOS-only
UnityConfig.ar() defaults to UnityArMode.overlay with a transparent background. On Android the transparentBackground flag is ignored and a warning is logged — it is an iOS-only feature today.
Reactive API
Streams & payload types
The bridge exposes five broadcast streams. Subscribe to the ones you need; each emits a strongly-typed payload.
Stream<UnityMessage> get messageStream;
Stream<UnityPerformanceStats> get performanceStream; // 2.0.0
Stream<UnityEvent> get eventStream;
Stream<SceneInfo> get sceneStream;
Stream<UnityLifecycleState> get lifecycleStream;| Stream | Emits | When |
|---|---|---|
messageStream | UnityMessage | Every message received from Unity. |
performanceStream | UnityPerformanceStats | When the Unity-side UnityKitPerformanceMonitor pushes frame stats (2.0.0). |
eventStream | UnityEvent | Lifecycle events emitted by the Unity player. |
sceneStream | SceneInfo | Scene load / unload events. |
lifecycleStream | UnityLifecycleState | Lifecycle state changes. |
SceneInfo
class SceneInfo {
final String name;
final int buildIndex; // -1 if unknown
final bool isLoaded;
final bool isValid;
final Map<String, dynamic>? metadata;
}UnityPerformanceStats (2.0.0)
class UnityPerformanceStats {
final double fps;
final double frameTimeMs;
final double usedMemoryMb;
final int drawCalls;
final int triangles;
}UnityEvent
class UnityEvent {
final UnityEventType type;
final DateTime timestamp;
final String? message;
final String? error;
}UnityEventType values: created, loaded, paused, resumed, unloaded, destroyed, error, message, sceneLoaded.
Lifecycle
Lifecycle state machine
UnityLifecycleState is a strict state machine. Only the transitions below are valid; isActive is true for ready and resumed, and canSend mirrors isActive.
enum UnityLifecycleState {
uninitialized,
initializing,
ready,
paused,
resumed,
disposed,
}| State | Valid transitions to |
|---|---|
uninitialized | initializing |
initializing | ready, disposed |
ready | paused, disposed |
paused | resumed, disposed |
resumed | paused, disposed |
disposed | Terminal — no further transitions. |
Errors
Exceptions
Every typed error extends UnityKitException, which carries a message, an optional cause and a stackTrace.
| Exception | Extends | Thrown when |
|---|---|---|
UnityKitException | implements Exception | Base for all unity_kit errors. |
EngineNotReadyException | UnityKitException | A message is sent before Unity is ready. Message: “Unity engine is not ready. Call initialize() first or use sendWhenReady().” |
BridgeException | UnityKitException | Bridge communication fails, e.g. the bridge is used after dispose(). |
CommunicationException | UnityKitException | A message cannot be delivered to Unity. Carries target, method and data. |
LifecycleException | UnityKitException | An invalid lifecycle transition is attempted. Carries currentState and attemptedAction. |
Asset streaming
Streaming module
StreamingController orchestrates manifest fetching, downloading, caching and Unity communication. This is the summary — see the Asset streaming guide for the full CDN / cache / Addressables walkthrough.
StreamingController({
required UnityBridge bridge,
required String manifestUrl,
UnityAssetLoader? assetLoader, // defaults to UnityAddressablesLoader
http.Client? httpClient,
CacheManager? cacheManager,
})| Property / Method | Description |
|---|---|
assetLoader | The loader strategy in use. |
state | Current StreamingState. |
downloadProgress | Stream of DownloadProgress. |
errors | Stream of StreamingError. |
stateChanges | Stream of StreamingState. |
initialize() | Fetch manifest, init cache, notify Unity. |
preloadContent({bundles, strategy}) | Download base bundles. |
loadBundle(name) | Download + tell Unity to load. |
loadScene(name, {loadMode}) | Download + tell Unity to load the scene. |
getCachedBundles() | List cached bundle names. |
isBundleCached(name) | Check whether a bundle is cached. |
getCacheSize() | Total cache size in bytes. |
clearCache() | Delete all cached content. |
dispose() | Release all resources. |
Two loaders swap behind the same controller: UnityAddressablesLoader (default) targets FlutterAddressablesManager, and UnityBundleLoader targets FlutterAssetBundleManager for raw AssetBundles.
Unity side
C# attribute dispatch
On the Unity side you expose C# to Flutter by name. Annotate methods with [UnityKitMethod] and register the instance with MessageRouter.RegisterMethods — no manual switch. From Flutter, route to it with UnityMessage.routed('player', 'Jump').
public class PlayerController : MonoBehaviour
{
[UnityKitMethod] // callable as "Jump"
public void Jump() { /* ... */ }
[UnityKitMethod("move")] // callable as "move"
public void Move(MovePayload p) { /* p deserialized from JSON */ }
void OnEnable() => MessageRouter.RegisterMethods("player", this);
void OnDisable() => MessageRouter.Unregister("player");
}For a message-per-object handler, extend FlutterMonoBehaviour. It auto-registers with MessageRouter on enable, overrides OnFlutterMessage for inbound messages, and sends outbound with SendToFlutter / SendToFlutterBatched.
using UnityKit;
public class EnemyManager : FlutterMonoBehaviour
{
protected override void OnFlutterMessage(string method, string data)
{
switch (method)
{
case "SpawnWave":
// parse data, spawn enemies
break;
case "Reset":
// reset game state
break;
}
}
private void OnWaveCleared()
{
// Direct send
SendToFlutter("wave_cleared", "{\"wave\": 3}");
// Batched send (via MessageBatcher component on FlutterBridge)
SendToFlutterBatched("score_updated", "{\"score\": 1500}");
}
}| C# type | Role |
|---|---|
FlutterMonoBehaviour | Base MonoBehaviour that auto-registers with MessageRouter. Override OnFlutterMessage(method, data); send with SendToFlutter(type, data) / SendToFlutterBatched(type, data). |
MessageRouter | Static registry routing FlutterBridge.ReceiveMessage() to handlers by target name. Register / Unregister / RegisterMethods(target, instance). |
NativeAPI | Low-level native bridge, NativeAPI.SendToFlutter(json). Prefer FlutterMonoBehaviour.SendToFlutter(). |
MessageBatcher | Batches outgoing Unity → Flutter messages per frame; sent as a JSON array in LateUpdate(). |
SceneTracker | Attach alongside FlutterBridge to notify Flutter on scene load / unload. No configuration. |
UnityKitGameManager | Handles LoadScene / UnloadScene / SetTargetFrameRate / PauseGame / ResumeGame and consumes the native __unitykit_init message. |
UnityKitPerformanceMonitor | Samples FPS / frame time / memory and streams them to bridge.performanceStream. |
Let's make something together.
If you have any questions about a new project or other inquiries, feel free to contact us. We will get back to you as soon as possible.
