Sidequest

Determine Next Spawn Point

Let's determine the next spawn point based on a set interval. We're going to update the spawn position every 1.5 seconds.

Pipe Manager Config

We need to store the spawn interval in seconds in our config:

1
export const config = {
2
/**
3
* In pixels
4
*/
5
gameWidth: 352,
6
7
/**
8
* In pixels
9
*/
10
gameHeight: 576,
11
12
pipe: {
13
collider: {
14
offsetX: 4,
15
width: 56,
16
},
17
},
18
19
pipeManager: {
20
pipeSpawnBuffer: 50,
+
spawnIntervalInSeconds: 1.5,
22
},
23
};
24
25
export type Config = typeof config;

Pipe Manager Timer

We'll update the pipe manager to track time, and update the spawn position every time our timer exceeds in the spawn interval:

1
export class PipeManager {
2
currentSpawnPosition: Vector2d;
3
game: Game;
+
spawnTimerThreshold: number;
+
spawnTimerAccumulator = 0;
6
spawnPositionYMinimum: number;
7
spawnPositionYMaximum: number;
8
spriteMap: SpriteMap;
9
10
constructor(options: PipeManagerOptions) {
11
this.game = options.game;
12
this.spriteMap = options.spriteMap;
+
this.spawnTimerThreshold =
+
this.game.config.pipeManager.spawnIntervalInSeconds;
15
16
// Account for ground height to make the distribution of pipes more even
17
const halfGameHeight =
18
(this.game.config.gameHeight - this.spriteMap.ground.height) / 2;
19
20
// Double the buffer for a wider distribution
21
const doubleBuffer = this.game.config.pipeManager.pipeSpawnBuffer * 2;
22
23
this.spawnPositionYMinimum = halfGameHeight - doubleBuffer;
24
this.spawnPositionYMaximum = halfGameHeight + doubleBuffer;
25
26
this.currentSpawnPosition = new Vector2d(
27
this.game.config.gameWidth,
28
halfGameHeight
29
);
30
}
31
+
public update(delta: number) {
+
this.spawnTimerAccumulator += delta;
+
+
if (this.spawnTimerAccumulator >= this.spawnTimerThreshold) {
+
this.spawnTimerAccumulator = 0;
+
+
// generate a random spawn position
+
}
+
}
41
}

We store our spawn interval in a class property called spawnTimerThreshold. Within update() we constantly add delta to our spawnTimerAccumulator and check if our accumulator exceeds the threshold. If so, we'll reset the accumulator and update the spawn position.

We need to replace that comment with some code to generate a random y value between our minimum and maximum y threshold.

Random Between Helper

Let's create a helper function to generate a random number between two numbers: min and max. The max value will be inclusive.

Create a new file:

1
/**
2
* Generates a random number between min and max (inclusive).
3
* @param min
4
* @param max inclusive
5
* @returns
6
*/
7
export const randomBetween = (min: number, max: number) =>
8
Math.floor(Math.random() * (max - min + 1)) + min;

Generate Next Spawn Position

Now replace the comment in update() with some code to generate a random y value between our minimum and maximum y threshold:

1
import { Vector2d } from "#/components/vector2d";
2
import { Game } from "#/game";
+
import { randomBetween } from "#/lib/random";
4
import { SpriteMap } from "#/sprite-map";
5
6
// ...
7
8
export class PipeManager {
9
// ...
10
11
public update(delta: number) {
12
this.spawnTimerAccumulator += delta;
13
14
if (this.spawnTimerAccumulator >= this.spawnTimerThreshold) {
15
this.spawnTimerAccumulator = 0;
16
+
this.currentSpawnPosition.y = Math.floor(
+
randomBetween(this.spawnPositionYMinimum, this.spawnPositionYMaximum)
+
);
20
}
21
}
22
}

Finally, don't forget to call the update() method on the pipe manager every frame:

1
/**
2
* The game loop.
3
*/
4
const frame = (hrt: DOMHighResTimeStamp) => {
5
const dt = Math.min(1000, hrt - last) / 1000;
6
7
context.clearRect(0, 0, canvas.width, canvas.height);
8
+
pipeManager.update(dt);
10
ground.update(dt);
11
bird.update(dt);
12
13
// ...
14
};

Back in the canvas you should see the red line update to our new spawn position every time our spawn interval is exceeded.

We're so close. Next we'll add the pipes to the game.