强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Pixi.js 游戏开发教程 / Pixi.js 完整项目:横版卷轴射击游戏

完整项目:横版卷轴射击游戏

从零构建一款经典横版卷轴射击游戏(Side-Scrolling Shooter)。


一、项目架构设计

1.1 系统架构图

┌─────────────────────────────────────────┐
│                  Game                    │
│  ┌──────────┬──────────┬──────────────┐ │
│  │  Scene    │  Input   │    Audio     │ │
│  │  Manager  │  Manager │   Manager    │ │
│  ├──────────┴──────────┴──────────────┤ │
│  │         GameScene (战斗场景)         │ │
│  │  ┌──────┬───────┬───────┬────────┐ │ │
│  │  │Player│ Enemy │Bullet │  Item  │ │ │
│  │  │      │ Wave  │ Pool  │ Spawn  │ │ │
│  │  └──────┴───────┴───────┴────────┘ │ │
│  │  ┌───────────┬────────────────────┐ │ │
│  │  │ Background │       HUD        │ │ │
│  │  │  Scroller  │ Score/HP/Energy  │ │ │
│  │  └───────────┴────────────────────┘ │ │
│  └─────────────────────────────────────┘ │
└─────────────────────────────────────────┘

1.2 管理器实现

// src/core/Game.ts
import { Application, Container } from 'pixi.js';
import { SceneManager } from './SceneManager';
import { InputManager } from './InputManager';
import { AudioManager } from './AudioManager';

export class Game {
    public app: Application;
    public scenes: SceneManager;
    public input: InputManager;
    public audio: AudioManager;
    public width = 800;
    public height = 600;

    constructor() {
        this.app = new Application();
        this.scenes = new SceneManager(this);
        this.input = new InputManager();
        this.audio = new AudioManager();
    }

    async init() {
        await this.app.init({
            width: this.width,
            height: this.height,
            backgroundColor: 0x0a0a1a,
            antialias: true,
            resolution: Math.min(window.devicePixelRatio, 2),
            autoDensity: true,
        });

        document.getElementById('app')!.appendChild(this.app.canvas);

        this.app.ticker.add((ticker) => {
            this.update(ticker.deltaTime);
        });

        await this.scenes.switchTo('menu');
    }

    private update(delta: number) {
        this.scenes.update(delta);
    }
}

// src/core/SceneManager.ts
import { Container } from 'pixi.js';
import type { Game } from './Game';

export interface Scene {
    container: Container;
    init(): Promise<void>;
    update(delta: number): void;
    destroy(): void;
}

export class SceneManager {
    private current: Scene | null = null;
    private scenes = new Map<string, () => Promise<Scene>>();
    private game: Game;

    constructor(game: Game) {
        this.game = game;
    }

    register(name: string, factory: () => Promise<Scene>) {
        this.scenes.set(name, factory);
    }

    async switchTo(name: string) {
        if (this.current) {
            this.game.app.stage.removeChild(this.current.container);
            this.current.destroy();
        }

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

        this.current = await factory();
        await this.current.init();
        this.game.app.stage.addChild(this.current.container);
    }

    update(delta: number) {
        this.current?.update(delta);
    }
}

// src/core/InputManager.ts
export class InputManager {
    private keys = new Set<string>();
    private _mouseX = 0;
    private _mouseY = 0;
    private _mouseDown = false;

    constructor() {
        window.addEventListener('keydown', (e) => this.keys.add(e.code));
        window.addEventListener('keyup', (e) => this.keys.delete(e.code));
        window.addEventListener('mousemove', (e) => {
            this._mouseX = e.clientX;
            this._mouseY = e.clientY;
        });
        window.addEventListener('mousedown', () => this._mouseDown = true);
        window.addEventListener('mouseup', () => this._mouseDown = false);
    }

    isKeyDown(code: string): boolean {
        return this.keys.has(code);
    }

    get mouseX() { return this._mouseX; }
    get mouseY() { return this._mouseY; }
    get mouseDown() { return this._mouseDown; }
}

// src/core/AudioManager.ts
export class AudioManager {
    private sounds = new Map<string, HTMLAudioElement>();
    private musicVolume = 0.5;
    private sfxVolume = 0.7;

    load(name: string, src: string) {
        const audio = new Audio(src);
        audio.preload = 'auto';
        this.sounds.set(name, audio);
    }

    play(name: string, loop = false) {
        const sound = this.sounds.get(name);
        if (!sound) return;
        const clone = sound.cloneNode() as HTMLAudioElement;
        clone.volume = this.sfxVolume;
        clone.loop = loop;
        clone.play();
    }

    playMusic(name: string) {
        const sound = this.sounds.get(name);
        if (!sound) return;
        sound.volume = this.musicVolume;
        sound.loop = true;
        sound.play();
    }

    stopAll() {
        this.sounds.forEach(s => { s.pause(); s.currentTime = 0; });
    }
}

二、玩家飞机

2.1 Player 类

// src/entities/Player.ts
import { Sprite, Texture, Container, Graphics } from 'pixi.js';
import type { InputManager } from '../core/InputManager';

export class Player extends Container {
    public sprite: Sprite;
    public hp = 100;
    public maxHp = 100;
    public lives = 3;
    public speed = 5;
    public fireRate = 150;        // 射击间隔 (ms)
    public fireLevel = 1;         // 火力等级
    public invincible = false;
    public collisionRadius = 16;

    private lastFireTime = 0;
    private invincibleTimer = 0;
    private blinkTimer = 0;

    constructor() {
        super();
        this.sprite = Sprite.from('/image/player.png');
        this.sprite.anchor.set(0.5);
        this.addChild(this.sprite);
    }

    update(delta: number, input: InputManager, now: number, bounds: { w: number; h: number }) {
        this.handleMovement(delta, input, bounds);
        this.handleInvincibility(delta);

        // 返回是否应该发射子弹
        return input.isKeyDown('Space') && now - this.lastFireTime > this.fireRate;
    }

    fire(now: number) {
        this.lastFireTime = now;
    }

    private handleMovement(delta: number, input: InputManager, bounds: { w: number; h: number }) {
        const spd = this.speed * delta;

        if (input.isKeyDown('ArrowUp') || input.isKeyDown('KeyW')) this.y -= spd;
        if (input.isKeyDown('ArrowDown') || input.isKeyDown('KeyS')) this.y += spd;
        if (input.isKeyDown('ArrowLeft') || input.isKeyDown('KeyA')) this.x -= spd;
        if (input.isKeyDown('ArrowRight') || input.isKeyDown('KeyD')) this.x += spd;

        // 限制在屏幕内
        this.x = Math.max(20, Math.min(bounds.w - 20, this.x));
        this.y = Math.max(20, Math.min(bounds.h - 20, this.y));
    }

    takeDamage(amount: number) {
        if (this.invincible) return;
        this.hp -= amount;
        if (this.hp <= 0) {
            this.lives--;
            if (this.lives > 0) {
                this.respawn();
            }
        }
    }

    respawn() {
        this.hp = this.maxHp;
        this.invincible = true;
        this.invincibleTimer = 120; // 2 秒无敌 (60fps)
    }

    private handleInvincibility(delta: number) {
        if (!this.invincible) return;

        this.invincibleTimer -= delta;
        this.blinkTimer += delta;

        // 闪烁效果
        this.sprite.alpha = Math.floor(this.blinkTimer / 4) % 2 === 0 ? 0.3 : 1;

        if (this.invincibleTimer <= 0) {
            this.invincible = false;
            this.sprite.alpha = 1;
        }
    }

    canFire(now: number): boolean {
        return now - this.lastFireTime > this.fireRate;
    }
}

三、敌人生成系统

3.1 波次管理器

// src/systems/WaveManager.ts
interface WaveConfig {
    enemies: { type: string; count: number; interval: number }[];
    delay: number; // 波次之间的延迟
}

const WAVE_DATA: WaveConfig[] = [
    {
        delay: 0,
        enemies: [{ type: 'grunt', count: 5, interval: 1000 }],
    },
    {
        delay: 3000,
        enemies: [
            { type: 'grunt', count: 8, interval: 800 },
            { type: 'fast', count: 3, interval: 1500 },
        ],
    },
    {
        delay: 3000,
        enemies: [
            { type: 'grunt', count: 10, interval: 600 },
            { type: 'tank', count: 2, interval: 2000 },
        ],
    },
    {
        delay: 5000,
        enemies: [{ type: 'boss', count: 1, interval: 0 }],
    },
];

export class WaveManager {
    private currentWave = 0;
    private spawnTimers = new Map<string, number>();
    private waveDelay = 0;
    private active = true;

    update(
        delta: number,
        now: number,
        spawnEnemy: (type: string) => void,
    ) {
        if (!this.active || this.currentWave >= WAVE_DATA.length) return;

        const wave = WAVE_DATA[this.currentWave];

        // 波次延迟
        if (this.waveDelay < wave.delay) {
            this.waveDelay += delta * 16.67;
            return;
        }

        // 生成敌人
        let allSpawned = true;
        for (const group of wave.enemies) {
            const key = `${this.currentWave}_${group.type}`;
            if (!this.spawnTimers.has(key)) {
                this.spawnTimers.set(key, 0);
            }

            const timer = this.spawnTimers.get(key)!;
            if (timer < group.count) {
                allSpawned = false;
                const elapsed = this.spawnTimers.get(key + '_time') ?? 0;
                if (elapsed >= group.interval) {
                    spawnEnemy(group.type);
                    this.spawnTimers.set(key, timer + 1);
                    this.spawnTimers.set(key + '_time', 0);
                } else {
                    this.spawnTimers.set(key + '_time', elapsed + delta * 16.67);
                }
            }
        }

        if (allSpawned) {
            this.currentWave++;
            this.waveDelay = 0;
        }
    }

    get isComplete(): boolean {
        return this.currentWave >= WAVE_DATA.length;
    }

    get waveNumber(): number {
        return this.currentWave + 1;
    }
}

3.2 Enemy 类型

// src/entities/Enemy.ts
import { Sprite, Container, Texture } from 'pixi.js';

export interface EnemyConfig {
    texture: string;
    hp: number;
    speed: number;
    score: number;
    collisionRadius: number;
    behavior: 'straight' | 'zigzag' | 'track';
}

export const ENEMY_CONFIGS: Record<string, EnemyConfig> = {
    grunt: {
        texture: '/image/enemy_grunt.png',
        hp: 20,
        speed: 2,
        score: 100,
        collisionRadius: 14,
        behavior: 'straight',
    },
    fast: {
        texture: '/image/enemy_fast.png',
        hp: 10,
        speed: 5,
        score: 150,
        collisionRadius: 10,
        behavior: 'zigzag',
    },
    tank: {
        texture: '/image/enemy_tank.png',
        hp: 80,
        speed: 1,
        score: 300,
        collisionRadius: 20,
        behavior: 'straight',
    },
    boss: {
        texture: '/image/enemy_boss.png',
        hp: 500,
        speed: 0.5,
        score: 5000,
        collisionRadius: 40,
        behavior: 'track',
    },
};

export class Enemy extends Container {
    public sprite: Sprite;
    public hp: number;
    public maxHp: number;
    public speed: number;
    public score: number;
    public collisionRadius: number;
    public behavior: string;
    public active = true;
    private time = 0;
    private startX = 0;

    constructor(config: EnemyConfig) {
        super();
        this.sprite = Sprite.from(config.texture);
        this.sprite.anchor.set(0.5);
        this.addChild(this.sprite);

        this.hp = config.hp;
        this.maxHp = config.hp;
        this.speed = config.speed;
        this.score = config.score;
        this.collisionRadius = config.collisionRadius;
        this.behavior = config.behavior;
    }

    update(delta: number, playerX: number, playerY: number) {
        this.time += delta;
        this.x -= this.speed * delta;

        switch (this.behavior) {
            case 'zigzag':
                this.y += Math.sin(this.time * 0.1) * 2;
                break;
            case 'track':
                const dy = playerY - this.y;
                this.y += Math.sign(dy) * Math.min(Math.abs(dy * 0.02), 2) * delta;
                break;
        }
    }

    takeDamage(amount: number): boolean {
        this.hp -= amount;
        return this.hp <= 0;
    }
}

四、子弹系统(对象池)

// src/entities/BulletPool.ts
import { Sprite, Texture, Container } from 'pixi.js';

export class Bullet extends Sprite {
    public vx = 0;
    public vy = 0;
    public active = false;
    public damage = 10;
    public collisionRadius = 4;

    constructor() {
        super(Texture.from('/image/bullet.png'));
        this.anchor.set(0.5);
    }

    fire(x: number, y: number, vx: number, vy: number, damage: number) {
        this.x = x;
        this.y = y;
        this.vx = vx;
        this.vy = vy;
        this.damage = damage;
        this.active = true;
        this.visible = true;
    }

    deactivate() {
        this.active = false;
        this.visible = false;
    }
}

export class BulletPool {
    private pool: Bullet[] = [];
    private active: Bullet[] = [];
    private container: Container;

    constructor(container: Container, preallocate = 50) {
        this.container = container;
        for (let i = 0; i < preallocate; i++) {
            const bullet = new Bullet();
            bullet.deactivate();
            this.pool.push(bullet);
            container.addChild(bullet);
        }
    }

    spawn(x: number, y: number, vx: number, vy: number, damage: number): Bullet {
        let bullet = this.pool.pop();
        if (!bullet) {
            bullet = new Bullet();
            this.container.addChild(bullet);
        }
        bullet.fire(x, y, vx, vy, damage);
        this.active.push(bullet);
        return bullet;
    }

    update(delta: number, bounds: { w: number; h: number }) {
        for (let i = this.active.length - 1; i >= 0; i--) {
            const bullet = this.active[i];
            bullet.x += bullet.vx * delta;
            bullet.y += bullet.vy * delta;

            // 超出边界回收
            if (bullet.x < -20 || bullet.x > bounds.w + 20 ||
                bullet.y < -20 || bullet.y > bounds.h + 20) {
                bullet.deactivate();
                this.active.splice(i, 1);
                this.pool.push(bullet);
            }
        }
    }

    getActiveBullets(): Bullet[] {
        return this.active;
    }

    recycle(bullet: Bullet) {
        const idx = this.active.indexOf(bullet);
        if (idx !== -1) {
            this.active.splice(idx, 1);
            bullet.deactivate();
            this.pool.push(bullet);
        }
    }
}

五、道具系统

// src/entities/Item.ts
import { Sprite, Container, Texture } from 'pixi.js';

export type ItemType = 'powerup' | 'shield' | 'life' | 'bomb';

export interface ItemConfig {
    texture: string;
    duration?: number;
    value: number;
}

export const ITEM_CONFIGS: Record<ItemType, ItemConfig> = {
    powerup: { texture: '/image/item_power.png', value: 1 },
    shield: { texture: '/image/item_shield.png', duration: 5000, value: 1 },
    life: { texture: '/image/item_life.png', value: 1 },
    bomb: { texture: '/image/item_bomb.png', value: 1 },
};

export class Item extends Container {
    public type: ItemType;
    public collisionRadius = 12;
    public active = true;
    private speed = 2;
    private time = 0;

    constructor(type: ItemType) {
        super();
        this.type = type;

        const config = ITEM_CONFIGS[type];
        const sprite = Sprite.from(config.texture);
        sprite.anchor.set(0.5);
        this.addChild(sprite);
    }

    update(delta: number) {
        this.time += delta;
        this.x -= this.speed * delta;
        this.y += Math.sin(this.time * 0.05) * 0.5; // 轻微浮动
    }

    apply(player: any) {
        const config = ITEM_CONFIGS[this.type];
        switch (this.type) {
            case 'powerup':
                player.fireLevel = Math.min(player.fireLevel + config.value, 5);
                break;
            case 'shield':
                player.invincible = true;
                player.invincibleTimer = (config.duration ?? 5000) / 16.67;
                break;
            case 'life':
                player.lives = Math.min(player.lives + config.value, 5);
                break;
            case 'bomb':
                // 清屏炸弹效果由 GameScene 处理
                break;
        }
        this.active = false;
    }
}

// 道具掉落管理
export class ItemSpawner {
    private dropRates: Record<string, number> = {
        grunt: 0.05,
        fast: 0.08,
        tank: 0.15,
        boss: 1.0,
    };

    rollDrop(enemyType: string): ItemType | null {
        const rate = this.dropRates[enemyType] ?? 0;
        if (Math.random() > rate) return null;

        const roll = Math.random();
        if (roll < 0.4) return 'powerup';
        if (roll < 0.6) return 'shield';
        if (roll < 0.8) return 'life';
        return 'bomb';
    }
}

六、背景滚动(多层视差)

// src/systems/ParallaxBackground.ts
import { TilingSprite, Texture, Container } from 'pixi.js';

export class ParallaxBackground extends Container {
    private layers: { sprite: TilingSprite; speed: number }[] = [];

    constructor(width: number, height: number) {
        super();

        // 星空远景(移动最慢)
        this.addLayer('/image/bg_stars.png', 0.2, width, height);
        // 星云中景
        this.addLayer('/image/bg_nebula.png', 0.5, width, height);
        // 小行星近景(移动最快)
        this.addLayer('/image/bg_asteroids.png', 1.0, width, height);
    }

    private addLayer(texturePath: string, speed: number, w: number, h: number) {
        const texture = Texture.from(texturePath);
        const sprite = new TilingSprite({
            texture,
            width: w,
            height: h,
        });
        this.addChild(sprite);
        this.layers.push({ sprite, speed });
    }

    update(delta: number) {
        for (const layer of this.layers) {
            // 向左滚动(tilePosition.x 递减)
            layer.sprite.tilePosition.x -= layer.speed * delta;
        }
    }
}

七、Boss 战

// src/entities/Boss.ts
import { Enemy, EnemyConfig } from './Enemy';
import { Graphics, Container } from 'pixi.js';

export class Boss extends Enemy {
    private phase = 1;
    private attackTimer = 0;
    private attackPatterns: (() => void)[] = [];
    private onAttack: (pattern: number) => void;

    constructor(onAttack: (pattern: number) => void) {
        const config: EnemyConfig = {
            texture: '/image/enemy_boss.png',
            hp: 500,
            speed: 0.5,
            score: 5000,
            collisionRadius: 40,
            behavior: 'track',
        };
        super(config);
        this.onAttack = onAttack;
    }

    update(delta: number, playerX: number, playerY: number) {
        super.update(delta, playerX, playerY);
        this.attackTimer += delta;

        // 根据血量切换阶段
        const hpPercent = this.hp / this.maxHp;
        if (hpPercent < 0.3) this.phase = 3;
        else if (hpPercent < 0.6) this.phase = 2;

        // 攻击模式
        this.handleAttack(delta);
    }

    private handleAttack(delta: number) {
        const attackInterval = this.phase === 3 ? 40 : this.phase === 2 ? 60 : 90;

        if (this.attackTimer >= attackInterval) {
            this.attackTimer = 0;

            switch (this.phase) {
                case 1:
                    this.onAttack(0); // 散射
                    break;
                case 2:
                    this.onAttack(1); // 瞄准射击
                    break;
                case 3:
                    this.onAttack(2); // 弹幕
                    break;
            }
        }
    }
}

八、碰撞检测

// src/systems/CollisionSystem.ts
import { Bullet } from '../entities/BulletPool';
import { Enemy } from '../entities/Enemy';
import { Item } from '../entities/Item';
import { Player } from '../entities/Player';

export interface CollisionResult {
    enemyHits: { enemy: Enemy; bullet: Bullet }[];
    playerHits: Enemy[];
    itemPickups: Item[];
}

export function checkCollisions(
    player: Player,
    playerBullets: Bullet[],
    enemyBullets: Bullet[],
    enemies: Enemy[],
    items: Item[],
): CollisionResult {
    const result: CollisionResult = {
        enemyHits: [],
        playerHits: [],
        itemPickups: [],
    };

    // 玩家子弹 vs 敌人
    for (const bullet of playerBullets) {
        for (const enemy of enemies) {
            if (!enemy.active || !bullet.active) continue;
            if (circleCollision(bullet, enemy)) {
                result.enemyHits.push({ enemy, bullet });
            }
        }
    }

    // 敌人子弹 vs 玩家
    for (const bullet of enemyBullets) {
        if (!bullet.active || player.invincible) continue;
        if (circleCollision(bullet, player)) {
            result.playerHits.push(null as any); // 标记玩家被击中
        }
    }

    // 敌人机体 vs 玩家
    for (const enemy of enemies) {
        if (!enemy.active || player.invincible) continue;
        if (circleCollision(player, enemy)) {
            result.playerHits.push(enemy);
        }
    }

    // 道具拾取
    for (const item of items) {
        if (!item.active) continue;
        if (circleCollision(player, item)) {
            result.itemPickups.push(item);
        }
    }

    return result;
}

function circleCollision(a: { x: number; y: number; collisionRadius: number },
                          b: { x: number; y: number; collisionRadius: number }): boolean {
    const dx = a.x - b.x;
    const dy = a.y - b.y;
    const dist = Math.sqrt(dx * dx + dy * dy);
    return dist < a.collisionRadius + b.collisionRadius;
}

九、HUD 界面

// src/ui/HUD.ts
import { Container, Text, Graphics } from 'pixi.js';

export class HUD extends Container {
    private scoreText: Text;
    private livesText: Text;
    private hpBar: Graphics;
    private energyBar: Graphics;
    private waveText: Text;

    constructor(width: number) {
        super();

        // 分数
        this.scoreText = new Text({
            text: 'SCORE: 0',
            style: { fill: 0xffffff, fontSize: 18, fontFamily: 'monospace' },
        });
        this.scoreText.x = 16;
        this.scoreText.y = 8;
        this.addChild(this.scoreText);

        // 生命
        this.livesText = new Text({
            text: '❤️ × 3',
            style: { fill: 0xff4444, fontSize: 18 },
        });
        this.livesText.x = width - 120;
        this.livesText.y = 8;
        this.addChild(this.livesText);

        // HP 条
        this.hpBar = new Graphics();
        this.hpBar.x = 16;
        this.hpBar.y = 32;
        this.addChild(this.hpBar);

        // 能量条
        this.energyBar = new Graphics();
        this.energyBar.x = 16;
        this.energyBar.y = 48;
        this.addChild(this.energyBar);

        // 波次提示
        this.waveText = new Text({
            text: '',
            style: { fill: 0xffff00, fontSize: 24, fontFamily: 'monospace' },
        });
        this.waveText.anchor.set(0.5, 0);
        this.waveText.x = width / 2;
        this.waveText.y = 60;
        this.addChild(this.waveText);
    }

    update(score: number, lives: number, hp: number, maxHp: number, energy: number, wave: number) {
        this.scoreText.text = `SCORE: ${score}`;
        this.livesText.text = `❤️ × ${lives}`;

        // HP 条
        this.hpBar.clear();
        this.hpBar.rect(0, 0, 120, 10).fill({ color: 0x333333 });
        const hpPercent = hp / maxHp;
        const hpColor = hpPercent > 0.5 ? 0x00ff00 : hpPercent > 0.25 ? 0xffaa00 : 0xff0000;
        this.hpBar.rect(0, 0, 120 * hpPercent, 10).fill({ color: hpColor });

        // 能量条
        this.energyBar.clear();
        this.energyBar.rect(0, 0, 120, 6).fill({ color: 0x333333 });
        this.energyBar.rect(0, 0, 120 * (energy / 100), 6).fill({ color: 0x4488ff });
    }

    showWave(number: number) {
        this.waveText.text = `— WAVE ${number} —`;
        // 2 秒后隐藏
        setTimeout(() => { this.waveText.text = ''; }, 2000);
    }
}

十、完整 GameScene

// src/scenes/GameScene.ts
import { Container } from 'pixi.js';
import { Game } from '../core/Game';
import { Player } from '../entities/Player';
import { Enemy, ENEMY_CONFIGS } from '../entities/Enemy';
import { BulletPool } from '../entities/BulletPool';
import { Item, ItemSpawner } from '../entities/Item';
import { WaveManager } from '../systems/WaveManager';
import { ParallaxBackground } from '../systems/ParallaxBackground';
import { checkCollisions } from '../systems/CollisionSystem';
import { HUD } from '../ui/HUD';

export class GameScene {
    container = new Container();
    private game: Game;
    private player!: Player;
    private enemies: Enemy[] = [];
    private playerBullets!: BulletPool;
    private enemyBullets!: BulletPool;
    private items: Item[] = [];
    private bg!: ParallaxBackground;
    private hud!: HUD;
    private waves!: WaveManager;
    private itemSpawner = new ItemSpawner();
    private score = 0;
    private gameTime = 0;

    constructor(game: Game) {
        this.game = game;
    }

    async init() {
        const { width, height } = this.game;

        // 背景
        this.bg = new ParallaxBackground(width, height);
        this.container.addChild(this.bg);

        // 子弹容器
        const bulletContainer = new Container();
        this.container.addChild(bulletContainer);
        this.playerBullets = new BulletPool(bulletContainer, 100);
        this.enemyBullets = new BulletPool(bulletContainer, 200);

        // 敌人容器
        const enemyContainer = new Container();
        this.container.addChild(enemyContainer);

        // 道具容器
        const itemContainer = new Container();
        this.container.addChild(itemContainer);

        // 玩家
        this.player = new Player();
        this.player.x = 100;
        this.player.y = height / 2;
        this.container.addChild(this.player);

        // HUD
        this.hud = new HUD(width);
        this.container.addChild(this.hud);

        // 波次管理
        this.waves = new WaveManager();

        // 加载音效
        this.game.audio.load('shoot', '/audio/shoot.mp3');
        this.game.audio.load('explosion', '/audio/explosion.mp3');
        this.game.audio.load('item', '/audio/item.mp3');
        this.game.audio.playMusic('/audio/bgm.mp3');
    }

    update(delta: number) {
        this.gameTime += delta;
        const now = performance.now();
        const bounds = { w: this.game.width, h: this.game.height };

        // 背景滚动
        this.bg.update(delta);

        // 玩家更新
        const shouldFire = this.player.update(delta, this.game.input, now, bounds);
        if (shouldFire && this.player.canFire(now)) {
            this.player.fire(now);
            this.firePlayerBullets();
        }

        // 敌人生成
        this.waves.update(delta, now, (type) => this.spawnEnemy(type));

        // 敌人更新
        for (const enemy of this.enemies) {
            if (enemy.active) {
                enemy.update(delta, this.player.x, this.player.y);
            }
        }

        // 子弹更新
        this.playerBullets.update(delta, bounds);
        this.enemyBullets.update(delta, bounds);

        // 道具更新
        for (const item of this.items) {
            if (item.active) item.update(delta);
        }

        // 碰撞检测
        this.handleCollisions();

        // 清理
        this.cleanup();

        // HUD 更新
        this.hud.update(
            this.score, this.player.lives,
            this.player.hp, this.player.maxHp, 0,
            this.waves.waveNumber,
        );

        // 游戏结束检查
        if (this.player.lives <= 0) {
            this.gameOver();
        }

        // 胜利检查
        if (this.waves.isComplete && this.enemies.length === 0) {
            this.victory();
        }
    }

    private firePlayerBullets() {
        const level = this.player.fireLevel;
        const x = this.player.x + 20;
        const y = this.player.y;

        // 根据火力等级发射不同数量的子弹
        this.playerBullets.spawn(x, y, 10, 0, 10);
        if (level >= 2) {
            this.playerBullets.spawn(x, y - 8, 10, -1, 10);
            this.playerBullets.spawn(x, y + 8, 10, 1, 10);
        }
        if (level >= 3) {
            this.playerBullets.spawn(x, y, 12, 0, 15);
        }
        if (level >= 4) {
            this.playerBullets.spawn(x, y - 16, 10, -2, 10);
            this.playerBullets.spawn(x, y + 16, 10, 2, 10);
        }

        this.game.audio.play('shoot');
    }

    private spawnEnemy(type: string) {
        const config = ENEMY_CONFIGS[type];
        if (!config) return;

        const enemy = new Enemy(config);
        enemy.x = this.game.width + 50;
        enemy.y = 50 + Math.random() * (this.game.height - 100);
        this.container.addChild(enemy);
        this.enemies.push(enemy);
    }

    private handleCollisions() {
        const result = checkCollisions(
            this.player,
            this.playerBullets.getActiveBullets(),
            this.enemyBullets.getActiveBullets(),
            this.enemies,
            this.items,
        );

        // 子弹命中敌人
        for (const { enemy, bullet } of result.enemyHits) {
            this.playerBullets.recycle(bullet);
            if (enemy.takeDamage(bullet.damage)) {
                // 敌人死亡
                this.score += enemy.score;
                this.spawnExplosion(enemy.x, enemy.y);
                this.tryDropItem(enemy);
                enemy.active = false;
                this.container.removeChild(enemy);
            }
        }

        // 玩家被击中
        if (result.playerHits.length > 0) {
            this.player.takeDamage(20);
            this.game.audio.play('explosion');
        }

        // 道具拾取
        for (const item of result.itemPickups) {
            item.apply(this.player);
            this.game.audio.play('item');
            this.container.removeChild(item);
        }
    }

    private spawnExplosion(x: number, y: number) {
        this.game.audio.play('explosion');
        // 简单爆炸效果(可用粒子系统替代)
        // 这里省略具体实现
    }

    private tryDropItem(enemy: Enemy) {
        const type = this.itemSpawner.rollDrop(enemy.constructor.name === 'Boss' ? 'boss' : 'grunt');
        if (type) {
            const item = new Item(type);
            item.x = enemy.x;
            item.y = enemy.y;
            this.container.addChild(item);
            this.items.push(item);
        }
    }

    private cleanup() {
        this.enemies = this.enemies.filter(e => {
            if (!e.active || e.x < -100) {
                this.container.removeChild(e);
                return false;
            }
            return true;
        });
        this.items = this.items.filter(i => {
            if (!i.active || i.x < -50) {
                this.container.removeChild(i);
                return false;
            }
            return true;
        });
    }

    private gameOver() {
        console.log('Game Over! Final Score:', this.score);
        this.game.scenes.switchTo('gameover');
    }

    private victory() {
        console.log('Victory! Final Score:', this.score);
        this.game.scenes.switchTo('victory');
    }

    destroy() {
        this.container.removeChildren();
        this.game.audio.stopAll();
    }
}

十一、发布与优化清单

□ 游戏功能
  ├─ 玩家移动/射击 ✓
  ├─ 敌人波次系统 ✓
  ├─ 道具系统 ✓
  ├─ Boss 战 ✓
  ├─ 视差滚动背景 ✓
  └─ HUD 界面 ✓

□ 优化
  ├─ 对象池 (子弹/敌人/粒子)
  ├─ 纹理图集
  ├─ Draw Call 优化
  ├─ 移动端适配
  └─ 音效压缩

□ 发布
  ├─ 构建优化 (Vite)
  ├─ PWA 离线支持
  ├─ CDN 部署
  └─ 错误监控 (Sentry)

扩展阅读