Making a Lunar Lander in JavaScript

Drawing Shapes

In this lesson, we will learn how to draw simple shapes using HTML5 canvas. We are going to draw circles, rectangles and polygons in different colours.

Setting up

First of all, create a new folder where you will store all your code. If you have trouble coming up with a name, I named mine LunarLander.

Then, in order to be able to open and preview our game in the web browser, let's write the HTML. Create a new file in the folder you created and name it index.html. Copy and paste the following into index.html:

<!doctype html>
<html>
  <head>
    <title>Moon Lander</title>
  </head>
  <body>
    <canvas id="game" width="800" height="500"></canvas>
    <script src="game.js"></script>
  </body>
</html>

That's all the HTML we're going to write. We will never touch index.html again in this tutorial. I promise. But before we move on, let's explain the new tags.

<canvas>

Just like a painter has a canvas, we programmers have one too. And as you guessed, we will be painting things on it. In fact, the whole game will be drawn in the <canvas>. You may notice it's empty, that's because we will be drawing using JavaScript, not HTML, as you will see later.

The width and height attributes specify the width and height in pixels. The id attribute will help us reference the canvas from JavaScript. Don't worry, this should start making sense soon.

<script>

In order not to mix up all our code in one file, we have written the JavaScript a bit differently in this tutorial. Notice the src attribute — it tells the browser that the JavaScript for this page is written in a file called game.js. Therefore we will not write any JavaScript in the <script> tag.

Drawing our first shape

Go ahead and create a new file (in the same folder as index.html) called game.js and paste this code in it:

var canvas = document.getElementById("game");
var context = canvas.getContext("2d");

context.rect(0, 0, 100, 100);
context.fillStyle = "black";
context.fill();

Now open the index.html file in a web browser. You should see... a black square!

The details of what happens in the first 2 lines are not really important right now. The main takeaway is that we need the context to draw things on the canvas. So we will be writing the first 2 lines in every example.

The drawing happens in the last 3 lines. First, we start by drawing a rectangle path:

context.rect(0, 0, 100, 100);

However, if you just stop here and open your web browser, you won't see anything. The canvas will be blank. That's because this only draws a path. You can imagine there is a rectangle drawn with an invisible pen. If we want to make visible what we draw, we must either use fill() to "fill" it with colour, or stroke() to paint the outlines.

TODO: beginPath(), closePath()?

fill()

context.fill() allows us to paint the inside of the path with a colour. But before we use it, we have to specify a colour we want to fill with fillStyle:

context.fillStyle = "red";

The code above sets the fill colour to red. Duh! In order to actually fill the path with colour, we need to call context.fill() at the end.

We can also use hex codes and RGBA values to specify colours, but we won't be using them in this tutorial.

stroke()

context.stroke() allows us to paint the outlines, much like you would with a pen. But as you guessed, we need to specify a colour here too, this time using strokeStyle:

context.strokeStyle = "green";

Finally, to actually apply the stroke we need to call context.stroke().

There are other options you can specify for a stroke, but we won't be using them in this tutorial.

Drawing other shapes

Rectangles

So you might be wondering what all the numbers in context.rect(0, 0, 100, 100) mean. This might help:

context.rect(x, y, width, height);

So, the first parameter of context.rect() moves the rect along the x axis, and the second—as you guessed it—moves it along the y axis. Try change the values in your text editor and see what happens. Oh, and here's the full code written in game.js for drawing a rectangle, don't forget the context.fill()!

var canvas = document.getElementById("game");
var context = canvas.getContext("2d");

context.rect(0, 0, 200, 100);
context.fillStyle = "black";
context.fill();

Circles

context.arc(x, y, radius, startingAngle, endingAngle);

As with the rectangle, the first two parameters here move the circle along the x and y axes. To be more specific, they specify the centre of the circle. The radius is simply the radius. The starting angle . Both angles are in radians!

var canvas = document.getElementById("game");
var context = canvas.getContext("2d");

context.arc(100, 100, 20, 0, 2*Math.PI);
context.fillStyle = "black";
context.fill();

Polygons

We also have the option to draw arbitary lines and polygons. We have 2 functions that help us do that, context.moveTo() and context.lineTo(), both of them accepting x and y coordinates:

context.moveTo(x, y);
context.lineTo(x, y);

You can imagine that the moveTo function moves our imaginary pen to the specified coordinates, while lineTo draws a straight line from where the pen is, to the specified coordinates.

For example, here's one way to draw a triangle:

var canvas = document.getElementById("game");
var context = canvas.getContext("2d");

context.moveTo(100, 100);
context.lineTo(200, 100);
context.lineTo(100, 200);
context.lineTo(100, 100);

context.fillStyle = "black";
context.fill();

beginPath()

If you play around enough with canvas, you will notice something weird:

var canvas = document.getElementById("game");
var context = canvas.getContext("2d");

context.rect(0, 0, 100, 100);
context.fillStyle = "red";
context.fill();

context.arc(200, 200, 10, 0, 2*Math.PI);
context.fillStyle = "blue";
context.fill();

So, we first draw a square and we paint it red. Then we draw a circle and we paint it blue. We would expect to see a red square and a blue circle, but if you actually try this, you will see that both the square and circle are blue!

When we draw, the canvas keeps track of the "path" that has been drawn so far. So when we draw a rectangle and then a circle, both the rectangle and circle are a part of the current path. If we add a triangle, it will also become a part of the path. When we finally use fill() to paint, the canvas paints the whole path, including all three shapes. So, the last colour will override any other colours previously applied.

Of course, this is not always what we intend. The context.beginPath() empties the current path from the canvas, letting us to start afresh. In order to get what we actually intended in the previous example, we can empty the path before drawing each shape:

var canvas = document.getElementById("game");
var context = canvas.getContext("2d");

context.beginPath();
context.rect(0, 0, 100, 100);
context.fillStyle = "red";
context.fill();

context.beginPath();
context.arc(200, 200, 10, 0, 2*Math.PI);
context.fillStyle = "blue";
context.fill();

It is good practise to always use beginPath() whenever we start drawing a new shape.

Erasing the canvas

Erasing the canvas can be done with context.clearRect(), which has the same parameters as context.rect():

context.clearRect(x, y, width, height);

In order to clear the whole canvas, we can write something like this:

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

We are making use of the canvas object, which has the width and height properties that refer to the HTML attributes we gave earlier. So in this case, canvas.width will be replaced with 800 and canvas.height with 500.