Server Setup

Configure and run the Web Engine multiplayer server with Colyseus for real-time networked gameplay.

The Web Engine multiplayer system uses Colyseus as its networking backend, providing robust room management, state synchronization, and scalability features.

Server Architecture#

Dedicated Server

Authoritative game server runs game logic and validates all client inputs to prevent cheating.

WebSocket Transport

Real-time bidirectional communication using WebSockets with automatic reconnection support.

Installation#

The multiplayer server is included in the monorepo under apps/server. Start it with:

Terminal
bash
# Start the multiplayer server
pnpm server
# Server will start on ws://localhost:2567

Development Mode

In development, the server automatically reloads when you make changes to server-side code. The server runs independently from the editor/client.

Server Configuration#

Configure the server via environment variables or the configuration file:

.env
bash
# Server configuration
PORT=2567
NODE_ENV=development
# Colyseus configuration
COLYSEUS_MONITOR_PASSWORD=admin123
COLYSEUS_PRESENCE_TYPE=local
# Room configuration
MAX_ROOM_CAPACITY=10
CONNECTION_TIMEOUT=30000
HEARTBEAT_INTERVAL=5000

Creating Rooms#

Rooms are the core concept in Colyseus. Each room manages a game session with its own state.

apps/server/src/rooms/GameRoom.ts
typescript
import { Room, Client } from 'colyseus';
import { GameRoomState } from './schema/GameRoomState';
export class GameRoom extends Room<GameRoomState> {
maxClients = 10;
onCreate(options: any) {
this.setState(new GameRoomState());
// Set up room logic
this.setSimulationInterval((deltaTime) => {
this.state.update(deltaTime);
});
// Set up message handlers
this.onMessage('input', (client, message) => {
this.handlePlayerInput(client, message);
});
console.log('GameRoom created!', options);
}
onJoin(client: Client, options: any) {
console.log(client.sessionId, 'joined!');
// Create player entity
this.state.createPlayer(client.sessionId, options);
}
onLeave(client: Client, consented: boolean) {
console.log(client.sessionId, 'left!');
// Remove player entity
this.state.removePlayer(client.sessionId);
}
onDispose() {
console.log('GameRoom disposed!');
}
private handlePlayerInput(client: Client, message: any) {
// Validate and process player input
const player = this.state.players.get(client.sessionId);
if (player) {
player.processInput(message);
}
}
}

Defining State Schema#

Use Colyseus schemas to define synchronized state. Schemas enable automatic state tracking and delta compression.

apps/server/src/rooms/schema/GameRoomState.ts
typescript
import { Schema, MapSchema, type } from '@colyseus/schema';
export class Player extends Schema {
@type('number') x = 0;
@type('number') y = 0;
@type('number') z = 0;
@type('number') rotX = 0;
@type('number') rotY = 0;
@type('number') rotZ = 0;
@type('number') rotW = 1;
@type('string') name = '';
@type('string') avatarId = '';
}
export class GameRoomState extends Schema {
@type({ map: Player }) players = new MapSchema<Player>();
@type('number') gameTime = 0;
createPlayer(sessionId: string, options: any) {
const player = new Player();
player.name = options.name || 'Player';
player.avatarId = options.avatarId || 'default';
this.players.set(sessionId, player);
}
removePlayer(sessionId: string) {
this.players.delete(sessionId);
}
update(deltaTime: number) {
this.gameTime += deltaTime;
// Update game logic
this.players.forEach((player) => {
// Physics, movement, etc.
});
}
}

Schema Benefits

Colyseus automatically tracks changes to schema properties and sends only deltas to clients, significantly reducing bandwidth usage. The schema is also type-safe on both client and server.

Connection Handling#

The server includes robust connection management with heartbeat monitoring, timeout handling, and automatic reconnection support:

apps/server/src/network/ConnectionManager.ts
typescript
import { getConnectionManager } from './ConnectionManager';
// Initialize connection manager
const connectionManager = getConnectionManager({
heartbeatInterval: 5000, // 5 seconds
connectionTimeout: 30000, // 30 seconds
maxReconnectionAttempts: 5,
autoReconnect: true,
});
// Register connection
connectionManager.registerConnection(clientId);
// Track connection quality
connectionManager.updateQuality(clientId, rtt, packetLoss, jitter);
// Handle heartbeat
connectionManager.updateHeartbeat(clientId);

Connection Quality Monitoring#

The connection manager automatically monitors connection quality and categorizes it:

  • Excellent — RTT < 50ms, Packet loss < 1%, Jitter < 10ms
  • Good — RTT < 100ms, Packet loss < 5%, Jitter < 20ms
  • Fair — RTT < 200ms, Packet loss < 10%, Jitter < 50ms
  • Poor — RTT < 500ms, Packet loss < 20%, Jitter < 100ms
  • Critical — Anything worse than poor

Environment Setup#

Configure different environments for development, staging, and production:

apps/server/src/config/environment.ts
typescript
export const environment = {
development: {
port: 2567,
wsUrl: 'ws://localhost:2567',
corsOrigins: ['http://localhost:3000', 'http://localhost:5173'],
logLevel: 'debug',
},
production: {
port: process.env.PORT || 8080,
wsUrl: process.env.WS_URL || 'wss://game.example.com',
corsOrigins: ['https://game.example.com'],
logLevel: 'info',
},
};
export const config = environment[process.env.NODE_ENV || 'development'];

Connecting from Client#

Connect to the server from your game client:

Client Connection
typescript
import { NetworkManager } from '@web-engine/core/network';
const network = NetworkManager.getInstance();
// Connect to server
await network.connectToColyseus(
'ws://localhost:2567',
'game_room',
{
name: 'Player1',
avatarId: 'avatar-001'
}
);
// Create private room
await network.createPrivateRoom('game_room', {
name: 'Player1',
avatarId: 'avatar-001'
});
// Join by invite code
await network.joinPrivateRoom('ABC123', {
name: 'Player2',
avatarId: 'avatar-002'
});

Server Monitoring#

Colyseus includes a built-in monitoring dashboard accessible at:

http://localhost:2567/colyseus
Login credentials (configure in .env):
Username: admin
Password: admin123

Production Security

Always change the default monitoring password in production environments. Consider disabling the monitor entirely or restricting access via firewall rules.

Best Practices#

  • Validate inputs — Never trust client data. Validate all inputs on the server.
  • Rate limiting — Prevent spam and DoS attacks with rate limiting on message handlers.
  • Graceful degradation — Handle disconnections gracefully with reconnection logic.
  • Monitor performance — Track room count, player count, and server metrics.
  • Secure production — Use WSS (secure WebSocket), change default passwords, enable CORS properly.
Multiplayer | Web Engine Docs | Web Engine Docs