Skip to content
dev-multiplayer-colyseus-state logo

Colyseus State Schema

dev-multiplayer-colyseus-state

Colyseus state schema definition, types, decorators, and serialization patterns. Use when defining room state.

SKILL.md

Full skill instructions

Colyseus State Schema

Define efficient binary-serializable state for Colyseus rooms using @colyseus/​schema.

When to Use

Use when:

  • Defining room state schemas
  • Creating player/​entity state
  • Setting up state collections
  • Optimizing network bandwidth

Schema Types

import { Schema, type, MapSchema, ArraySchema } from '@colyseus/​schema';

// Primitive types
@type('string')   // String
@type('number')   // Number (float)
@type('uint8')    // Unsigned 8-bit (0-255)
@type('uint16')   // Unsigned 16-bit (0-65535)
@type('uint32')   // Unsigned 32-bit
@type('int8')     // Signed 8-bit
@type('int16')    // Signed 16-bit
@type('int32')    // Signed 32-bit
@type('boolean')  // Boolean
@type('float32')  // 32-bit float

// Collection types
@type({ map: PlayerState })  // Map<string, PlayerState>
@type([PlayerState])         // Array<PlayerState>

Basic Player State

import { Schema, type } from '@colyseus/​schema';

class PlayerState extends Schema {
  @type('string') clientId: string = '';
  @type('uint8') team: number = 0;  // 0 = orange, 1 = blue
  @type('float32') x: number = 0;
  @type('float32') y: number = 0;
  @type('float32') z: number = 0;
  @type('float32') rotation: number = 0;
  @type('uint16') score: number = 0;
  @type('boolean') isAlive: boolean = true;
}

Room State Schema

import { Schema, type, MapSchema } from '@colyseus/​schema';

class GameRoomState extends Schema {
  @type({ map: PlayerState }) players = new MapSchema<PlayerState>();
  @type('uint8') phase: number = 0; // 0=waiting, 1=playing, 2=ended
  @type('uint16') orangeScore: number = 0;
  @type('uint16') blueScore: number = 0;
}

Complex Nested Schema

class Vector3Schema extends Schema {
  @type('float32') x: number = 0;
  @type('float32') y: number = 0;
  @type('float32') z: number = 0;
}

class PlayerState extends Schema {
  @type('string') clientId: string = '';
  @type(Vector3Schema) position: Vector3Schema = new Vector3Schema();
  @type(Vector3Schema) velocity: Vector3Schema = new Vector3Schema();
  @type('uint16') score: number = 0;
  @type('uint8') health: number = 100;
  @type('uint8') inkTank: number = 100;
  @type('boolean') isAlive: boolean = true;
}

class TeamScore extends Schema {
  @type('uint16') paintCoverage: number = 0;
  @type('uint16') kills: number = 0;
  @type('uint16') deaths: number = 0;
  @type('boolean') hasWon: boolean = false;
}

class MatchState extends Schema {
  @type('string') phase: string = 'waiting';
  @type('uint16') timeRemaining: number = 180;
  @type({ map: TeamScore }) teamScores = new MapSchema<TeamScore>();
  @type({ map: PlayerState }) players = new MapSchema<PlayerState>();
  @type([PaintSplat]) paintSplats = new ArraySchema<PaintSplat>();
}

Using State in Room Handler

export class GameRoom extends Room<GameRoomState> {
  onCreate(options: any) {
    this.setState(new GameRoomState());
  }

  onJoin(client: Client, options: any) {
    const player = new PlayerState();
    player.clientId = client.sessionId;
    player.x = 0; player.y = 0; player.z = 0;

    // Assign team
    const orangeCount = this.getOrangeCount();
    const blueCount = this.getBlueCount();
    player.team = orangeCount <= blueCount ? 0 : 1;

    this.state.players.set(client.sessionId, player);
  }

  onLeave(client: Client, consented: boolean) {
    this.state.players.delete(client.sessionId);
  }

  onMessage(client: Client, data: any) {
    const player = this.state.players.get(client.sessionId);
    if (!player) return;

    // Update player state
    if (data.type === 'move') {
      player.x = data.x;
      player.y = data.y;
      player.z = data.z;
    }
  }

  private getOrangeCount(): number {
    return Array.from(this.state.players.values())
      .filter(p => p.team === 0).length;
  }

  private getBlueCount(): number {
    return Array.from(this.state.players.values())
      .filter(p => p.team === 1).length;
  }
}

Array Schema Operations

class MyState extends Schema {
  @type([PlayerState]) players = new ArraySchema<PlayerState>();
}

// Add to array
this.state.players.push(new PlayerState());

// Remove from array
this.state.players.splice(index, 1);

// Iterate
this.state.players.forEach((player, index) => {
  console.log(player.clientId);
});

Type Selection Guidelines

Use CaseTypeBytesRange
Player health 0-100uint810-255
Score 0-65535uint1620-65535
Coordinates (-100 to 100)float324±3.4E38
Team enumuint810-255
Player IDstringvariabletext
Boolean flagboolean1true/​false

Best Practices

  1. Use smallest type that fits - Saves bandwidth
  2. Always add @type decorators - Required for serialization
  3. Use collections efficiently - MapSchema for dynamic keys, ArraySchema for ordered lists
  4. Initialize default values - Prevents undefined issues
  5. Keep state flat - Deep nesting increases complexity

Common Mistakes

❌ Wrong✅ Right
Missing @type decoratorAlways add @type('string')
Using number for small rangesUse uint8, uint16 for savings
Deep nesting (4+ levels)Keep state shallow
Not initializing defaultsSet default: x: number = 0

Reference


Schema Definition Best Practices (Updated 2026-01-28)

From arch-003 retrospective - proven patterns for @colyseus/​schema.

Complete Player Schema Pattern

import { Schema, type } from '@colyseus/​schema';

/​**
 * Player state schema for server-authoritative multiplayer
 *
 * Type Selection Guidelines:
 * - uint8: 0-255 (health, armor, small counters)
 * - uint16: 0-65535 (scores, larger counters)
 * - float32: Coordinates, rotation (precision needed)
 * - string: Variable text (sessionId, weapon names)
 * - boolean: Flags (isAlive, connected)
 */
export class PlayerState extends Schema {
  // Position (float32 for precision)
  @type('float32') x: number = 0;
  @type('float32') y: number = 0;
  @type('float32') z: number = 0;

  // Rotation (degrees or radians)
  @type('float32') rotation: number = 0;

  // Combat stats (uint8 sufficient for 0-100 ranges)
  @type('uint8') health: number = 100;
  @type('uint8') armor: number = 0;

  // Weapon (string for flexibility - consider enum for type safety)
  @type('string') weapon: string = 'blaster';

  // Score tracking
  @type('uint8') kills: number = 0;
  @type('boolean') isAlive: boolean = true;
}

Complete Room State Pattern

import { Schema, type, MapSchema } from '@colyseus/​schema';
import { PlayerState } from './​PlayerState';

export class ArenaState extends Schema {
  // Players map - keyed by sessionId
  @type({ map: PlayerState }) players = new MapSchema<PlayerState>();

  // Room settings
  @type('string') mapSeed: string = '';

  // Match state
  @type('uint16') playersAlive: number = 0;
  @type('string') phase: string = 'lobby';  // lobby, playing, ended
}

Schema Type Selection Guide

DataTypeBytesRangeExample
Health 0-100uint810-255@type('uint8') health: number = 100
Armor 0-100uint810-255@type('uint8') armor: number = 0
Kills 0-63uint810-255@type('uint8') kills: number = 0
Score 0-65535uint1620-65535@type('uint16') score: number = 0
Position X/​Y/​Zfloat324±3.4E38@type('float32') x: number = 0
Rotationfloat324±3.4E38@type('float32') rotation: number = 0
Session IDstringvariabletext@type('string') sessionId: string = ''
Weapon namestringvariabletext@type('string') weapon: string = ''
Alive statusboolean1true/​false@type('boolean') isAlive: boolean = true
Player mapMapSchema4+ bytesdynamic@type({ map: PlayerState })

Common Schema Mistakes

❌ Wrong✅ RightWhy
@type() missing@type('uint8')Required for serialization
@type('number') for health@type('uint8')Saves 3 bytes per player
health without defaulthealth: number = 100Prevents undefined
Using Array for playersMapSchema<Player>Efficient lookup by ID

Sources:

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View