Game Framework
SDK DocumentationAPI Reference

Messaging API

Complete reference for sending messages to and receiving messages from game engines.

GameEngineController Methods

The GameEngineController provides methods for bidirectional communication between Flutter and game engines.

sendMessage

Send a string message to the engine.

Future<void> sendMessage(
  String target,
  String method,
  String data,
)

Parameters:

  • target: Target object/actor name in the engine
  • method: Method/function name to call
  • data: String data to send

Example:

await controller.sendMessage(
  'GameManager',
  'SetDifficulty',
  'Hard',
);

sendJsonMessage

Send structured data as JSON to the engine.

Future<void> sendJsonMessage(
  String target,
  String method,
  Map<String, dynamic> data,
)

Parameters:

  • target: Target object/actor name in the engine
  • method: Method/function name to call
  • data: Map that will be automatically serialized to JSON

Example:

await controller.sendJsonMessage(
  'Player',
  'LoadProfile',
  {
    'playerId': 'user_123',
    'level': 15,
    'inventory': ['sword', 'shield'],
    'stats': {
      'health': 100,
      'mana': 50,
    },
  },
);

JSON messages are automatically serialized and deserialized, providing type-safe communication.

Receiving Messages

messageStream

Stream of messages from the engine.

Stream<GameEngineMessage> get messageStream;

Usage:

controller.messageStream.listen((message) {
  print('Received from ${message.target}.${message.method}');
  print('Data: ${message.data}');
});

GameEngineMessage

Message object received from the engine.

class GameEngineMessage {
  final String target;   // Target object name
  final String method;   // Method name
  final String data;     // Message data (string or JSON string)
}

Message Types

String Messages

Simple string-based communication.

Flutter → Engine

await controller.sendMessage('Player', 'Jump', '');
await controller.sendMessage('GameManager', 'LoadLevel', 'Level2');

Engine → Flutter

controller.messageStream.listen((message) {
  if (message.method == 'OnLevelComplete') {
    final levelName = message.data;
    print('Completed: $levelName');
  }
});

JSON Messages

Structured data with type safety.

Flutter → Engine

await controller.sendJsonMessage('Player', 'TakeDamage', {
  'amount': 25,
  'damageType': 'fire',
  'source': 'Dragon',
});

await controller.sendJsonMessage('GameManager', 'SaveGame', {
  'slotId': 1,
  'playerData': {
    'position': {'x': 10.5, 'y': 0, 'z': 20.3},
    'rotation': {'x': 0, 'y': 180, 'z': 0},
  },
  'gameState': {
    'level': 5,
    'score': 1500,
    'time': 3600,
  },
});

Engine → Flutter

controller.messageStream.listen((message) {
  if (message.method == 'OnScoreUpdate') {
    // Parse JSON data
    final data = jsonDecode(message.data);
    final score = data['score'] as int;
    final combo = data['combo'] as int;
    final multiplier = data['multiplier'] as double;
    
    setState(() {
      _score = score;
      _combo = combo;
    });
  }
});

Binary Messages

For images, files, and large data transfers. Available in platform-specific implementations.

// Send binary data
final bytes = await file.readAsBytes();
await controller.sendBinaryMessage('TextureManager', 'LoadTexture', bytes);

Binary messaging is engine-specific. Check your engine plugin documentation for availability.

Message Patterns

Request-Response Pattern

Implement async request-response using message IDs.

class MessageManager {
  final _pending = <String, Completer<dynamic>>{};
  
  Future<dynamic> request(
    GameEngineController controller,
    String target,
    String method,
    Map<String, dynamic> params,
  ) async {
    final requestId = Uuid().v4();
    final completer = Completer<dynamic>();
    _pending[requestId] = completer;
    
    await controller.sendJsonMessage(target, method, {
      'requestId': requestId,
      ...params,
    });
    
    return completer.future.timeout(
      Duration(seconds: 5),
      onTimeout: () {
        _pending.remove(requestId);
        throw TimeoutException('Request timed out');
      },
    );
  }
  
  void handleResponse(GameEngineMessage message) {
    final data = jsonDecode(message.data);
    final requestId = data['requestId'] as String?;
    
    if (requestId != null && _pending.containsKey(requestId)) {
      _pending[requestId]!.complete(data);
      _pending.remove(requestId);
    }
  }
}

// Usage
final manager = MessageManager();

// Listen for responses
controller.messageStream.listen((message) {
  if (message.method == 'OnResponse') {
    manager.handleResponse(message);
  }
});

// Make request
final result = await manager.request(
  controller,
  'GameManager',
  'GetPlayerStats',
  {},
);
print('Stats: $result');

Event Broadcasting

Listen for specific game events.

class GameEventHandler {
  final Map<String, List<Function(dynamic)>> _listeners = {};
  
  void on(String eventName, Function(dynamic) callback) {
    _listeners.putIfAbsent(eventName, () => []).add(callback);
  }
  
  void off(String eventName, Function(dynamic) callback) {
    _listeners[eventName]?.remove(callback);
  }
  
  void handleMessage(GameEngineMessage message) {
    if (message.method == 'OnEvent') {
      final data = jsonDecode(message.data);
      final eventName = data['event'] as String;
      final eventData = data['data'];
      
      _listeners[eventName]?.forEach((callback) {
        callback(eventData);
      });
    }
  }
}

// Usage
final events = GameEventHandler();

// Listen to message stream
controller.messageStream.listen(events.handleMessage);

// Register event handlers
events.on('PlayerDied', (data) {
  print('Player died at: ${data['position']}');
});

events.on('LevelComplete', (data) {
  print('Level ${data['level']} completed in ${data['time']}s');
});

events.on('AchievementUnlocked', (data) {
  _showAchievementNotification(data['achievementId']);
});

Typed Messages

Create type-safe message classes.

// Define message types
class PlayerMoveMessage {
  final double x;
  final double y;
  final double z;
  
  PlayerMoveMessage({
    required this.x,
    required this.y,
    required this.z,
  });
  
  Map<String, dynamic> toJson() => {
    'x': x,
    'y': y,
    'z': z,
  };
}

class GameStateMessage {
  final int health;
  final int score;
  final String status;
  
  GameStateMessage.fromJson(Map<String, dynamic> json)
      : health = json['health'],
        score = json['score'],
        status = json['status'];
}

// Send typed message
await controller.sendJsonMessage(
  'Player',
  'MoveTo',
  PlayerMoveMessage(x: 10.5, y: 0, z: 20.3).toJson(),
);

// Receive typed message
controller.messageStream.listen((message) {
  if (message.method == 'OnStateUpdate') {
    final state = GameStateMessage.fromJson(jsonDecode(message.data));
    print('Health: ${state.health}, Score: ${state.score}');
  }
});

Performance Tips

Message Batching

For high-frequency updates, enable batching on the Unity side:

// Unity
protected override void Awake()
{
    base.Awake();
    EnableBatching = true;
    BatchInterval = 0.016f; // 60 FPS
}

Throttling

Throttle rapid Flutter messages:

class MessageThrottler {
  Timer? _timer;
  final Duration interval;
  
  MessageThrottler(this.interval);
  
  void send(
    GameEngineController controller,
    String target,
    String method,
    Map<String, dynamic> data,
  ) {
    _timer?.cancel();
    _timer = Timer(interval, () {
      controller.sendJsonMessage(target, method, data);
    });
  }
  
  void dispose() {
    _timer?.cancel();
  }
}

// Usage
final throttler = MessageThrottler(Duration(milliseconds: 16));

void onSliderChanged(double value) {
  throttler.send(_controller!, 'Player', 'SetSpeed', {
    'speed': value,
  });
}

Minimize Data Size

// Good - only send what's needed
await controller.sendJsonMessage('Player', 'UpdatePosition', {
  'x': position.x,
  'z': position.z,
});

// Avoid - sending unnecessary data
await controller.sendJsonMessage('Player', 'UpdatePosition', {
  'x': position.x,
  'y': position.y,  // Not needed for 2D movement
  'z': position.z,
  'timestamp': DateTime.now().toString(),  // Usually not needed
  'metadata': {...},  // Unnecessary
});

Error Handling

Message Send Errors

try {
  await controller.sendJsonMessage('Player', 'DoAction', {
    'action': 'jump',
  });
} catch (e) {
  print('Failed to send message: $e');
  // Handle error - maybe retry or notify user
}

Message Receive Validation

controller.messageStream.listen((message) {
  try {
    final data = jsonDecode(message.data);
    
    // Validate expected fields
    if (!data.containsKey('score')) {
      print('Warning: score field missing');
      return;
    }
    
    final score = data['score'];
    if (score is! int) {
      print('Warning: score is not an integer');
      return;
    }
    
    // Process valid data
    setState(() => _score = score);
  } catch (e) {
    print('Failed to parse message: $e');
  }
});

Best Practices

1. Use JSON for Structured Data

// Good
await controller.sendJsonMessage('Player', 'SetStats', {
  'health': 100,
  'mana': 50,
});

// Avoid
await controller.sendMessage('Player', 'SetStats', 'health:100,mana:50');

2. Document Your Message Protocol

/// Send player movement command
/// 
/// Target: Player
/// Method: MoveTo
/// Data: {
///   'x': double,      // X position
///   'z': double,      // Z position
///   'speed': double,  // Movement speed (optional)
/// }
Future<void> movePlayer(double x, double z, {double? speed}) async {
  await controller.sendJsonMessage('Player', 'MoveTo', {
    'x': x,
    'z': z,
    if (speed != null) 'speed': speed,
  });
}

3. Handle Message Stream Lifecycle

class _GameScreenState extends State<GameScreen> {
  GameEngineController? _controller;
  StreamSubscription<GameEngineMessage>? _messageSubscription;
  
  void _setupMessageListener() {
    _messageSubscription = _controller?.messageStream.listen((message) {
      // Handle messages
    });
  }
  
  @override
  void dispose() {
    _messageSubscription?.cancel();
    _controller?.dispose();
    super.dispose();
  }
}

Well-structured messaging enables powerful, type-safe communication between Flutter and game engines!

See Also