# 小游戏实战
本章通过实现一个 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
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
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
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
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