원클릭으로
dev-multiplayer-colyseus-state
Colyseus state schema definition, types, decorators, and serialization patterns. Use when defining room state.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Colyseus state schema definition, types, decorators, and serialization patterns. Use when defining room state.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Complete Developer workflow orchestration - task research sequence, implementation flow, validation gates, PRD synchronization, exit conditions.
Complete Game Designer workflow - skill invocation protocol, GDD creation, playtest flow with GDD review, design sessions. MUST load before starting assignments.
Complete PM Coordinator workflow - task assignment, project orchestration, PRD management, worker coordination. Use proactively when starting PM agent work.
Complete PM Coordinator workflow - task assignment, project orchestration, PRD management, worker coordination. Use proactively when starting PM agent work.
Complete QA Validator workflow orchestration. References specialized skills for each validation step. Load at session startup for full protocol.
Base instructions and guidelines for all agents in the system. This skill provides foundational behaviors and communication protocols that all agents should follow.
| name | dev-multiplayer-colyseus-state |
| description | Colyseus state schema definition, types, decorators, and serialization patterns. Use when defining room state. |
| category | multiplayer |
Define efficient binary-serializable state for Colyseus rooms using @colyseus/schema.
Use when:
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>
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;
}
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;
}
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>();
}
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;
}
}
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);
});
| Use Case | Type | Bytes | Range |
|---|---|---|---|
| Player health 0-100 | uint8 | 1 | 0-255 |
| Score 0-65535 | uint16 | 2 | 0-65535 |
| Coordinates (-100 to 100) | float32 | 4 | ±3.4E38 |
| Team enum | uint8 | 1 | 0-255 |
| Player ID | string | variable | text |
| Boolean flag | boolean | 1 | true/false |
| ❌ Wrong | ✅ Right |
|---|---|
| Missing @type decorator | Always add @type('string') |
Using number for small ranges | Use uint8, uint16 for savings |
| Deep nesting (4+ levels) | Keep state shallow |
| Not initializing defaults | Set default: x: number = 0 |
From arch-003 retrospective - proven patterns for @colyseus/schema.
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;
}
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
}
| Data | Type | Bytes | Range | Example |
|---|---|---|---|---|
| Health 0-100 | uint8 | 1 | 0-255 | @type('uint8') health: number = 100 |
| Armor 0-100 | uint8 | 1 | 0-255 | @type('uint8') armor: number = 0 |
| Kills 0-63 | uint8 | 1 | 0-255 | @type('uint8') kills: number = 0 |
| Score 0-65535 | uint16 | 2 | 0-65535 | @type('uint16') score: number = 0 |
| Position X/Y/Z | float32 | 4 | ±3.4E38 | @type('float32') x: number = 0 |
| Rotation | float32 | 4 | ±3.4E38 | @type('float32') rotation: number = 0 |
| Session ID | string | variable | text | @type('string') sessionId: string = '' |
| Weapon name | string | variable | text | @type('string') weapon: string = '' |
| Alive status | boolean | 1 | true/false | @type('boolean') isAlive: boolean = true |
| Player map | MapSchema | 4+ bytes | dynamic | @type({ map: PlayerState }) |
| ❌ Wrong | ✅ Right | Why |
|---|---|---|
@type() missing | @type('uint8') | Required for serialization |
@type('number') for health | @type('uint8') | Saves 3 bytes per player |
health without default | health: number = 100 | Prevents undefined |
Using Array for players | MapSchema<Player> | Efficient lookup by ID |
Sources: