Mamun Labs Learn
JourneyGlossary
esc
  • Try “map”, “flexbox” or “state”.
Log in
Start learning
Phase 7 · 4 / 5Collisions and score
Phase 7Games
5 topics · 3–4 wks
  1. 1The game loop
  2. 2Touch input
  3. 3Simple animation
  4. 4Collisions and score
  5. 5Sound and saving the best score
JourneyPhase 7Collisions and score
01 / 10Overview

Collisions and score

You'll learn to check if two boxes touch, end the game when they do, and add a point for every obstacle you pass.

Why it mattersAlmost every game asks "do these two things touch?" many times a second. Coins, walls, enemies and finish lines all work this way.
about 90 min·needs: Simple animation
02 / 10

Watch & read

Start with the required ones. The others are there when you need them.

Article
2D collision detectionMDN Web Docs · 10 min · English
MMamun's noteRead "Axis-aligned bounding box" and move the box in the live example until they touch. That is our isColliding, line by line. Circles and the Separating Axis Theorem are for later; skip them.
Video
How to code: rectangular collision detection with JavaScriptYouTube · Chris Courses · 11 min · English
MMamun's noteWatch all 11 minutes. He first draws how it works (0:00–2:07), then builds the x check (2:07) and adds the y check (6:40). He uses a web canvas, but the four comparisons are the same as ours.
VideoOptional
Collision detection between rectangles in JavaScriptYouTube · Franks laboratory · 4 min · English
MMamun's noteWatch 1:59–5:52. A second, short explanation of the same check, then a second way to write it. Good if the first video went too fast.
03 / 10

See it step by step

Two boxes touch only when they overlap on x and on y.

obstacleX150
playerY0
player x"50 to 90"
obstacle x"150 to 180"
Screen ■■■■ ▮▮▮──────────────────────────────────────────
App.jsx
1function isColliding(a, b) {
2 return (
3 a.x < b.x + b.width &&
4 a.x + a.width > b.x &&
5 a.y < b.y + b.height &&
6 a.y + a.height > b.y
7 );
8}
9
10const player = { x: 50, y: playerY, width: 40, height: 40 };
11const obstacle = { x: obstacleX, y: 0, width: 30, height: 50 };
12isColliding(player, obstacle);
The square is on the ground, so playerY is 0. It covers x from 50 to 90. Each character on the screen is 10 points.
Step 1 / 9
04 / 10

Explanation

You want the game to end when the square runs into the obstacle, and the score to go up each time you jump over one.

Think of two students sitting on one bench. They touch only if their places on the bench overlap. Our square and obstacle are boxes, so we ask that question twice: do they overlap left to right (x), and do they overlap up and down (y)? Both must be true. This kind of box check is called a bounding box collision.

function isColliding(a, b) {
return (
a.x < b.x + b.width && a.x + a.width > b.x &&
a.y < b.y + b.height && a.y + a.height > b.y
);
}

The first line checks x: a starts before b ends, and a ends after b starts. The second line does the same for y. If any one is false, there is a gap, and they do not touch.

Each tick, step now does three things. If the boxes touch, it sets gameOver: true. Otherwise it moves the obstacle. And when the obstacle goes off the left edge, the player has passed it, so score goes up by one.

step needs the square's height, but playerY is an Animated.Value, not a plain number. So we copy the latest number into a ref with playerY.addListener(({ value }) => (yNow.current = value)), and the loop calls step(g, yNow.current).

People often write setScore(score + 1) inside setInterval. The timer was made on the first render, so score inside it is always 0, and the score gets stuck at 1. Always use the version with a function, setGame((g) => step(g, yNow.current)). It is called a functional update, and g is always the newest game.

When you read a collision check, look for four comparisons, two for x and two for y, joined with &&.

Words to recognise
collisionbounding boxoverlapgame overfunctional updateaddListener
05 / 10

Code tour

Click a line number with a ring to read the note for that line.

App.jsx5 notes
1function step(game, playerY) {
if (game.gameOver) return game;
Mentor note · line 2
Once the game is over, nothing moves any more. The loop keeps ticking, but step gives back the same game.
const player = { x: PLAYER_X, y: playerY, width: 40, height: 40 };
4 const obstacle = { x: game.obstacleX, y: 0, width: 30, height: 50 };
if (isColliding(player, obstacle)) {
6 return { ...game, gameOver: true };
7 }
8 let obstacleX = game.obstacleX - SPEED;
9 let score = game.score;
if (obstacleX < -OBSTACLE_WIDTH) {
11 obstacleX = SCREEN_WIDTH;
12 score = score + 1;
13 }
return { ...game, obstacleX: obstacleX, score: score };
15}
06 / 10

Practice

Short drills. Take your time and read the code before you answer.

DrillPredict1 / 3
1export default function App() {
2 const [score, setScore] = useState(0);
3
4 useEffect(() => {
5 const id = setInterval(() => {
6 // pretend the obstacle passes once every second
7 setScore(score + 1);
8 }, 1000);
9 return () => clearInterval(id);
10 }, []);
11
12 return <Text>{score}</Text>;
13}
14
15// You wait 3 seconds, so the obstacle passes 3 times.
What does the score show after three passes?
DrillFind the bug2 / 3
isColliding(a, b) should be true only when two boxes overlap. Each box is { x, y, width, height }, with y the height above the ground. Right now the game ends even when the square jumps right over the obstacle. Fix it.
1
2
3
4
5
6
Try 1 of 3 · solution unlocks after 3
DrillGame over and score3 / 3
Now it is a real game. Jump over the obstacle to score. Touch it and the game is over; tap to play again.
1import { useEffect, useRef, useState } from "react";
2import { Animated, Pressable, StyleSheet, Text, View } from "react-native";
3
4const SPEED = 10, SCREEN_WIDTH = 400, PLAYER_X = 50, JUMP_HEIGHT = 150;
5const OBSTACLE_WIDTH = 30, OBSTACLE_HEIGHT = 50, PLAYER_SIZE = 40;
6const START = { obstacleX: SCREEN_WIDTH, score: 0, gameOver: false };

Runs in the browser. Choose “My Device” in Snack to try it on your phone with Expo Go.

Working whenJump over the obstacle three times. The score shows 3.Run into it on purpose. The game stops and says game over. Tap to start again at 0.Make it harder: change SPEED to 15. Can you still score 5?
Check each goal yourself, then mark it.
07 / 10

Quick check

3 questions. Each one explains itself after you answer.

  1. 1 / 3The square covers x from 50 to 90. The obstacle covers x from 100 to 130. Both are on the ground. Do they touch?
  2. 2 / 3The boxes overlap on x, but the square is 120 points up and the obstacle is 50 tall. What does isColliding say?
  3. 3 / 3In our game, when does the score go up?
08 / 10

Explain it back

Only you can see this. Log in to keep it.0 words
09 / 10

How confident are you with Collisions and score?

10 / 10

Done

You can now
Read a rectangle collision check and say, from the numbers, whether two boxes touch.
Next: Sound and saving the best score Back to Phase 7
On this page
  1. 01Collisions and score
  2. 02Watch & read
  3. 03See it step by step
  4. 04Explanation
  5. 05Code tour
  6. 06Practice
  7. 07Quick check
  8. 08Explain it back
  9. 09Confidence
  10. 10Done
Up next · 02 / 10Watch & read
Mamun Labs LearnFound a mistake on this page? Tell Mamun.
Mamun Labs Learn
JourneyGlossary
esc
  • Try “map”, “flexbox” or “state”.
Log in
Start learning
Log in to save your progress. Everything here works without an account.Log in to save progressLog in
Phase 7Games
5 topics · 3–4 wks
  1. 1The game loop
  2. 2Touch input
  3. 3Simple animation
  4. 4Collisions and score
  5. 5Sound and saving the best score
JourneyPhase 7Collisions and score
01 / 10Overview

Collisions and score

You'll learn to check if two boxes touch, end the game when they do, and add a point for every obstacle you pass.

Why it mattersAlmost every game asks "do these two things touch?" many times a second. Coins, walls, enemies and finish lines all work this way.
about 90 min·needs: Simple animation
02 / 10

Watch & read

Start with the required ones. The others are there when you need them.

Article
2D collision detectionMDN Web Docs · 10 min · English
MMamun's noteRead "Axis-aligned bounding box" and move the box in the live example until they touch. That is our isColliding, line by line. Circles and the Separating Axis Theorem are for later; skip them.
Video
How to code: rectangular collision detection with JavaScriptYouTube · Chris Courses · 11 min · English
MMamun's noteWatch all 11 minutes. He first draws how it works (0:00–2:07), then builds the x check (2:07) and adds the y check (6:40). He uses a web canvas, but the four comparisons are the same as ours.
VideoOptional
Collision detection between rectangles in JavaScriptYouTube · Franks laboratory · 4 min · English
MMamun's noteWatch 1:59–5:52. A second, short explanation of the same check, then a second way to write it. Good if the first video went too fast.
03 / 10

See it step by step

Two boxes touch only when they overlap on x and on y.

obstacleX150
playerY0
player x"50 to 90"
obstacle x"150 to 180"
Screen ■■■■ ▮▮▮──────────────────────────────────────────
App.jsx
1function isColliding(a, b) {
2 return (
3 a.x < b.x + b.width &&
4 a.x + a.width > b.x &&
5 a.y < b.y + b.height &&
6 a.y + a.height > b.y
7 );
8}
9
10const player = { x: 50, y: playerY, width: 40, height: 40 };
11const obstacle = { x: obstacleX, y: 0, width: 30, height: 50 };
12isColliding(player, obstacle);
The square is on the ground, so playerY is 0. It covers x from 50 to 90. Each character on the screen is 10 points.
Step 1 / 9
04 / 10

Explanation

You want the game to end when the square runs into the obstacle, and the score to go up each time you jump over one.

Think of two students sitting on one bench. They touch only if their places on the bench overlap. Our square and obstacle are boxes, so we ask that question twice: do they overlap left to right (x), and do they overlap up and down (y)? Both must be true. This kind of box check is called a bounding box collision.

function isColliding(a, b) {
return (
a.x < b.x + b.width && a.x + a.width > b.x &&
a.y < b.y + b.height && a.y + a.height > b.y
);
}

The first line checks x: a starts before b ends, and a ends after b starts. The second line does the same for y. If any one is false, there is a gap, and they do not touch.

Each tick, step now does three things. If the boxes touch, it sets gameOver: true. Otherwise it moves the obstacle. And when the obstacle goes off the left edge, the player has passed it, so score goes up by one.

step needs the square's height, but playerY is an Animated.Value, not a plain number. So we copy the latest number into a ref with playerY.addListener(({ value }) => (yNow.current = value)), and the loop calls step(g, yNow.current).

People often write setScore(score + 1) inside setInterval. The timer was made on the first render, so score inside it is always 0, and the score gets stuck at 1. Always use the version with a function, setGame((g) => step(g, yNow.current)). It is called a functional update, and g is always the newest game.

When you read a collision check, look for four comparisons, two for x and two for y, joined with &&.

Words to recognise
collisionbounding boxoverlapgame overfunctional updateaddListener
05 / 10

Code tour

Click a line number with a ring to read the note for that line.

App.jsx5 notes
1function step(game, playerY) {
if (game.gameOver) return game;
Mentor note · line 2
Once the game is over, nothing moves any more. The loop keeps ticking, but step gives back the same game.
const player = { x: PLAYER_X, y: playerY, width: 40, height: 40 };
4 const obstacle = { x: game.obstacleX, y: 0, width: 30, height: 50 };
if (isColliding(player, obstacle)) {
6 return { ...game, gameOver: true };
7 }
8 let obstacleX = game.obstacleX - SPEED;
9 let score = game.score;
if (obstacleX < -OBSTACLE_WIDTH) {
11 obstacleX = SCREEN_WIDTH;
12 score = score + 1;
13 }
return { ...game, obstacleX: obstacleX, score: score };
15}
06 / 10

Practice

Short drills. Take your time and read the code before you answer.

DrillPredict1 / 3
1export default function App() {
2 const [score, setScore] = useState(0);
3
4 useEffect(() => {
5 const id = setInterval(() => {
6 // pretend the obstacle passes once every second
7 setScore(score + 1);
8 }, 1000);
9 return () => clearInterval(id);
10 }, []);
11
12 return <Text>{score}</Text>;
13}
14
15// You wait 3 seconds, so the obstacle passes 3 times.
What does the score show after three passes?
DrillFind the bug2 / 3
isColliding(a, b) should be true only when two boxes overlap. Each box is { x, y, width, height }, with y the height above the ground. Right now the game ends even when the square jumps right over the obstacle. Fix it.
1
2
3
4
5
6
Try 1 of 3 · solution unlocks after 3
DrillGame over and score3 / 3
Now it is a real game. Jump over the obstacle to score. Touch it and the game is over; tap to play again.
1import { useEffect, useRef, useState } from "react";
2import { Animated, Pressable, StyleSheet, Text, View } from "react-native";
3
4const SPEED = 10, SCREEN_WIDTH = 400, PLAYER_X = 50, JUMP_HEIGHT = 150;
5const OBSTACLE_WIDTH = 30, OBSTACLE_HEIGHT = 50, PLAYER_SIZE = 40;
6const START = { obstacleX: SCREEN_WIDTH, score: 0, gameOver: false };

Runs in the browser. Choose “My Device” in Snack to try it on your phone with Expo Go.

Working whenJump over the obstacle three times. The score shows 3.Run into it on purpose. The game stops and says game over. Tap to start again at 0.Make it harder: change SPEED to 15. Can you still score 5?
Check each goal yourself, then mark it.
07 / 10

Quick check

3 questions. Each one explains itself after you answer.

  1. 1 / 3The square covers x from 50 to 90. The obstacle covers x from 100 to 130. Both are on the ground. Do they touch?
  2. 2 / 3The boxes overlap on x, but the square is 120 points up and the obstacle is 50 tall. What does isColliding say?
  3. 3 / 3In our game, when does the score go up?
08 / 10

Explain it back

Only you can see this. Log in to keep it.0 words
09 / 10

How confident are you with Collisions and score?

10 / 10

Done

You can now
Read a rectangle collision check and say, from the numbers, whether two boxes touch.
Next: Sound and saving the best score Back to Phase 7
On this page
  1. 01Collisions and score
  2. 02Watch & read
  3. 03See it step by step
  4. 04Explanation
  5. 05Code tour
  6. 06Practice
  7. 07Quick check
  8. 08Explain it back
  9. 09Confidence
  10. 10Done
Up next · 02 / 10Watch & read

You want the game to end when the square runs into the obstacle, and the score to go up each time you jump over one.

Think of two students sitting on one bench. They touch only if their places on the bench overlap. Our square and obstacle are boxes, so we ask that question twice: do they overlap left to right (x), and do they overlap up and down (y)? Both must be true. This kind of box check is called a bounding box collision.

function isColliding(a, b) {
return (
a.x < b.x + b.width && a.x + a.width > b.x &&
a.y < b.y + b.height && a.y + a.height > b.y
);
}

The first line checks x: a starts before b ends, and a ends after b starts. The second line does the same for y. If any one is false, there is a gap, and they do not touch.

Each tick, step now does three things. If the boxes touch, it sets gameOver: true. Otherwise it moves the obstacle. And when the obstacle goes off the left edge, the player has passed it, so score goes up by one.

step needs the square's height, but playerY is an Animated.Value, not a plain number. So we copy the latest number into a ref with playerY.addListener(({ value }) => (yNow.current = value)), and the loop calls step(g, yNow.current).

People often write setScore(score + 1) inside setInterval. The timer was made on the first render, so score inside it is always 0, and the score gets stuck at 1. Always use the version with a function, setGame((g) => step(g, yNow.current)). It is called a functional update, and g is always the newest game.

When you read a collision check, look for four comparisons, two for x and two for y, joined with &&.

Words to recognise
collisionbounding boxoverlapgame overfunctional updateaddListener

You want the game to end when the square runs into the obstacle, and the score to go up each time you jump over one.

Think of two students sitting on one bench. They touch only if their places on the bench overlap. Our square and obstacle are boxes, so we ask that question twice: do they overlap left to right (x), and do they overlap up and down (y)? Both must be true. This kind of box check is called a bounding box collision.

function isColliding(a, b) {
return (
a.x < b.x + b.width && a.x + a.width > b.x &&
a.y < b.y + b.height && a.y + a.height > b.y
);
}

The first line checks x: a starts before b ends, and a ends after b starts. The second line does the same for y. If any one is false, there is a gap, and they do not touch.

Each tick, step now does three things. If the boxes touch, it sets gameOver: true. Otherwise it moves the obstacle. And when the obstacle goes off the left edge, the player has passed it, so score goes up by one.

step needs the square's height, but playerY is an Animated.Value, not a plain number. So we copy the latest number into a ref with playerY.addListener(({ value }) => (yNow.current = value)), and the loop calls step(g, yNow.current).

People often write setScore(score + 1) inside setInterval. The timer was made on the first render, so score inside it is always 0, and the score gets stuck at 1. Always use the version with a function, setGame((g) => step(g, yNow.current)). It is called a functional update, and g is always the newest game.

When you read a collision check, look for four comparisons, two for x and two for y, joined with &&.

Words to recognise
collisionbounding boxoverlapgame overfunctional updateaddListener