JavaScript Gaming

JavaScript Game Development: Canvas, WebGL, Physics, and Game Engines

Build games with JavaScript using Canvas API, WebGL, physics engines, and game development frameworks. Master 2D/3D graphics and game mechanics.

By JavaScript Document Team
game-developmentcanvaswebglphysicsgame-enginesgraphics

JavaScript game development has evolved dramatically, enabling the creation of sophisticated 2D and 3D games that run directly in browsers. Using the Canvas API, WebGL, physics engines, and modern game frameworks, developers can build everything from simple arcade games to complex multiplayer experiences. This comprehensive guide covers the complete game development pipeline with JavaScript.

Canvas API and 2D Graphics

Game Engine Foundation

// Core Game Engine for 2D games
class GameEngine {
  constructor(canvasId, width = 800, height = 600) {
    this.canvas = document.getElementById(canvasId);
    this.ctx = this.canvas.getContext('2d');
    this.canvas.width = width;
    this.canvas.height = height;

    // Game state
    this.isRunning = false;
    this.lastTime = 0;
    this.deltaTime = 0;
    this.fps = 0;
    this.frameCount = 0;

    // Game systems
    this.scenes = new Map();
    this.currentScene = null;
    this.input = new InputManager();
    this.audio = new AudioManager();
    this.assets = new AssetManager();
    this.physics = new PhysicsEngine();
    this.renderer = new Renderer(this.ctx);

    // Performance monitoring
    this.performanceStats = {
      drawCalls: 0,
      entities: 0,
      averageFPS: 0,
    };

    this.setupCanvas();
  }

  setupCanvas() {
    // Set up canvas styling
    this.canvas.style.display = 'block';
    this.canvas.style.margin = '0 auto';
    this.canvas.style.border = '1px solid #333';
    this.canvas.style.background = '#000';

    // Handle high DPI displays
    const devicePixelRatio = window.devicePixelRatio || 1;
    const backingStoreRatio =
      this.ctx.webkitBackingStorePixelRatio ||
      this.ctx.mozBackingStorePixelRatio ||
      this.ctx.msBackingStorePixelRatio ||
      this.ctx.oBackingStorePixelRatio ||
      this.ctx.backingStorePixelRatio ||
      1;

    const ratio = devicePixelRatio / backingStoreRatio;

    if (ratio !== 1) {
      this.canvas.width = this.canvas.width * ratio;
      this.canvas.height = this.canvas.height * ratio;
      this.canvas.style.width = this.canvas.width / ratio + 'px';
      this.canvas.style.height = this.canvas.height / ratio + 'px';
      this.ctx.scale(ratio, ratio);
    }
  }

  // Scene management
  addScene(name, scene) {
    this.scenes.set(name, scene);
    scene.engine = this;
  }

  setScene(name) {
    const scene = this.scenes.get(name);
    if (!scene) {
      throw new Error(`Scene "${name}" not found`);
    }

    if (this.currentScene) {
      this.currentScene.onExit();
    }

    this.currentScene = scene;
    scene.onEnter();
  }

  // Game loop
  start() {
    if (this.isRunning) return;

    this.isRunning = true;
    this.lastTime = performance.now();

    // Start input system
    this.input.initialize(this.canvas);

    // Start game loop
    this.gameLoop();
  }

  stop() {
    this.isRunning = false;
    this.input.cleanup();
  }

  gameLoop() {
    if (!this.isRunning) return;

    const currentTime = performance.now();
    this.deltaTime = (currentTime - this.lastTime) / 1000;
    this.lastTime = currentTime;

    // Calculate FPS
    this.frameCount++;
    if (this.frameCount % 60 === 0) {
      this.fps = Math.round(1 / this.deltaTime);
      this.performanceStats.averageFPS = this.fps;
    }

    // Update and render
    this.update(this.deltaTime);
    this.render();

    // Continue loop
    requestAnimationFrame(() => this.gameLoop());
  }

  update(deltaTime) {
    // Update input
    this.input.update();

    // Update current scene
    if (this.currentScene) {
      this.currentScene.update(deltaTime);
    }

    // Update physics
    this.physics.update(deltaTime);

    // Update audio
    this.audio.update();
  }

  render() {
    // Clear canvas
    this.renderer.clear();

    // Reset performance stats
    this.performanceStats.drawCalls = 0;
    this.performanceStats.entities = 0;

    // Render current scene
    if (this.currentScene) {
      this.currentScene.render(this.renderer);
    }

    // Render debug info
    this.renderDebugInfo();
  }

  renderDebugInfo() {
    this.ctx.fillStyle = 'white';
    this.ctx.font = '12px monospace';
    this.ctx.fillText(`FPS: ${this.fps}`, 10, 20);
    this.ctx.fillText(`Entities: ${this.performanceStats.entities}`, 10, 35);
    this.ctx.fillText(`Draw Calls: ${this.performanceStats.drawCalls}`, 10, 50);
  }

  // Utility methods
  getMousePosition() {
    return this.input.mouse.position;
  }

  isKeyPressed(key) {
    return this.input.isKeyPressed(key);
  }

  isKeyDown(key) {
    return this.input.isKeyDown(key);
  }
}

// Input Manager
class InputManager {
  constructor() {
    this.keys = {};
    this.keysPressed = {};
    this.mouse = {
      position: { x: 0, y: 0 },
      buttons: {},
      wheel: 0,
    };
    this.touch = {
      touches: [],
      isActive: false,
    };

    this.eventHandlers = [];
  }

  initialize(canvas) {
    this.canvas = canvas;

    // Keyboard events
    const keyDownHandler = (e) => {
      this.keys[e.code] = true;
      this.keysPressed[e.code] = true;
      e.preventDefault();
    };

    const keyUpHandler = (e) => {
      this.keys[e.code] = false;
      e.preventDefault();
    };

    // Mouse events
    const mouseDownHandler = (e) => {
      this.mouse.buttons[e.button] = true;
      this.updateMousePosition(e);
      e.preventDefault();
    };

    const mouseUpHandler = (e) => {
      this.mouse.buttons[e.button] = false;
      e.preventDefault();
    };

    const mouseMoveHandler = (e) => {
      this.updateMousePosition(e);
    };

    const wheelHandler = (e) => {
      this.mouse.wheel = e.deltaY;
      e.preventDefault();
    };

    // Touch events
    const touchStartHandler = (e) => {
      this.updateTouches(e);
      e.preventDefault();
    };

    const touchMoveHandler = (e) => {
      this.updateTouches(e);
      e.preventDefault();
    };

    const touchEndHandler = (e) => {
      this.updateTouches(e);
      e.preventDefault();
    };

    // Add event listeners
    document.addEventListener('keydown', keyDownHandler);
    document.addEventListener('keyup', keyUpHandler);

    canvas.addEventListener('mousedown', mouseDownHandler);
    canvas.addEventListener('mouseup', mouseUpHandler);
    canvas.addEventListener('mousemove', mouseMoveHandler);
    canvas.addEventListener('wheel', wheelHandler);

    canvas.addEventListener('touchstart', touchStartHandler);
    canvas.addEventListener('touchmove', touchMoveHandler);
    canvas.addEventListener('touchend', touchEndHandler);

    // Store handlers for cleanup
    this.eventHandlers = [
      { element: document, event: 'keydown', handler: keyDownHandler },
      { element: document, event: 'keyup', handler: keyUpHandler },
      { element: canvas, event: 'mousedown', handler: mouseDownHandler },
      { element: canvas, event: 'mouseup', handler: mouseUpHandler },
      { element: canvas, event: 'mousemove', handler: mouseMoveHandler },
      { element: canvas, event: 'wheel', handler: wheelHandler },
      { element: canvas, event: 'touchstart', handler: touchStartHandler },
      { element: canvas, event: 'touchmove', handler: touchMoveHandler },
      { element: canvas, event: 'touchend', handler: touchEndHandler },
    ];
  }

  updateMousePosition(e) {
    const rect = this.canvas.getBoundingClientRect();
    this.mouse.position.x = e.clientX - rect.left;
    this.mouse.position.y = e.clientY - rect.top;
  }

  updateTouches(e) {
    this.touch.touches = Array.from(e.touches).map((touch) => {
      const rect = this.canvas.getBoundingClientRect();
      return {
        id: touch.identifier,
        x: touch.clientX - rect.left,
        y: touch.clientY - rect.top,
      };
    });

    this.touch.isActive = this.touch.touches.length > 0;
  }

  update() {
    // Reset pressed keys (only true for one frame)
    this.keysPressed = {};

    // Reset mouse wheel
    this.mouse.wheel = 0;
  }

  isKeyDown(key) {
    return !!this.keys[key];
  }

  isKeyPressed(key) {
    return !!this.keysPressed[key];
  }

  isMouseButtonDown(button) {
    return !!this.mouse.buttons[button];
  }

  cleanup() {
    this.eventHandlers.forEach(({ element, event, handler }) => {
      element.removeEventListener(event, handler);
    });
    this.eventHandlers = [];
  }
}

// Game Object base class
class GameObject {
  constructor(x = 0, y = 0) {
    this.position = { x, y };
    this.velocity = { x: 0, y: 0 };
    this.acceleration = { x: 0, y: 0 };
    this.rotation = 0;
    this.scale = { x: 1, y: 1 };
    this.anchor = { x: 0.5, y: 0.5 };

    this.width = 0;
    this.height = 0;
    this.bounds = { x: 0, y: 0, width: 0, height: 0 };

    this.visible = true;
    this.active = true;
    this.destroyed = false;

    this.components = new Map();
    this.children = [];
    this.parent = null;

    this.tags = new Set();
  }

  // Component system
  addComponent(name, component) {
    this.components.set(name, component);
    component.gameObject = this;
    component.onAdd?.();
    return this;
  }

  getComponent(name) {
    return this.components.get(name);
  }

  removeComponent(name) {
    const component = this.components.get(name);
    if (component) {
      component.onRemove?.();
      this.components.delete(name);
    }
    return this;
  }

  // Hierarchy management
  addChild(child) {
    if (child.parent) {
      child.parent.removeChild(child);
    }

    child.parent = this;
    this.children.push(child);
    return this;
  }

  removeChild(child) {
    const index = this.children.indexOf(child);
    if (index > -1) {
      this.children.splice(index, 1);
      child.parent = null;
    }
    return this;
  }

  // Update methods
  update(deltaTime) {
    if (!this.active) return;

    // Update physics
    this.velocity.x += this.acceleration.x * deltaTime;
    this.velocity.y += this.acceleration.y * deltaTime;

    this.position.x += this.velocity.x * deltaTime;
    this.position.y += this.velocity.y * deltaTime;

    // Update components
    this.components.forEach((component) => {
      component.update?.(deltaTime);
    });

    // Update children
    this.children.forEach((child) => {
      child.update(deltaTime);
    });

    // Update bounds
    this.updateBounds();

    // Custom update
    this.onUpdate(deltaTime);
  }

  render(renderer) {
    if (!this.visible) return;

    renderer.save();

    // Apply transform
    renderer.translate(this.position.x, this.position.y);
    renderer.rotate(this.rotation);
    renderer.scale(this.scale.x, this.scale.y);

    // Render components
    this.components.forEach((component) => {
      component.render?.(renderer);
    });

    // Custom render
    this.onRender(renderer);

    // Render children
    this.children.forEach((child) => {
      child.render(renderer);
    });

    renderer.restore();
  }

  updateBounds() {
    this.bounds.x = this.position.x - this.width * this.anchor.x;
    this.bounds.y = this.position.y - this.height * this.anchor.y;
    this.bounds.width = this.width;
    this.bounds.height = this.height;
  }

  // Collision detection
  intersects(other) {
    return !(
      this.bounds.x + this.bounds.width < other.bounds.x ||
      other.bounds.x + other.bounds.width < this.bounds.x ||
      this.bounds.y + this.bounds.height < other.bounds.y ||
      other.bounds.y + other.bounds.height < this.bounds.y
    );
  }

  containsPoint(x, y) {
    return (
      x >= this.bounds.x &&
      x <= this.bounds.x + this.bounds.width &&
      y >= this.bounds.y &&
      y <= this.bounds.y + this.bounds.height
    );
  }

  // Tag system
  addTag(tag) {
    this.tags.add(tag);
    return this;
  }

  removeTag(tag) {
    this.tags.delete(tag);
    return this;
  }

  hasTag(tag) {
    return this.tags.has(tag);
  }

  // Lifecycle methods (override in subclasses)
  onUpdate(deltaTime) {}
  onRender(renderer) {}
  onDestroy() {}

  // Utility methods
  destroy() {
    this.destroyed = true;
    this.onDestroy();

    // Remove from parent
    if (this.parent) {
      this.parent.removeChild(this);
    }

    // Destroy children
    this.children.forEach((child) => child.destroy());

    // Clean up components
    this.components.forEach((component) => {
      component.onRemove?.();
    });
  }

  setPosition(x, y) {
    this.position.x = x;
    this.position.y = y;
    return this;
  }

  setVelocity(x, y) {
    this.velocity.x = x;
    this.velocity.y = y;
    return this;
  }

  setScale(x, y = x) {
    this.scale.x = x;
    this.scale.y = y;
    return this;
  }

  getGlobalPosition() {
    let x = this.position.x;
    let y = this.position.y;
    let parent = this.parent;

    while (parent) {
      x += parent.position.x;
      y += parent.position.y;
      parent = parent.parent;
    }

    return { x, y };
  }

  distanceTo(other) {
    const dx = this.position.x - other.position.x;
    const dy = this.position.y - other.position.y;
    return Math.sqrt(dx * dx + dy * dy);
  }

  angleTo(other) {
    const dx = other.position.x - this.position.x;
    const dy = other.position.y - this.position.y;
    return Math.atan2(dy, dx);
  }
}

// Sprite component for rendering images
class SpriteComponent {
  constructor(image, sourceRect = null) {
    this.image = image;
    this.sourceRect = sourceRect;
    this.tint = null;
    this.alpha = 1;
    this.flipX = false;
    this.flipY = false;
  }

  onAdd() {
    if (this.image && !this.gameObject.width) {
      this.gameObject.width = this.sourceRect
        ? this.sourceRect.width
        : this.image.width;
      this.gameObject.height = this.sourceRect
        ? this.sourceRect.height
        : this.image.height;
    }
  }

  render(renderer) {
    if (!this.image) return;

    renderer.save();

    // Apply sprite-specific transforms
    if (this.flipX || this.flipY) {
      renderer.scale(this.flipX ? -1 : 1, this.flipY ? -1 : 1);
    }

    if (this.alpha < 1) {
      renderer.setAlpha(this.alpha);
    }

    // Calculate draw position
    const width = this.gameObject.width;
    const height = this.gameObject.height;
    const x = -width * this.gameObject.anchor.x;
    const y = -height * this.gameObject.anchor.y;

    if (this.sourceRect) {
      renderer.drawImage(
        this.image,
        this.sourceRect.x,
        this.sourceRect.y,
        this.sourceRect.width,
        this.sourceRect.height,
        x,
        y,
        width,
        height
      );
    } else {
      renderer.drawImage(this.image, x, y, width, height);
    }

    renderer.restore();
  }
}

// Animation component for sprite animations
class AnimationComponent {
  constructor(spriteSheet, animations) {
    this.spriteSheet = spriteSheet;
    this.animations = animations;
    this.currentAnimation = null;
    this.currentFrame = 0;
    this.frameTime = 0;
    this.isPlaying = false;
    this.loop = true;
    this.onComplete = null;
  }

  play(animationName, loop = true) {
    const animation = this.animations[animationName];
    if (!animation) {
      console.warn(`Animation "${animationName}" not found`);
      return;
    }

    this.currentAnimation = animation;
    this.currentFrame = 0;
    this.frameTime = 0;
    this.isPlaying = true;
    this.loop = loop;

    // Update sprite component
    const sprite = this.gameObject.getComponent('sprite');
    if (sprite) {
      sprite.sourceRect = animation.frames[0];
    }
  }

  stop() {
    this.isPlaying = false;
  }

  update(deltaTime) {
    if (!this.isPlaying || !this.currentAnimation) return;

    this.frameTime += deltaTime;

    if (this.frameTime >= this.currentAnimation.frameRate) {
      this.frameTime = 0;
      this.currentFrame++;

      if (this.currentFrame >= this.currentAnimation.frames.length) {
        if (this.loop) {
          this.currentFrame = 0;
        } else {
          this.currentFrame = this.currentAnimation.frames.length - 1;
          this.isPlaying = false;
          this.onComplete?.();
        }
      }

      // Update sprite component
      const sprite = this.gameObject.getComponent('sprite');
      if (sprite) {
        sprite.sourceRect = this.currentAnimation.frames[this.currentFrame];
      }
    }
  }
}

// Simple renderer wrapper
class Renderer {
  constructor(ctx) {
    this.ctx = ctx;
    this.drawCalls = 0;
  }

  clear() {
    this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
    this.drawCalls = 0;
  }

  save() {
    this.ctx.save();
  }

  restore() {
    this.ctx.restore();
  }

  translate(x, y) {
    this.ctx.translate(x, y);
  }

  rotate(angle) {
    this.ctx.rotate(angle);
  }

  scale(x, y) {
    this.ctx.scale(x, y);
  }

  setAlpha(alpha) {
    this.ctx.globalAlpha = alpha;
  }

  drawImage(image, ...args) {
    this.ctx.drawImage(image, ...args);
    this.drawCalls++;
  }

  fillRect(x, y, width, height) {
    this.ctx.fillRect(x, y, width, height);
    this.drawCalls++;
  }

  strokeRect(x, y, width, height) {
    this.ctx.strokeRect(x, y, width, height);
    this.drawCalls++;
  }

  fillCircle(x, y, radius) {
    this.ctx.beginPath();
    this.ctx.arc(x, y, radius, 0, Math.PI * 2);
    this.ctx.fill();
    this.drawCalls++;
  }

  fillText(text, x, y) {
    this.ctx.fillText(text, x, y);
    this.drawCalls++;
  }

  setFillStyle(style) {
    this.ctx.fillStyle = style;
  }

  setStrokeStyle(style) {
    this.ctx.strokeStyle = style;
  }

  setFont(font) {
    this.ctx.font = font;
  }
}

// Usage example - Simple game scene
class GameScene {
  constructor() {
    this.entities = [];
    this.player = null;
    this.enemies = [];
    this.projectiles = [];
  }

  onEnter() {
    console.log('Game scene entered');
    this.setupScene();
  }

  onExit() {
    console.log('Game scene exited');
    this.cleanup();
  }

  setupScene() {
    // Create player
    this.player = new GameObject(400, 500);
    this.player.width = 32;
    this.player.height = 32;
    this.player.addTag('player');

    // Add player to entities
    this.entities.push(this.player);

    // Create some enemies
    for (let i = 0; i < 5; i++) {
      const enemy = new GameObject(100 + i * 120, 100);
      enemy.width = 24;
      enemy.height = 24;
      enemy.addTag('enemy');
      enemy.setVelocity(50, 0);

      this.enemies.push(enemy);
      this.entities.push(enemy);
    }
  }

  update(deltaTime) {
    // Update all entities
    this.entities.forEach((entity) => {
      entity.update(deltaTime);
    });

    // Handle input
    this.handleInput(deltaTime);

    // Update game logic
    this.updateGameLogic(deltaTime);

    // Remove destroyed entities
    this.entities = this.entities.filter((entity) => !entity.destroyed);
  }

  handleInput(deltaTime) {
    if (!this.player) return;

    const speed = 200;

    if (this.engine.isKeyDown('ArrowLeft') || this.engine.isKeyDown('KeyA')) {
      this.player.velocity.x = -speed;
    } else if (
      this.engine.isKeyDown('ArrowRight') ||
      this.engine.isKeyDown('KeyD')
    ) {
      this.player.velocity.x = speed;
    } else {
      this.player.velocity.x = 0;
    }

    if (this.engine.isKeyPressed('Space')) {
      this.shootProjectile();
    }
  }

  shootProjectile() {
    const projectile = new GameObject(
      this.player.position.x,
      this.player.position.y - 20
    );
    projectile.width = 4;
    projectile.height = 10;
    projectile.setVelocity(0, -300);
    projectile.addTag('projectile');

    this.projectiles.push(projectile);
    this.entities.push(projectile);
  }

  updateGameLogic(deltaTime) {
    // Move enemies
    this.enemies.forEach((enemy) => {
      if (enemy.position.x <= 0 || enemy.position.x >= 800) {
        enemy.velocity.x *= -1;
        enemy.position.y += 20;
      }
    });

    // Check collisions
    this.checkCollisions();

    // Remove off-screen projectiles
    this.projectiles = this.projectiles.filter((projectile) => {
      if (projectile.position.y < -10) {
        projectile.destroy();
        return false;
      }
      return true;
    });
  }

  checkCollisions() {
    // Projectile vs enemy collisions
    this.projectiles.forEach((projectile) => {
      this.enemies.forEach((enemy) => {
        if (projectile.intersects(enemy)) {
          projectile.destroy();
          enemy.destroy();

          // Remove from arrays
          const projIndex = this.projectiles.indexOf(projectile);
          if (projIndex > -1) this.projectiles.splice(projIndex, 1);

          const enemyIndex = this.enemies.indexOf(enemy);
          if (enemyIndex > -1) this.enemies.splice(enemyIndex, 1);
        }
      });
    });
  }

  render(renderer) {
    // Render all entities
    this.entities.forEach((entity) => {
      this.renderEntity(entity, renderer);
    });

    // Render UI
    this.renderUI(renderer);
  }

  renderEntity(entity, renderer) {
    renderer.save();

    // Set color based on tag
    if (entity.hasTag('player')) {
      renderer.setFillStyle('#00ff00');
    } else if (entity.hasTag('enemy')) {
      renderer.setFillStyle('#ff0000');
    } else if (entity.hasTag('projectile')) {
      renderer.setFillStyle('#ffff00');
    } else {
      renderer.setFillStyle('#ffffff');
    }

    // Draw entity as rectangle
    renderer.fillRect(
      entity.bounds.x,
      entity.bounds.y,
      entity.bounds.width,
      entity.bounds.height
    );

    renderer.restore();
  }

  renderUI(renderer) {
    renderer.setFillStyle('white');
    renderer.setFont('16px Arial');
    renderer.fillText(`Enemies: ${this.enemies.length}`, 10, 80);
    renderer.fillText('Arrow keys or WASD to move, Space to shoot', 10, 580);
  }

  cleanup() {
    this.entities.forEach((entity) => entity.destroy());
    this.entities = [];
    this.enemies = [];
    this.projectiles = [];
    this.player = null;
  }
}

// Initialize game
const canvas = document.createElement('canvas');
canvas.id = 'game-canvas';
document.body.appendChild(canvas);

const game = new GameEngine('game-canvas', 800, 600);
const gameScene = new GameScene();

game.addScene('game', gameScene);
game.setScene('game');
game.start();

console.log('Game started! Use arrow keys or WASD to move, Space to shoot');

Physics Engine Integration

2D Physics System

// Physics Engine for game physics simulation
class PhysicsEngine {
  constructor() {
    this.gravity = { x: 0, y: 980 }; // pixels/second²
    this.bodies = [];
    this.constraints = [];
    this.collisionPairs = [];
    this.spatialHash = new SpatialHash(64);

    // Physics settings
    this.iterations = 6;
    this.damping = 0.99;
    this.timeScale = 1;
  }

  // Add physics body
  addBody(body) {
    this.bodies.push(body);
    body.physicsEngine = this;
    return body;
  }

  removeBody(body) {
    const index = this.bodies.indexOf(body);
    if (index > -1) {
      this.bodies.splice(index, 1);
    }
  }

  // Physics update
  update(deltaTime) {
    const dt = deltaTime * this.timeScale;

    // Clear spatial hash
    this.spatialHash.clear();

    // Update bodies
    this.bodies.forEach((body) => {
      this.updateBody(body, dt);
      this.spatialHash.insert(body);
    });

    // Detect collisions
    this.detectCollisions();

    // Solve constraints and collisions
    for (let i = 0; i < this.iterations; i++) {
      this.solveConstraints();
      this.solveCollisions();
    }

    // Update positions
    this.bodies.forEach((body) => {
      this.updateBodyPosition(body, dt);
    });
  }

  updateBody(body, deltaTime) {
    if (body.isStatic) return;

    // Apply gravity
    if (body.useGravity) {
      body.force.x += this.gravity.x * body.mass;
      body.force.y += this.gravity.y * body.mass;
    }

    // Apply forces (F = ma)
    body.acceleration.x = body.force.x / body.mass;
    body.acceleration.y = body.force.y / body.mass;

    // Integrate velocity (Verlet integration)
    body.velocity.x += body.acceleration.x * deltaTime;
    body.velocity.y += body.acceleration.y * deltaTime;

    // Apply damping
    body.velocity.x *= this.damping;
    body.velocity.y *= this.damping;

    // Store previous position for Verlet integration
    body.previousPosition.x = body.position.x;
    body.previousPosition.y = body.position.y;

    // Clear forces
    body.force.x = 0;
    body.force.y = 0;
  }

  updateBodyPosition(body, deltaTime) {
    if (body.isStatic) return;

    // Update position
    body.position.x += body.velocity.x * deltaTime;
    body.position.y += body.velocity.y * deltaTime;

    // Update game object position if linked
    if (body.gameObject) {
      body.gameObject.position.x = body.position.x;
      body.gameObject.position.y = body.position.y;
    }
  }

  detectCollisions() {
    this.collisionPairs = [];

    // Use spatial hashing for broad phase
    const potentialCollisions = this.spatialHash.getPotentialCollisions();

    // Narrow phase collision detection
    potentialCollisions.forEach(([bodyA, bodyB]) => {
      const collision = this.checkCollision(bodyA, bodyB);
      if (collision) {
        this.collisionPairs.push(collision);
      }
    });
  }

  checkCollision(bodyA, bodyB) {
    // Circle vs Circle collision
    if (bodyA.shape.type === 'circle' && bodyB.shape.type === 'circle') {
      return this.circleVsCircle(bodyA, bodyB);
    }

    // Rectangle vs Rectangle collision
    if (bodyA.shape.type === 'rectangle' && bodyB.shape.type === 'rectangle') {
      return this.rectangleVsRectangle(bodyA, bodyB);
    }

    // Circle vs Rectangle collision
    if (bodyA.shape.type === 'circle' && bodyB.shape.type === 'rectangle') {
      return this.circleVsRectangle(bodyA, bodyB);
    }

    if (bodyA.shape.type === 'rectangle' && bodyB.shape.type === 'circle') {
      return this.circleVsRectangle(bodyB, bodyA);
    }

    return null;
  }

  circleVsCircle(bodyA, bodyB) {
    const dx = bodyB.position.x - bodyA.position.x;
    const dy = bodyB.position.y - bodyA.position.y;
    const distance = Math.sqrt(dx * dx + dy * dy);
    const combinedRadius = bodyA.shape.radius + bodyB.shape.radius;

    if (distance < combinedRadius) {
      const overlap = combinedRadius - distance;
      const normalX = dx / distance;
      const normalY = dy / distance;

      return {
        bodyA,
        bodyB,
        normal: { x: normalX, y: normalY },
        overlap,
        contactPoint: {
          x: bodyA.position.x + normalX * bodyA.shape.radius,
          y: bodyA.position.y + normalY * bodyA.shape.radius,
        },
      };
    }

    return null;
  }

  rectangleVsRectangle(bodyA, bodyB) {
    const boundsA = this.getRectangleBounds(bodyA);
    const boundsB = this.getRectangleBounds(bodyB);

    const overlapX =
      Math.min(boundsA.right, boundsB.right) -
      Math.max(boundsA.left, boundsB.left);
    const overlapY =
      Math.min(boundsA.bottom, boundsB.bottom) -
      Math.max(boundsA.top, boundsB.top);

    if (overlapX > 0 && overlapY > 0) {
      let normalX, normalY, overlap;

      if (overlapX < overlapY) {
        // Horizontal collision
        overlap = overlapX;
        normalX = bodyA.position.x < bodyB.position.x ? -1 : 1;
        normalY = 0;
      } else {
        // Vertical collision
        overlap = overlapY;
        normalX = 0;
        normalY = bodyA.position.y < bodyB.position.y ? -1 : 1;
      }

      return {
        bodyA,
        bodyB,
        normal: { x: normalX, y: normalY },
        overlap,
        contactPoint: {
          x: (boundsA.left + boundsA.right + boundsB.left + boundsB.right) / 4,
          y: (boundsA.top + boundsA.bottom + boundsB.top + boundsB.bottom) / 4,
        },
      };
    }

    return null;
  }

  circleVsRectangle(circle, rectangle) {
    const bounds = this.getRectangleBounds(rectangle);

    // Find closest point on rectangle to circle center
    const closestX = Math.max(
      bounds.left,
      Math.min(circle.position.x, bounds.right)
    );
    const closestY = Math.max(
      bounds.top,
      Math.min(circle.position.y, bounds.bottom)
    );

    const dx = circle.position.x - closestX;
    const dy = circle.position.y - closestY;
    const distance = Math.sqrt(dx * dx + dy * dy);

    if (distance < circle.shape.radius) {
      const overlap = circle.shape.radius - distance;
      const normalX = distance > 0 ? dx / distance : 1;
      const normalY = distance > 0 ? dy / distance : 0;

      return {
        bodyA: circle,
        bodyB: rectangle,
        normal: { x: normalX, y: normalY },
        overlap,
        contactPoint: { x: closestX, y: closestY },
      };
    }

    return null;
  }

  getRectangleBounds(body) {
    const halfWidth = body.shape.width / 2;
    const halfHeight = body.shape.height / 2;

    return {
      left: body.position.x - halfWidth,
      right: body.position.x + halfWidth,
      top: body.position.y - halfHeight,
      bottom: body.position.y + halfHeight,
    };
  }

  solveCollisions() {
    this.collisionPairs.forEach((collision) => {
      this.resolveCollision(collision);
    });
  }

  resolveCollision(collision) {
    const { bodyA, bodyB, normal, overlap } = collision;

    // Skip if either body is static
    if (bodyA.isStatic && bodyB.isStatic) return;

    // Calculate relative velocity
    const relativeVelocityX = bodyB.velocity.x - bodyA.velocity.x;
    const relativeVelocityY = bodyB.velocity.y - bodyA.velocity.y;

    const separatingVelocity =
      relativeVelocityX * normal.x + relativeVelocityY * normal.y;

    // Don't resolve if objects are separating
    if (separatingVelocity > 0) return;

    // Calculate restitution (bounciness)
    const restitution = Math.min(bodyA.restitution, bodyB.restitution);

    // Calculate impulse
    const newSeparatingVelocity = -separatingVelocity * restitution;
    const deltaVelocity = newSeparatingVelocity - separatingVelocity;

    const totalInverseMass = bodyA.getInverseMass() + bodyB.getInverseMass();

    if (totalInverseMass <= 0) return;

    const impulse = deltaVelocity / totalInverseMass;
    const impulsePerIMass = { x: impulse * normal.x, y: impulse * normal.y };

    // Apply impulse
    if (!bodyA.isStatic) {
      bodyA.velocity.x -= impulsePerIMass.x * bodyA.getInverseMass();
      bodyA.velocity.y -= impulsePerIMass.y * bodyA.getInverseMass();
    }

    if (!bodyB.isStatic) {
      bodyB.velocity.x += impulsePerIMass.x * bodyB.getInverseMass();
      bodyB.velocity.y += impulsePerIMass.y * bodyB.getInverseMass();
    }

    // Position correction to prevent overlap
    const correctionPercent = 0.2;
    const slop = 0.01;
    const correction =
      (Math.max(overlap - slop, 0) / totalInverseMass) * correctionPercent;

    if (!bodyA.isStatic) {
      bodyA.position.x -= correction * bodyA.getInverseMass() * normal.x;
      bodyA.position.y -= correction * bodyA.getInverseMass() * normal.y;
    }

    if (!bodyB.isStatic) {
      bodyB.position.x += correction * bodyB.getInverseMass() * normal.x;
      bodyB.position.y += correction * bodyB.getInverseMass() * normal.y;
    }

    // Trigger collision callbacks
    bodyA.onCollision?.(bodyB, collision);
    bodyB.onCollision?.(bodyA, collision);
  }

  solveConstraints() {
    // Implement constraint solving (springs, joints, etc.)
  }

  // Utility methods
  applyForce(body, force, point = null) {
    body.force.x += force.x;
    body.force.y += force.y;

    // Apply torque if point is specified
    if (point) {
      const torque =
        (point.x - body.position.x) * force.y -
        (point.y - body.position.y) * force.x;
      body.torque += torque;
    }
  }

  raycast(start, end, filter = null) {
    // Implement raycasting
    const results = [];

    this.bodies.forEach((body) => {
      if (filter && !filter(body)) return;

      const intersection = this.rayIntersectBody(start, end, body);
      if (intersection) {
        results.push(intersection);
      }
    });

    // Sort by distance
    results.sort((a, b) => a.distance - b.distance);

    return results;
  }

  rayIntersectBody(start, end, body) {
    // Simplified ray-body intersection
    // In a real implementation, you'd have specific methods for each shape type
    return null;
  }
}

// Physics Body class
class PhysicsBody {
  constructor(gameObject, shape, options = {}) {
    this.gameObject = gameObject;
    this.shape = shape;

    this.position = { x: gameObject.position.x, y: gameObject.position.y };
    this.previousPosition = { x: this.position.x, y: this.position.y };
    this.velocity = { x: 0, y: 0 };
    this.acceleration = { x: 0, y: 0 };
    this.force = { x: 0, y: 0 };

    this.mass = options.mass || 1;
    this.inverseMass = this.mass > 0 ? 1 / this.mass : 0;
    this.restitution = options.restitution || 0.6;
    this.friction = options.friction || 0.3;

    this.isStatic = options.isStatic || false;
    this.isSensor = options.isSensor || false;
    this.useGravity = options.useGravity !== false;

    this.rotation = 0;
    this.angularVelocity = 0;
    this.torque = 0;

    this.onCollision = options.onCollision;
  }

  getInverseMass() {
    return this.isStatic ? 0 : this.inverseMass;
  }

  setStatic(isStatic) {
    this.isStatic = isStatic;
    this.inverseMass = isStatic ? 0 : this.mass > 0 ? 1 / this.mass : 0;
    return this;
  }

  setMass(mass) {
    this.mass = mass;
    this.inverseMass = this.isStatic ? 0 : mass > 0 ? 1 / mass : 0;
    return this;
  }

  applyForce(force, point = null) {
    this.force.x += force.x;
    this.force.y += force.y;

    if (point) {
      const torque =
        (point.x - this.position.x) * force.y -
        (point.y - this.position.y) * force.x;
      this.torque += torque;
    }

    return this;
  }

  applyImpulse(impulse) {
    if (this.isStatic) return this;

    this.velocity.x += impulse.x * this.inverseMass;
    this.velocity.y += impulse.y * this.inverseMass;

    return this;
  }

  getBounds() {
    switch (this.shape.type) {
      case 'circle':
        return {
          x: this.position.x - this.shape.radius,
          y: this.position.y - this.shape.radius,
          width: this.shape.radius * 2,
          height: this.shape.radius * 2,
        };

      case 'rectangle':
        return {
          x: this.position.x - this.shape.width / 2,
          y: this.position.y - this.shape.height / 2,
          width: this.shape.width,
          height: this.shape.height,
        };

      default:
        return { x: 0, y: 0, width: 0, height: 0 };
    }
  }
}

// Shape definitions
class CircleShape {
  constructor(radius) {
    this.type = 'circle';
    this.radius = radius;
  }
}

class RectangleShape {
  constructor(width, height) {
    this.type = 'rectangle';
    this.width = width;
    this.height = height;
  }
}

// Spatial Hash for efficient collision detection
class SpatialHash {
  constructor(cellSize) {
    this.cellSize = cellSize;
    this.grid = new Map();
  }

  clear() {
    this.grid.clear();
  }

  getHash(x, y) {
    const cellX = Math.floor(x / this.cellSize);
    const cellY = Math.floor(y / this.cellSize);
    return `${cellX},${cellY}`;
  }

  insert(body) {
    const bounds = body.getBounds();
    const minCellX = Math.floor(bounds.x / this.cellSize);
    const minCellY = Math.floor(bounds.y / this.cellSize);
    const maxCellX = Math.floor((bounds.x + bounds.width) / this.cellSize);
    const maxCellY = Math.floor((bounds.y + bounds.height) / this.cellSize);

    for (let x = minCellX; x <= maxCellX; x++) {
      for (let y = minCellY; y <= maxCellY; y++) {
        const hash = `${x},${y}`;
        if (!this.grid.has(hash)) {
          this.grid.set(hash, []);
        }
        this.grid.get(hash).push(body);
      }
    }
  }

  getPotentialCollisions() {
    const pairs = [];
    const checked = new Set();

    this.grid.forEach((bodies) => {
      for (let i = 0; i < bodies.length; i++) {
        for (let j = i + 1; j < bodies.length; j++) {
          const bodyA = bodies[i];
          const bodyB = bodies[j];
          const pairKey = `${Math.min(bodyA.id, bodyB.id)}-${Math.max(bodyA.id, bodyB.id)}`;

          if (!checked.has(pairKey)) {
            checked.add(pairKey);
            pairs.push([bodyA, bodyB]);
          }
        }
      }
    });

    return pairs;
  }
}

// Physics component for game objects
class PhysicsComponent {
  constructor(shape, options = {}) {
    this.shape = shape;
    this.options = options;
    this.body = null;
  }

  onAdd() {
    this.body = new PhysicsBody(this.gameObject, this.shape, this.options);
    this.body.id = Math.random(); // Simple ID for spatial hashing

    // Add to physics engine
    if (this.gameObject.engine?.physics) {
      this.gameObject.engine.physics.addBody(this.body);
    }
  }

  onRemove() {
    if (this.body && this.gameObject.engine?.physics) {
      this.gameObject.engine.physics.removeBody(this.body);
    }
  }

  update(deltaTime) {
    if (this.body) {
      // Sync game object with physics body
      this.gameObject.position.x = this.body.position.x;
      this.gameObject.position.y = this.body.position.y;
      this.gameObject.rotation = this.body.rotation;
    }
  }

  applyForce(force, point = null) {
    if (this.body) {
      this.body.applyForce(force, point);
    }
  }

  applyImpulse(impulse) {
    if (this.body) {
      this.body.applyImpulse(impulse);
    }
  }

  setVelocity(x, y) {
    if (this.body) {
      this.body.velocity.x = x;
      this.body.velocity.y = y;
    }
  }

  setStatic(isStatic) {
    if (this.body) {
      this.body.setStatic(isStatic);
    }
  }
}

// Example usage of physics system
function createPhysicsExample() {
  // Create a physics-enabled game object
  const ball = new GameObject(400, 100);
  ball.width = 32;
  ball.height = 32;

  // Add physics component
  ball.addComponent(
    'physics',
    new PhysicsComponent(new CircleShape(16), {
      mass: 1,
      restitution: 0.8,
      useGravity: true,
      onCollision: (otherBody, collision) => {
        console.log('Ball collided with something!');
      },
    })
  );

  // Create static ground
  const ground = new GameObject(400, 550);
  ground.width = 800;
  ground.height = 50;

  ground.addComponent(
    'physics',
    new PhysicsComponent(new RectangleShape(800, 50), {
      isStatic: true,
      restitution: 0.3,
    })
  );

  console.log('Physics example created - ball will fall and bounce on ground');

  return { ball, ground };
}

// Uncomment to test physics
// const physicsExample = createPhysicsExample();

Audio Management and Asset Loading

Comprehensive Audio System

// Audio Manager for game audio
class AudioManager {
  constructor() {
    this.audioContext = null;
    this.masterGain = null;
    this.sounds = new Map();
    this.music = new Map();
    this.currentMusic = null;

    // Volume controls
    this.masterVolume = 1.0;
    this.sfxVolume = 1.0;
    this.musicVolume = 1.0;

    // Audio pools for efficient playback
    this.audioPools = new Map();

    this.initialize();
  }

  async initialize() {
    try {
      // Create audio context
      this.audioContext = new (window.AudioContext ||
        window.webkitAudioContext)();

      // Create master gain node
      this.masterGain = this.audioContext.createGain();
      this.masterGain.connect(this.audioContext.destination);
      this.masterGain.gain.value = this.masterVolume;

      // Handle audio context state
      if (this.audioContext.state === 'suspended') {
        // Audio context is suspended, will need user interaction to resume
        this.setupUserInteractionResume();
      }

      console.log('Audio Manager initialized');
    } catch (error) {
      console.error('Failed to initialize Audio Manager:', error);
    }
  }

  setupUserInteractionResume() {
    const resumeAudio = () => {
      if (this.audioContext.state === 'suspended') {
        this.audioContext.resume();
      }

      // Remove listeners after first interaction
      document.removeEventListener('click', resumeAudio);
      document.removeEventListener('keydown', resumeAudio);
      document.removeEventListener('touchstart', resumeAudio);
    };

    document.addEventListener('click', resumeAudio);
    document.addEventListener('keydown', resumeAudio);
    document.addEventListener('touchstart', resumeAudio);
  }

  // Load audio file
  async loadSound(name, url, isMusic = false) {
    try {
      const response = await fetch(url);
      const arrayBuffer = await response.arrayBuffer();
      const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);

      const audioData = {
        buffer: audioBuffer,
        isMusic,
        volume: 1.0,
        loop: false,
      };

      if (isMusic) {
        this.music.set(name, audioData);
      } else {
        this.sounds.set(name, audioData);

        // Create audio pool for sound effects
        this.createAudioPool(name, audioData, 5);
      }

      console.log(`Loaded ${isMusic ? 'music' : 'sound'}: ${name}`);
    } catch (error) {
      console.error(`Failed to load audio "${name}":`, error);
    }
  }

  createAudioPool(name, audioData, poolSize) {
    const pool = [];

    for (let i = 0; i < poolSize; i++) {
      const source = this.createAudioSource(audioData);
      pool.push(source);
    }

    this.audioPools.set(name, pool);
  }

  createAudioSource(audioData) {
    const source = this.audioContext.createBufferSource();
    const gainNode = this.audioContext.createGain();

    source.buffer = audioData.buffer;
    source.loop = audioData.loop;

    // Connect audio graph
    source.connect(gainNode);
    gainNode.connect(this.masterGain);

    return {
      source,
      gainNode,
      isPlaying: false,
      startTime: 0,
      pauseTime: 0,
    };
  }

  // Play sound effect
  playSound(name, options = {}) {
    const audioData = this.sounds.get(name);
    if (!audioData) {
      console.warn(`Sound "${name}" not found`);
      return null;
    }

    const pool = this.audioPools.get(name);
    if (!pool) {
      console.warn(`Audio pool for "${name}" not found`);
      return null;
    }

    // Find available audio source from pool
    let audioSource = pool.find((source) => !source.isPlaying);

    if (!audioSource) {
      // All sources are busy, create a new one
      audioSource = this.createAudioSource(audioData);
      pool.push(audioSource);
    }

    // Configure audio source
    const volume = (options.volume || 1.0) * audioData.volume * this.sfxVolume;
    audioSource.gainNode.gain.value = volume;

    const playbackRate = options.playbackRate || 1.0;
    audioSource.source.playbackRate.value = playbackRate;

    const loop = options.loop || audioData.loop;
    audioSource.source.loop = loop;

    // Play audio
    try {
      audioSource.source.start(0);
      audioSource.isPlaying = true;
      audioSource.startTime = this.audioContext.currentTime;

      // Mark as not playing when finished
      audioSource.source.onended = () => {
        audioSource.isPlaying = false;

        // Create new source for reuse
        const newSource = this.createAudioSource(audioData);
        const index = pool.indexOf(audioSource);
        if (index > -1) {
          pool[index] = newSource;
        }
      };

      return audioSource;
    } catch (error) {
      console.error(`Failed to play sound "${name}":`, error);
      return null;
    }
  }

  // Play music
  playMusic(name, options = {}) {
    const audioData = this.music.get(name);
    if (!audioData) {
      console.warn(`Music "${name}" not found`);
      return null;
    }

    // Stop current music
    this.stopMusic();

    // Create music source
    const musicSource = this.createAudioSource({
      ...audioData,
      loop: options.loop !== false, // Default to loop
    });

    // Configure music
    const volume =
      (options.volume || 1.0) * audioData.volume * this.musicVolume;
    musicSource.gainNode.gain.value = volume;

    // Fade in if specified
    if (options.fadeIn) {
      musicSource.gainNode.gain.value = 0;
      musicSource.gainNode.gain.linearRampToValueAtTime(
        volume,
        this.audioContext.currentTime + options.fadeIn
      );
    }

    // Play music
    try {
      musicSource.source.start(0);
      musicSource.isPlaying = true;
      musicSource.startTime = this.audioContext.currentTime;

      this.currentMusic = musicSource;

      return musicSource;
    } catch (error) {
      console.error(`Failed to play music "${name}":`, error);
      return null;
    }
  }

  // Stop music
  stopMusic(fadeOut = 0) {
    if (!this.currentMusic || !this.currentMusic.isPlaying) return;

    if (fadeOut > 0) {
      // Fade out
      this.currentMusic.gainNode.gain.linearRampToValueAtTime(
        0,
        this.audioContext.currentTime + fadeOut
      );

      setTimeout(() => {
        if (this.currentMusic) {
          this.currentMusic.source.stop();
          this.currentMusic.isPlaying = false;
          this.currentMusic = null;
        }
      }, fadeOut * 1000);
    } else {
      // Immediate stop
      this.currentMusic.source.stop();
      this.currentMusic.isPlaying = false;
      this.currentMusic = null;
    }
  }

  // Pause/Resume music
  pauseMusic() {
    if (this.currentMusic && this.currentMusic.isPlaying) {
      this.currentMusic.pauseTime = this.audioContext.currentTime;
      this.currentMusic.source.stop();
      this.currentMusic.isPlaying = false;
    }
  }

  resumeMusic() {
    if (
      this.currentMusic &&
      !this.currentMusic.isPlaying &&
      this.currentMusic.pauseTime > 0
    ) {
      // Create new source from pause point
      const newSource = this.audioContext.createBufferSource();
      newSource.buffer = this.currentMusic.source.buffer;
      newSource.loop = this.currentMusic.source.loop;

      newSource.connect(this.currentMusic.gainNode);

      const offset = this.currentMusic.pauseTime - this.currentMusic.startTime;
      newSource.start(0, offset);

      this.currentMusic.source = newSource;
      this.currentMusic.isPlaying = true;
      this.currentMusic.startTime = this.audioContext.currentTime - offset;
      this.currentMusic.pauseTime = 0;
    }
  }

  // Volume controls
  setMasterVolume(volume) {
    this.masterVolume = Math.max(0, Math.min(1, volume));
    if (this.masterGain) {
      this.masterGain.gain.value = this.masterVolume;
    }
  }

  setSFXVolume(volume) {
    this.sfxVolume = Math.max(0, Math.min(1, volume));
  }

  setMusicVolume(volume) {
    this.musicVolume = Math.max(0, Math.min(1, volume));

    if (this.currentMusic) {
      const audioData = Array.from(this.music.values()).find(
        (data) => data.buffer === this.currentMusic.source.buffer
      );

      if (audioData) {
        const newVolume = audioData.volume * this.musicVolume;
        this.currentMusic.gainNode.gain.value = newVolume;
      }
    }
  }

  // 3D Positional Audio
  createPositionalSound(name, position, options = {}) {
    const audioData = this.sounds.get(name);
    if (!audioData) {
      console.warn(`Sound "${name}" not found`);
      return null;
    }

    // Create 3D audio source
    const source = this.audioContext.createBufferSource();
    const panner = this.audioContext.createPanner();
    const gainNode = this.audioContext.createGain();

    source.buffer = audioData.buffer;

    // Configure panner
    panner.panningModel = 'HRTF';
    panner.distanceModel = options.distanceModel || 'inverse';
    panner.refDistance = options.refDistance || 1;
    panner.maxDistance = options.maxDistance || 10000;
    panner.rolloffFactor = options.rolloffFactor || 1;

    // Set position
    panner.setPosition(position.x, position.y, position.z || 0);

    // Connect audio graph
    source.connect(panner);
    panner.connect(gainNode);
    gainNode.connect(this.masterGain);

    // Configure volume
    const volume = (options.volume || 1.0) * audioData.volume * this.sfxVolume;
    gainNode.gain.value = volume;

    const positionalSource = {
      source,
      panner,
      gainNode,
      isPlaying: false,
      position: { ...position },
    };

    // Play audio
    try {
      source.start(0);
      positionalSource.isPlaying = true;

      source.onended = () => {
        positionalSource.isPlaying = false;
      };

      return positionalSource;
    } catch (error) {
      console.error(`Failed to play positional sound "${name}":`, error);
      return null;
    }
  }

  // Update listener position for 3D audio
  setListenerPosition(position, orientation = null) {
    if (!this.audioContext.listener) return;

    this.audioContext.listener.setPosition(
      position.x,
      position.y,
      position.z || 0
    );

    if (orientation) {
      this.audioContext.listener.setOrientation(
        orientation.forward.x,
        orientation.forward.y,
        orientation.forward.z,
        orientation.up.x,
        orientation.up.y,
        orientation.up.z
      );
    }
  }

  // Audio effects
  createReverb(impulseResponse) {
    const convolver = this.audioContext.createConvolver();
    convolver.buffer = impulseResponse;
    return convolver;
  }

  createDelay(delayTime = 0.3, feedback = 0.3) {
    const delay = this.audioContext.createDelay();
    const feedbackGain = this.audioContext.createGain();
    const wetGain = this.audioContext.createGain();

    delay.delayTime.value = delayTime;
    feedbackGain.gain.value = feedback;
    wetGain.gain.value = 0.3;

    // Connect delay feedback loop
    delay.connect(feedbackGain);
    feedbackGain.connect(delay);
    delay.connect(wetGain);

    return { delay, wetGain, feedbackGain };
  }

  // Update method for continuous effects
  update() {
    // Update any time-based audio effects
  }

  // Cleanup
  cleanup() {
    this.stopMusic();

    // Stop all playing sounds
    this.audioPools.forEach((pool) => {
      pool.forEach((audioSource) => {
        if (audioSource.isPlaying) {
          audioSource.source.stop();
        }
      });
    });

    // Close audio context
    if (this.audioContext) {
      this.audioContext.close();
    }
  }
}

// Asset Manager for loading game assets
class AssetManager {
  constructor() {
    this.images = new Map();
    this.audio = new Map();
    this.fonts = new Map();
    this.data = new Map();

    this.loadQueue = [];
    this.isLoading = false;
    this.totalAssets = 0;
    this.loadedAssets = 0;

    this.onProgress = null;
    this.onComplete = null;
    this.onError = null;
  }

  // Add assets to load queue
  addImage(name, url) {
    this.loadQueue.push({ type: 'image', name, url });
    return this;
  }

  addAudio(name, url, isMusic = false) {
    this.loadQueue.push({ type: 'audio', name, url, isMusic });
    return this;
  }

  addFont(name, url) {
    this.loadQueue.push({ type: 'font', name, url });
    return this;
  }

  addData(name, url) {
    this.loadQueue.push({ type: 'data', name, url });
    return this;
  }

  // Load all queued assets
  async loadAll() {
    if (this.isLoading) return;

    this.isLoading = true;
    this.totalAssets = this.loadQueue.length;
    this.loadedAssets = 0;

    const promises = this.loadQueue.map((asset) => this.loadAsset(asset));

    try {
      await Promise.all(promises);
      console.log('All assets loaded successfully');
      this.onComplete?.();
    } catch (error) {
      console.error('Asset loading failed:', error);
      this.onError?.(error);
    } finally {
      this.isLoading = false;
      this.loadQueue = [];
    }
  }

  async loadAsset(asset) {
    try {
      switch (asset.type) {
        case 'image':
          await this.loadImageAsset(asset);
          break;
        case 'audio':
          await this.loadAudioAsset(asset);
          break;
        case 'font':
          await this.loadFontAsset(asset);
          break;
        case 'data':
          await this.loadDataAsset(asset);
          break;
      }

      this.loadedAssets++;
      this.onProgress?.(this.loadedAssets / this.totalAssets);
    } catch (error) {
      console.error(`Failed to load ${asset.type} "${asset.name}":`, error);
      throw error;
    }
  }

  async loadImageAsset(asset) {
    return new Promise((resolve, reject) => {
      const image = new Image();

      image.onload = () => {
        this.images.set(asset.name, image);
        console.log(`Loaded image: ${asset.name}`);
        resolve();
      };

      image.onerror = () => {
        reject(new Error(`Failed to load image: ${asset.url}`));
      };

      image.src = asset.url;
    });
  }

  async loadAudioAsset(asset) {
    // Delegate to audio manager if available
    // For now, just store the URL
    this.audio.set(asset.name, {
      url: asset.url,
      isMusic: asset.isMusic,
    });

    console.log(`Queued audio: ${asset.name}`);
  }

  async loadFontAsset(asset) {
    const font = new FontFace(asset.name, `url(${asset.url})`);

    try {
      await font.load();
      document.fonts.add(font);
      this.fonts.set(asset.name, font);
      console.log(`Loaded font: ${asset.name}`);
    } catch (error) {
      throw new Error(`Failed to load font: ${asset.url}`);
    }
  }

  async loadDataAsset(asset) {
    try {
      const response = await fetch(asset.url);
      const data = await response.json();

      this.data.set(asset.name, data);
      console.log(`Loaded data: ${asset.name}`);
    } catch (error) {
      throw new Error(`Failed to load data: ${asset.url}`);
    }
  }

  // Get loaded assets
  getImage(name) {
    const image = this.images.get(name);
    if (!image) {
      console.warn(`Image "${name}" not found`);
    }
    return image;
  }

  getAudio(name) {
    const audio = this.audio.get(name);
    if (!audio) {
      console.warn(`Audio "${name}" not found`);
    }
    return audio;
  }

  getFont(name) {
    const font = this.fonts.get(name);
    if (!font) {
      console.warn(`Font "${name}" not found`);
    }
    return font;
  }

  getData(name) {
    const data = this.data.get(name);
    if (!data) {
      console.warn(`Data "${name}" not found`);
    }
    return data;
  }

  // Check if asset is loaded
  isImageLoaded(name) {
    return this.images.has(name);
  }

  isAudioLoaded(name) {
    return this.audio.has(name);
  }

  // Create sprite sheet from loaded image
  createSpriteSheet(imageName, frameWidth, frameHeight, frames = null) {
    const image = this.getImage(imageName);
    if (!image) return null;

    const spriteSheet = {
      image,
      frameWidth,
      frameHeight,
      framesPerRow: Math.floor(image.width / frameWidth),
      totalFrames:
        frames ||
        Math.floor((image.width / frameWidth) * (image.height / frameHeight)),
      frames: [],
    };

    // Generate frame rectangles
    for (let i = 0; i < spriteSheet.totalFrames; i++) {
      const col = i % spriteSheet.framesPerRow;
      const row = Math.floor(i / spriteSheet.framesPerRow);

      spriteSheet.frames.push({
        x: col * frameWidth,
        y: row * frameHeight,
        width: frameWidth,
        height: frameHeight,
      });
    }

    return spriteSheet;
  }

  // Get loading progress
  getProgress() {
    return {
      loaded: this.loadedAssets,
      total: this.totalAssets,
      percentage:
        this.totalAssets > 0 ? this.loadedAssets / this.totalAssets : 0,
      isLoading: this.isLoading,
    };
  }
}

// Usage example
async function initializeAudio() {
  const audioManager = new AudioManager();
  const assetManager = new AssetManager();

  // Set up progress callback
  assetManager.onProgress = (progress) => {
    console.log(`Loading progress: ${(progress * 100).toFixed(1)}%`);
  };

  assetManager.onComplete = () => {
    console.log('All assets loaded!');
  };

  // Add some example assets
  assetManager
    .addImage('player', '/images/player.png')
    .addImage('enemy', '/images/enemy.png')
    .addAudio('jump', '/audio/jump.wav')
    .addAudio('bgm', '/audio/background.mp3', true);

  // Load all assets
  await assetManager.loadAll();

  return { audioManager, assetManager };
}

// Initialize audio system
// initializeAudio().then(({ audioManager, assetManager }) => {
//   console.log('Audio and asset systems ready');
//   window.audioManager = audioManager;
//   window.assetManager = assetManager;
// });

console.log('Game development systems loaded');

Conclusion

JavaScript game development has matured into a powerful platform for creating sophisticated games that run directly in browsers. Through the Canvas API, WebGL, physics engines, and comprehensive audio systems, developers can build everything from simple 2D arcade games to complex 3D experiences. The key to successful game development is understanding performance optimization, implementing efficient game loops, and creating modular, reusable systems.

When building games with JavaScript, focus on creating solid foundations with proper entity-component systems, efficient rendering pipelines, and robust input handling. Implement physics systems thoughtfully, optimize for different devices and browsers, and always consider the user experience across various platforms. Modern web games can achieve near-native performance while providing the accessibility and reach that only web platforms can offer.