Link Search Menu Expand Document

Dinosaur Game Clone

Fork this repl to begin: https://replit.com/@buckldav/DinoGameP5Starter#index.html. Here’s what we will be doing:

  1. Adding more enemies.
    • Enemies spawn in random positions.
    • Enemies speed up as the game goes along.
    • Enemies move across the screen.
    • If an enemy collides with the player, the game ends.
  2. Adding game over functionality (i.e. end screen, message to player, etc.).

How it Works

1. Multiple Script Files

In index.html, we have 5 different script files loaded. We could combine all of these scripts to one file, but it’s a good idea in large projects to organize and compartmentalize code.

<!--  Files are loaded from top to bottom.  -->
<script src="globals.js"></script>
<script src="enemy.js"></script>
<script src="environment.js"></script>
<script src="player.js"></script>
<script src="script.js"></script>

2. Globals

Some data is needed throughout the whole program. It’s also convienent to keep similar data together in one spot (i.e. colors) so that if you want to change the color scheme of your entire app or game, you don’t need to comb through files to try to find what you need.

In globals.js an object called GLOBALS contains colors and a few positions of other objects.

// The const keyword is used instead of let if the data doesn't change
const GLOBALS = {
  GRAVITY: 3,
  JUMP_STRENGTH: -30,
  DRAG: 3,
  GROUND_H: 90,
  PLAYER_H: 30,
  COLORS: {
    SKY: "#87CEEB",
    GROUND: "gray",
    PLAYER_FILL: "red",
    PLAYER_STROKE: "black",
    COLLISION_BOX: "pink",
    SCORE: "black",
  },
}

3. Classes Make Objects

Also in the globals.js file is a class Box. Classes are templates for creating objects. Here’s how the Box class does that.

class Box {
  constructor(x, y, w, h) {
    // Class variables start with 'this.'
    this.x = x
    this.y = y
    this.w = w
    this.h = h
  }
}

// Create a Box object
// The Box's constructor takes in the parameters
let box = new Box(0, 10, 20, 30)

// box is a Box object
console.log(box)
{
  x: 0,
  y: 10,
  w: 20,
  h: 30,
}

console.log(box.y)
10

Boxes keep track of position for the object and its collision in our game.

4. The Other JavaScript Files

FileDescription
enemy.jsThe Enemy class. Enemies can have different pictures. Enemies also end the game if they collide with the player.
environment.jsThe Environment class. Contains a ground box to prevent the player from falling forever.
Add other background elements in addition to the sky if you’d like.
globals.jsContains global constants and the functions updateScore(), showCollisionBox(), and gameOver().
player.jsThe Player class. Update the player’s position and check for collisions.
script.jsContains the game logic, which is executed in p5.js’ functions (setup(), draw(), keyPressed()).

Make an Enemy Randomly Spawn and Move

To figure out how to move the enemy, perhaps we should reference the player’s update() function to see how the player moves.

// Player.js
update(env) {
  // gravity and jumping
  if (this.jumping || !this.isGrounded(env)) {
    this.box.y += this.velY
    this.velY += GLOBALS.GRAVITY
    this.jumping = false
  }
}

The key part is this line:

this.box.y += this.velY

The velY (y velocity, can be +, -, or 0) of the player gets added to the y position of box. We can use this same idea in the Enemy class’s code to update its position and move it across the screen.

Try to add an update() function to the Enemy class and call it in draw() in script.js for each enemy.

Answer
// Enemy.js
class Enemy {
  constructor(img) {
    this.img = img
    this.box = new Box(
      40, // x
      windowHeight - 250, // y
      GLOBALS.ENEMY_W, // w
      GLOBALS.ENEMY_H // h
    )
    // Change the velocity.
    this.vel = 1
  }

  draw() {
    // showCollisionBox(this.box)
    image(this.img, this.box.x, this.box.y, this.box.w, this.box.h)
  }

  // Add this function
  update() {
    // Move the box to the left
    this.box.x -= this.vel
  }
}

Then, we need to call the update() function in the main script.js for each enemy.

// script.js
// in the draw() function's for loop:
for (let i = 0; i < enemies.length; i++) {
  enemies[i].draw()
  if (player.isBonk(enemies[i])) {
    // ends the program
    gameOver()
  }
  // Add this
  enemies[i].update()
}

Code at this point: https://replit.com/@buckldav/DinoGameP5EnemyMove.

Spawn the Enemy Off Screen

How could you change the Enemy.js constructor so that the Enemy spawns off screen to the right?

Answer

Here’s one way to go about it:

// Enemy.js
constructor(img) {
  this.img = img
  // The x position of the box needs to start off screen (windowWidth + GLOBALS.ENEMY_W).
  this.box = new Box(
    windowWidth + GLOBALS.ENEMY_W,
    windowHeight - 250,
    GLOBALS.ENEMY_W,
    GLOBALS.ENEMY_H
  )
  // Try out other numbers for the velocity.
  this.vel = 3
}

How would you make the enemy spawn at a random y value? Recall the Snowflakes Project if needed.

Answer

Here’s one way to go about it:

// Enemy.js
constructor(img) {
  this.img = img
  // Calculate the y with GROUND_Y or PLAYER_Y somehow to make it measure off of where those are.
  this.box = new Box(
    windowWidth + GLOBALS.ENEMY_W,
    windowHeight - random(GLOBALS.GROUND_H, GLOBALS.GROUND_H + 100),
    GLOBALS.ENEMY_W,
    GLOBALS.ENEMY_H
  )
  this.vel = 3
}

Spawn More Enemies

Here are some questions that need to be answered.

  1. How to make a lot of enemies quickly?
  2. Should we make all the enemies at the beginning or make them along the way?
Answer

For question 2, there is no right answer here, both have merit. The answer to 2 influences the answer to 1. In this walkthrough, I’ll show you the “make them along the way” approach.

In script.js, check out the setup() function. Near the bottom, there is the line that spawns an enemy.

// script.js
// array.push adds an element to the array
enemies.push(new Enemy(enemyImg1))

If we use this line in the draw() function, enemies will spawn every frame.

// script.js
function draw() {
  env.draw()
  player.draw()
  for (let i = 0; i < enemies.length; i++) {
    enemies[i].draw()
    if (player.isBonk(enemies[i])) {
      gameOver()
    }
    enemies[i].update()
  }

  // NEW CODE: spawn an enemy
  enemies.push(new Enemy(enemyImg1))

  updateScore()
  player.update(env)
}

This may create too many enemies.

Too many enemies

To solve this, let’s add an if statement that will only add an enemy if the current frame is a multiple of something. To do this, we can use the modulo operator which can get the remainder of division. If the remainder is zero, we can spawn an enemy. Also, our score variable is

// script.js
function draw() {
  // ...
  // The for loop is right here
  // You'll need to make the variable GLOBALS.SPAWN_RATE in globals.js
  if (score % GLOBALS.SPAWN_RATE === 0) {
    enemies.push(new Enemy(enemyImg1))
  }
  // updateScore()
  // ...
}
// globals.js
const GLOBALS = {
  // ...
  SPAWN_RATE: 50,
  // ...
}

Here’s the code at this point: https://replit.com/@buckldav/DinoGameP5EnemySpawn.

Speed Enemies Up

In Enemy.js, how could you use the score variable so that the velocity of new enemies increases as the score gets higher?

Answer

Here’s one way to go about it:

// Enemy.js
constructor(img) {
  this.img = img
  this.box = new Box(
    windowWidth + GLOBALS.ENEMY_W,
    windowHeight - random(GLOBALS.GROUND_H, GLOBALS.GROUND_H + 100),
    GLOBALS.ENEMY_W,
    GLOBALS.ENEMY_H
  )
  // Just this line needs modified. The number "1000" can be whatever you want.
  this.vel = 3 + score / 1000
}

You might consider making that number 1000 a constant in your globals.js. This practice is called avoiding “magic numbers” (numbers that show up out of nowhere in your code). Same thing with the other numbers (250 and 3). Here’s mine:

// Enemy.js
constructor(img) {
  this.img = img
  this.box = new Box(
    windowWidth + GLOBALS.ENEMY_W,
    random(GLOBALS.GROUND_Y - GLOBALS.ENEMY_RANGE, GLOBALS.GROUND_Y),
    GLOBALS.ENEMY_W,
    GLOBALS.ENEMY_H
  )
  this.vel = GLOBALS.ENEMY_V + score * GLOBALS.ACCELERATION
}

Game Over Screen

In globals.js, there is a gameOver() function where you can add background, text, etc. for when the game ends. Try adding everything yourself!

Final Code

The code below doesn’t have a game over screen, but everything else is there. Try adding your own enemy sprites. Change the jump strength and other globals. Get creative, and good luck!

https://replit.com/@buckldav/DinoGameP5EnemyFinal#enemy.js.