# 小游戏实战

本章通过实现一个 Flappy Bird 风格的小游戏,综合运用 PixiJS 的各项知识。

# 游戏设计 {#game-design}

# 核心玩法 {#gameplay}

  • 点击/按空格键让小鸟向上飞
  • 小鸟需要穿过管道间隙
  • 碰到管道或边界则游戏结束
  • 每穿过一个管道得一分

# 技术要点 {#tech-highlights}

模块 技术
物理系统 重力 + 跳跃速度
碰撞检测 AABB 碰撞检测
动画循环 Ticker 驱动
资源管理 Assets 加载
场景管理 游戏场景、结束场景切换

# 核心代码 {#core-code}

# 小鸟实现 {#bird-implementation}

class Bird {
    constructor() {
        this.sprite = new Graphics()
            .circle(0, 0, 20)
            .fill(0xffff00);
        this.sprite.x = 100;
        this.sprite.y = 300;
        this.velocity = 0;
        this.gravity = 0.5;
        this.jumpForce = -8;
    }

    jump() {
        this.velocity = this.jumpForce;
    }

    update() {
        this.velocity += this.gravity;
        this.sprite.y += this.velocity;
        this.sprite.rotation = Math.min(
            Math.max(this.velocity * 0.05, -0.5),
            1.5
        );
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 管道系统 {#pipe-system}

class PipePair {
    constructor(x) {
        this.gap = 150;       // 间隙大小
        this.gapY = 200 + Math.random() * 200;  // 间隙位置
        this.passed = false;

        // 上管道
        this.topPipe = new Graphics()
            .rect(0, 0, 50, this.gapY)
            .fill(0x00aa00);
        this.topPipe.x = x;

        // 下管道
        this.bottomPipe = new Graphics()
            .rect(0, this.gapY + this.gap, 50,
                  app.screen.height - this.gapY - this.gap)
            .fill(0x00aa00);
        this.bottomPipe.x = x;
    }

    update(speed) {
        this.topPipe.x -= speed;
        this.bottomPipe.x -= speed;
    }

    getBounds() {
        return {
            top: this.topPipe.getBounds(),
            bottom: this.bottomPipe.getBounds(),
        };
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 碰撞检测 {#collision-detection}

function checkCollision(bird, pipePair) {
    const birdBounds = bird.sprite.getBounds();
    const { top, bottom } = pipePair.getBounds();

    // AABB 碰撞检测
    return (
        checkAABB(birdBounds, top) ||
        checkAABB(birdBounds, bottom)
    );
}

function checkAABB(r1, r2) {
    return (
        r1.x < r2.x + r2.width &&
        r1.x + r1.width > r2.x &&
        r1.y < r2.y + r2.height &&
        r1.y + r1.height > r2.y
    );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 游戏主循环 {#game-loop}

const SPEED = 3;
let pipes = [];
let score = 0;
let gameOver = false;

function gameLoop(delta) {
    if (gameOver) return;

    // 更新小鸟
    bird.update();

    // 生成管道
    if (frames % 100 === 0) {
        pipes.push(new PipePair(app.screen.width));
    }

    // 更新管道
    for (const pipe of pipes) {
        pipe.update(SPEED * delta);

        // 计分
        if (!pipe.passed && pipe.topPipe.x + 50 < bird.sprite.x) {
            pipe.passed = true;
            score++;
            updateScore();
        }
    }

    // 移除屏幕外的管道
    pipes = pipes.filter(p => p.topPipe.x > -50);

    // 碰撞检测
    for (const pipe of pipes) {
        if (checkCollision(bird, pipe)) {
            endGame();
        }
    }

    // 边界检测
    if (bird.sprite.y < 0 || bird.sprite.y > app.screen.height) {
        endGame();
    }

    frames++;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

# 在线 Demo {#online-demo}