How to code Monaco GP (1979) – 8

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!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s