Unreal Engine Plugin for Game Framework

pub package Platform License

Integrate Unreal Engine 5.x into your Flutter applications with full bidirectional communication, quality settings control, console commands, and level loading.

Features

⚠️ Development Status: Unreal Engine integration is currently under active development. Features and APIs may change.

Core Features (WIP):

  • 🎮 Unreal Engine 5.x integration
  • 🔄 Bidirectional communication (Flutter ↔ Unreal)
  • 🎯 Blueprint support for non-programmers
  • 📱 Multi-platform support focus: Android, iOS
  • ⚡ High-performance native bridges (JNI, Objective-C++)

🎨 Unreal-Specific Features (Planned):

  • 🎚️ Quality settings with 5 presets (low, medium, high, epic, cinematic)
  • 🖥️ Console command execution (stat fps, r.SetRes, etc.)
  • 🗺️ Level/map loading and streaming
  • 📊 Quality level control (AA, shadows, textures, effects, etc.)
  • 🎬 Blueprint events for lifecycle and messaging

🚀 Advanced Features (Planned):

  • 📦 Binary messaging with compression and chunked transfers
  • ⏱️ Message batching and throttling for performance optimization
  • 📊 Delta compression for efficient state synchronization
  • 🗃️ Asset management with caching and progress tracking
  • 🔀 Message routing with target-based dispatch

Platform Support

Platform Status Requirements
Android 🚧 Work in Progress API 21+, NDK r25+
iOS 🚧 Work in Progress iOS 12.0+, Xcode 14+
macOS Planned macOS 10.14+, Xcode 14+
Windows Planned Windows 10+, Visual Studio 2022
Linux Planned Ubuntu 20.04+, GTK 3.0+

Note: Unreal Engine integration is currently under active development. Android and iOS support are the primary focus.

Quick Start

Installation

Add to your pubspec.yaml:

dependencies:
  gameframework: ^0.0.2
  gameframework_unreal: ^0.0.2

Install:

flutter pub get

Basic Usage

import 'package:gameframework_unreal/gameframework_unreal.dart';

class MyGameScreen extends StatefulWidget {
  @override
  _MyGameScreenState createState() => _MyGameScreenState();
}

class _MyGameScreenState extends State<MyGameScreen> {
  UnrealController? _controller;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GameEngineWidget(
        engineType: GameEngineType.unreal,
        onControllerCreated: (controller) {
          _controller = controller as UnrealController;
          _initializeUnreal();
        },
      ),
    );
  }

  Future<void> _initializeUnreal() async {
    // Wait for engine to be ready
    await _controller!.create();

    // Apply quality settings
    await _controller!.applyQualitySettings(
      UnrealQualitySettings.high(),
    );

    // Load initial level
    await _controller!.loadLevel('MainMenu');

    // Listen for messages from Unreal
    _controller!.messages.listen((message) {
      print('Message from Unreal: ${message.method}');
    });
  }

  @override
  void dispose() {
    _controller?.quit();
    super.dispose();
  }
}

Core API

UnrealController

The main interface for controlling Unreal Engine:

// Lifecycle
await controller.create();           // Initialize engine
await controller.pause();             // Pause rendering
await controller.resume();            // Resume rendering
await controller.unload();            // Unload but keep ready
await controller.quit();              // Complete shutdown

// Communication
await controller.sendMessage('GameManager', 'onScoreChanged', '{"score": 100}');
await controller.sendJsonMessage('Player', 'takeDamage', {'amount': 25});

// Unreal-specific
await controller.executeConsoleCommand('stat fps');
await controller.loadLevel('Level_01');
await controller.applyQualitySettings(UnrealQualitySettings.epic());

// Get current settings
final settings = await controller.getQualitySettings();

Advanced Features

Binary Messaging

Send binary data efficiently with compression and chunked transfers:

import 'package:gameframework_unreal/gameframework_unreal.dart';

// Send binary data
final imageBytes = await loadImage();
await controller.sendBinaryMessage('TextureManager', 'updateTexture', imageBytes);

// Send compressed binary data (auto-compresses with GZip)
await controller.sendCompressedMessage('AssetLoader', 'loadAsset', largeData);

// Send large data in chunks (for data > 64KB)
await controller.sendChunkedBinaryMessage(
  'AssetLoader',
  'loadLargeAsset',
  veryLargeData,
  chunkSize: 32 * 1024, // 32KB chunks
);

// Listen for binary progress
controller.binaryProgressStream.listen((progress) {
  print('Transfer: ${progress.progress * 100}%');
});

// Access the binary protocol directly
final protocol = controller.binaryProtocol;
final compressed = protocol.compressGzip(data);
final encoded = protocol.encodeBase64(compressed);

Message Batching

Optimize high-frequency messaging with batching:

import 'package:gameframework_unreal/gameframework_unreal.dart';

// Create a batcher
final batcher = UnrealMessageBatcher(
  onFlush: (batch) {
    for (final msg in batch) {
      controller.sendMessage(msg['target'], msg['method'], msg['data']);
    }
  },
);

// Configure batching
batcher.configure(
  maxBatchSize: 50,        // Flush when 50 messages queued
  flushIntervalMs: 16,     // Or every 16ms (60 FPS)
  enableCoalescing: true,  // Combine duplicate messages
);

// Queue messages - they'll be batched automatically
batcher.queue('Player', 'updatePosition', '{"x": 10, "y": 20}');
batcher.queue('Player', 'updatePosition', '{"x": 11, "y": 21}'); // Coalesced
batcher.queue('Enemy', 'updateHealth', '{"hp": 50}');

// Manual flush if needed
final batch = batcher.flush();

// Get statistics
final stats = batcher.statistics;
print('Total queued: ${stats.totalMessagesQueued}');
print('Coalesced: ${stats.totalMessagesCoalesced}');

// Cleanup
batcher.dispose();

Message Throttling

Rate-limit high-frequency events:

import 'package:gameframework_unreal/gameframework_unreal.dart';

// Create a throttler
final throttler = UnrealMessageThrottler();

// Set rate limits (messages per second)
throttler.setRateLimit('Player', 'updatePosition', 30); // Max 30/sec
throttler.setRateLimit('UI', 'updateScore', 10);         // Max 10/sec

// Throttle strategies
throttler.setRateLimit('Input', 'mouseMove', 60, 
  strategy: ThrottleStrategy.keepLatest); // Keep most recent

throttler.setRateLimit('Network', 'sync', 5,
  strategy: ThrottleStrategy.queue);       // Queue for later

// Send throttled messages
await throttler.send(
  controller,
  'Player',
  'updatePosition',
  '{"x": 100, "y": 200}',
);

// Flush any pending messages
await throttler.flushPending();

// Statistics
final stats = throttler.statistics;
print('Dropped: ${stats.messagesDropped}');
print('Queued: ${stats.messagesQueued}');

throttler.dispose();

Delta Compression

Efficiently synchronize state with delta encoding:

import 'package:gameframework_unreal/gameframework_unreal.dart';

// Create a compressor
final compressor = UnrealDeltaCompressor();

// Configure
compressor.configure(
  maxHistorySize: 10,        // Keep last 10 states
  enableDeepComparison: true,
);

// Compute delta between states
final oldState = {'x': 0, 'y': 0, 'health': 100};
final newState = {'x': 10, 'y': 0, 'health': 75};

final delta = compressor.computeDelta('player', oldState, newState);

if (delta.hasChanges) {
  // Only send changed values: {'x': 10, 'health': 75}
  controller.sendJsonMessage('Player', 'syncState', delta.delta);
}

// Record state for future deltas
compressor.recordState('player', newState);

// Compute delta from history
final nextState = {'x': 15, 'y': 5, 'health': 75};
final nextDelta = compressor.computeDeltaFromHistory('player', nextState);
// Only {'x': 15, 'y': 5} will be in delta

// Apply delta on receiving end
final baseState = {'x': 0, 'y': 0, 'health': 100};
final applied = compressor.applyDelta(baseState, delta.delta);

// Statistics
final stats = compressor.statistics;
print('Compression ratio: ${stats.averageCompressionRatio}');

compressor.dispose();

Asset Manager

Manage asset loading with progress tracking and caching:

import 'package:gameframework_unreal/gameframework_unreal.dart';

// Create asset manager
final assetManager = UnrealAssetManager();

// Configure cache
assetManager.setCacheMaxSize(512 * 1024 * 1024); // 512 MB

// Load single asset
await assetManager.loadAsset('/Game/Textures/PlayerTexture');

// Load multiple assets with progress
assetManager.startBatchLoad([
  '/Game/Meshes/Character',
  '/Game/Textures/CharacterSkin',
  '/Game/Animations/Walk',
]);

// Monitor progress
while (!assetManager.isBatchComplete) {
  print('Progress: ${(assetManager.batchProgress * 100).toInt()}%');
  await Future.delayed(Duration(milliseconds: 100));
}

// Check if loaded
if (assetManager.isLoaded('/Game/Textures/PlayerTexture')) {
  final info = assetManager.getAssetInfo('/Game/Textures/PlayerTexture');
  print('Size: ${info?.sizeBytes} bytes');
  print('Load time: ${info?.loadTimeMs}ms');
}

// Load level
await assetManager.loadLevel('MainMenu');

// Unload assets
assetManager.unloadAsset('/Game/Textures/OldTexture');

// Cache management
print('Cache size: ${assetManager.currentCacheSize} bytes');
assetManager.clearCache();

// Statistics
final stats = assetManager.statistics;
print('Cache hit rate: ${(stats.cacheHitRate * 100).toInt()}%');
print('Total loaded: ${stats.totalAssetsLoaded}');

assetManager.dispose();

Quality Settings

Control Unreal Engine's quality with presets or custom settings:

// Use presets
await controller.applyQualitySettings(UnrealQualitySettings.low());      // Mobile/low-end
await controller.applyQualitySettings(UnrealQualitySettings.medium());   // Balanced
await controller.applyQualitySettings(UnrealQualitySettings.high());     // High-end devices
await controller.applyQualitySettings(UnrealQualitySettings.epic());     // Very high quality
await controller.applyQualitySettings(UnrealQualitySettings.cinematic());// Maximum quality

// Custom settings
await controller.applyQualitySettings(
  UnrealQualitySettings(
    qualityLevel: 3,              // Overall level (0-4)
    antiAliasingQuality: 4,       // Anti-aliasing (0-4)
    shadowQuality: 3,             // Shadows (0-4)
    postProcessQuality: 4,        // Post-processing (0-4)
    textureQuality: 4,            // Textures (0-4)
    effectsQuality: 3,            // Effects (0-4)
    foliageQuality: 2,            // Foliage (0-4)
    viewDistanceQuality: 3,       // View distance (0-4)
    targetFrameRate: 60,          // Target FPS
    enableVSync: false,           // VSync on/off
    resolutionScale: 1.0,         // Resolution scale (0.5-2.0)
  ),
);

// Get current settings
final currentSettings = await controller.getQualitySettings();
print('Current quality level: ${currentSettings.qualityLevel}');

Console Commands

Execute Unreal Engine console commands:

// Performance monitoring
await controller.executeConsoleCommand('stat fps');
await controller.executeConsoleCommand('stat unit');
await controller.executeConsoleCommand('stat gpu');

// Quality overrides
await controller.executeConsoleCommand('r.SetRes 1920x1080');
await controller.executeConsoleCommand('r.VSync 0');
await controller.executeConsoleCommand('sg.ViewDistanceQuality 3');

// Debugging
await controller.executeConsoleCommand('showdebug');
await controller.executeConsoleCommand('freezerendering');

See CONSOLE_COMMANDS.md for a complete reference.

Level Loading

Load levels/maps dynamically:

// Load a level
await controller.loadLevel('MainMenu');
await controller.loadLevel('Level_01');
await controller.loadLevel('/Game/Maps/Arena');

// Listen for level loads
controller.sceneLoads.listen((scene) {
  print('Level loaded: ${scene.name}');
  print('Build index: ${scene.buildIndex}');
  print('Is loaded: ${scene.isLoaded}');
});

See LEVEL_LOADING.md for more details.

Event Streams

Listen to engine events:

// Lifecycle events
controller.events.listen((event) {
  switch (event.type) {
    case GameEngineEventType.created:
      print('Engine created');
      break;
    case GameEngineEventType.loaded:
      print('Engine loaded');
      break;
    case GameEngineEventType.paused:
      print('Engine paused');
      break;
    case GameEngineEventType.resumed:
      print('Engine resumed');
      break;
    case GameEngineEventType.unloaded:
      print('Engine unloaded');
      break;
    case GameEngineEventType.destroyed:
      print('Engine destroyed');
      break;
    case GameEngineEventType.error:
      print('Engine error: ${event.message}');
      break;
  }
});

// Messages from Unreal
controller.messages.listen((message) {
  print('From: ${message.target}');
  print('Method: ${message.method}');
  print('Data: ${message.data}');

  // Parse JSON data
  if (message.data != null) {
    final json = jsonDecode(message.data!);
    // Handle data...
  }
});

// Scene/level loads
controller.sceneLoads.listen((scene) {
  print('Level: ${scene.name}');
  print('Index: ${scene.buildIndex}');
});

Unreal Engine Integration

Setting Up Your Unreal Project

  1. Add Flutter Plugin to Your Unreal Project:
YourUnrealProject/
├── Plugins/
│   └── FlutterPlugin/
│       ├── FlutterPlugin.uplugin
│       ├── Source/
│       │   └── FlutterPlugin/
│       │       ├── Public/
│       │       │   └── FlutterBridge.h
│       │       └── Private/
│       │           ├── FlutterBridge.cpp
│       │           └── Android/
│       │               └── FlutterBridge_Android.cpp
  1. Add FlutterBridge Actor to Your Level:

In your Unreal Editor:

  • Drag FlutterBridge actor into your level
  • Or create it programmatically in your GameMode
  1. Use in Blueprints:

Send message to Flutter:

FlutterBridge → SendToFlutter
  Target: "GameManager"
  Method: "onScoreChanged"
  Data: "{\"score\": 100}"

Receive messages from Flutter:

FlutterBridge → OnMessageFromFlutter (Event)
  → Print String (Target)
  → Print String (Method)
  → Print String (Data)

Execute console commands:

FlutterBridge → ExecuteConsoleCommand
  Command: "stat fps"

Load levels:

FlutterBridge → LoadLevel
  LevelName: "Level_01"

Lifecycle events:

FlutterBridge → OnEnginePausedBP (Event)
FlutterBridge → OnEngineResumedBP (Event)
FlutterBridge → OnEngineQuitBP (Event)

C++ Usage

// Get FlutterBridge instance
AFlutterBridge* Bridge = AFlutterBridge::GetInstance(GetWorld());

// Send message to Flutter
Bridge->SendToFlutter(TEXT("GameManager"), TEXT("onScoreChanged"), TEXT("{\"score\": 100}"));

// Execute console command
Bridge->ExecuteConsoleCommand(TEXT("stat fps"));

// Load level
Bridge->LoadLevel(TEXT("Level_01"));

// Apply quality settings
Bridge->ApplyQualitySettings(
    3,  // Quality level
    4,  // Anti-aliasing
    3,  // Shadows
    4,  // Post-process
    4,  // Textures
    3,  // Effects
    2,  // Foliage
    3   // View distance
);

// Get quality settings
TMap<FString, int32> Settings = Bridge->GetQualitySettings();
int32 AAQuality = Settings[TEXT("antiAliasing")];

Platform-Specific Setup

Android

See SETUP_GUIDE.md#Android for complete instructions.

Requirements:

  • Unreal Engine 5.3.x or 5.4.x Android build
  • Android NDK r25 or later
  • Minimum API level 21

Key Steps:

  1. Build your Unreal project for Android
  2. Configure Flutter app to embed Unreal APK
  3. Add required permissions to AndroidManifest.xml

iOS

See SETUP_GUIDE.md#iOS for complete instructions.

Requirements:

  • Unreal Engine 5.3.x or 5.4.x iOS framework
  • Xcode 14.0 or later
  • iOS 12.0+

Key Steps:

  1. Build your Unreal project for iOS
  2. Embed UnrealFramework.framework in Flutter app
  3. Configure build settings in Xcode

macOS

See SETUP_GUIDE.md#macOS for complete instructions.

Requirements:

  • Unreal Engine 5.3.x or 5.4.x macOS build
  • Xcode 14.0 or later
  • macOS 10.14+

Windows & Linux

See SETUP_GUIDE.md#Windows and SETUP_GUIDE.md#Linux.

Examples

Basic Example

import 'package:flutter/material.dart';
import 'package:gameframework_unreal/gameframework_unreal.dart';

void main() {
  // Initialize Unreal Engine plugin
  UnrealEnginePlugin.initialize();

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: GameScreen(),
    );
  }
}

class GameScreen extends StatefulWidget {
  @override
  _GameScreenState createState() => _GameScreenState();
}

class _GameScreenState extends State<GameScreen> {
  UnrealController? _controller;
  String _statusText = 'Initializing...';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Unreal Engine Game')),
      body: Stack(
        children: [
          // Unreal Engine rendering view
          GameEngineWidget(
            engineType: GameEngineType.unreal,
            onControllerCreated: _onControllerCreated,
          ),

          // UI overlay
          Positioned(
            top: 16,
            left: 16,
            child: Container(
              padding: EdgeInsets.all(8),
              color: Colors.black54,
              child: Text(
                _statusText,
                style: TextStyle(color: Colors.white),
              ),
            ),
          ),

          // Controls
          Positioned(
            bottom: 16,
            left: 16,
            right: 16,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton(
                  onPressed: _showFPS,
                  child: Text('Show FPS'),
                ),
                ElevatedButton(
                  onPressed: _changeQuality,
                  child: Text('Change Quality'),
                ),
                ElevatedButton(
                  onPressed: _loadLevel,
                  child: Text('Load Level'),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  void _onControllerCreated(GameEngineController controller) {
    _controller = controller as UnrealController;
    _initializeGame();
  }

  Future<void> _initializeGame() async {
    setState(() => _statusText = 'Loading...');

    // Create engine
    await _controller!.create();

    // Apply quality settings
    await _controller!.applyQualitySettings(UnrealQualitySettings.high());

    // Load first level
    await _controller!.loadLevel('MainMenu');

    // Listen for events
    _controller!.events.listen((event) {
      setState(() => _statusText = 'Event: ${event.type}');
    });

    // Listen for messages
    _controller!.messages.listen((message) {
      print('Message from Unreal: ${message.method}');
    });

    setState(() => _statusText = 'Ready');
  }

  Future<void> _showFPS() async {
    await _controller?.executeConsoleCommand('stat fps');
  }

  Future<void> _changeQuality() async {
    await _controller?.applyQualitySettings(UnrealQualitySettings.epic());
    setState(() => _statusText = 'Quality: Epic');
  }

  Future<void> _loadLevel() async {
    await _controller?.loadLevel('Level_01');
    setState(() => _statusText = 'Loading Level_01...');
  }

  @override
  void dispose() {
    _controller?.quit();
    super.dispose();
  }
}

Advanced Example with Custom Quality

class AdvancedGameScreen extends StatefulWidget {
  @override
  _AdvancedGameScreenState createState() => _AdvancedGameScreenState();
}

class _AdvancedGameScreenState extends State<AdvancedGameScreen> {
  UnrealController? _controller;
  UnrealQualitySettings _currentSettings = UnrealQualitySettings.high();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          // Game view
          Expanded(
            child: GameEngineWidget(
              engineType: GameEngineType.unreal,
              onControllerCreated: (controller) {
                _controller = controller as UnrealController;
                _controller!.create();
              },
            ),
          ),

          // Quality controls
          Container(
            padding: EdgeInsets.all(16),
            child: Column(
              children: [
                _buildQualitySlider('Overall Quality', 0, 4,
                  _currentSettings.qualityLevel?.toDouble() ?? 3.0,
                  (value) => _updateQuality(qualityLevel: value.toInt()),
                ),
                _buildQualitySlider('Anti-Aliasing', 0, 4,
                  _currentSettings.antiAliasingQuality?.toDouble() ?? 3.0,
                  (value) => _updateQuality(antiAliasingQuality: value.toInt()),
                ),
                _buildQualitySlider('Shadows', 0, 4,
                  _currentSettings.shadowQuality?.toDouble() ?? 3.0,
                  (value) => _updateQuality(shadowQuality: value.toInt()),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildQualitySlider(
    String label,
    double min,
    double max,
    double value,
    ValueChanged<double> onChanged,
  ) {
    return Row(
      children: [
        SizedBox(
          width: 120,
          child: Text(label),
        ),
        Expanded(
          child: Slider(
            min: min,
            max: max,
            divisions: max.toInt(),
            value: value,
            onChanged: onChanged,
          ),
        ),
        Text(value.toInt().toString()),
      ],
    );
  }

  Future<void> _updateQuality({
    int? qualityLevel,
    int? antiAliasingQuality,
    int? shadowQuality,
  }) async {
    _currentSettings = UnrealQualitySettings(
      qualityLevel: qualityLevel ?? _currentSettings.qualityLevel,
      antiAliasingQuality: antiAliasingQuality ?? _currentSettings.antiAliasingQuality,
      shadowQuality: shadowQuality ?? _currentSettings.shadowQuality,
      postProcessQuality: _currentSettings.postProcessQuality,
      textureQuality: _currentSettings.textureQuality,
      effectsQuality: _currentSettings.effectsQuality,
      foliageQuality: _currentSettings.foliageQuality,
      viewDistanceQuality: _currentSettings.viewDistanceQuality,
    );

    await _controller?.applyQualitySettings(_currentSettings);
    setState(() {});
  }
}

Performance Tips

  1. Use Quality Presets: Start with presets and adjust based on device capability
  2. Monitor FPS: Use stat fps console command during development
  3. Adjust Resolution Scale: Lower resolution scale for better performance
  4. Disable VSync on Mobile: Can improve responsiveness
  5. Profile with Unreal Insights: Use Unreal's profiling tools

See QUALITY_SETTINGS_GUIDE.md for detailed optimization tips.

Troubleshooting

Common Issues

Engine not starting:

  • Check Unreal framework/APK is properly embedded
  • Verify minimum platform versions
  • Check logs for errors

Black screen:

  • Ensure Unreal view is properly attached
  • Check quality settings aren't too high for device
  • Verify level is loaded correctly

Messages not received:

  • Check FlutterBridge actor is in the level
  • Verify message target and method names match
  • Check JSON formatting

See TROUBLESHOOTING.md for more solutions.

Documentation

API Reference

See API documentation on pub.dev.

Requirements

Flutter

  • Flutter 3.10.0 or later
  • Dart 3.0.0 or later

Unreal Engine

  • Unreal Engine 5.3.x or 5.4.x
  • Visual Studio 2022 (Windows)
  • Xcode 14+ (macOS/iOS)
  • Android NDK r25+ (Android)

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

Support

Credits

Created and maintained by xraph.

Built with Flutter and Unreal Engine.

Libraries

gameframework_unreal
Unreal Engine plugin for Flutter Game Framework