In this third and last part of the series on how to develop a game with p5.js we will implement the actual game logic: eat enemies, get bigger and faster and game over handling. You will find the full code at the end of the article and you can play the game here.
New state
First of all let’s add the missing variables to complete our game.
- We will add a boolean variable to store the information whether the game has ended.
- As we want our player to get faster and faster with every bite, we need a variable to store the current player’s velocity
let direction = null; let velocity = 1; let gameOver = false;
Eating enemies / collision detection
This is the hardest part. But when we move our player around we need a way to find out whether we catched an enemy. Touching the enemy is good enough in our game, so we will need some sort of collision detection!
As all our game objects are circles, the circle collision detection is our best friend. It is pretty easy to understand and also easy to code. But it requires some understanding of the Pythagoras theorem (find the distance between two points). You can read this great article here for a detailed explanation.
In short we first find out the distance of two points using the Pythagoras theorem and then check if we we meet the condition for a collision.

I tried to visualize this here, so if the circles get closer and closer, the distance gets smaller. Once the distance is smaller than the sum of the two radiuses, we have a collision.
Let’s now add this to our game!
Which can be written in JavaScript using the built in functions sqrt and pow.
let distance = sqrt(pow(playerPositionX -enemyPositionX ,2) + pow(playerPositionY - enemyPositionY, 2));
Now that we have the distance, we can check for collision, this is simple:
let radii = 10 + playerWeight / 2; let collisionDetected = distance <= radii;
The radii variable may be a bit surprising: this is just adding up the radius of the player and the enemy. The circle function accepts the diameter, we have a diameter of 20 for the enemy (we should use a constant for this..) the diameter of the player is equal to it’s weight we take 10 for the enemy and half the weight for the player.
Now that we know that we have a collision, we need to update the variables to reflect the new reality. We want to make everything faster, bigger and find a new position for the enemy.
if (collisionDetected) { playerWeight += 2; velocity += 1; enemyPositionX = random(500); enemyPositionY = random(500); }
Where to put the code? We can simply add it to the draw function. As we know, it is called repeatedly.
function draw() { // get distance of the two circles using Pythagoras let distance = sqrt(pow(playerPositionX -enemyPositionX ,2) + pow(playerPositionY - enemyPositionY, 2)); // get the sum of the two radii (note we pass the diameter to the circle function, so we need to half it!) let radii = 10 + playerWeight / 2; // check if the distance is smaller than the sum let collisionDetected = distance <= radii; if (collisionDetected) { playerWeight += 2; velocity += 1; enemyPositionX = random(500); enemyPositionY = random(500); } ... rest of the function
Almost done
You can play around with the updates to the game world. Why not make it even faster, or add way more weight each time you eat an enemy? I found the configuration above to be quite challenging, but still playable.
But one thing is missing: if we hit the border of the playfield, we need to stop the game. In addition we want to print the current weight somewhere on the screen so that we can compare highscores with our friends!
Print the Score
p5.js comes with a handy function to draw text, we just use that one. We can do this as the last thing in our draw function:
// current score fill(60,200,120); textSize(20); text("your current weight " + playerWeight + " kg",20,20); }
Hit a Wall
This is another part where we need some sort of collision detection. If we touch the left, right, bottom or top edge the game is over.
// Hit a wall? if (playerPositionX < 0 || playerPositionX > 500 || playerPositionY < 0 || playerPositionY > 500) { gameOver = true; }
And last but not least, we need to stop the game:
// Game over? if (gameOver) { fill(200,50,40); textSize(20); text("GAME OVER", 200,250); return; // just skip everything else }
The End
We created a very simple game with just a few lines of code. I really like p5.js and their editor. My highscore:

What is yours? 😉
Full Code
let playerWeight = 10; let playerPositionX = 300; let playerPositionY = 300; let enemyPositionX = 0; let enemyPositionY = 0; let direction = null; let velocity = 1; let gameOver = false; function setup() { // setup the random initial location of the enemy enemyPositionX = random(500); enemyPositionY = random(500); createCanvas(500, 500); } function draw() { // Game over? if (gameOver) { fill(200,50,40); textSize(20); text("GAME OVER", 200,250); return; // just skip everything else } // Hit a wall? if (playerPositionX < 0 || playerPositionX > 500 || playerPositionY < 0 || playerPositionY > 500) { gameOver = true; } // get distance of the two circles using Pythagoras let distance = sqrt(pow(playerPositionX -enemyPositionX ,2) + pow(playerPositionY - enemyPositionY, 2)); // get the sum of the two radii (note we pass the diameter to the circle function, so we need to half it!) let radii = 10 + playerWeight / 2; // check if the distance is smaller than the sum let collisionDetected = distance <= radii; if (collisionDetected) { playerWeight += 2; velocity += 1; enemyPositionX = random(500); enemyPositionY = random(500); } // Update the world if (direction == 'up') { playerPositionY = playerPositionY - velocity; } if (direction == 'left') { playerPositionX = playerPositionX - velocity; } if (direction == 'right') { playerPositionX = playerPositionX + velocity; } if (direction == 'down') { playerPositionY = playerPositionY + velocity; } // draw a black background background(0); // define the fill color for the player fill(255, 120, 90); // draw a circle (our player) circle(playerPositionX, playerPositionY, playerWeight); // define the fill color of the enemy fill(0, 255, 0); // draw the enemy as a circle circle(enemyPositionX, enemyPositionY, 20); // current score fill(60,200,120); textSize(20); text("your current weight " + playerWeight + " kg",20,20); } function keyPressed() { if (key == 'w') { direction = 'up'; } if (key == 'a') { direction = 'left'; } if (key == 's') { direction = 'down'; } if (key == 'd') { direction = 'right'; } }