Sidequest

Bird Collisions

Before we wrap this section up, let's add collision detection between the bird and the pipes. We'll stop the pipe manager when the bird hits the pipe, just like we do with the ground.

Stop and Reset

We'll add a few methods to the pipe manager to stop the pipe manager and reset it.

1
export class PipeManager {
2
currentSpawnPosition: Vector2d;
3
game: Game;
4
pipes: Pipe[] = [];
5
pipeScrollSpeed: number;
6
spawnTimerThreshold: number;
7
spawnTimerAccumulator = 0;
8
spawnPositionYMinimum: number;
9
spawnPositionYMaximum: number;
10
spriteMap: SpriteMap;
11
spriteSheet: HTMLImageElement;
+
stopped = false;
13
14
// ...
15
+
public reset() {
+
this.pipes = [];
+
this.spawnTimerAccumulator = 0;
+
this.stopped = false;
+
}
21
+
public stop() {
+
this.stopped = true;
+
}
25
26
public update(delta: number) {
+
if (this.stopped) {
+
return;
+
}
30
31
// ...
32
}
33
}

We've also modified update() to check if the pipe manager is stopped. If it is, we'll return early.

Detecting Collisions

After we check if the bird hit the ground, let's detect collisions with the pipes:

1
// ...
2
3
canvas.addEventListener("click", () => {
4
switch (game.state) {
5
case GameState.Title: {
6
game.state = GameState.Playing;
7
bird.flap();
8
9
break;
10
}
11
12
case GameState.Playing: {
13
bird.flap();
14
15
break;
16
}
17
18
case GameState.GameOver: {
19
game.reset();
20
bird.reset();
21
ground.start();
+
pipeManager.reset();
23
24
break;
25
}
26
}
27
});
28
29
// ...
30
31
function frame(delta: number) {
32
// ...
33
-
if (didBirdHitGround) {
-
bird.die();
-
ground.stop();
-
game.state = GameState.GameOver;
-
}
39
+
let didBirdHitPipe = false;
+
for (const pipe of pipeManager.pipes) {
+
didBirdHitPipe = false;
+
+
const pipeRectangleColliderY =
+
pipe.type === "top" ? 0 : pipe.position.y + pipe.boxCollider.offsetY;
+
+
didBirdHitPipe = circleRectangleIntersects(
+
bird.position.x + bird.circleCollider.offsetX,
+
bird.position.y + bird.circleCollider.offsetY,
+
bird.circleCollider.radius,
+
pipe.position.x + pipe.boxCollider.offsetX,
+
pipeRectangleColliderY,
+
pipe.boxCollider.width,
+
pipe.boxCollider.height
+
);
+
+
if (didBirdHitPipe) {
+
bird.die();
+
ground.stop();
+
game.state = GameState.GameOver;
+
+
break;
+
}
+
}
+
+
if (didBirdHitGround || didBirdHitPipe) {
+
bird.die();
+
ground.stop();
+
pipeManager.stop();
+
game.state = GameState.GameOver;
+
}
72
73
// ...
74
}

We refactored the original if block to account for both hitting the ground or pipes.

We added a loop to detect collisions with any of the pipes, and differentiate between top and bottom pipes. We do this because we want to override the y position (for the top pipe) of the rectangle in the collision check to be 0 and not use the pipes position.

Lastly, we called pipeManager.reset() when the game is about to restart.

Running the game now you should get the full game loop experience. You can hit either the ground or the pipes and the game will stop, reset on click, and play again.