fisrt commit
This commit is contained in:
944
docs/ARCHITECTURE.md
Normal file
944
docs/ARCHITECTURE.md
Normal file
@ -0,0 +1,944 @@
|
||||
# Architecture
|
||||
|
||||
Technical architecture for the Roguelite Platformer game.
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── main.ts # Entry point, Phaser config
|
||||
├── types/ # TypeScript interfaces
|
||||
│ ├── index.ts # Re-exports
|
||||
│ ├── game-state.ts # RunState, MetaState
|
||||
│ ├── entities.ts # EnemyConfig, HazardConfig
|
||||
│ ├── upgrades.ts # Upgrade, UpgradeEffect
|
||||
│ └── level-gen.ts # RoomTemplate, SpawnPoint
|
||||
│
|
||||
├── config/ # Configuration
|
||||
│ ├── game.config.ts # Phaser config
|
||||
│ ├── physics.config.ts # Arcade physics settings
|
||||
│ ├── controls.config.ts # Key bindings
|
||||
│ ├── balance.config.ts # Balance (speeds, timings)
|
||||
│ └── upgrades.config.ts # All shop upgrades
|
||||
│
|
||||
├── scenes/ # Phaser scenes
|
||||
│ ├── BootScene.ts # Initialization
|
||||
│ ├── PreloadScene.ts # Asset loading
|
||||
│ ├── MenuScene.ts # Main menu
|
||||
│ ├── GameScene.ts # Core gameplay
|
||||
│ ├── UIScene.ts # HUD (parallel with GameScene)
|
||||
│ ├── PauseScene.ts # Pause menu
|
||||
│ ├── ShopScene.ts # Upgrade shop
|
||||
│ └── GameOverScene.ts # Death screen
|
||||
│
|
||||
├── entities/ # Game objects
|
||||
│ ├── Player.ts # Player entity
|
||||
│ ├── PlayerController.ts # Controls + game feel
|
||||
│ ├── enemies/ # Enemy types
|
||||
│ │ ├── Enemy.ts # Base class
|
||||
│ │ ├── Patroller.ts # Patrol enemy
|
||||
│ │ ├── Jumper.ts # Jumping enemy
|
||||
│ │ ├── Flyer.ts # Flying enemy
|
||||
│ │ ├── Chaser.ts # Chasing enemy
|
||||
│ │ └── Sprinter.ts # Fast patrol enemy
|
||||
│ ├── Projectile.ts # Player projectile
|
||||
│ ├── Coin.ts # Collectible coin
|
||||
│ ├── PowerUp.ts # Power-up item
|
||||
│ └── hazards/ # Hazard types
|
||||
│ ├── Spikes.ts # Static spikes
|
||||
│ ├── FallingPlatform.ts # Crumbling platform
|
||||
│ ├── Saw.ts # Moving saw
|
||||
│ ├── Turret.ts # Shooting turret
|
||||
│ └── Laser.ts # Toggle laser
|
||||
│
|
||||
├── systems/ # Managers/Systems
|
||||
│ ├── InputManager.ts # Input buffering, abstraction
|
||||
│ ├── AudioManager.ts # Sound and music
|
||||
│ ├── SaveManager.ts # localStorage persistence
|
||||
│ ├── GameStateManager.ts # Run state + Meta state
|
||||
│ ├── UpgradeManager.ts # Upgrade application
|
||||
│ ├── ScoreManager.ts # Score and combo
|
||||
│ ├── ParticleManager.ts # Particle effects
|
||||
│ └── CameraManager.ts # Screen shake
|
||||
│
|
||||
├── level-generation/ # Procedural generation
|
||||
│ ├── LevelGenerator.ts # Main generator
|
||||
│ ├── RoomPlacer.ts # Room placement
|
||||
│ ├── PathValidator.ts # Path validation
|
||||
│ └── templates/ # JSON room templates
|
||||
│
|
||||
├── ui/ # UI components
|
||||
│ ├── HUD.ts # Heads-up display
|
||||
│ ├── Button.ts # Reusable button
|
||||
│ ├── ProgressBar.ts # Progress/health bar
|
||||
│ └── ComboDisplay.ts # Combo counter
|
||||
│
|
||||
└── utils/ # Utilities
|
||||
├── math.ts # Math helpers
|
||||
└── SeededRandom.ts # Seed-based RNG
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Scene Architecture
|
||||
|
||||
### Scene Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ BootScene → PreloadScene → MenuScene ─┬→ GameScene ←──┐ │
|
||||
│ │ │ │ │
|
||||
│ │ ▼ │ │
|
||||
│ │ UIScene │ │
|
||||
│ │ (parallel) │ │
|
||||
│ │ │ │ │
|
||||
│ │ ▼ │ │
|
||||
│ │ PauseScene │ │
|
||||
│ │ │ │ │
|
||||
│ │ ▼ │ │
|
||||
│ │ GameOverScene│ │
|
||||
│ │ │ │ │
|
||||
│ │ ▼ │ │
|
||||
│ └→ ShopScene ───┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Scene Responsibilities
|
||||
|
||||
| Scene | Responsibility |
|
||||
|-------|---------------|
|
||||
| **BootScene** | Initialize game settings, configure physics |
|
||||
| **PreloadScene** | Load all assets (sprites, audio, tilemaps) |
|
||||
| **MenuScene** | Main menu, navigation to other scenes |
|
||||
| **GameScene** | Core gameplay loop, entity management |
|
||||
| **UIScene** | HUD overlay, runs parallel with GameScene |
|
||||
| **PauseScene** | Pause menu, settings access during gameplay |
|
||||
| **ShopScene** | Meta-progression shop, upgrade purchases |
|
||||
| **GameOverScene** | Death screen, stats, navigation |
|
||||
|
||||
### Scene Communication
|
||||
|
||||
Scenes communicate through:
|
||||
|
||||
1. **Registry** (`this.registry`) - shared data store
|
||||
2. **Events** (`this.events`) - event-based messaging
|
||||
3. **Scene Manager** - scene transitions
|
||||
|
||||
```typescript
|
||||
// Example: GameScene emits coin collection
|
||||
this.events.emit('coin-collected', { amount: 1, total: this.coins });
|
||||
|
||||
// UIScene listens and updates display
|
||||
this.scene.get('GameScene').events.on('coin-collected', this.updateCoinDisplay, this);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## State Management
|
||||
|
||||
### Dual-State Pattern
|
||||
|
||||
The game uses two separate state objects:
|
||||
|
||||
```typescript
|
||||
// RunState - reset every run
|
||||
interface RunState {
|
||||
currentLevel: number;
|
||||
coins: number;
|
||||
score: number;
|
||||
combo: number;
|
||||
projectilesAvailable: number;
|
||||
activePowerUp: PowerUpType | null;
|
||||
powerUpTimer: number;
|
||||
hp: number;
|
||||
hasUsedSecondChance: boolean;
|
||||
enemiesStunned: number;
|
||||
timeElapsed: number;
|
||||
levelSeed: string;
|
||||
}
|
||||
|
||||
// MetaState - persists across runs
|
||||
interface MetaState {
|
||||
gasCoins: number;
|
||||
purchasedUpgrades: string[];
|
||||
activeUpgrades: string[];
|
||||
achievements: string[];
|
||||
totalCoinsCollected: number;
|
||||
totalEnemiesStunned: number;
|
||||
totalDeaths: number;
|
||||
highScores: HighScore[];
|
||||
settings: GameSettings;
|
||||
}
|
||||
```
|
||||
|
||||
### State Flow
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ GameStateManager │
|
||||
├────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌─────────────────────────┐ │
|
||||
│ │ RunState │ │ MetaState │ │
|
||||
│ │ (memory) │ │ (localStorage) │ │
|
||||
│ ├─────────────┤ ├─────────────────────────┤ │
|
||||
│ │ level: 1 │ ──death──▶ │ gasCoins += runCoins │ │
|
||||
│ │ coins: 42 │ │ totalDeaths++ │ │
|
||||
│ │ score: 1200 │ │ highScores.push(...) │ │
|
||||
│ │ ... │ │ │ │
|
||||
│ └─────────────┘ └─────────────────────────┘ │
|
||||
│ │ │ │
|
||||
│ │ reset on new run │ load on boot │
|
||||
│ ▼ ▼ save on change │
|
||||
│ │
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Entity System
|
||||
|
||||
### Entity Hierarchy
|
||||
|
||||
```
|
||||
Phaser.GameObjects.Sprite
|
||||
│
|
||||
│
|
||||
┌───────────────┼───────────────┐
|
||||
│ │ │
|
||||
Player Enemy Hazard
|
||||
│ │ │
|
||||
│ ┌───────┼───────┐ │
|
||||
│ │ │ │ │
|
||||
│ Patroller Flyer Chaser │
|
||||
│ │ │ │
|
||||
│ Jumper Sprinter │
|
||||
│ │
|
||||
│ ┌───────┼───────┐
|
||||
│ │ │ │
|
||||
│ Spikes Saw Laser
|
||||
│ │
|
||||
│ FallingPlatform
|
||||
│ │
|
||||
│ Turret
|
||||
│
|
||||
PlayerController
|
||||
```
|
||||
|
||||
### Enemy States
|
||||
|
||||
```typescript
|
||||
enum EnemyState {
|
||||
ACTIVE = 'active', // Dangerous, kills on contact
|
||||
STUNNED = 'stunned', // Safe, can pass through
|
||||
RECOVERING = 'recovering' // Brief transition state
|
||||
}
|
||||
```
|
||||
|
||||
State machine for enemies:
|
||||
|
||||
```
|
||||
┌─────────┐ hit by projectile ┌─────────┐ timer expires ┌────────────┐
|
||||
│ ACTIVE │ ─────────────────▶ │ STUNNED │ ──────────────▶ │ RECOVERING │
|
||||
└─────────┘ └─────────┘ └────────────┘
|
||||
▲ │
|
||||
│ recovery complete │
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Physics Configuration
|
||||
|
||||
### Arcade Physics Settings
|
||||
|
||||
```typescript
|
||||
// physics.config.ts
|
||||
export const PHYSICS_CONFIG = {
|
||||
gravity: { y: 1200 },
|
||||
|
||||
player: {
|
||||
speed: 300,
|
||||
jumpVelocity: -500,
|
||||
acceleration: 2000,
|
||||
drag: 1500,
|
||||
},
|
||||
|
||||
projectile: {
|
||||
speed: 600,
|
||||
lifetime: 1000, // ms
|
||||
},
|
||||
|
||||
enemies: {
|
||||
patroller: { speed: 100 },
|
||||
jumper: { speed: 80, jumpForce: -400 },
|
||||
flyer: { speed: 120 },
|
||||
chaser: { speed: 60 },
|
||||
sprinter: { speed: 200 },
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Collision Groups
|
||||
|
||||
```
|
||||
Player ←→ Enemy (active) = Death
|
||||
Player ←→ Enemy (stunned) = Pass through
|
||||
Player ←→ Hazard = Death
|
||||
Player ←→ Coin = Collect
|
||||
Player ←→ PowerUp = Collect
|
||||
Player ←→ Platform = Land
|
||||
Projectile ←→ Enemy = Stun enemy
|
||||
Projectile ←→ Wall = Destroy (or ricochet)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Game Feel Implementation
|
||||
|
||||
### Coyote Time
|
||||
|
||||
```typescript
|
||||
// PlayerController.ts
|
||||
private coyoteTime = 100; // ms
|
||||
private coyoteTimer = 0;
|
||||
private wasGrounded = false;
|
||||
|
||||
update(delta: number) {
|
||||
if (this.body.onFloor()) {
|
||||
this.coyoteTimer = this.coyoteTime;
|
||||
this.wasGrounded = true;
|
||||
} else if (this.wasGrounded) {
|
||||
this.coyoteTimer -= delta;
|
||||
if (this.coyoteTimer <= 0) {
|
||||
this.wasGrounded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
canJump(): boolean {
|
||||
return this.body.onFloor() || this.coyoteTimer > 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Input Buffering
|
||||
|
||||
```typescript
|
||||
// InputManager.ts
|
||||
private jumpBufferTime = 100; // ms
|
||||
private jumpBufferTimer = 0;
|
||||
|
||||
update(delta: number) {
|
||||
if (this.jumpPressed) {
|
||||
this.jumpBufferTimer = this.jumpBufferTime;
|
||||
} else {
|
||||
this.jumpBufferTimer = Math.max(0, this.jumpBufferTimer - delta);
|
||||
}
|
||||
}
|
||||
|
||||
hasBufferedJump(): boolean {
|
||||
return this.jumpBufferTimer > 0;
|
||||
}
|
||||
|
||||
consumeJumpBuffer() {
|
||||
this.jumpBufferTimer = 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Variable Jump Height
|
||||
|
||||
```typescript
|
||||
// PlayerController.ts
|
||||
private minJumpVelocity = -250;
|
||||
private maxJumpVelocity = -500;
|
||||
|
||||
onJumpPressed() {
|
||||
if (this.canJump()) {
|
||||
this.body.setVelocityY(this.maxJumpVelocity);
|
||||
this.isJumping = true;
|
||||
}
|
||||
}
|
||||
|
||||
onJumpReleased() {
|
||||
if (this.isJumping && this.body.velocity.y < this.minJumpVelocity) {
|
||||
this.body.setVelocityY(this.minJumpVelocity);
|
||||
}
|
||||
this.isJumping = false;
|
||||
}
|
||||
```
|
||||
|
||||
### Edge Correction
|
||||
|
||||
```typescript
|
||||
// PlayerController.ts
|
||||
private edgeCorrectionDistance = 4; // pixels
|
||||
|
||||
handleCollision(tile: Phaser.Tilemaps.Tile) {
|
||||
if (this.body.velocity.y < 0) { // Moving upward
|
||||
const overlap = this.getHorizontalOverlap(tile);
|
||||
if (overlap > 0 && overlap <= this.edgeCorrectionDistance) {
|
||||
this.x += overlap * this.getOverlapDirection(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Level Generation
|
||||
|
||||
### Spelunky-Style Room Grid
|
||||
|
||||
```
|
||||
┌─────┬─────┬─────┬─────┐
|
||||
│ S │ │ │ │ S = Start
|
||||
├─────┼─────┼─────┼─────┤ E = Exit
|
||||
│ ↓ │ ←──┤ │ │ ↓ → = Path direction
|
||||
├─────┼─────┼─────┼─────┤
|
||||
│ │ ↓ │ │ │
|
||||
├─────┼─────┼─────┼─────┤
|
||||
│ │ E │ │ │
|
||||
└─────┴─────┴─────┴─────┘
|
||||
```
|
||||
|
||||
### Generation Algorithm
|
||||
|
||||
```typescript
|
||||
// LevelGenerator.ts
|
||||
class LevelGenerator {
|
||||
generateLevel(level: number, seed: string): Level {
|
||||
const rng = new SeededRandom(seed);
|
||||
const config = this.getLevelConfig(level);
|
||||
|
||||
// 1. Create room grid
|
||||
const grid = this.createGrid(config.gridSize);
|
||||
|
||||
// 2. Generate guaranteed path
|
||||
const path = this.generatePath(grid, rng);
|
||||
|
||||
// 3. Place room templates along path
|
||||
this.placeRooms(grid, path, config.difficulty, rng);
|
||||
|
||||
// 4. Fill remaining cells with optional rooms
|
||||
this.fillOptionalRooms(grid, rng);
|
||||
|
||||
// 5. Place entities
|
||||
this.placeCoins(grid, config.coinCount, rng);
|
||||
this.placeEnemies(grid, config.enemyCount, level, rng);
|
||||
this.placeHazards(grid, config.hazardCount, level, rng);
|
||||
this.placePowerUp(grid, rng); // 10% chance
|
||||
|
||||
// 6. Validate
|
||||
if (!this.validatePath(grid)) {
|
||||
return this.generateLevel(level, seed + '_retry');
|
||||
}
|
||||
|
||||
return this.buildLevel(grid);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Room Templates
|
||||
|
||||
```typescript
|
||||
// types/level-gen.ts
|
||||
interface RoomTemplate {
|
||||
id: string;
|
||||
width: number; // tiles
|
||||
height: number; // tiles
|
||||
difficulty: number; // 1-10
|
||||
exits: {
|
||||
top: boolean;
|
||||
bottom: boolean;
|
||||
left: boolean;
|
||||
right: boolean;
|
||||
};
|
||||
tiles: number[][]; // tile IDs
|
||||
spawnPoints: SpawnPoint[];
|
||||
}
|
||||
|
||||
interface SpawnPoint {
|
||||
type: 'coin' | 'enemy' | 'hazard' | 'powerup';
|
||||
x: number;
|
||||
y: number;
|
||||
probability?: number;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Upgrade System
|
||||
|
||||
### Upgrade Configuration
|
||||
|
||||
```typescript
|
||||
// config/upgrades.config.ts
|
||||
export const UPGRADES: Upgrade[] = [
|
||||
{
|
||||
id: 'light_boots',
|
||||
name: 'Light Boots',
|
||||
description: '+10% movement speed',
|
||||
category: 'character',
|
||||
price: 100,
|
||||
effects: [{ type: 'speed_multiplier', value: 1.1 }],
|
||||
unlockRequirement: null,
|
||||
},
|
||||
{
|
||||
id: 'heavy_armor',
|
||||
name: 'Heavy Armor',
|
||||
description: '+1 HP per run, -15% speed',
|
||||
category: 'character',
|
||||
price: 500,
|
||||
effects: [
|
||||
{ type: 'extra_hp', value: 1 },
|
||||
{ type: 'speed_multiplier', value: 0.85 },
|
||||
],
|
||||
unlockRequirement: null,
|
||||
},
|
||||
// ... more upgrades
|
||||
];
|
||||
```
|
||||
|
||||
### Upgrade Application
|
||||
|
||||
```typescript
|
||||
// UpgradeManager.ts
|
||||
class UpgradeManager {
|
||||
applyUpgrades(player: Player, activeUpgrades: string[]) {
|
||||
const stats = { ...DEFAULT_PLAYER_STATS };
|
||||
|
||||
for (const upgradeId of activeUpgrades) {
|
||||
const upgrade = this.getUpgrade(upgradeId);
|
||||
for (const effect of upgrade.effects) {
|
||||
this.applyEffect(stats, effect);
|
||||
}
|
||||
}
|
||||
|
||||
player.setStats(stats);
|
||||
}
|
||||
|
||||
private applyEffect(stats: PlayerStats, effect: UpgradeEffect) {
|
||||
switch (effect.type) {
|
||||
case 'speed_multiplier':
|
||||
stats.speed *= effect.value;
|
||||
break;
|
||||
case 'jump_multiplier':
|
||||
stats.jumpVelocity *= effect.value;
|
||||
break;
|
||||
case 'extra_hp':
|
||||
stats.maxHp += effect.value;
|
||||
break;
|
||||
case 'extra_projectile':
|
||||
stats.maxProjectiles += effect.value;
|
||||
break;
|
||||
// ... more effect types
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Audio System
|
||||
|
||||
### Audio Manager
|
||||
|
||||
```typescript
|
||||
// AudioManager.ts
|
||||
class AudioManager {
|
||||
private music: Phaser.Sound.BaseSound | null = null;
|
||||
private sfxVolume: number = 1;
|
||||
private musicVolume: number = 0.5;
|
||||
|
||||
playMusic(key: string, loop = true) {
|
||||
if (this.music) this.music.stop();
|
||||
this.music = this.scene.sound.add(key, {
|
||||
loop,
|
||||
volume: this.musicVolume
|
||||
});
|
||||
this.music.play();
|
||||
}
|
||||
|
||||
playSFX(key: string, config?: Phaser.Types.Sound.SoundConfig) {
|
||||
this.scene.sound.play(key, {
|
||||
volume: this.sfxVolume,
|
||||
...config
|
||||
});
|
||||
}
|
||||
|
||||
setMusicVolume(volume: number) {
|
||||
this.musicVolume = volume;
|
||||
if (this.music) this.music.setVolume(volume);
|
||||
}
|
||||
|
||||
setSFXVolume(volume: number) {
|
||||
this.sfxVolume = volume;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Sound Effects List
|
||||
|
||||
| Event | Sound Key |
|
||||
|-------|-----------|
|
||||
| Jump | `sfx_jump` |
|
||||
| Land | `sfx_land` |
|
||||
| Shoot | `sfx_shoot` |
|
||||
| Enemy stunned | `sfx_stun` |
|
||||
| Coin collected | `sfx_coin` |
|
||||
| PowerUp collected | `sfx_powerup` |
|
||||
| Player death | `sfx_death` |
|
||||
| Level complete | `sfx_level_complete` |
|
||||
| Menu select | `sfx_menu_select` |
|
||||
| Purchase | `sfx_purchase` |
|
||||
|
||||
---
|
||||
|
||||
## Save System
|
||||
|
||||
### SaveManager
|
||||
|
||||
```typescript
|
||||
// SaveManager.ts
|
||||
class SaveManager {
|
||||
private readonly SAVE_KEY = 'roguelite_platformer_save';
|
||||
|
||||
save(metaState: MetaState): void {
|
||||
const data = JSON.stringify(metaState);
|
||||
localStorage.setItem(this.SAVE_KEY, data);
|
||||
}
|
||||
|
||||
load(): MetaState | null {
|
||||
const data = localStorage.getItem(this.SAVE_KEY);
|
||||
if (!data) return null;
|
||||
|
||||
try {
|
||||
return JSON.parse(data) as MetaState;
|
||||
} catch {
|
||||
console.error('Failed to parse save data');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
localStorage.removeItem(this.SAVE_KEY);
|
||||
}
|
||||
|
||||
exists(): boolean {
|
||||
return localStorage.getItem(this.SAVE_KEY) !== null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Data Stored
|
||||
|
||||
```typescript
|
||||
interface SaveData {
|
||||
version: string; // For migration
|
||||
metaState: MetaState;
|
||||
lastPlayed: number; // Timestamp
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TypeScript Interfaces
|
||||
|
||||
### Core Types
|
||||
|
||||
```typescript
|
||||
// types/game-state.ts
|
||||
interface RunState {
|
||||
currentLevel: number;
|
||||
coins: number;
|
||||
totalCoinsOnLevel: number;
|
||||
score: number;
|
||||
combo: number;
|
||||
comboTimer: number;
|
||||
projectilesAvailable: number;
|
||||
projectileCooldowns: number[];
|
||||
activePowerUp: PowerUpType | null;
|
||||
powerUpTimer: number;
|
||||
hp: number;
|
||||
maxHp: number;
|
||||
hasUsedSecondChance: boolean;
|
||||
enemiesStunned: number;
|
||||
timeElapsed: number;
|
||||
levelSeed: string;
|
||||
isPaused: boolean;
|
||||
}
|
||||
|
||||
interface MetaState {
|
||||
gasCoins: number;
|
||||
purchasedUpgrades: string[];
|
||||
activeUpgrades: string[];
|
||||
achievements: Achievement[];
|
||||
stats: GlobalStats;
|
||||
highScores: HighScore[];
|
||||
settings: GameSettings;
|
||||
}
|
||||
|
||||
interface GlobalStats {
|
||||
totalCoinsCollected: number;
|
||||
totalEnemiesStunned: number;
|
||||
totalDeaths: number;
|
||||
totalLevelsCompleted: number;
|
||||
totalPlayTime: number;
|
||||
highestLevel: number;
|
||||
highestCombo: number;
|
||||
}
|
||||
|
||||
interface HighScore {
|
||||
score: number;
|
||||
level: number;
|
||||
date: number;
|
||||
seed?: string;
|
||||
}
|
||||
|
||||
interface GameSettings {
|
||||
musicVolume: number;
|
||||
sfxVolume: number;
|
||||
screenShake: boolean;
|
||||
showFPS: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### Entity Types
|
||||
|
||||
```typescript
|
||||
// types/entities.ts
|
||||
interface EnemyConfig {
|
||||
type: EnemyType;
|
||||
speed: number;
|
||||
patrolDistance?: number;
|
||||
jumpInterval?: number;
|
||||
flightPattern?: FlightPattern;
|
||||
detectionRange?: number;
|
||||
}
|
||||
|
||||
type EnemyType = 'patroller' | 'jumper' | 'flyer' | 'chaser' | 'sprinter';
|
||||
|
||||
interface HazardConfig {
|
||||
type: HazardType;
|
||||
damage: number;
|
||||
interval?: number; // For turrets, lasers
|
||||
path?: Phaser.Math.Vector2[]; // For saws
|
||||
}
|
||||
|
||||
type HazardType = 'spikes' | 'falling_platform' | 'saw' | 'turret' | 'laser';
|
||||
|
||||
type PowerUpType = 'shield' | 'magnet' | 'clock' | 'coffee' | 'infinity' | 'ghost';
|
||||
|
||||
interface PowerUpConfig {
|
||||
type: PowerUpType;
|
||||
duration: number;
|
||||
effect: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
### Upgrade Types
|
||||
|
||||
```typescript
|
||||
// types/upgrades.ts
|
||||
interface Upgrade {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
category: UpgradeCategory;
|
||||
price: number;
|
||||
effects: UpgradeEffect[];
|
||||
unlockRequirement: UnlockRequirement | null;
|
||||
tradeoff?: string; // Description of downside
|
||||
}
|
||||
|
||||
type UpgradeCategory =
|
||||
| 'character'
|
||||
| 'weapon'
|
||||
| 'survival'
|
||||
| 'risk_reward'
|
||||
| 'cosmetic';
|
||||
|
||||
interface UpgradeEffect {
|
||||
type: EffectType;
|
||||
value: number;
|
||||
}
|
||||
|
||||
type EffectType =
|
||||
| 'speed_multiplier'
|
||||
| 'jump_multiplier'
|
||||
| 'extra_hp'
|
||||
| 'extra_projectile'
|
||||
| 'projectile_cooldown_multiplier'
|
||||
| 'projectile_range_multiplier'
|
||||
| 'stun_duration_multiplier'
|
||||
| 'coin_multiplier'
|
||||
| 'score_multiplier'
|
||||
| 'hitbox_multiplier'
|
||||
| 'enable_double_jump'
|
||||
| 'enable_dash'
|
||||
| 'enable_ricochet'
|
||||
| 'enable_explosive';
|
||||
|
||||
interface UnlockRequirement {
|
||||
type: 'level_reached' | 'coins_collected' | 'deaths' | 'enemies_stunned' | 'achievement';
|
||||
value: number | string;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Visual Effects
|
||||
|
||||
### Particle System
|
||||
|
||||
```typescript
|
||||
// ParticleManager.ts
|
||||
class ParticleManager {
|
||||
private emitters: Map<string, Phaser.GameObjects.Particles.ParticleEmitter>;
|
||||
|
||||
createDustEffect(x: number, y: number) {
|
||||
this.emitters.get('dust')?.explode(5, x, y);
|
||||
}
|
||||
|
||||
createStunEffect(x: number, y: number) {
|
||||
this.emitters.get('stars')?.explode(8, x, y);
|
||||
}
|
||||
|
||||
createCoinSparkle(x: number, y: number) {
|
||||
this.emitters.get('sparkle')?.explode(3, x, y);
|
||||
}
|
||||
|
||||
createDeathEffect(x: number, y: number) {
|
||||
this.emitters.get('explosion')?.explode(20, x, y);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Screen Shake
|
||||
|
||||
```typescript
|
||||
// CameraManager.ts
|
||||
class CameraManager {
|
||||
private camera: Phaser.Cameras.Scene2D.Camera;
|
||||
private shakeEnabled: boolean = true;
|
||||
|
||||
shake(intensity: number, duration: number) {
|
||||
if (!this.shakeEnabled) return;
|
||||
this.camera.shake(duration, intensity);
|
||||
}
|
||||
|
||||
microShake() {
|
||||
this.shake(0.002, 50);
|
||||
}
|
||||
|
||||
deathShake() {
|
||||
this.shake(0.01, 200);
|
||||
}
|
||||
|
||||
setShakeEnabled(enabled: boolean) {
|
||||
this.shakeEnabled = enabled;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Squash & Stretch
|
||||
|
||||
```typescript
|
||||
// Player.ts
|
||||
jumpSquash() {
|
||||
this.setScale(0.8, 1.2);
|
||||
this.scene.tweens.add({
|
||||
targets: this,
|
||||
scaleX: 1,
|
||||
scaleY: 1,
|
||||
duration: 150,
|
||||
ease: 'Back.out',
|
||||
});
|
||||
}
|
||||
|
||||
landSquash() {
|
||||
this.setScale(1.2, 0.8);
|
||||
this.scene.tweens.add({
|
||||
targets: this,
|
||||
scaleX: 1,
|
||||
scaleY: 1,
|
||||
duration: 100,
|
||||
ease: 'Back.out',
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Hitstop
|
||||
|
||||
```typescript
|
||||
// GameScene.ts
|
||||
hitStop(duration: number = 50) {
|
||||
this.physics.pause();
|
||||
this.time.delayedCall(duration, () => {
|
||||
this.physics.resume();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Object Pooling
|
||||
|
||||
```typescript
|
||||
// Projectile pooling
|
||||
class ProjectilePool {
|
||||
private pool: Phaser.GameObjects.Group;
|
||||
|
||||
get(): Projectile {
|
||||
const projectile = this.pool.getFirstDead(true);
|
||||
if (projectile) {
|
||||
projectile.setActive(true).setVisible(true);
|
||||
return projectile;
|
||||
}
|
||||
return this.pool.add(new Projectile(this.scene));
|
||||
}
|
||||
|
||||
release(projectile: Projectile) {
|
||||
projectile.setActive(false).setVisible(false);
|
||||
projectile.body.stop();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Culling
|
||||
|
||||
- Entities outside viewport are deactivated
|
||||
- Physics calculations only for active entities
|
||||
- Particle systems use burst mode, not continuous
|
||||
|
||||
### Asset Loading
|
||||
|
||||
- Sprites loaded as atlases
|
||||
- Audio preloaded in PreloadScene
|
||||
- Level templates loaded on demand
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
- State management logic
|
||||
- Level generation algorithms
|
||||
- Upgrade calculations
|
||||
- Collision detection helpers
|
||||
|
||||
### Integration Tests
|
||||
- Scene transitions
|
||||
- Save/load functionality
|
||||
- Input handling
|
||||
|
||||
### Manual Testing
|
||||
- Game feel tuning
|
||||
- Balance verification
|
||||
- Performance profiling
|
||||
Reference in New Issue
Block a user