colyseus 0.18.0
colyseus: ^0.18.0 copied to clipboard
Colyseus multiplayer game client for Flutter, backed by the native C SDK.
Colyseus for Flutter #
The Colyseus multiplayer client, as dart:ffi bindings
over the native C SDK. Prebuilt native libraries ship with the package, so
there is no toolchain to install.
Supports macOS, iOS, Android, Linux and Windows. Web is not supported: that target needs the SDK's Emscripten build and a different transport.
Install #
flutter pub add colyseus
macOS and iOS apps need the outbound-network entitlement
(com.apple.security.network.client) in both DebugProfile.entitlements and
Release.entitlements. Without it every connection fails silently inside the
sandbox, and flutter create does not add it.
Use #
import 'package:colyseus/colyseus.dart';
final client = ColyseusClient('ws://localhost:2567');
final room = await client.joinOrCreate('my_room');
room.onStateChange.listen((_) {
final players = room.state!.getMap('players')!;
for (final entry in players.entries) { ... }
});
room.onMessage('chat').listen((data) => print(data['text']));
room.send('move', {'x': 10, 'y': 20});
Prediction #
Waiting for the server to confirm your own movement costs a round trip. The predict layer applies each input immediately and reconciles when the server disagrees:
Colyseus.autoPoll = false; // the app drives the frame
final predict = Predict.of(room);
final input = room.input()!;
// Other players: smoothed, since their inputs aren't yours to predict.
predict.attachAll('players',
config: {'x': PredictMode.damped, 'y': PredictMode.damped},
exceptKey: room.sessionId);
// Yours: predicted and reconciled.
final me = room.state!.getMap('players')![room.sessionId] as SchemaInstance;
final recon = predict.reconciler(me,
input: input,
fields: const ['x', 'y', 'vx', 'vy'],
step: (ctx, state, cmd) => stepPlayer(state, cmd, ctx.dt), // shared with the server
);
void onFrame() {
Colyseus.pump(); // decode inbound, deliver events
final steps = predict.tick(clock.now);
for (var i = 0; i < steps; i++) {
input.data['moveX'] = keyboard.x;
input.send(); // predicted immediately
}
draw(recon.value('x'), recon.value('y'));
}
step has to compute exactly what the server computes. When it does,
recon.drift.ema stays at the floating-point noise floor; when it drifts, that
number tells you.
HTTP and auth #
final res = await client.http.get('/test');
print(res.json['things']);
await client.http.post('/save', body: {'name': 'endel'});
final data = await client.auth.signInAnonymously();
print(data.user?['anonymousId']);
// The token is shared with client.http, so later requests are authenticated.
client.auth.onChange.listen((d) {
if (d.token == null) showLoginScreen();
});
Both run on a worker thread and answer through a NativeCallable.listener, so
neither call stalls the frame loop. A non-2xx reply throws
ColyseusHttpException; a rejected auth call throws ColyseusAuthException.
The token is persisted through the platform's secure storage under one process-wide key, so it survives a restart and leaks between test runs. A suite that signs in should sign out again, or later clients will send a token the next server rejects.