Skip to content

Quick Start - Build Your First Game in 30 Minutes

Complete tutorial from installation to Multi-Scale game with Vue UI


📦 Step 1: Installation (2 minutes)

Prerequisites

  • Node.js 18+ and pnpm 8+
  • Code editor (VS Code recommended)
  • Modern browser

Clone and Install

bash
# Clone repository
git clone <your-repo-url> my-game
cd my-game

# Install dependencies
ppnpm install

# Verify installation
pnpm dev

Open http://localhost:5173 - you should see LINX game demo.

Press TAB to cycle modes
Resize window to see auto-adaptation

✅ If game loads - installation successful!


🎮 Step 2: Create Your First Scene (5 minutes)

Create Scene File

Create src/game/scenes/MyFirstScene.ts:

typescript
import Phaser from "phaser";
import { UIManager } from "@core/ui/UIManager";
import { GameModeManager } from "@core/systems/GameModeManager";
import { ViewportManager } from "@core/utils/ViewportManager";

export class MyFirstScene extends Phaser.Scene {
  private uiManager!: UIManager;
  private gameModeManager!: GameModeManager;
  private viewportManager!: ViewportManager;

  constructor() {
    super({ key: "MyFirstScene" });
  }

  create(): void {
    // Initialize viewport
    this.viewportManager = new ViewportManager(this);
    this.viewportManager.init();

    // Initialize game modes
    this.gameModeManager = new GameModeManager(this, this.viewportManager);
    this.gameModeManager.init();

    // Initialize UI
    this.uiManager = new UIManager(this, {
      enableLogging: true,
    });

    // Add visual feedback
    this.add
      .text(400, 300, "My First CASCADA Game!", {
        fontSize: "32px",
        color: "#00ffff",
      })
      .setOrigin(0.5);

    // Log current mode
    this.gameModeManager.onModeChange((event) => {
      console.log(`Mode: ${event.newMode}`);
    });
  }
}

Register Scene

Edit src/game/config/game.config.ts:

typescript
import { MyFirstScene } from "@game/scenes/MyFirstScene";

// Find scene array and add:
scene: [
  BootScene,
  PreloaderScene,
  MyFirstScene, // ← Add this
  // ... other scenes
];

Launch Scene

Edit src/game/scenes/PreloaderScene.ts:

typescript
create() {
  // Change this line:
  this.scene.start("MyFirstScene"); // Instead of "MainMenuScene"
}

Test: pnpm dev → you should see "My First CASCADA Game!"

Press TAB - mode info appears in console!


🎨 Step 3: Add Vue UI (8 minutes)

Create Vue Component

Create src/game/ui/MyGameHUD.vue:

vue
<script setup lang="ts">
import { defineProps, defineEmits, computed } from "vue";

const props = defineProps({
  score: { type: Number, default: 0 },
  mode: { type: String, default: "NANO" },
});

const emit = defineEmits(["reset-game"]);

const scoreFormatted = computed(() => {
  return props.score.toString().padStart(6, "0");
});
</script>

<template>
  <div class="game-hud">
    <div class="hud-top">
      <div class="mode-display">MODE: {{ mode }}</div>
      <div class="score">SCORE: {{ scoreFormatted }}</div>
    </div>

    <div class="hud-bottom">
      <button @click="emit('reset-game')" class="btn-reset">Reset</button>
    </div>
  </div>
</template>

<style scoped>
.game-hud {
  position: fixed;
  inset: 0;
  pointer-events: none;
  font-family: monospace;
  color: #00ffff;
}

.hud-top {
  display: flex;
  justify-content: space-between;
  padding: 20px;
  pointer-events: none;
}

.mode-display,
.score {
  background: rgba(0, 0, 0, 0.8);
  padding: 10px 20px;
  border: 1px solid #00ffff;
  font-size: 16px;
}

.hud-bottom {
  position: absolute;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
}

.btn-reset {
  padding: 10px 30px;
  background: rgba(0, 255, 255, 0.1);
  border: 1px solid #00ffff;
  color: #00ffff;
  cursor: pointer;
  pointer-events: auto;
  transition: all 0.3s;
}

.btn-reset:hover {
  background: rgba(0, 255, 255, 0.3);
}
</style>

Integrate in Scene

Update src/game/scenes/MyFirstScene.ts:

typescript
import { VueUIBridge } from "@core/ui/VueUIBridge";
import MyGameHUD from "@game/ui/MyGameHUD.vue";

export class MyFirstScene extends Phaser.Scene {
  private vueBridge!: VueUIBridge;
  private score: number = 0;

  create(): void {
    // ... existing code ...

    // Create Vue bridge
    this.vueBridge = new VueUIBridge(this.uiManager);

    // Mount HUD
    this.vueBridge.mount("hud", MyGameHUD, {
      props: {
        score: this.score,
        mode: this.gameModeManager.getConfig().name,
      },
      events: {
        "reset-game": () => this.resetGame(),
      },
    });

    this.vueBridge.show("hud");

    // Update on mode change
    this.gameModeManager.onModeChange((event) => {
      const config = this.gameModeManager.getConfig();
      this.vueBridge.updateProps("hud", {
        mode: config.name,
      });
    });
  }

  update(): void {
    // Update score every frame for demo
    this.score += 1;
    this.vueBridge.updateProps("hud", { score: this.score });
  }

  resetGame(): void {
    this.score = 0;
    console.log("Game reset!");
  }

  shutdown(): void {
    this.vueBridge.unmountAll();
    this.uiManager.shutdown();
  }
}

Test: pnpm dev

  • HUD with mode and score visible ✅
  • Press TAB - mode changes in HUD ✅
  • Click Reset - score resets ✅

You just created reactive Vue UI in Phaser!


🎯 Step 4: Add Multi-Scale Gameplay (10 minutes)

Add Player Object

Update MyFirstScene.ts:

typescript
export class MyFirstScene extends Phaser.Scene {
  private player!: Phaser.GameObjects.Arc;
  private moveSpeed: number = 5;

  create(): void {
    // ... existing code ...

    // Create player
    this.player = this.add.circle(400, 300, 20, 0x00ffff);

    // Keyboard controls
    this.cursors = this.input.keyboard.createCursorKeys();

    // React to mode changes
    this.gameModeManager.onModeChange(() => {
      this.adaptToMode();
    });

    // Initial adaptation
    this.adaptToMode();
  }

  update(): void {
    // Update score
    this.score += 1;
    this.vueBridge.updateProps("hud", { score: this.score });

    // Move player
    if (this.cursors.left.isDown) {
      this.player.x -= this.moveSpeed;
    }
    if (this.cursors.right.isDown) {
      this.player.x += this.moveSpeed;
    }
    if (this.cursors.up.isDown) {
      this.player.y -= this.moveSpeed;
    }
    if (this.cursors.down.isDown) {
      this.player.y += this.moveSpeed;
    }
  }

  adaptToMode(): void {
    const mode = this.gameModeManager.getCurrentMode();
    const config = this.gameModeManager.getConfig();

    // Adapt player speed based on mode
    if (this.gameModeManager.can("detailedInteraction")) {
      // NANO/MICRO - detailed control, slow movement
      this.moveSpeed = 5;
      this.player.setScale(1);
    } else {
      // MESO/MACRO/MEGA - fast movement, smaller player
      this.moveSpeed = 15;
      this.player.setScale(0.5);
    }

    console.log(`Adapted to ${config.name}:`, {
      speed: this.moveSpeed,
      zoom: config.cameraZoom,
      timeScale: config.timeScale,
    });
  }
}

Test: pnpm dev

  • Move player with arrow keys ✅
  • Press TAB - player speed CHANGES based on mode ✅
  • NANO mode (zoom 2.5x): slow, precise movement
  • MEGA mode (zoom 0.3x): fast, strategic movement

You just added adaptive gameplay!


🌊 Step 5: Add Enemies (5 minutes)

Spawn Enemies

typescript
export class MyFirstScene extends Phaser.Scene {
  private enemies: Phaser.GameObjects.Arc[] = [];

  create(): void {
    // ... existing code ...

    // Spawn enemies
    this.spawnEnemies();

    // Respawn timer
    this.time.addEvent({
      delay: 3000,
      callback: () => this.spawnEnemies(),
      loop: true,
    });
  }

  spawnEnemies(): void {
    const maxEnemies = this.gameModeManager.can("detailedInteraction")
      ? 5 // NANO/MICRO - few enemies, detailed combat
      : 20; // MESO/MACRO/MEGA - many enemies, strategic

    // Spawn up to max
    while (this.enemies.length < maxEnemies) {
      const enemy = this.add.circle(
        Phaser.Math.Between(100, 700),
        Phaser.Math.Between(100, 500),
        15,
        0xff0000
      );

      this.enemies.push(enemy);
    }
  }

  update(): void {
    // ... existing movement code ...

    // Check collisions
    this.enemies.forEach((enemy, index) => {
      const distance = Phaser.Math.Distance.Between(
        this.player.x,
        this.player.y,
        enemy.x,
        enemy.y
      );

      if (distance < 35) {
        // Collision!
        enemy.destroy();
        this.enemies.splice(index, 1);
        this.score += 100;
      }
    });

    // Update UI
    this.vueBridge.updateProps("hud", { score: this.score });
  }

  adaptToMode(): void {
    // ... existing code ...

    // Clear old enemies, spawn new count
    this.enemies.forEach((e) => e.destroy());
    this.enemies = [];
    this.spawnEnemies();
  }
}

Test: pnpm dev

  • Red enemies spawn ✅
  • Touch enemy - destroyed, score +100 ✅
  • Press TAB:
    • NANO mode → 5 enemies (tactical combat)
    • MEGA mode → 20 enemies (strategic overview)

Gameplay now adapts to scale!


🎨 Step 6: Add Theme Switching (5 minutes)

Update HUD Component

Edit src/game/ui/MyGameHUD.vue:

vue
<script setup lang="ts">
const props = defineProps({
  score: { type: Number, default: 0 },
  mode: { type: String, default: "NANO" },
  theme: { type: String, default: "minimal" }, // ← Add theme
});

const emit = defineEmits(["reset-game", "change-theme"]); // ← Add event
</script>

<template>
  <div class="game-hud">
    <div class="hud-top">
      <div class="mode-display">MODE: {{ mode }}</div>
      <div class="score">SCORE: {{ scoreFormatted }}</div>
    </div>

    <!-- Theme selector -->
    <div class="hud-theme">
      <select
        @change="emit('change-theme', $event.target.value)"
        :value="theme"
        class="theme-select"
      >
        <option value="minimal">Minimal</option>
        <option value="neon-grid">Neon Grid</option>
        <option value="brutalist">Brutalist</option>
        <option value="liquid-crystal">Liquid Crystal</option>
      </select>
    </div>

    <div class="hud-bottom">
      <button @click="emit('reset-game')" class="btn-reset">Reset</button>
    </div>
  </div>
</template>

<style scoped>
/* ... existing styles ... */

.hud-theme {
  position: absolute;
  top: 20px;
  left: 50%;
  transform: translateX(-50%);
}

.theme-select {
  background: rgba(0, 0, 0, 0.8);
  border: 1px solid #00ffff;
  color: #00ffff;
  padding: 8px 16px;
  font-family: monospace;
  pointer-events: auto;
  cursor: pointer;
}
</style>

Handle Theme Changes

Update MyFirstScene.ts:

typescript
import { ThemeManager } from "@core/config/theme-manager";

export class MyFirstScene extends Phaser.Scene {
  private themeManager!: ThemeManager;
  private currentTheme: string = "minimal";

  create(): void {
    // ... existing code ...

    // Initialize theme manager
    this.themeManager = new ThemeManager();
    this.themeManager.setTheme("minimal");

    // Mount HUD with theme prop
    this.vueBridge.mount("hud", MyGameHUD, {
      props: {
        score: this.score,
        mode: this.gameModeManager.getConfig().name,
        theme: this.currentTheme, // ← Add theme
      },
      events: {
        "reset-game": () => this.resetGame(),
        "change-theme": (theme: string) => this.changeTheme(theme), // ← Add handler
      },
    });
  }

  changeTheme(themeId: string): void {
    this.themeManager.setTheme(themeId);
    this.currentTheme = themeId;
    console.log(`Theme changed to: ${themeId}`);

    // Update player color based on theme
    const theme = this.themeManager.getCurrentTheme();
    this.player.setFillStyle(parseInt(theme.primary.replace("#", ""), 16));
  }
}

Test: pnpm dev

  • Theme selector visible at top ✅
  • Change theme - colors update instantly ✅
  • Try "Neon Grid" - cyberpunk aesthetic!
  • Try "Liquid Crystal" - rainbow shimmer!

🚀 Step 7: Add Scoring System (5 minutes)

Create Score Manager

Create src/game/core/ScoreManager.ts:

typescript
export class ScoreManager {
  private score: number = 0;
  private highScore: number = 0;
  private multiplier: number = 1;

  constructor() {
    // Load high score from localStorage
    const saved = localStorage.getItem("highScore");
    this.highScore = saved ? parseInt(saved) : 0;
  }

  addScore(points: number): void {
    this.score += points * this.multiplier;

    if (this.score > this.highScore) {
      this.highScore = this.score;
      localStorage.setItem("highScore", this.highScore.toString());
    }
  }

  setMultiplier(value: number): void {
    this.multiplier = value;
  }

  getScore(): number {
    return this.score;
  }

  getHighScore(): number {
    return this.highScore;
  }

  reset(): void {
    this.score = 0;
    this.multiplier = 1;
  }
}

Use in Scene

typescript
import { ScoreManager } from "@game/core/ScoreManager";

export class MyFirstScene extends Phaser.Scene {
  private scoreManager!: ScoreManager;

  create(): void {
    // ... existing code ...

    this.scoreManager = new ScoreManager();

    // Update HUD with high score
    this.vueBridge.mount("hud", MyGameHUD, {
      props: {
        score: this.scoreManager.getScore(),
        highScore: this.scoreManager.getHighScore(), // ← Add
        mode: this.gameModeManager.getConfig().name,
        theme: this.currentTheme,
      },
      // ... events ...
    });
  }

  update(): void {
    // ... existing code ...

    // Enemy collision
    this.enemies.forEach((enemy, index) => {
      const distance = Phaser.Math.Distance.Between(
        this.player.x,
        this.player.y,
        enemy.x,
        enemy.y
      );

      if (distance < 35) {
        enemy.destroy();
        this.enemies.splice(index, 1);

        // Use score manager
        this.scoreManager.addScore(100);

        // Update UI
        this.vueBridge.updateProps("hud", {
          score: this.scoreManager.getScore(),
        });
      }
    });
  }

  resetGame(): void {
    this.scoreManager.reset();
    this.vueBridge.updateProps("hud", {
      score: 0,
    });
  }
}

Test: High score persists across reloads!


🎮 Step 8: Test Multi-Scale Adaptation (5 minutes)

Add Keyboard Controls

Update MyFirstScene.ts:

typescript
create(): void {
  // ... existing code ...

  // Keyboard shortcuts
  this.input.keyboard?.on("keydown-TAB", () => {
    this.gameModeManager.cycleMode();
  });

  this.input.keyboard?.on("keydown-ONE", () => {
    this.gameModeManager.setMode(GameModeType.MODE_1, "manual");
  });

  this.input.keyboard?.on("keydown-TWO", () => {
    this.gameModeManager.setMode(GameModeType.MODE_2, "manual");
  });

  this.input.keyboard?.on("keydown-THREE", () => {
    this.gameModeManager.setMode(GameModeType.MODE_3, "manual");
  });

  this.input.keyboard?.on("keydown-FOUR", () => {
    this.gameModeManager.setMode(GameModeType.MODE_4, "manual");
  });

  this.input.keyboard?.on("keydown-FIVE", () => {
    this.gameModeManager.setMode(GameModeType.MODE_5, "manual");
  });

  this.input.keyboard?.on("keydown-A", () => {
    const currentAuto = this.gameModeManager["autoModeEnabled"];
    this.gameModeManager.setAutoMode(!currentAuto);
  });
}

Test All Modes

Run: pnpm dev

Try each mode:

Press 1 (NANO):
├─ Zoom: 2.5x (very close)
├─ Speed: Slow, precise
├─ Enemies: 5 (tactical combat)
└─ Time: 1.0x (normal)

Press 3 (MESO):
├─ Zoom: 0.8x (medium)
├─ Speed: Fast movement
├─ Enemies: 20 (strategic)
└─ Time: 2.0x (faster)

Press 5 (MEGA):
├─ Zoom: 0.3x (very far)
├─ Speed: Very fast
├─ Enemies: 20 (overview)
└─ Time: 10.0x (very fast)

Resize browser window:

  • Small (< 768px) → NANO mode
  • Medium (1024px) → MESO mode
  • Large (1920px) → MEGA mode

Your game now adapts to device!


✨ Final Result (30 minutes total)

What You Built

Multi-Scale Game:

  • 5 gameplay modes with different mechanics
  • Automatic device detection
  • Manual mode switching

Vue UI Integration:

  • Reactive HUD with score and mode
  • Theme selector with hot-swapping
  • Reset button with event handling

Adaptive Mechanics:

  • Player speed changes per mode
  • Enemy count adapts to scale
  • Time scale affects gameplay

Production-Ready Systems:

  • UIManager for UI layer
  • VueUIBridge for Vue integration
  • GameModeManager for scales
  • ViewportManager for devices
  • ThemeManager for visual styles

Your Game in Numbers

MetricValue
Lines of code~150 lines
Time taken30 minutes
Features5 game modes, Vue UI, themes
Platform supportWeb (iOS/Android via Capacitor)
UI frameworkVue 3 with Composition API

🚀 Next Steps

Enhance Your Game

Add more gameplay:

Improve UI:

Study Example:

  • LINX Game - Complete game breakdown
  • Graph-based gameplay
  • Energy economy
  • Bot AI

Go Mobile:

  • Mobile Export - iOS/Android with Capacitor
  • Touch controls
  • Performance optimization

📚 Full Documentation


🎯 Tips for Success

1. Use Capability Flags

typescript
// ✅ GOOD - adapts to mode
if (gameModeManager.can("specialAbilities")) {
  this.enableDivinePowers();
}

// ❌ BAD - hardcoded mode check
if (mode === GameModeType.MODE_5) {
  this.enableDivinePowers();
}

2. React to Mode Changes

typescript
// ✅ GOOD - event-driven
gameModeManager.onModeChange(() => {
  this.adaptGameplay();
});

// ❌ BAD - polling in update
update() {
  if (this.mode !== this.gameModeManager.getCurrentMode()) {
    this.adaptGameplay();
  }
}

3. Use Reactive Props

typescript
// ✅ GOOD - Vue handles rendering
vueBridge.updateProps("hud", { score: newScore });

// ❌ BAD - manual DOM manipulation
document.getElementById("score").textContent = newScore;

Congratulations! You built your first Multi-Scale game with CASCADA! 🎉

View Complete Example →Read Full GuideAPI Reference

MIT Licensed