Lifecycle API
Complete reference for managing the game engine lifecycle including creation, pause/resume, unload, and disposal.
GameEngineController Lifecycle Methods
create()
Create and initialize the engine player.
Future<bool> create()Returns: true if creation was successful
Usage:
// Manual engine start (when runImmediately is false)
if (!await controller.isReady()) {
final success = await controller.create();
if (success) {
print('Engine started successfully');
}
}If GameEngineConfig.runImmediately is true (default), the engine starts automatically and you don't need to call create().
pause()
Pause the engine execution.
Future<void> pause()Usage:
// Pause when app goes to background
await controller.pause();When to use:
- App goes to background
- User pauses the game
- Modal dialog is shown
- User switches tabs
resume()
Resume the engine execution after pause.
Future<void> resume()Usage:
// Resume when app comes to foreground
await controller.resume();unload()
Unload the engine (keeps in memory but stops execution).
Future<void> unload()Usage:
// Temporarily stop engine execution
await controller.unload();
// Later, restart if needed
await controller.create();Use cases:
- Switching between multiple game instances
- Temporarily freeing resources
- Preserving state while paused for extended period
quit()
Completely quit and destroy the engine.
Future<void> quit()Usage:
// Completely terminate the engine
await controller.quit();After calling quit(), the engine cannot be restarted. You must create a new GameWidget instance.
dispose()
Dispose of the controller and clean up resources.
void dispose()Usage:
@override
void dispose() {
_controller?.dispose();
super.dispose();
}Important: Always call dispose() in your widget's dispose() method to prevent memory leaks.
State Query Methods
isReady()
Check if the engine is initialized and ready.
Future<bool> isReady()Usage:
if (await controller.isReady()) {
// Engine is ready - send messages
await controller.sendMessage('GameManager', 'Start', 'data');
}isPaused()
Check if the engine is currently paused.
Future<bool> isPaused()Usage:
if (await controller.isPaused()) {
print('Engine is paused');
} else {
print('Engine is running');
}isLoaded()
Check if the engine is loaded.
Future<bool> isLoaded()Usage:
if (await controller.isLoaded()) {
// Engine is loaded and can be used
}isInBackground()
Check if the engine is running in background.
Future<bool> isInBackground()Usage:
if (await controller.isInBackground()) {
// Engine is in background - reduce updates
}Engine States
The engine can be in one of these states:
stateDiagram-v2
[*] --> Created: create()
Created --> Running: Engine starts
Running --> Paused: pause()
Paused --> Running: resume()
Running --> Unloaded: unload()
Unloaded --> Running: create()
Running --> Destroyed: quit()
Paused --> Destroyed: quit()
Unloaded --> Destroyed: quit()
Destroyed --> [*]State Descriptions
| State | Description | Can Send Messages? |
|---|---|---|
| Created | Engine initialized but not running | No |
| Running | Engine actively running | Yes |
| Paused | Execution paused, state preserved | Yes (buffered) |
| Unloaded | Stopped but can be restarted | No |
| Destroyed | Completely terminated | No |
Lifecycle Patterns
Basic Lifecycle Management
class GameScreen extends StatefulWidget {
@override
State<GameScreen> createState() => _GameScreenState();
}
class _GameScreenState extends State<GameScreen> with WidgetsBindingObserver {
GameEngineController? _controller;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_controller?.dispose();
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.paused:
_controller?.pause();
break;
case AppLifecycleState.resumed:
_controller?.resume();
break;
case AppLifecycleState.inactive:
case AppLifecycleState.detached:
case AppLifecycleState.hidden:
break;
}
}
@override
Widget build(BuildContext context) {
return GameWidget(
engineType: GameEngineType.unity,
onEngineCreated: (controller) {
setState(() => _controller = controller);
},
);
}
}Manual Control Pattern
class _GameScreenState extends State<GameScreen> {
GameEngineController? _controller;
bool _isEngineRunning = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: GameWidget(
engineType: GameEngineType.unity,
config: GameEngineConfig(
runImmediately: false, // Manual control
),
onEngineCreated: (controller) {
setState(() => _controller = controller);
},
),
floatingActionButton: FloatingActionButton(
onPressed: _toggleEngine,
child: Icon(_isEngineRunning ? Icons.pause : Icons.play_arrow),
),
);
}
Future<void> _toggleEngine() async {
if (_controller == null) return;
if (_isEngineRunning) {
await _controller!.pause();
setState(() => _isEngineRunning = false);
} else {
if (await _controller!.isReady()) {
await _controller!.resume();
} else {
await _controller!.create();
}
setState(() => _isEngineRunning = true);
}
}
@override
void dispose() {
_controller?.quit();
super.dispose();
}
}Multiple Game Instances Pattern
class _MultiGameScreenState extends State<MultiGameScreen> {
GameEngineController? _game1Controller;
GameEngineController? _game2Controller;
int _activeGame = 1;
Future<void> _switchToGame(int gameNumber) async {
if (gameNumber == 1) {
// Unload game 2, load game 1
await _game2Controller?.unload();
if (await _game1Controller?.isLoaded() ?? false) {
await _game1Controller?.create();
}
} else {
// Unload game 1, load game 2
await _game1Controller?.unload();
if (await _game2Controller?.isLoaded() ?? false) {
await _game2Controller?.create();
}
}
setState(() => _activeGame = gameNumber);
}
@override
void dispose() {
_game1Controller?.quit();
_game2Controller?.quit();
super.dispose();
}
}Platform-Specific Behavior
iOS Background Handling
iOS automatically pauses engines when app goes to background:
class _GameScreenState extends State<GameScreen> with WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (Platform.isIOS) {
switch (state) {
case AppLifecycleState.paused:
// iOS: Engine auto-pauses, save state if needed
_saveGameState();
break;
case AppLifecycleState.resumed:
// iOS: Engine auto-resumes
_restoreGameState();
break;
default:
break;
}
}
}
}Android Background Handling
Android requires explicit pause/resume:
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (Platform.isAndroid) {
switch (state) {
case AppLifecycleState.paused:
_controller?.pause();
break;
case AppLifecycleState.resumed:
_controller?.resume();
break;
default:
break;
}
}
}Desktop (macOS, Windows, Linux)
Desktop platforms handle background differently:
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (Platform.isMacOS || Platform.isWindows || Platform.isLinux) {
switch (state) {
case AppLifecycleState.inactive:
// Window lost focus - optionally pause
if (_pauseWhenInactive) {
_controller?.pause();
}
break;
case AppLifecycleState.resumed:
_controller?.resume();
break;
default:
break;
}
}
}Event Streams
eventStream
Stream of engine lifecycle events.
Stream<GameEngineEvent> get eventStream;Usage:
controller.eventStream.listen((event) {
switch (event.type) {
case GameEngineEventType.created:
print('Engine created');
break;
case GameEngineEventType.started:
print('Engine started');
break;
case GameEngineEventType.paused:
print('Engine paused');
break;
case GameEngineEventType.resumed:
print('Engine resumed');
break;
case GameEngineEventType.stopped:
print('Engine stopped');
break;
}
});Best Practices
1. Always Dispose Controllers
@override
void dispose() {
_controller?.dispose();
super.dispose();
}2. Handle App Lifecycle
class _GameScreenState extends State<GameScreen> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_controller?.dispose();
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
// Handle state changes
}
}3. Check State Before Operations
Future<void> _sendMessage() async {
if (await _controller?.isReady() ?? false) {
await _controller!.sendMessage('Target', 'Method', 'data');
} else {
print('Engine not ready');
}
}4. Graceful Shutdown
Future<void> _cleanup() async {
// Save state
await _saveGameState();
// Wait for pending operations
await Future.delayed(Duration(milliseconds: 100));
// Quit engine
await _controller?.quit();
}5. Handle Errors
Future<void> _pauseEngine() async {
try {
await _controller?.pause();
} catch (e) {
print('Failed to pause engine: $e');
// Handle error gracefully
}
}Common Patterns
Save State on Pause
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.paused) {
_saveGameState().then((_) {
_controller?.pause();
});
}
}Lazy Engine Start
class _GameScreenState extends State<GameScreen> {
GameEngineController? _controller;
bool _hasUserInteracted = false;
void _onUserInteraction() {
if (!_hasUserInteracted && _controller != null) {
_hasUserInteracted = true;
_controller!.create();
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _onUserInteraction,
child: GameWidget(
engineType: GameEngineType.unity,
config: GameEngineConfig(runImmediately: false),
onEngineCreated: (controller) {
setState(() => _controller = controller);
},
),
);
}
}Proper lifecycle management ensures smooth user experience and prevents memory leaks!