Tutorial: How to make a top-down shooter in JavaScript

Lesson 3) Creating a game loop

Let’s go for a pseudo-random walk…

As noted previously, to create the illusion of smooth movement in our game we’re going to clear and re-draw our canvas quicker than the eye is able to detect. There’s a really handy context method for that, called clearRect.

c.clearRect(0, 0, canvas.width, canvas.height);

clearRect erases from the canvas anything that’s within its bounds. So here we’ve created a clearRect starting at coordinates 0,0 (the upper-left corner of the canvas as you may recall), that is the same width and height as the canvas, using the variables we defined previously.

We’re going to create a loop. In every cycle of this loop, our program will clear the canvas, update the positions of all our game elements, and then draw them again in their new positions. This will happen many times every second. There are a few ways of creating such a loop, but we’re going to use setInterval, because it lets us choose the speed of the looping:

setInterval(mainDraw, 20);

This command repeats a set of code – in this case a function called mainDraw – at an interval that you specify – in this case every 20 milliseconds, which is 50 frames per second. There’s another function, clearInterval() which breaks the program out of the loop. We won’t need it here, but it has good potential for games, for example you could set up different loops for the title screen, menu screens, the pause menu, the game itself, and then use setInterval/clearInverval to move between these as needed.

Anyway, for now let’s just have one loop. We’re calling it mainDraw:

function mainDraw() {

} // mainDraw

Our program will run anything we put in mainDraw, every 20 seconds. The “// mainDraw” bit is just a comment so you know what that closing bracket is actually closing – this is very handy when your functions start getting longer.

So let’s draw a little box, which could be a placeholder for our player’s sprite, and then clear the canvas every loop:

c.clearRect(0, 0, canvas.width, canvas.height);
c.fillStyle="red";
c.strokeStyle="blue";
c.beginPath();
c.rect(395,295,10,10);
c.lineWidth=1;
c.stroke();
c.fill();

It will look like this:

There’s our hero!

Now we just need to update the player’s x and y coordinates from within the mainDraw loop, and he’ll move around the screen. For now we’ll make our four-sided hero move randomly, so that we can get the hang of this looping and clearing business. Later we’ll use key-press detectors, and move the character when we detect input from the player.

To code our random movement, we’ll replace the x and y coordinates of the box (presently x=395 and y=295) with two variables, player1x and player1y. First let’s define these variables — later we’ll use an object to contain all of our data about the Player, but for now, let’s just define them as separate variables:

var player1x = 395;
var player1y = 295;

And then we plug these variables into the rect():

c.clearRect(0, 0, canvas.width, canvas.height);
c.fillStyle="red";
c.strokeStyle="blue";
c.rect(player1x, player1y, 10,10);
c.lineWidth=1;
c.stroke();
c.fill();

This puts our boxy protagonist in the middle of the canvas. By the way, why 395 and 295, why not 400 and 300? Because his x and y coordinates at at the top-left of the box, so to perfectly center him in the canvas, we need to deduct half his width from his x coordinate and half his height from his y coordinate.

Then we’ll put the below code in the loop, which adds a random number between -1 and 1 to the player’s present x and y coordinates (Math.random returns a number between 0 and 1).

player1x += (Math.random() * 2) - 1;
player1y += (Math.random() * 2) - 1;

We’ll also put a little statement to check whether the player has reached the edge of the screen, and if so, regenerate these changer variables and put him back in the middle. Note here that || means “OR,” so if ANY of the conditions specified are true, the program will run the code between the brackets. If NONE are true, it will ignore it.

if (player1x < 0 || player1x > canvas.width || player1y < 0 || player1y > canvas.height) {
  player1x = 395;
  player1y = 295;
  player1xChanger += (Math.random() * 2) – 1;
  player1yChanger += (Math.random() * 2) – 1;
}

And here is the result:

Well, it’s not going to be a best-seller on Steam, I grant you, but we have a moving character on the canvas, and a functioning game loop, and that’s a start.

Choose your path wisely

As an experiment, try removing the clearRect line and see what happens. You’ll see a blue smudge appearing. This is because rect basically “stamps” our character onto the canvas at the coordinates we specify. Because the blue border is 1 pixel thick, and because we moved the player less than 1 px per loop, we get a trail of blue where the player used to be. Generally you’ll want to clear the canvas to get rid of these trails, but there are some situations you might want to keep them, maybe tyre skid marks in a driving game, or smoke trails behind a rocket.

Put clearRect back, remove the beginPath line, and run the program again. Now you’ll see a red smudge with a blue border around it. Why is this? It’s all to do with this “path” thing. You can think of a path as a way to combine several shapes/lines into one shape. Therefore we need to beginPath in every cycle of the loop, which closes the previous path. If we don’t use beginPath, we’re adding to the shape with every loop, building a strange and wonderful pattern with a blue border around it.

If you want to challenge yourself, can you adapt the code so that our quadrilateral character moves in ONE direction until it reaches a border, and then moves back to the center and picks another random direction to go in?

Go to Lesson 4 – Getting started with objects

2 Comments

  • Johan says:

    Hi there! I’ve been trying to follow your tutorial. I’m a beginner when it comes to javascript and I think it would be helpful to see the code in larger context from time to time. It’s sometimes hard to understand where in the scope of the script the next snippets go. I’m sure it’s totally understandable for someone who knows more than me 🙂

    Have a nice day!

    • Warren Davies says:

      Hi Johan, thanks for your comment! Sorry it took over a year to reply! How’s the JavaScript learning going, are you still working on it?

      OK gotcha, I hear what you’re saying there! Next tutorial, which I think I’ll complete sometime between now and 2048, I’ll put the whole code into each lesson. Thanks for the feedback!

      For now, you can also press ctrl+u to see the full code on each lesson – it’s in the HTML, just search for “init” and you’ll find it!

      Cheers!
      Warren

Leave a Reply

Your email address will not be published. Required fields are marked *