We start with creating a canvas and displaying text, then move on to drawing lines and shapes, and finally show how to draw "pixies" -- 2D pixel arrays that form the basis of almost every 2D game.
Before we get into the good stuff, we'll make a small foray into the topic of "code cleanliness". If you read our earlier tutorials, you might recall we discused "scope". Scope is a complex topic, but it boils down to a simple idea: every program running on your computer reserves memory for its data. Some of that memory is visible to other programs. Other bits of memory are visible only to the programs themselves, and even within a program, some of the memory is visible only to certain parts of the program at certain times. The rules defining what memory is visible to which program(s) is called "scope".
We don't need to worry about scope too much, but there is one thing we'd like to manage: we don't want the details of our programs to pollute the "global scope," which is the memory visible to all programs.
When you are running in a web browser, the global scope is the window in which you are running. Windows are large objects with many variables and functions. Here's a peek at the just the first bits of a Window object:
Every time we execute the following commands:
we create a new object in the global scope. For instance, suppose we execute the command
and once again look at the Window object:
If you look carefully, you can see our 'aardvark' variable in the list of Window fields. Insert sad emoji here.
To minimize our footprint in the global scope, we are going to put all our tutorial code inside an object. We'll call it the 'ct' object, short for "CanvasTutorial":
As we cover more topics, we will continue to add on to this object as follows:
Also note that, when you look in the tutorial code, you will see commands like:
The this keyword means "whatever object I am currently in", which, for us, usually means the ct object.
Otherwise, this tutorial should look, walk, and smell just like the others we have done.
Let's get to the good stuff!
Creating a Canvas
1) create a Canvas:
2) obtain a context object:
3) add the new canvas to the document:
Now, to draw to the canvas, you just call a method inside the context object. For example, to fill the canvas with a solid color:
where 'fillStyle' is the color the canvas will use on all subsequent 'fill' commands, and 'fillRect' draws a rectangle with the top corner at x = 0 (the left side of the canvas) and y = 0 (the top of the canvas) with a width equal to the width of the canvas and the height equal to the height of the canvas.
Important Point: the left edge of the canvas has an x coordinate of 0, and increasing 'x' values move across the canvas to the right. The top edge of the canvas has a y coordinate of 0, and increasing 'y' values move down the canvas toward the bottom.
If you check out the first half of createCanvas.js, you can see all these steps in action.
Since this is a graphics tutorial, naturally, the first thing we will display is...text???
Well, yes, but the good news is we'll be displaying the text in a canvas-y way, rather than a writeln-y way as we did previously.
To draw text to the canvas, we need to specify the font and color, then actually draw the text:
ct.myContext.font = "20px Arial";
ct.myContext.fillStyle = "green";
ct.myContext.fillText("My Message", 10, 50); // 10 = x-position, 50 = y-position
In the tutorial, we use fillText to display a collection of menu items as follows:
Let's draw lines. The 2D context provides 2 methods to make this easy:
The moveTo method puts the 2D graphics cursor at the specified (x, y) position without drawing anything.
The lineTo method moves the 2D graphics cursor to a new (x, y) position, drawing a line between the previous position and the new one.
Unfortunately, if you just execute these two commands, you won't see anything. For many commands like moveTo and lineTo, the Canvas expects you to move the graphics cursor several times, then draw all the lines in one go. In other words, it wants to process a whole batch of commands in one shot, which is often more efficient that drawing one line at a time.
In order to process batches of commands (often called "batch commands"), the canvas provides two methods:
Then, to execute all the commands in the batch, the context provides:
Note that shapes like circles and rectangles can be drawn with either 'stoke' or 'fill' commands, or both -- depending on whether you want just the border, just the interior, or an interior with a border. We'll see how this works in the next section.
For now, we just want to draw grid lines across the screen. To do this, we use two loops: one to draw horizontal lines, and one to draw vertical lines. Here is the code (see grid.js):
Important Point: if you omit the beginPath and closePath calls, you may see strange artifacts in your graphics. For instance, you might see an errant diagonal line connecting the end of one grid line to the start of the next. beginPath and closePath help the 2D context to track where the graphics cursor is and when it should be moved without drawing versus moved while drawing. Make sure you wrap your batch commands correctly, or you may get unexpected results.
It's easy to draw rectangles and arcs with the 2D canvas.
Arcs (partial circles):
For our tutorial, we randomly-generate a collection of rectangles and a collection of circles. The only thing we haven't seen in that code is the use of the round, random, and min Math functions:
round rounds the input to the nearest whole number,
random generates a random decimal number between 0 and 1, and
min selects the smaller of two input numbers.
Combining these functions allows us to create shapes of random sizes and random locations on the screen.
The canvas also allows you do draw arbitrary images (as opposed to just shapes) to the screen. To do this, you create an offscreen canvas, draw into it, and then draw that canvas to the screen with the drawImage context method:
That's all there is to it!
In our tutorial, we'll draw a red box with a white cross into the offscreen canvas, then draw that canvas at a random position on the screen:
Way back in the 80's, the Commodore 64 and Atari home computers introduced the idea of the "sprite" -- a 2D grid of pixels rendered at high speed in the graphics hardware. Sprites made it fast and easy to render thigns like animated characters, and they often supported collision detection in the hardware, too.
In the spirit of these early sprites, we introduce the "Pixie" -- a 2D array of pixels drawn via the 2D context. We accomplish this by creating a 2D array that represents the pixels, then looping through this array and drawing the pixels to an offscreen canvas. When we want to display the Pixie, we just draw its offscreen canvas to the visible canvas.
Before we look at that code, we need to cover a few new concepts.
we can access the fields like this:
This would print out:
Second, if you have defined a string like this:
You can access each character like this:
which would produce the following output:
With these concepts in our pocket, let's take a look at our "Pixie" code:
We will use setInterval to call a method that switches back and forth between two frames of an animated helicopter Pixie. We store the frames in an array. Each time we call the 'animate' function, we change to the other frame, clear the screen, and draw the new Pixie:
Notice that we use the '%=' operator, which performs the modulus operation on the frame number. Check out the 2nd tutorial in the series (Awari) for a discussion of that operation.
Go out there and see what you can do!