Package Exports
- @martini-kit/transport-colyseus
- @martini-kit/transport-colyseus/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@martini-kit/transport-colyseus) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
@martini-kit/transport-colyseus
Colyseus room adapter for martini-kit multiplayer SDK. Use Colyseus for matchmaking, rooms, and server infrastructure while using martini-kit's declarative API for game logic.
Why Use This?
Best of Both Worlds:
- ✅ Colyseus handles rooms, matchmaking, and server management
- ✅ martini-kit handles game logic with a clean, declarative API
Perfect For:
- Web games that need matchmaking/lobbies
- Server-authoritative multiplayer games
- Production deployments with managed infrastructure
- Teams familiar with Colyseus who want cleaner game logic code
Installation
pnpm add @martini-kit/core @martini-kit/transport-colyseus colyseus.jsQuick Start
Client-Side
import { Client } from 'colyseus.js';
import { defineGame, GameRuntime } from '@martini-kit/core';
import { ColyseusTransport } from '@martini-kit/transport-colyseus';
// Define your game logic with martini-kit
const game = defineGame({
minPlayers: 2,
maxPlayers: 4,
setup: () => ({
players: {},
score: {}
}),
actions: {
move: (state, playerId, input) => {
state.players[playerId] = input.position;
},
updateScore: (state, playerId, points) => {
state.score[playerId] = (state.score[playerId] || 0) + points;
}
}
});
// Connect to Colyseus
const client = new Client('ws://localhost:2567');
const room = await client.joinOrCreate('my_game_room');
// Create martini-kit transport from Colyseus room
const transport = new ColyseusTransport(room);
// Create game runtime
const runtime = new GameRuntime(game, transport, {
isHost: room.sessionId === '...' // Determine host via Colyseus
});
// Use martini-kit's clean API
runtime.submitAction('move', { position: { x: 10, y: 20 } });Server-Side (Colyseus)
Create a Colyseus room that relays martini-kit messages:
import { Room, Client } from '@colyseus/core';
export class martini-kitGameRoom extends Room {
private hostId: string | null = null;
onCreate(options: any) {
console.log('martini-kitGameRoom created');
// Handle martini-kit messages
this.onMessage('martini-kit', (client, message) => {
// Relay to all clients or targeted client
if (message.targetId) {
const targetClient = Array.from(this.clients).find(
c => c.sessionId === message.targetId
);
targetClient?.send('martini-kit', message);
} else {
// Broadcast to all except sender
this.broadcast('martini-kit', message, { except: client });
}
});
}
onJoin(client: Client, options: any) {
console.log(client.sessionId, 'joined');
// Elect first player as host
if (!this.hostId) {
this.hostId = client.sessionId;
this.broadcast('martini-kit', {
type: 'host_announce',
hostId: this.hostId,
senderId: 'server'
});
}
// Notify all clients of new player
this.broadcast('martini-kit', {
type: 'player_join',
payload: { playerId: client.sessionId },
senderId: 'server'
});
// Send current peers list to new player
const peers = Array.from(this.clients).map(c => c.sessionId);
client.send('martini-kit', {
type: 'peers_list',
payload: { peers },
senderId: 'server'
});
}
onLeave(client: Client, consented: boolean) {
console.log(client.sessionId, 'left');
// Notify remaining clients
this.broadcast('martini-kit', {
type: 'player_leave',
payload: { playerId: client.sessionId },
senderId: 'server'
});
// Elect new host if needed
if (client.sessionId === this.hostId && this.clients.length > 0) {
this.hostId = Array.from(this.clients)[0].sessionId;
this.broadcast('martini-kit', {
type: 'host_announce',
hostId: this.hostId,
senderId: 'server'
});
}
}
}API Reference
new ColyseusTransport(room: Room)
Create a martini-kit transport from a Colyseus room.
Parameters:
room: A connected Colyseus room instance
Example:
const room = await client.joinOrCreate('game_room');
const transport = new ColyseusTransport(room);Transport Methods
All standard martini-kit Transport methods are supported:
send(message: WireMessage, targetId?: string): void
Send a message through the Colyseus room.
transport.send({
type: 'action',
payload: { action: 'move', x: 10, y: 20 }
});
// Send to specific player
transport.send({
type: 'state_sync',
payload: { state: {...} }
}, 'player-123');onMessage(handler: (message, senderId) => void): () => void
Listen for messages from other players.
const unsubscribe = transport.onMessage((message, senderId) => {
console.log('Received message from:', senderId, message);
});
// Stop listening
unsubscribe();onPeerJoin(handler: (peerId: string) => void): () => void
Listen for players joining.
transport.onPeerJoin((peerId) => {
console.log('Player joined:', peerId);
});onPeerLeave(handler: (peerId: string) => void): () => void
Listen for players leaving.
transport.onPeerLeave((peerId) => {
console.log('Player left:', peerId);
});getPlayerId(): string
Get your player ID (same as room.sessionId).
const myId = transport.getPlayerId();getPeerIds(): string[]
Get list of all connected players (excluding yourself).
const peers = transport.getPeerIds();
console.log('Connected players:', peers);isHost(): boolean
Check if you are the current host.
if (transport.isHost()) {
console.log('I am the host');
}Additional Methods
onError(handler: (error: Error) => void): () => void
Listen for connection errors.
transport.onError((error) => {
console.error('Transport error:', error);
});disconnect(): void
Leave the Colyseus room and clean up.
transport.disconnect();getRoom(): Room
Get the underlying Colyseus room (for advanced use cases).
const room = transport.getRoom();
console.log('Room ID:', room.id);
console.log('Room state:', room.state);Message Protocol
The transport uses a 'martini-kit' message type on the Colyseus room. All martini-kit messages are wrapped in this format:
{
type: 'action' | 'state_sync' | 'player_join' | 'player_leave' | 'host_announce' | ...,
payload?: any,
senderId: string,
targetId?: string // For targeted messages
}Control Messages
The server should send these control messages:
player_join
{
type: 'player_join',
payload: { playerId: string },
senderId: 'server'
}player_leave
{
type: 'player_leave',
payload: { playerId: string },
senderId: 'server'
}host_announce
{
type: 'host_announce',
hostId: string,
senderId: 'server'
}peers_list
{
type: 'peers_list',
payload: { peers: string[] },
senderId: 'server'
}Complete Example
See examples/colyseus-game for a full working example with:
- Colyseus server setup
- Client-side game logic with martini-kit
- Matchmaking UI
- Host migration handling
Comparison to Other Approaches
Pure Colyseus
// ❌ Imperative, verbose
room.onMessage('move', (message) => {
const player = players.get(message.playerId);
player.x = message.x;
player.y = message.y;
// Sync to other clients...
// Handle edge cases...
// Validate input...
});martini-kit + Colyseus
// ✅ Declarative, concise
const game = defineGame({
actions: {
move: (state, playerId, input) => {
state.players[playerId] = input.position;
}
}
});Best Practices
Use Colyseus for Infrastructure
- Room management
- Matchmaking
- Persistence (via Colyseus state)
- Authentication
Use martini-kit for Game Logic
- Player actions
- Game state updates
- Collision detection
- Game rules
Host Election
- Let Colyseus determine the host
- Send
host_announcemessages - martini-kit will handle host-authoritative logic
Error Handling
- Listen to both Colyseus and martini-kit errors
- Handle reconnection via Colyseus
- martini-kit will auto-resume game state
Troubleshooting
"Messages not being received"
- Ensure your Colyseus server relays
'martini-kit'messages - Check that
onMessage('martini-kit', ...)is set up on the server - Verify the message format includes
senderId
"Host is always false"
- Server must send
host_announcemessages - Check that
hostIdmatches a client'ssessionId
"Peers list is empty"
- Server should send
player_join/player_leavemessages - Or send a
peers_listmessage on join
"TypeScript errors"
- Ensure
colyseus.jsis installed - Check that types are imported correctly:
import type { Room } from 'colyseus.js'
See Also
License
MIT