2D Web Game
Interactive 2D web game with real-time multiplayer, physics, and leaderboards
2D Web Game
This example demonstrates building a complete 2D web game with real-time multiplayer capabilities, physics simulation, scoring system, and persistent leaderboards.
OSpec Document
ospec_version: "1.0.0"
id: "space-shooter-game"
name: "Cosmic Defender - Space Shooter Game"
description: "Real-time multiplayer 2D space shooter game with physics, power-ups, and leaderboards"
outcome_type: "game"
# Game-specific configuration
game:
title: "Cosmic Defender"
genre: "Action/Shooter"
platform: "Web Browser"
target_rating: "E10+" # Everyone 10 and older
# Core gameplay mechanics
mechanics:
- "real_time_movement"
- "projectile_physics"
- "collision_detection"
- "power_up_system"
- "scoring_system"
- "lives_system"
- "progressive_difficulty"
# Game modes
modes:
- name: "single_player"
description: "Solo campaign with progressive waves"
max_players: 1
- name: "multiplayer_coop"
description: "Cooperative multiplayer up to 4 players"
max_players: 4
- name: "competitive"
description: "Player vs player combat"
max_players: 8
# Visual and audio requirements
assets:
graphics:
style: "pixel_art"
resolution: "1920x1080"
sprites: "animated"
particles: "enabled"
ui_theme: "sci_fi"
audio:
music: "ambient_electronic"
sound_effects: "retro_arcade"
spatial_audio: true
adaptive_music: true
stack:
frontend:
game_engine: "Phaser.js@3.70"
language: "JavaScript ES6+"
bundler: "Webpack@5"
ui_framework: "Custom Game UI"
backend:
runtime: "Node.js@18"
framework: "Express@4"
websockets: "Socket.io@4"
database: "MongoDB@6"
cache: "Redis@7"
infrastructure:
hosting: "AWS EC2"
cdn: "CloudFront"
load_balancer: "AWS ALB"
monitoring: "CloudWatch"
# Game performance requirements
acceptance:
performance:
target_fps: 60
min_fps: 45
load_time_max_seconds: 5
memory_usage_max_mb: 512
gameplay:
input_latency_max_ms: 50
network_latency_max_ms: 100
physics_accuracy: 99.9
collision_detection_accuracy: 100
multiplayer:
concurrent_players_max: 1000
rooms_max: 250 # 4 players per room
synchronization_accuracy: 99.5
lag_compensation_enabled: true
compatibility:
browsers: ["Chrome 90+", "Firefox 88+", "Safari 14+", "Edge 90+"]
devices: ["Desktop", "Tablet (landscape)"]
mobile_support: false # Desktop/tablet focus
accessibility:
keyboard_controls: true
gamepad_support: true
colorblind_friendly: true
audio_cues: true
text_scaling: true
# Content and progression
content:
levels_min: 20
enemy_types_min: 8
power_ups_min: 6
achievements_min: 15
unlockable_ships_min: 5
# Detailed game systems
game_systems:
player_character:
attributes:
health: 100
speed: 300 # pixels per second
fire_rate: 5 # shots per second
damage: 25
controls:
movement: "WASD or Arrow Keys"
shooting: "Spacebar or Mouse Click"
special_ability: "Shift"
pause: "P or Escape"
customization:
ship_variants: ["Fighter", "Cruiser", "Interceptor", "Bomber"]
color_schemes: 8
weapon_types: ["Laser", "Plasma", "Missiles", "Energy Beam"]
enemy_ai:
behavior_patterns:
- name: "basic_patrol"
description: "Simple movement patterns"
- name: "aggressive_pursuit"
description: "Actively chase player"
- name: "formation_flying"
description: "Coordinated group movement"
- name: "boss_phases"
description: "Multi-phase boss encounters"
difficulty_scaling:
method: "progressive"
factors: ["spawn_rate", "enemy_health", "enemy_speed", "enemy_damage"]
scaling_curve: "exponential"
physics_engine:
gravity: 0 # Space environment
friction: 0.95
collision_layers: ["players", "enemies", "projectiles", "powerups", "environment"]
physics_steps_per_second: 60
power_up_system:
types:
- name: "health_pack"
effect: "restore_25_health"
duration: "instant"
rarity: "common"
- name: "rapid_fire"
effect: "double_fire_rate"
duration: 15 # seconds
rarity: "uncommon"
- name: "shield"
effect: "absorb_3_hits"
duration: 30
rarity: "rare"
- name: "multi_shot"
effect: "shoot_5_projectiles"
duration: 20
rarity: "rare"
spawn_mechanics:
drop_chance: 0.15 # 15% chance on enemy death
despawn_time: 30 # seconds if not collected
# Multiplayer architecture
multiplayer:
networking:
protocol: "WebSocket"
tick_rate: 20 # updates per second
interpolation: "client_side"
prediction: "client_side"
lag_compensation: "server_authoritative"
synchronization:
player_positions: "continuous"
projectiles: "event_based"
health_changes: "immediate"
power_ups: "authoritative_server"
anti_cheat:
server_validation: true
movement_bounds_checking: true
fire_rate_limiting: true
score_verification: true
matchmaking:
method: "skill_based"
max_wait_time_seconds: 30
region_preference: true
ping_limit_ms: 150
# Progression and monetization
progression:
experience_system:
sources: ["enemy_kills", "level_completion", "achievements"]
level_cap: 50
unlocks: ["ships", "weapons", "cosmetics", "abilities"]
achievements:
categories: ["combat", "survival", "exploration", "social"]
rewards: ["experience", "cosmetics", "titles"]
hidden_achievements: 5
leaderboards:
types: ["high_score", "survival_time", "multiplayer_ranking"]
reset_schedule: "weekly"
seasons: "monthly"
# Security and data protection
security:
user_data:
authentication: "optional" # Guest play allowed
data_encryption: "AES-256"
privacy_compliance: "GDPR"
game_security:
input_validation: "server_side"
rate_limiting: true
ddos_protection: true
cheat_detection: "heuristic"
guardrails:
content_safety:
violence_level: "cartoon" # No realistic violence
language_filter: true
user_generated_content: false
chat_moderation: true
performance_monitoring:
fps_tracking: true
memory_leak_detection: true
crash_reporting: true
user_analytics: "privacy_preserving"
quality_assurance:
automated_testing: "gameplay_scenarios"
load_testing: "1000_concurrent_users"
cross_browser_testing: true
accessibility_testing: true
deployment:
environments:
development:
players_max: 10
debugging: true
hot_reload: true
staging:
players_max: 100
analytics: true
load_testing: true
production:
players_max: 1000
cdn_enabled: true
monitoring: "full"
backup_frequency: "daily"
cdn_configuration:
assets_cache_duration: "7 days"
game_code_cache_duration: "1 hour"
static_compression: "gzip + brotli"
metadata:
development:
team_size: 4 # 2 developers, 1 artist, 1 designer
estimated_timeline_weeks: 16
target_audience: "casual gamers, 13-35 years"
similar_games: ["Asteroids", "Geometry Wars", "Space Invaders"]
technical_debt:
refactoring_budget: 20 # 20% of development time
code_review_required: true
documentation_coverage: 80
test_coverage: 70
Key Game Systems
1. Core Game Engine Setup
// game.js - Main game configuration
import Phaser from 'phaser';
import { MainMenuScene } from './scenes/MainMenuScene.js';
import { GameScene } from './scenes/GameScene.js';
import { MultiplayerScene } from './scenes/MultiplayerScene.js';
import { UIScene } from './scenes/UIScene.js';
const config = {
type: Phaser.AUTO,
width: 1920,
height: 1080,
parent: 'game-container',
backgroundColor: '#000011',
physics: {
default: 'arcade',
arcade: {
gravity: { y: 0 }, // Space environment
debug: false,
fps: 60
}
},
scene: [MainMenuScene, GameScene, MultiplayerScene, UIScene],
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
min: {
width: 800,
height: 600
},
max: {
width: 1920,
height: 1080
}
},
input: {
gamepad: true,
keyboard: true,
mouse: true
},
audio: {
disableWebAudio: false,
context: false
}
};
const game = new Phaser.Game(config);
2. Player Character System
// Player.js - Player character class
export class Player extends Phaser.Physics.Arcade.Sprite {
constructor(scene, x, y, texture = 'player-ship') {
super(scene, x, y, texture);
// Add to scene and physics
scene.add.existing(this);
scene.physics.add.existing(this);
// Player properties
this.health = 100;
this.maxHealth = 100;
this.speed = 300;
this.fireRate = 200; // milliseconds between shots
this.damage = 25;
this.lives = 3;
this.score = 0;
// Input handling
this.keys = scene.input.keyboard.addKeys('W,S,A,D,SPACE,SHIFT');
this.gamepad = scene.input.gamepad.pad1;
// Weapons system
this.weapons = {
current: 'laser',
types: {
laser: { damage: 25, fireRate: 200, texture: 'laser' },
plasma: { damage: 40, fireRate: 300, texture: 'plasma' },
missiles: { damage: 80, fireRate: 800, texture: 'missile' }
}
};
// Power-ups tracking
this.powerUps = {
rapidFire: { active: false, timer: 0 },
shield: { active: false, hits: 0, timer: 0 },
multiShot: { active: false, timer: 0 }
};
// Visual effects
this.thruster = scene.add.particles('thruster-particle');
this.thrusterEmitter = this.thruster.createEmitter({
follow: this,
followOffset: { x: 0, y: 20 },
speed: { min: 50, max: 100 },
lifespan: 300,
quantity: 2,
scale: { start: 0.3, end: 0 }
});
this.lastFired = 0;
}
update(time, delta) {
this.handleInput(time);
this.updatePowerUps(delta);
this.updateVisualEffects();
}
handleInput(time) {
const { keys, gamepad } = this;
// Movement
this.setVelocity(0, 0);
// Keyboard movement
if (keys.A.isDown || (gamepad && gamepad.left)) {
this.setVelocityX(-this.speed);
}
if (keys.D.isDown || (gamepad && gamepad.right)) {
this.setVelocityX(this.speed);
}
if (keys.W.isDown || (gamepad && gamepad.up)) {
this.setVelocityY(-this.speed);
}
if (keys.S.isDown || (gamepad && gamepad.down)) {
this.setVelocityY(this.speed);
}
// Shooting
if ((keys.SPACE.isDown || (gamepad && gamepad.A)) &&
time > this.lastFired + this.getCurrentFireRate()) {
this.shoot(time);
}
// Special ability
if (keys.SHIFT.isDown || (gamepad && gamepad.B)) {
this.useSpecialAbility();
}
}
shoot(time) {
const weapon = this.weapons.types[this.weapons.current];
if (this.powerUps.multiShot.active) {
// Shoot 5 projectiles in a spread
for (let i = -2; i <= 2; i++) {
this.createProjectile(weapon, i * 10); // 10-degree spread
}
} else {
this.createProjectile(weapon, 0);
}
this.lastFired = time;
// Play sound effect
this.scene.sound.play('laser-sound', { volume: 0.5 });
}
createProjectile(weapon, angleOffset = 0) {
const projectile = this.scene.physics.add.sprite(
this.x,
this.y - 30,
weapon.texture
);
projectile.setRotation(this.rotation + Phaser.Math.DegToRad(angleOffset));
projectile.damage = weapon.damage;
// Set velocity based on rotation
this.scene.physics.velocityFromRotation(
projectile.rotation,
600,
projectile.body.velocity
);
// Add to projectiles group
this.scene.playerProjectiles.add(projectile);
// Auto-destroy after 2 seconds
this.scene.time.delayedCall(2000, () => {
if (projectile.active) {
projectile.destroy();
}
});
}
takeDamage(damage) {
if (this.powerUps.shield.active) {
this.powerUps.shield.hits--;
if (this.powerUps.shield.hits <= 0) {
this.deactivatePowerUp('shield');
}
return;
}
this.health -= damage;
// Visual feedback
this.scene.cameras.main.shake(100, 0.02);
this.setTint(0xff0000);
this.scene.time.delayedCall(100, () => this.clearTint());
if (this.health <= 0) {
this.die();
}
// Update UI
this.scene.events.emit('player-health-changed', this.health, this.maxHealth);
}
die() {
this.lives--;
// Death animation
this.scene.add.particles('explosion-particle')
.createEmitter({
x: this.x,
y: this.y,
speed: { min: 100, max: 200 },
quantity: 20,
lifespan: 500
});
this.scene.sound.play('explosion-sound');
if (this.lives > 0) {
// Respawn after delay
this.setPosition(this.scene.cameras.main.width / 2, this.scene.cameras.main.height - 100);
this.health = this.maxHealth;
this.setVisible(false);
this.scene.time.delayedCall(2000, () => {
this.setVisible(true);
this.setTint(0x00ff00); // Invincibility indicator
this.scene.time.delayedCall(3000, () => this.clearTint());
});
} else {
// Game over
this.scene.events.emit('game-over', this.score);
this.destroy();
}
}
getCurrentFireRate() {
return this.powerUps.rapidFire.active ?
this.fireRate / 2 :
this.fireRate;
}
activatePowerUp(type, duration) {
const powerUp = this.powerUps[type];
if (!powerUp) return;
powerUp.active = true;
powerUp.timer = duration * 1000; // Convert to milliseconds
switch (type) {
case 'shield':
powerUp.hits = 3;
this.setTint(0x00ffff);
break;
case 'rapidFire':
this.setTint(0xffff00);
break;
case 'multiShot':
this.setTint(0xff00ff);
break;
}
this.scene.events.emit('powerup-activated', type, duration);
}
updatePowerUps(delta) {
Object.keys(this.powerUps).forEach(type => {
const powerUp = this.powerUps[type];
if (powerUp.active) {
powerUp.timer -= delta;
if (powerUp.timer <= 0) {
this.deactivatePowerUp(type);
}
}
});
}
deactivatePowerUp(type) {
const powerUp = this.powerUps[type];
powerUp.active = false;
powerUp.timer = 0;
this.clearTint();
this.scene.events.emit('powerup-deactivated', type);
}
}
3. Enemy AI System
// Enemy.js - Enemy AI system
export class Enemy extends Phaser.Physics.Arcade.Sprite {
constructor(scene, x, y, type = 'basic') {
super(scene, x, y, `enemy-${type}`);
scene.add.existing(this);
scene.physics.add.existing(this);
this.type = type;
this.setupEnemyType(type);
this.target = null;
this.behavior = 'patrol';
this.lastShot = 0;
this.waypoints = [];
this.currentWaypoint = 0;
// AI state machine
this.state = 'spawning';
this.stateTimer = 0;
this.setupBehavior();
}
setupEnemyType(type) {
const types = {
basic: {
health: 50,
speed: 150,
damage: 15,
fireRate: 1000,
points: 100,
behavior: 'patrol'
},
aggressive: {
health: 75,
speed: 200,
damage: 20,
fireRate: 800,
points: 150,
behavior: 'chase'
},
heavy: {
health: 150,
speed: 100,
damage: 30,
fireRate: 1500,
points: 300,
behavior: 'formation'
},
boss: {
health: 500,
speed: 80,
damage: 50,
fireRate: 500,
points: 1000,
behavior: 'boss_pattern'
}
};
const config = types[type] || types.basic;
Object.assign(this, config);
this.maxHealth = this.health;
}
update(time, delta) {
this.updateAI(time, delta);
this.updateState(delta);
this.updateVisuals();
}
updateAI(time, delta) {
switch (this.behavior) {
case 'patrol':
this.patrolBehavior();
break;
case 'chase':
this.chaseBehavior();
break;
case 'formation':
this.formationBehavior();
break;
case 'boss_pattern':
this.bossBehavior(time);
break;
}
// Shooting AI
if (time > this.lastShot + this.fireRate && this.canShoot()) {
this.shoot(time);
}
}
patrolBehavior() {
// Simple back-and-forth movement
if (this.waypoints.length === 0) {
this.waypoints = [
{ x: this.x - 100, y: this.y },
{ x: this.x + 100, y: this.y }
];
}
this.moveToWaypoint();
}
chaseBehavior() {
if (!this.target) {
this.target = this.scene.player;
}
if (this.target && this.target.active) {
// Move towards player
this.scene.physics.moveToObject(this, this.target, this.speed);
// Face the target
this.rotation = Phaser.Math.Angle.Between(
this.x, this.y,
this.target.x, this.target.y
) + Math.PI / 2;
}
}
formationBehavior() {
// Maintain formation with other enemies
const formation = this.scene.enemyFormations.get(this.formationId);
if (formation) {
const targetPos = formation.getPositionFor(this);
this.scene.physics.moveTo(this, targetPos.x, targetPos.y, this.speed);
}
}
bossBehavior(time) {
// Multi-phase boss behavior
const phase = Math.floor(this.health / this.maxHealth * 3) + 1;
switch (phase) {
case 1: // High health - slow, predictable
this.patrolBehavior();
break;
case 2: // Medium health - more aggressive
this.chaseBehavior();
this.fireRate = 300; // Faster shooting
break;
case 3: // Low health - erratic movement
this.erraticMovement();
this.fireRate = 200; // Very fast shooting
break;
}
}
shoot(time) {
let targetAngle = 0;
if (this.target && this.target.active) {
targetAngle = Phaser.Math.Angle.Between(
this.x, this.y,
this.target.x, this.target.y
);
}
const projectile = this.scene.physics.add.sprite(
this.x,
this.y + 20,
'enemy-projectile'
);
projectile.setRotation(targetAngle + Math.PI / 2);
projectile.damage = this.damage;
this.scene.physics.velocityFromRotation(
targetAngle,
300,
projectile.body.velocity
);
this.scene.enemyProjectiles.add(projectile);
// Auto-destroy after 3 seconds
this.scene.time.delayedCall(3000, () => {
if (projectile.active) {
projectile.destroy();
}
});
this.lastShot = time;
this.scene.sound.play('enemy-shoot', { volume: 0.3 });
}
takeDamage(damage) {
this.health -= damage;
// Visual feedback
this.setTint(0xff0000);
this.scene.time.delayedCall(50, () => this.clearTint());
// Health bar update
if (this.healthBar) {
this.healthBar.setScale(this.health / this.maxHealth, 1);
}
if (this.health <= 0) {
this.die();
}
}
die() {
// Death effects
this.scene.add.particles('enemy-explosion')
.createEmitter({
x: this.x,
y: this.y,
speed: { min: 50, max: 150 },
quantity: 15,
lifespan: 300
});
this.scene.sound.play('enemy-death');
// Award points
this.scene.events.emit('enemy-killed', this.points);
// Chance to drop power-up
if (Math.random() < 0.15) { // 15% chance
this.scene.spawnPowerUp(this.x, this.y);
}
this.destroy();
}
}
4. Multiplayer Networking
// MultiplayerManager.js - Real-time multiplayer system
import io from 'socket.io-client';
export class MultiplayerManager {
constructor(scene) {
this.scene = scene;
this.socket = null;
this.players = new Map();
this.interpolationBuffer = [];
this.serverTick = 0;
this.clientTick = 0;
this.ping = 0;
this.predictionEnabled = true;
this.interpolationEnabled = true;
}
connect(serverUrl) {
this.socket = io(serverUrl, {
transports: ['websocket']
});
this.setupEventHandlers();
}
setupEventHandlers() {
this.socket.on('connect', () => {
console.log('Connected to game server');
this.scene.events.emit('multiplayer-connected');
});
this.socket.on('player-joined', (data) => {
this.createRemotePlayer(data);
});
this.socket.on('player-left', (playerId) => {
this.removeRemotePlayer(playerId);
});
this.socket.on('game-state', (state) => {
this.handleServerUpdate(state);
});
this.socket.on('player-input', (data) => {
this.handleRemoteInput(data);
});
this.socket.on('projectile-fired', (data) => {
this.handleRemoteProjectile(data);
});
this.socket.on('player-hit', (data) => {
this.handlePlayerHit(data);
});
this.socket.on('ping-response', (timestamp) => {
this.ping = Date.now() - timestamp;
});
// Regular ping to measure latency
setInterval(() => {
this.socket.emit('ping', Date.now());
}, 1000);
}
sendPlayerInput(input) {
if (!this.socket || !this.socket.connected) return;
const inputData = {
tick: this.clientTick++,
timestamp: Date.now(),
playerId: this.socket.id,
input: input,
position: { x: this.scene.player.x, y: this.scene.player.y },
rotation: this.scene.player.rotation
};
this.socket.emit('player-input', inputData);
// Client-side prediction
if (this.predictionEnabled) {
this.applyInput(this.scene.player, input);
}
}
handleServerUpdate(state) {
this.serverTick = state.tick;
// Add to interpolation buffer
if (this.interpolationEnabled) {
this.interpolationBuffer.push({
timestamp: Date.now(),
state: state
});
// Keep only last 200ms of states
this.interpolationBuffer = this.interpolationBuffer.filter(
entry => Date.now() - entry.timestamp < 200
);
}
// Update remote players
Object.entries(state.players).forEach(([playerId, playerState]) => {
if (playerId !== this.socket.id) {
this.updateRemotePlayer(playerId, playerState);
} else {
// Server reconciliation for local player
this.reconcileLocalPlayer(playerState);
}
});
// Update game objects
this.updateGameObjects(state.gameObjects);
}
updateRemotePlayer(playerId, playerState) {
let player = this.players.get(playerId);
if (!player) {
player = this.createRemotePlayer({ id: playerId, ...playerState });
this.players.set(playerId, player);
}
if (this.interpolationEnabled) {
// Smooth interpolation to server position
this.scene.tweens.add({
targets: player,
x: playerState.x,
y: playerState.y,
rotation: playerState.rotation,
duration: 50, // 50ms interpolation
ease: 'Linear'
});
} else {
// Direct update
player.setPosition(playerState.x, playerState.y);
player.setRotation(playerState.rotation);
}
// Update visual state
player.health = playerState.health;
player.setTint(playerState.tint);
}
reconcileLocalPlayer(serverState) {
const player = this.scene.player;
// Check if server position differs significantly from client
const positionError = Phaser.Math.Distance.Between(
player.x, player.y,
serverState.x, serverState.y
);
if (positionError > 50) { // 50 pixel threshold
console.log('Server reconciliation triggered');
// Snap to server position
player.setPosition(serverState.x, serverState.y);
// Re-apply any inputs that happened after server timestamp
// (This would require storing input history)
}
// Always trust server for health/game state
player.health = serverState.health;
player.score = serverState.score;
}
createRemotePlayer(data) {
const sprite = this.scene.add.sprite(data.x, data.y, 'player-ship');
sprite.playerId = data.id;
sprite.health = data.health || 100;
// Add name tag
const nameText = this.scene.add.text(
sprite.x, sprite.y - 30,
data.name || `Player ${data.id.slice(-4)}`,
{
fontSize: '14px',
fill: '#ffffff',
stroke: '#000000',
strokeThickness: 2
}
);
sprite.nameTag = nameText;
return sprite;
}
removeRemotePlayer(playerId) {
const player = this.players.get(playerId);
if (player) {
if (player.nameTag) player.nameTag.destroy();
player.destroy();
this.players.delete(playerId);
}
}
sendProjectileFired(projectileData) {
if (!this.socket) return;
this.socket.emit('projectile-fired', {
playerId: this.socket.id,
timestamp: Date.now(),
...projectileData
});
}
handleRemoteProjectile(data) {
// Create projectile from remote player
const projectile = this.scene.physics.add.sprite(
data.x, data.y, data.texture
);
projectile.setRotation(data.rotation);
projectile.damage = data.damage;
projectile.ownerId = data.playerId;
this.scene.physics.velocityFromRotation(
data.rotation, data.velocity, projectile.body.velocity
);
this.scene.allProjectiles.add(projectile);
}
sendPlayerHit(targetId, damage) {
if (!this.socket) return;
this.socket.emit('player-hit', {
shooterId: this.socket.id,
targetId: targetId,
damage: damage,
timestamp: Date.now()
});
}
handlePlayerHit(data) {
// Apply damage to local player if we're the target
if (data.targetId === this.socket.id) {
this.scene.player.takeDamage(data.damage);
}
// Visual effects for all players
const targetPlayer = this.players.get(data.targetId) ||
(data.targetId === this.socket.id ? this.scene.player : null);
if (targetPlayer) {
// Hit effect
targetPlayer.setTint(0xff0000);
this.scene.time.delayedCall(100, () => targetPlayer.clearTint());
}
}
disconnect() {
if (this.socket) {
this.socket.disconnect();
this.socket = null;
}
// Clean up remote players
this.players.forEach(player => {
if (player.nameTag) player.nameTag.destroy();
player.destroy();
});
this.players.clear();
}
}
5. Game Server (Node.js)
// server.js - Multiplayer game server
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const GameRoom = require('./GameRoom');
const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
cors: {
origin: "*",
methods: ["GET", "POST"]
}
});
const gameRooms = new Map();
const waitingPlayers = [];
io.on('connection', (socket) => {
console.log(`Player ${socket.id} connected`);
socket.on('join-game', (data) => {
const room = findOrCreateRoom(data.gameMode, data.maxPlayers);
room.addPlayer(socket, data);
});
socket.on('player-input', (data) => {
const room = gameRooms.get(socket.roomId);
if (room) {
room.handlePlayerInput(socket.id, data);
}
});
socket.on('projectile-fired', (data) => {
const room = gameRooms.get(socket.roomId);
if (room) {
room.handleProjectileFired(socket.id, data);
}
});
socket.on('player-hit', (data) => {
const room = gameRooms.get(socket.roomId);
if (room) {
room.handlePlayerHit(data);
}
});
socket.on('ping', (timestamp) => {
socket.emit('ping-response', timestamp);
});
socket.on('disconnect', () => {
console.log(`Player ${socket.id} disconnected`);
const room = gameRooms.get(socket.roomId);
if (room) {
room.removePlayer(socket.id);
// Remove empty rooms
if (room.players.size === 0) {
gameRooms.delete(socket.roomId);
}
}
});
});
function findOrCreateRoom(gameMode, maxPlayers) {
// Find existing room with space
for (let room of gameRooms.values()) {
if (room.gameMode === gameMode &&
room.players.size < maxPlayers &&
room.state === 'waiting') {
return room;
}
}
// Create new room
const roomId = generateRoomId();
const room = new GameRoom(roomId, gameMode, maxPlayers, io);
gameRooms.set(roomId, room);
return room;
}
function generateRoomId() {
return Math.random().toString(36).substring(2, 8).toUpperCase();
}
const PORT = process.env.PORT || 3001;
server.listen(PORT, () => {
console.log(`Game server running on port ${PORT}`);
});
Project Structure
space-shooter-game/
├── client/
│ ├── src/
│ │ ├── scenes/
│ │ │ ├── MainMenuScene.js
│ │ │ ├── GameScene.js
│ │ │ ├── MultiplayerScene.js
│ │ │ └── UIScene.js
│ │ ├── entities/
│ │ │ ├── Player.js
│ │ │ ├── Enemy.js
│ │ │ └── PowerUp.js
│ │ ├── systems/
│ │ │ ├── MultiplayerManager.js
│ │ │ ├── AudioManager.js
│ │ │ └── InputManager.js
│ │ ├── assets/
│ │ │ ├── sprites/
│ │ │ ├── audio/
│ │ │ └── particles/
│ │ └── index.js
│ ├── public/
│ │ └── index.html
│ └── webpack.config.js
├── server/
│ ├── GameRoom.js
│ ├── GameState.js
│ ├── CollisionSystem.js
│ └── server.js
├── shared/
│ ├── GameConstants.js
│ └── GameMath.js
└── package.json
Benefits
For Players
- Smooth gameplay with high frame rates and responsive controls
- Multiplayer fun with real-time cooperative and competitive modes
- Progressive challenge with adaptive difficulty scaling
- Persistent progress with achievements and leaderboards
For Developers
- Modular architecture makes adding features easier
- Real-time networking handles latency and synchronization
- Scalable server supports many concurrent rooms
- Rich tooling for debugging and performance monitoring
Advanced Features
Achievement System
// AchievementSystem.js
export class AchievementSystem {
constructor(scene) {
this.scene = scene;
this.achievements = new Map();
this.loadAchievements();
}
loadAchievements() {
const achievements = [
{
id: 'first_kill',
name: 'First Blood',
description: 'Destroy your first enemy',
condition: (stats) => stats.enemiesKilled >= 1,
reward: { type: 'experience', amount: 100 }
},
{
id: 'survivor',
name: 'Survivor',
description: 'Survive for 5 minutes without dying',
condition: (stats) => stats.survivalTime >= 300000,
reward: { type: 'ship_unlock', ship: 'interceptor' }
},
// ... more achievements
];
achievements.forEach(achievement => {
this.achievements.set(achievement.id, {
...achievement,
unlocked: this.isUnlocked(achievement.id),
progress: 0
});
});
}
checkAchievements(stats) {
this.achievements.forEach((achievement, id) => {
if (!achievement.unlocked && achievement.condition(stats)) {
this.unlockAchievement(id);
}
});
}
unlockAchievement(id) {
const achievement = this.achievements.get(id);
if (achievement) {
achievement.unlocked = true;
this.scene.events.emit('achievement-unlocked', achievement);
this.saveProgress();
}
}
}
Related Examples
- API Service → - Game backend APIs
- Mobile App → - Mobile game development
- Real-time Chat → - WebSocket patterns
Next Steps
- Mobile version - Adapt for touch controls and mobile performance
- Tournament system - Organized competitive events
- Level editor - User-generated content tools
- 3D graphics - Upgrade to 3D using Three.js or Babylon.js
- VR support - Virtual reality game modes