19 - Audio

The only thing our game is missing now is some sound! Let's play the boop sound that replay-starter comes with whenever the bird hits a pipe.

The device parameter has an audio method, which takes a file name and returns a sound object. We can then call play() on it to play a sound.

Lastly we need to ensure the audio file is being loaded in our Game Sprite.

BackNext
1import { makeSprite, t } from "@replay/core";
2import { Bird, birdWidth, birdHeight } from "./bird";
3import { Pipe, pipeWidth, pipeGap, getPipeYPositions } from "./pipe";
4
5const speedX = 2;
6const birdX = 0;
7
8export const Level = makeSprite({
9 init({ device, props }) {
10 return {
11 birdY: 10,
12 birdGravity: -12,
13 pipes: props.paused ? [] : [newPipe(device)],
14 score: 0,
15 };
16 },
17
18 loop({ props, state, getInputs, device }) {
19 if (props.paused) {
20 return state;
21 }
22
23 const inputs = getInputs();
24
25 let { birdGravity, birdY, pipes, score } = state;
26
27 birdGravity += 0.8;
28 birdY -= birdGravity;
29
30 if (inputs.pointer.justPressed || inputs.keysJustPressed[" "]) {
31 birdGravity = -12;
32 }
33
34 const lastPipe = pipes[pipes.length - 1];
35 if (lastPipe.x < 140) {
36 pipes = [...pipes, newPipe(device)]
37 // Remove the pipes off screen on left
38 .filter(
39 (pipe) =>
40 pipe.x > -(device.size.width + device.size.widthMargin + pipeWidth)
41 );
42 }
43
44 if (didHitPipe(birdY, device.size, pipes)) {
45 device.audio("boop.wav").play();
46 props.gameOver(state.score);
47 }
48
49 // Move pipes to the left
50 pipes = pipes.map((pipe) => {
51 let passed = pipe.passed;
52 if (!passed && pipe.x < birdX - birdWidth / 2 - pipeWidth / 2) {
53 // Mark pipe as having passed bird's x position
54 passed = true;
55 score++;
56 }
57 return { ...pipe, passed, x: pipe.x - speedX };
58 });
59
60 return {
61 birdGravity,
62 birdY,
63 pipes,
64 score,
65 };
66 },
67
68 render({ state, device }) {
69 const { size } = device;
70 return [
71 t.rectangle({
72 color: "#add8e6",
73 width: size.width + size.widthMargin * 2,
74 height: size.height + size.heightMargin * 2,
75 }),
76 Bird({
77 id: "bird",
78 x: birdX,
79 y: state.birdY,
80 rotation: Math.max(-30, state.birdGravity * 3 - 30),
81 }),
82 ...state.pipes.map((pipe, index) =>
83 Pipe({
84 id: `pipe-${index}`,
85 pipe,
86 x: pipe.x,
87 })
88 ),
89 t.text({
90 text: `Score: ${state.score}`,
91 color: "white",
92 x: -device.size.width / 2 + 10,
93 y: device.size.height / 2 + device.size.heightMargin - 80,
94 font: {
95 align: "left",
96 },
97 }),
98 ];
99 },
100});
101
102function newPipe(device) {
103 const height = device.size.height + device.size.heightMargin * 2;
104 const randomY = (height - pipeGap * 2) * (device.random() - 0.5);
105
106 return {
107 x: device.size.width + device.size.widthMargin + 50,
108 gapY: randomY,
109 passed: false,
110 };
111}
112
113function didHitPipe(birdY, size, pipes) {
114 if (
115 birdY - birdHeight / 2 < -(size.height / 2 + size.heightMargin) ||
116 birdY + birdHeight / 2 > size.height / 2 + size.heightMargin
117 ) {
118 // hit bottom or top
119 return true;
120 }
121 for (const pipe of pipes) {
122 if (
123 pipe.x > birdX + birdWidth / 2 + pipeWidth / 2 ||
124 pipe.x < birdX - birdWidth / 2 - pipeWidth / 2
125 ) {
126 // bird isn't at pipe
127 continue;
128 }
129 const {
130 yUpperTop,
131 yUpperBottom,
132 yLowerTop,
133 yLowerBottom,
134 } = getPipeYPositions(size, pipe.gapY);
135 const topPipeRect = {
136 x: pipe.x,
137 y: (yUpperTop + yUpperBottom) / 2,
138 width: pipeWidth,
139 height: yUpperTop - yUpperBottom,
140 };
141 const bottomPipeRect = {
142 x: pipe.x,
143 y: (yLowerTop + yLowerBottom) / 2,
144 width: pipeWidth,
145 height: yLowerTop - yLowerBottom,
146 };
147 // Check a few points at edges of bird
148 const birdPoints = [
149 { x: birdX + birdWidth / 2, y: birdY + birdHeight / 2 },
150 { x: birdX + birdWidth / 2, y: birdY - birdHeight / 2 },
151 { x: birdX, y: birdY + birdHeight / 2 },
152 { x: birdX, y: birdY - birdHeight / 2 },
153 { x: birdX - birdWidth / 2, y: birdY + birdHeight / 2 },
154 { x: birdX - birdWidth / 2, y: birdY - birdHeight / 2 },
155 ];
156 if (
157 birdPoints.some(
158 (point) =>
159 pointInRect(point, topPipeRect) || pointInRect(point, bottomPipeRect)
160 )
161 ) {
162 // Bird hit a pipe!
163 return true;
164 }
165 }
166 return false;
167}
168
169function pointInRect(point, rect) {
170 return (
171 point.x > rect.x - rect.width / 2 &&
172 point.x < rect.x + rect.width / 2 &&
173 point.y > rect.y - rect.height / 2 &&
174 point.y < rect.y + rect.height / 2
175 );
176}
177
Preview