+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 324 of 355

๐Ÿ“˜ Game Development: Canvas and WebGL

Master game development: canvas and webgl in TypeScript with practical examples, best practices, and real-world applications ๐Ÿš€

๐Ÿš€Intermediate
25 min read

Prerequisites

  • Basic understanding of JavaScript ๐Ÿ“
  • TypeScript installation โšก
  • VS Code or preferred IDE ๐Ÿ’ป

What you'll learn

  • Understand the concept fundamentals ๐ŸŽฏ
  • Apply the concept in real projects ๐Ÿ—๏ธ
  • Debug common issues ๐Ÿ›
  • Write type-safe code โœจ

๐ŸŽฏ Introduction

Welcome to the exciting world of game development with TypeScript! ๐ŸŽฎ In this tutorial, weโ€™ll explore how to create interactive games using HTML5 Canvas and WebGL, all powered by TypeScriptโ€™s type safety.

Youโ€™ll discover how Canvas and WebGL can transform your creative ideas into playable games. Whether youโ€™re building simple 2D puzzles ๐Ÿงฉ, action-packed shooters ๐Ÿš€, or stunning 3D experiences ๐ŸŒŸ, understanding these technologies is essential for modern web game development.

By the end of this tutorial, youโ€™ll be creating your own games with confidence! Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Canvas and WebGL

๐Ÿค” What are Canvas and WebGL?

Think of Canvas as your digital drawing board ๐ŸŽจ and WebGL as your supercharged paintbrush that can create 3D masterpieces! Canvas gives you a 2D drawing surface, while WebGL unleashes the power of your GPU for stunning graphics.

In TypeScript terms, Canvas provides a simple 2D rendering context with methods for drawing shapes, images, and text. WebGL takes it further by giving you access to the GPU for hardware-accelerated 3D graphics. This means you can:

  • โœจ Create smooth 60 FPS games
  • ๐Ÿš€ Render thousands of objects efficiently
  • ๐Ÿ›ก๏ธ Build type-safe game engines

๐Ÿ’ก Why Use TypeScript for Game Development?

Hereโ€™s why game developers love TypeScript:

  1. Type Safety ๐Ÿ”’: Catch bugs before they crash your game
  2. Better IDE Support ๐Ÿ’ป: Autocomplete for game objects and methods
  3. Code Documentation ๐Ÿ“–: Types document your game architecture
  4. Refactoring Confidence ๐Ÿ”ง: Safely modify complex game systems

Real-world example: Imagine building a space shooter ๐Ÿš€. With TypeScript, you can define exact types for enemies, weapons, and power-ups, preventing runtime errors when a laser collides with an asteroid!

๐Ÿ”ง Basic Syntax and Usage

๐Ÿ“ Simple Canvas Example

Letโ€™s start with a friendly bouncing ball:

// ๐Ÿ‘‹ Hello, Canvas!
interface GameConfig {
  width: number;     // ๐Ÿ“ Canvas width
  height: number;    // ๐Ÿ“ Canvas height
  fps: number;       // ๐ŸŽฌ Frames per second
}

// ๐ŸŽจ Our bouncing ball
interface Ball {
  x: number;         // ๐Ÿ“ X position
  y: number;         // ๐Ÿ“ Y position
  vx: number;        // โžก๏ธ X velocity
  vy: number;        // โฌ‡๏ธ Y velocity
  radius: number;    // โญ• Ball size
  color: string;     // ๐ŸŽจ Ball color
}

// ๐ŸŽฎ Simple game class
class BounceGame {
  private canvas: HTMLCanvasElement;
  private ctx: CanvasRenderingContext2D;
  private ball: Ball;
  
  constructor(config: GameConfig) {
    // ๐Ÿ–ผ๏ธ Create canvas
    this.canvas = document.createElement('canvas');
    this.canvas.width = config.width;
    this.canvas.height = config.height;
    
    // ๐ŸŽจ Get drawing context
    this.ctx = this.canvas.getContext('2d')!;
    
    // โšฝ Initialize ball
    this.ball = {
      x: config.width / 2,
      y: config.height / 2,
      vx: 5,
      vy: 3,
      radius: 20,
      color: '#FF6B6B'
    };
  }
  
  // ๐ŸŽฏ Game loop
  update(): void {
    // ๐Ÿ”„ Update position
    this.ball.x += this.ball.vx;
    this.ball.y += this.ball.vy;
    
    // ๐Ÿ“ Bounce off walls
    if (this.ball.x <= this.ball.radius || 
        this.ball.x >= this.canvas.width - this.ball.radius) {
      this.ball.vx = -this.ball.vx;
    }
    
    if (this.ball.y <= this.ball.radius || 
        this.ball.y >= this.canvas.height - this.ball.radius) {
      this.ball.vy = -this.ball.vy;
    }
  }
  
  // ๐ŸŽจ Render everything
  render(): void {
    // ๐Ÿงน Clear canvas
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    
    // ๐ŸŽจ Draw ball
    this.ctx.beginPath();
    this.ctx.arc(this.ball.x, this.ball.y, this.ball.radius, 0, Math.PI * 2);
    this.ctx.fillStyle = this.ball.color;
    this.ctx.fill();
  }
}

๐Ÿ’ก Explanation: Notice how TypeScript helps us define exact types for our game objects. The ! after getContext('2d') tells TypeScript weโ€™re sure it wonโ€™t be null.

๐ŸŽฏ WebGL Basics

Hereโ€™s a simple WebGL triangle with types:

// ๐ŸŒŸ WebGL shader types
interface ShaderSource {
  vertex: string;    // ๐Ÿ“ Vertex shader
  fragment: string;  // ๐ŸŽจ Fragment shader
}

// ๐Ÿš€ WebGL game class
class WebGLGame {
  private gl: WebGLRenderingContext;
  private program: WebGLProgram;
  
  constructor(canvas: HTMLCanvasElement) {
    // ๐ŸŽฎ Get WebGL context
    this.gl = canvas.getContext('webgl')!;
    
    // ๐ŸŽจ Shaders for a colorful triangle
    const shaders: ShaderSource = {
      vertex: `
        attribute vec2 position;
        void main() {
          gl_Position = vec4(position, 0.0, 1.0);
        }
      `,
      fragment: `
        precision mediump float;
        uniform float time;
        void main() {
          // ๐ŸŒˆ Rainbow colors!
          gl_FragColor = vec4(
            sin(time) * 0.5 + 0.5,
            sin(time + 2.0) * 0.5 + 0.5,
            sin(time + 4.0) * 0.5 + 0.5,
            1.0
          );
        }
      `
    };
    
    // ๐Ÿ—๏ธ Build shader program
    this.program = this.createShaderProgram(shaders);
  }
  
  // ๐Ÿ”ง Helper to create shaders
  private createShaderProgram(source: ShaderSource): WebGLProgram {
    // Implementation details...
    return this.gl.createProgram()!;
  }
}

๐Ÿ’ก Practical Examples

๐Ÿ›’ Example 1: Particle System

Letโ€™s build a fun particle explosion effect:

// ๐ŸŽ† Particle definition
interface Particle {
  x: number;          // ๐Ÿ“ Position X
  y: number;          // ๐Ÿ“ Position Y
  vx: number;         // โžก๏ธ Velocity X
  vy: number;         // โฌ‡๏ธ Velocity Y
  life: number;       // โฐ Lifetime (0-1)
  size: number;       // ๐Ÿ“ Particle size
  color: string;      // ๐ŸŽจ Particle color
  emoji?: string;     // ๐Ÿ˜Š Optional emoji particle!
}

// ๐ŸŽ† Particle system manager
class ParticleSystem {
  private particles: Particle[] = [];
  private canvas: HTMLCanvasElement;
  private ctx: CanvasRenderingContext2D;
  
  constructor(canvas: HTMLCanvasElement) {
    this.canvas = canvas;
    this.ctx = canvas.getContext('2d')!;
  }
  
  // ๐Ÿ’ฅ Create explosion
  explode(x: number, y: number, count: number = 50): void {
    const emojis = ['โœจ', 'โญ', '๐Ÿ’ซ', '๐ŸŒŸ', 'โšก'];
    
    for (let i = 0; i < count; i++) {
      const angle = (Math.PI * 2 * i) / count;
      const speed = 2 + Math.random() * 4;
      
      this.particles.push({
        x,
        y,
        vx: Math.cos(angle) * speed,
        vy: Math.sin(angle) * speed,
        life: 1.0,
        size: 3 + Math.random() * 5,
        color: `hsl(${Math.random() * 360}, 100%, 50%)`,
        emoji: Math.random() > 0.7 ? emojis[Math.floor(Math.random() * emojis.length)] : undefined
      });
    }
    
    console.log(`๐Ÿ’ฅ Boom! Created ${count} particles!`);
  }
  
  // ๐Ÿ”„ Update all particles
  update(deltaTime: number): void {
    this.particles = this.particles.filter(particle => {
      // ๐ŸŒ  Apply gravity
      particle.vy += 0.2;
      
      // ๐Ÿš€ Update position
      particle.x += particle.vx;
      particle.y += particle.vy;
      
      // โฐ Reduce lifetime
      particle.life -= deltaTime * 0.02;
      
      // ๐Ÿ—‘๏ธ Remove dead particles
      return particle.life > 0;
    });
  }
  
  // ๐ŸŽจ Draw particles
  render(): void {
    this.particles.forEach(particle => {
      this.ctx.save();
      this.ctx.globalAlpha = particle.life;
      
      if (particle.emoji) {
        // ๐Ÿ˜Š Draw emoji particle
        this.ctx.font = `${particle.size * 2}px Arial`;
        this.ctx.fillText(particle.emoji, particle.x, particle.y);
      } else {
        // ๐ŸŽจ Draw colored circle
        this.ctx.beginPath();
        this.ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
        this.ctx.fillStyle = particle.color;
        this.ctx.fill();
      }
      
      this.ctx.restore();
    });
  }
}

// ๐ŸŽฎ Usage
const particleCanvas = document.getElementById('game') as HTMLCanvasElement;
const particleSystem = new ParticleSystem(particleCanvas);

// ๐Ÿ–ฑ๏ธ Click to explode!
particleCanvas.addEventListener('click', (e) => {
  const rect = particleCanvas.getBoundingClientRect();
  particleSystem.explode(e.clientX - rect.left, e.clientY - rect.top);
});

๐ŸŽฏ Try it yourself: Add different particle shapes, gravity wells, or color gradients!

๐ŸŽฎ Example 2: Simple Game Engine

Letโ€™s create a basic game engine structure:

// ๐ŸŽฎ Game object interface
interface GameObject {
  id: string;
  x: number;
  y: number;
  width: number;
  height: number;
  update(deltaTime: number): void;
  render(ctx: CanvasRenderingContext2D): void;
}

// ๐Ÿƒโ€โ™‚๏ธ Player character
class Player implements GameObject {
  id = 'player';
  x = 100;
  y = 100;
  width = 32;
  height = 32;
  speed = 200; // ๐Ÿš€ Pixels per second
  emoji = '๐Ÿš€';
  
  private keys: Set<string> = new Set();
  
  constructor() {
    // โŒจ๏ธ Setup input handling
    window.addEventListener('keydown', (e) => this.keys.add(e.key));
    window.addEventListener('keyup', (e) => this.keys.delete(e.key));
  }
  
  update(deltaTime: number): void {
    const distance = this.speed * (deltaTime / 1000);
    
    // ๐ŸŽฎ WASD controls
    if (this.keys.has('w')) this.y -= distance;
    if (this.keys.has('s')) this.y += distance;
    if (this.keys.has('a')) this.x -= distance;
    if (this.keys.has('d')) this.x += distance;
  }
  
  render(ctx: CanvasRenderingContext2D): void {
    // ๐Ÿš€ Draw player emoji
    ctx.font = '32px Arial';
    ctx.fillText(this.emoji, this.x, this.y);
  }
}

// ๐ŸŒŸ Collectible item
class Star implements GameObject {
  id: string;
  x: number;
  y: number;
  width = 24;
  height = 24;
  collected = false;
  
  constructor(x: number, y: number) {
    this.id = `star-${Date.now()}-${Math.random()}`;
    this.x = x;
    this.y = y;
  }
  
  update(deltaTime: number): void {
    // โœจ Sparkle effect
    this.width = 24 + Math.sin(Date.now() * 0.005) * 4;
    this.height = this.width;
  }
  
  render(ctx: CanvasRenderingContext2D): void {
    if (!this.collected) {
      ctx.font = `${this.width}px Arial`;
      ctx.fillText('โญ', this.x, this.y);
    }
  }
}

// ๐ŸŽฎ Game engine
class GameEngine {
  private objects: Map<string, GameObject> = new Map();
  private canvas: HTMLCanvasElement;
  private ctx: CanvasRenderingContext2D;
  private lastTime = 0;
  
  constructor(canvas: HTMLCanvasElement) {
    this.canvas = canvas;
    this.ctx = canvas.getContext('2d')!;
  }
  
  // โž• Add game object
  addObject(obj: GameObject): void {
    this.objects.set(obj.id, obj);
    console.log(`โœ… Added ${obj.id} to game!`);
  }
  
  // ๐Ÿ”„ Main game loop
  gameLoop = (timestamp: number): void => {
    const deltaTime = timestamp - this.lastTime;
    this.lastTime = timestamp;
    
    // ๐Ÿงน Clear screen
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    
    // ๐Ÿ”„ Update and render all objects
    this.objects.forEach(obj => {
      obj.update(deltaTime);
      obj.render(this.ctx);
    });
    
    // ๐ŸŽฌ Next frame
    requestAnimationFrame(this.gameLoop);
  };
  
  // ๐Ÿš€ Start the engine!
  start(): void {
    console.log('๐ŸŽฎ Game engine started!');
    requestAnimationFrame(this.gameLoop);
  }
}

๐Ÿš€ Advanced Concepts

๐Ÿง™โ€โ™‚๏ธ Advanced Topic 1: Sprite Animation System

When youโ€™re ready to level up, try this sprite animation system:

// ๐ŸŽฏ Advanced sprite animation
interface AnimationFrame {
  x: number;      // ๐Ÿ“ X position in sprite sheet
  y: number;      // ๐Ÿ“ Y position in sprite sheet
  width: number;  // ๐Ÿ“ Frame width
  height: number; // ๐Ÿ“ Frame height
  duration: number; // โฐ Frame duration in ms
}

interface Animation {
  name: string;
  frames: AnimationFrame[];
  loop: boolean;
}

// ๐Ÿช„ Sprite animator
class SpriteAnimator {
  private animations: Map<string, Animation> = new Map();
  private currentAnimation: string = 'idle';
  private currentFrame = 0;
  private frameTime = 0;
  
  // โœจ Add animation
  addAnimation(animation: Animation): void {
    this.animations.set(animation.name, animation);
    console.log(`๐ŸŽฌ Added animation: ${animation.name}`);
  }
  
  // ๐ŸŽฎ Play animation
  play(name: string): void {
    if (this.currentAnimation !== name) {
      this.currentAnimation = name;
      this.currentFrame = 0;
      this.frameTime = 0;
      console.log(`โ–ถ๏ธ Playing: ${name}`);
    }
  }
  
  // ๐Ÿ”„ Update animation
  update(deltaTime: number): AnimationFrame | null {
    const animation = this.animations.get(this.currentAnimation);
    if (!animation) return null;
    
    this.frameTime += deltaTime;
    const currentFrameData = animation.frames[this.currentFrame];
    
    // โญ๏ธ Next frame?
    if (this.frameTime >= currentFrameData.duration) {
      this.frameTime = 0;
      this.currentFrame++;
      
      // ๐Ÿ” Loop or stop
      if (this.currentFrame >= animation.frames.length) {
        if (animation.loop) {
          this.currentFrame = 0;
        } else {
          this.currentFrame = animation.frames.length - 1;
        }
      }
    }
    
    return currentFrameData;
  }
}

๐Ÿ—๏ธ Advanced Topic 2: WebGL Shader Effects

For the brave developers, hereโ€™s a WebGL post-processing effect:

// ๐Ÿš€ Post-processing effects
type ShaderEffect = 'normal' | 'blur' | 'pixelate' | 'wave';

interface PostProcessor {
  applyEffect(effect: ShaderEffect, time: number): void;
}

// ๐ŸŒˆ Shader effect manager
class WebGLEffects implements PostProcessor {
  private gl: WebGLRenderingContext;
  private shaders: Map<ShaderEffect, WebGLProgram> = new Map();
  
  constructor(gl: WebGLRenderingContext) {
    this.gl = gl;
    this.initializeShaders();
  }
  
  // ๐ŸŽจ Wave effect shader
  private getWaveShader(): string {
    return `
      precision mediump float;
      uniform sampler2D texture;
      uniform float time;
      varying vec2 vTexCoord;
      
      void main() {
        vec2 uv = vTexCoord;
        // ๐ŸŒŠ Wave distortion
        uv.x += sin(uv.y * 10.0 + time) * 0.02;
        uv.y += sin(uv.x * 10.0 + time) * 0.02;
        
        gl_FragColor = texture2D(texture, uv);
      }
    `;
  }
  
  // โœจ Apply selected effect
  applyEffect(effect: ShaderEffect, time: number): void {
    const program = this.shaders.get(effect);
    if (program) {
      this.gl.useProgram(program);
      const timeLocation = this.gl.getUniformLocation(program, 'time');
      this.gl.uniform1f(timeLocation, time);
      console.log(`โœจ Applied ${effect} effect!`);
    }
  }
  
  private initializeShaders(): void {
    // Initialize all shader effects
    console.log('๐ŸŽจ Initializing shader effects...');
  }
}

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Forgetting RequestAnimationFrame

// โŒ Wrong way - burns CPU and looks choppy!
class BadGame {
  gameLoop(): void {
    while (true) {
      this.update();
      this.render();
    }
  }
}

// โœ… Correct way - smooth 60 FPS!
class GoodGame {
  private lastTime = 0;
  
  gameLoop = (timestamp: number): void => {
    const deltaTime = timestamp - this.lastTime;
    this.lastTime = timestamp;
    
    this.update(deltaTime);
    this.render();
    
    requestAnimationFrame(this.gameLoop); // ๐ŸŽฌ Smooth animation!
  };
}

๐Ÿคฏ Pitfall 2: Not Handling Canvas Resize

// โŒ Dangerous - stretched graphics!
const canvas = document.getElementById('game') as HTMLCanvasElement;
canvas.style.width = '100%';
canvas.style.height = '100%';

// โœ… Safe - proper resolution!
function resizeCanvas(canvas: HTMLCanvasElement): void {
  const rect = canvas.getBoundingClientRect();
  canvas.width = rect.width * window.devicePixelRatio;
  canvas.height = rect.height * window.devicePixelRatio;
  
  const ctx = canvas.getContext('2d')!;
  ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
  
  console.log('๐Ÿ“ Canvas resized properly!');
}

window.addEventListener('resize', () => resizeCanvas(canvas));

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Use TypeScript Interfaces: Define types for all game objects
  2. ๐Ÿ“ Separate Logic and Rendering: Keep update() and render() methods distinct
  3. ๐Ÿ›ก๏ธ Handle Edge Cases: Always check canvas context exists
  4. ๐ŸŽจ Optimize Drawing: Batch similar draw calls together
  5. โœจ Use Object Pools: Reuse objects instead of creating new ones

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Space Invaders Clone

Create a simple space shooter game:

๐Ÿ“‹ Requirements:

  • โœ… Player spaceship that moves left/right
  • ๐Ÿท๏ธ Enemy invaders that move in formation
  • ๐Ÿ‘ค Shooting mechanics with collision detection
  • ๐Ÿ“… Score tracking and lives system
  • ๐ŸŽจ Each enemy type needs a unique emoji!

๐Ÿš€ Bonus Points:

  • Add power-ups with special effects
  • Implement particle explosions
  • Create multiple levels with increasing difficulty

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
// ๐ŸŽฏ Space Invaders in TypeScript!
interface SpaceObject {
  x: number;
  y: number;
  width: number;
  height: number;
  emoji: string;
  alive: boolean;
}

interface Bullet extends SpaceObject {
  vy: number;
}

class SpaceInvaders {
  private canvas: HTMLCanvasElement;
  private ctx: CanvasRenderingContext2D;
  private player: SpaceObject;
  private enemies: SpaceObject[][] = [];
  private bullets: Bullet[] = [];
  private score = 0;
  private lives = 3;
  private enemyDirection = 1;
  
  constructor(canvas: HTMLCanvasElement) {
    this.canvas = canvas;
    this.ctx = canvas.getContext('2d')!;
    
    // ๐Ÿš€ Initialize player
    this.player = {
      x: canvas.width / 2 - 16,
      y: canvas.height - 60,
      width: 32,
      height: 32,
      emoji: '๐Ÿš€',
      alive: true
    };
    
    // ๐Ÿ‘พ Create enemy grid
    this.createEnemies();
    
    // ๐ŸŽฎ Controls
    this.setupControls();
  }
  
  // ๐Ÿ‘พ Create enemy formation
  private createEnemies(): void {
    const enemyEmojis = ['๐Ÿ‘พ', '๐Ÿ‘ฝ', '๐Ÿ›ธ'];
    
    for (let row = 0; row < 5; row++) {
      this.enemies[row] = [];
      for (let col = 0; col < 11; col++) {
        this.enemies[row][col] = {
          x: col * 50 + 50,
          y: row * 40 + 50,
          width: 30,
          height: 30,
          emoji: enemyEmojis[Math.floor(row / 2)],
          alive: true
        };
      }
    }
    console.log('๐Ÿ‘พ Enemy formation ready!');
  }
  
  // ๐ŸŽฎ Setup keyboard controls
  private setupControls(): void {
    window.addEventListener('keydown', (e) => {
      if (e.key === 'ArrowLeft' && this.player.x > 0) {
        this.player.x -= 20;
      }
      if (e.key === 'ArrowRight' && this.player.x < this.canvas.width - this.player.width) {
        this.player.x += 20;
      }
      if (e.key === ' ') {
        this.shoot();
      }
    });
  }
  
  // ๐Ÿ”ซ Fire bullet
  private shoot(): void {
    this.bullets.push({
      x: this.player.x + this.player.width / 2 - 4,
      y: this.player.y,
      width: 8,
      height: 16,
      emoji: '๐Ÿ”ฅ',
      vy: -10,
      alive: true
    });
    console.log('๐Ÿ”ซ Pew pew!');
  }
  
  // ๐Ÿ”„ Update game state
  update(): void {
    // ๐Ÿš€ Update bullets
    this.bullets = this.bullets.filter(bullet => {
      bullet.y += bullet.vy;
      
      // ๐Ÿ’ฅ Check enemy collisions
      for (const row of this.enemies) {
        for (const enemy of row) {
          if (enemy.alive && this.checkCollision(bullet, enemy)) {
            enemy.alive = false;
            bullet.alive = false;
            this.score += 10;
            console.log(`๐Ÿ’ฅ Hit! Score: ${this.score}`);
          }
        }
      }
      
      return bullet.alive && bullet.y > 0;
    });
    
    // ๐Ÿ‘พ Move enemies
    this.moveEnemies();
  }
  
  // ๐Ÿ‘พ Enemy movement
  private moveEnemies(): void {
    let shouldDescend = false;
    
    // Check boundaries
    for (const row of this.enemies) {
      for (const enemy of row) {
        if (enemy.alive) {
          if ((enemy.x <= 0 && this.enemyDirection < 0) ||
              (enemy.x >= this.canvas.width - enemy.width && this.enemyDirection > 0)) {
            shouldDescend = true;
          }
        }
      }
    }
    
    // Move enemies
    for (const row of this.enemies) {
      for (const enemy of row) {
        if (shouldDescend) {
          enemy.y += 20;
        } else {
          enemy.x += this.enemyDirection * 2;
        }
      }
    }
    
    if (shouldDescend) {
      this.enemyDirection *= -1;
    }
  }
  
  // ๐Ÿ’ฅ Collision detection
  private checkCollision(a: SpaceObject, b: SpaceObject): boolean {
    return a.x < b.x + b.width &&
           a.x + a.width > b.x &&
           a.y < b.y + b.height &&
           a.y + a.height > b.y;
  }
  
  // ๐ŸŽจ Render everything
  render(): void {
    // ๐Ÿงน Clear screen
    this.ctx.fillStyle = '#000033';
    this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
    
    // ๐Ÿš€ Draw player
    this.ctx.font = '32px Arial';
    this.ctx.fillText(this.player.emoji, this.player.x, this.player.y);
    
    // ๐Ÿ‘พ Draw enemies
    for (const row of this.enemies) {
      for (const enemy of row) {
        if (enemy.alive) {
          this.ctx.font = '30px Arial';
          this.ctx.fillText(enemy.emoji, enemy.x, enemy.y);
        }
      }
    }
    
    // ๐Ÿ”ฅ Draw bullets
    this.ctx.font = '16px Arial';
    for (const bullet of this.bullets) {
      this.ctx.fillText(bullet.emoji, bullet.x, bullet.y);
    }
    
    // ๐Ÿ“Š Draw UI
    this.ctx.fillStyle = 'white';
    this.ctx.font = '20px Arial';
    this.ctx.fillText(`Score: ${this.score} ๐Ÿ†`, 10, 30);
    this.ctx.fillText(`Lives: ${'โค๏ธ'.repeat(this.lives)}`, this.canvas.width - 100, 30);
  }
}

// ๐ŸŽฎ Start the game!
const gameCanvas = document.getElementById('game') as HTMLCanvasElement;
const game = new SpaceInvaders(gameCanvas);

function gameLoop(): void {
  game.update();
  game.render();
  requestAnimationFrame(gameLoop);
}

gameLoop();
console.log('๐ŸŽฎ Space Invaders started! Use arrow keys and space to play!');

๐ŸŽ“ Key Takeaways

Youโ€™ve learned so much! Hereโ€™s what you can now do:

  • โœ… Create Canvas games with smooth animations ๐Ÿ’ช
  • โœ… Use WebGL for advanced graphics effects ๐Ÿ›ก๏ธ
  • โœ… Build game engines with TypeScriptโ€™s type safety ๐ŸŽฏ
  • โœ… Handle input and collisions like a pro ๐Ÿ›
  • โœ… Optimize performance for 60 FPS gameplay! ๐Ÿš€

Remember: Game development is about experimentation and fun! Donโ€™t be afraid to try wild ideas. ๐Ÿค

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered game development with Canvas and WebGL!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Build your own game using the concepts learned
  2. ๐Ÿ—๏ธ Explore game physics libraries like Matter.js
  3. ๐Ÿ“š Learn about game design patterns and architectures
  4. ๐ŸŒŸ Share your games with the world!

Remember: Every great game started with a simple prototype. Keep creating, keep learning, and most importantly, have fun! ๐Ÿš€


Happy gaming! ๐ŸŽฎ๐Ÿš€โœจ