Today’s blog is a really fun one, an endless racer game called Monaco GP. I’m going to take my own spin on the game but I’ll keep the core concepts the same, such as the seamless wrapping the game has making it feel as though it’s one very large track!
Here are the images we’ll use, yes I tried for a good 2 minutes to put them next to each other haha, I failed.





Making the endless map
So this really is the most important part of the game and is the main concept I want to try and explain.

The image below should help you to understand how we go about creating the seamless effect. For a given car we render all 2 clones, so if for instance you’re about to go over the top of the map you’ll see the cars that are in front of you but are actually at the bottom of the map, giving us the illusion we want.

Player.js
This is a standard class, pretty much exactly what you’d expect except we have just downsized the image, as I was too lazy to do it in Photoshop.
class Player { constructor(playerImage) { this.playerImage = playerImage; this.x = width / 2; this.y = height / 2; this.carWidth = this.playerImage.width * 0.4; this.carHeight = this.playerImage.height * 0.4; } draw() { push(); imageMode(CENTER); image(this.playerImage, this.x, this.y, this.carWidth, this.carHeight); pop(); } update() { this.y-=2; if (this.y < -MAP_HEIGHT) { this.y = 0; } } }
Racer.js
This represents the other Racers in the game, this is the most important class as it contains the logic for creating the clones!
class Racer { constructor(racerImage) { this.racerImage = racerImage; this.x = random (110, 290); this.y = random(0, MAP_HEIGHT); this.carWidth = this.racerImage.width * 0.4; this.carHeight = this.racerImage.height * 0.4; } draw() { // draw the normal car push(); imageMode(CENTER); image(this.racerImage, this.x, this.y, this.carWidth, this.carHeight); pop(); // draw the clones let clones = this.getClones(); for (let clone of clones) { push(); imageMode(CENTER); image(this.racerImage, clone.x, clone.y, this.carWidth, this.carHeight); pop(); } } // gets the position of the clones as represented by the diagram above getClones() { let clones = []; let topClone = { x: this.x, y: this.y + MAP_HEIGHT } let bottomClone = { x: this.x, y: this.y - MAP_HEIGHT } clones.push(topClone, bottomClone); return clones; } update() { this.y--; if (this.y < -MAP_HEIGHT) { this.y = 0; } } }
Sketch.js
Once again this is just as we’d expect if you’ve followed any other tutorials in the series (If you haven’t, you lucky bugger you’ve got so much great content to enjoy). One thing to note is that we translate so that the player is the center of the screen.
const MAP_HEIGHT = 800; let player; let racers = []; let carImages = []; let playerCarImage; function preload() { // get the other racer images carImages.push(loadImage("pink.png")); carImages.push(loadImage("green.png")); carImages.push(loadImage("lightblue.png")); carImages.push(loadImage("red.png")); playerCarImage = loadImage("teal.png"); } function setup() { createCanvas(400, 400); player = new Player(playerCarImage); for (let i = 0; i < carImages.length; i++) { racers.push(new Racer(carImages[i])); } } function draw() { background(255); fill(170); // so we follow the players position translate(0, height / 2 - player.y); rect(100, -MAP_HEIGHT*2, 200, MAP_HEIGHT*3); player.update(); player.draw(); for (let racer of racers) { racer.update(); racer.draw(); } }
Making the Racers hit the wall
I don’t know why, but in the game the racers just bump off each wall, quite funny really, let’s do that.

class Racer { constructor(racerImage) { this.racerImage = racerImage; this.x = random (110, 290); this.y = random(0, MAP_HEIGHT); this.carWidth = this.racerImage.width * 0.4; this.carHeight = this.racerImage.height * 0.4; this.isLeft = false; this.isRight = false; // choose the initial direction this.direction = random(1); if (this.direction > 0.5) { this.isLeft = true; } else { this.isRight = true; } } .... update() { this.y--; if (this.y < -MAP_HEIGHT) { this.y = 0; } if (this.isLeft) { this.x--; } else if (this.isRight) { this.x++; } if (this.x > 290) { this.isRight = false; this.isLeft = true; } else if (this.x < 110) { this.isRight = true; this.isLeft = false; } }
Player left/right movement
Left and right

Player.js
Two new functions
moveLeft() { if (this.x > 110) { this.x-=2; } } moveRight() { if (this.x < 290) { this.x+=2; } }
Sketch.js
In the draw function:
if (keyIsDown(RIGHT_ARROW)) { player.moveRight(); } if (keyIsDown(LEFT_ARROW)) { player.moveLeft(); }
Player accelerate/decelerate

Player.js
Two new functions
accelerate() { if (this.speed < 10) { this.speed += 0.05; } } decelerate () { if (this.speed > 0.1 ) { this.speed -= 0.05; } }
Sketch.js
In the draw function:
if (keyIsDown(UP_ARROW)) { player.accelerate(); } if (keyIsDown(DOWN_ARROW)) { player.decelerate(); }
Adding a score
So the score will just be the distance the player has travelled

So just initialise a variable called distance set it to 0!
distance += player.speed/ 100; textAlign(CENTER); textSize(40); text(floor(distance), 340, player.y);
Hit detection
Let’s be honest, there’s not much of a game if there’s no actual objective. We need the objective to be to avoid cars and get as far as you can!

Let’s add a new function called reset and if a collision happens that will be called.
Sketch.js
function setup() { createCanvas(400, 400); reset(); } function reset() { racers = []; player = new Player(playerCarImage); for (let i = 0; i < carImages.length; i++) { racers.push(new Racer(carImages[i])); } distance = 0; }
Finished!
There are still so many things you could potentially add that are in the original game, I just really wanted to cover the cloning/wrapping.

Things to add:
- Changing race track!
- Changing speed of other players
- More cars
- A life system
Thank you for getting this far in the tutorial, hope you enjoyed it. And if you’re new, I have a tonne of other tutorials that could be of use to you!
[…] than moving a galaxy at a time make them seemingly merge into one. You can leverage ideas from here to do […]