Create a Game Character with HTML5 and JavaScript - Part 1
In Part 1 of this series we will design a game character from scratch. We will start with a drawing on paper and with the help of JavaScript we will create a breathing, blinking character on HTML5 canvas. Hopefully by the end of Part 1 you will have the tools and inspiration to create a character of your own.
Idea to Pixels
First we start with an idea. For this character I chose to draw him on paper first. I wanted him to be very simple with few details (e.g. nose, mouth). Although I did not skimp on head size.
The next step is to get our idea to pixels. In this case since I had a drawing, I scanned it in and outlined the drawing in Adobe Illustrator. I chose varying outline thickeness, thicker around the edges of body parts and thinner for the details. Also somehow during the process his head got even bigger.
Next we color in the outlines. Keeping the design simple I chose one solid colors per body part, with an additional shade as a highlight color for added detail.
We are creating a dynamic character so we create our character in distinct parts. For our example we keep it simple and segment our character into six parts:
- Head
- Hair
- Torso
- Legs
- Left Arm
- Right Arm
Each part is saved as a seperate png image. We will be drawing parts on top of one another so we save each one with a transparent background.
Draw on HTML5 Canvas
With the design of our character complete and in the form of six images, we start the process putting our character on canvas. The first step of that process is to load those images using JavaScript.
var images = {}; loadImage("leftArm"); loadImage("legs"); loadImage("torso"); loadImage("rightArm"); loadImage("head"); loadImage("hair"); function loadImage(name) { images[name] = new Image(); images[name].onload = function() { resourceLoaded(); } images[name].src = "images/" + name + ".png"; }
First we create a new object to hold our image references called images
. Next we load each of our character parts via the loadImage
function with the parameter corresponding to the part name (e.g. leftArm, legs, etc.). The loadImage
function creates a new image object pointing to an image with the filename of the part name with the extension ".png" and in the folder "images". It also assigns an onload method to each image so when the image is loaded into memory it will caled the function resourceLoaded
.
We want to know when all of the images are loaded so we can begin drawing.
var totalResources = 6; var numResourcesLoaded = 0; var fps = 30; function resourceLoaded() { numResourcesLoaded += 1; if(numResourcesLoaded === totalResources) { setInterval(redraw, 1000 / fps); } }
We create a couple variables to track the image load process: totalResources
and numResourcesLoaded
. The resourceLoaded
function increments the number of images that have been loaded. When all the images are ready we start a timer using setInterval
that will call the redraw
function 30 times a second.
During the redraw process the canvas will be cleared and all parts will be redrawn. The order of that process is important. We first draw the the parts farthest away such as the left arm that will be covered up by our character's legs and torso.
We need access to the HTML5 canvas context that we will draw on. For more information on how to access the canvas' context (and a workaround for IE) refer to HTML5 Canvas Example.
var context = document.getElementById('canvas').getContext("2d");
Layer by layer, each body part's image is positioned and then drawn on an HTML5 canvas.
var charX = 245; var charY = 185; function redraw() { var x = charX; var y = charY; canvas.width = canvas.width; // clears the canvas context.drawImage(images["leftArm"], x + 40, y - 42); context.drawImage(images["legs"], x, y); context.drawImage(images["torso"], x, y - 50); context.drawImage(images["rightArm"], x - 15, y - 42); context.drawImage(images["head"], x - 10, y - 125); context.drawImage(images["hair"], x - 37, y - 138); }
Before drawing anything we first clear the canvas using the weird assignment canvas.width = canvas.width
. Then we draw each image using the context drawImage
method specifying three parameters: the image reference, its x position, its y position. The image positions are relative to the canvas' top left hand corner.
Here is what the canvas looks like so far:
Something's missing...
Eyes
To add the eyes we are going to draw two ovals. We could have added the eyes to the eyes to the head image, but we want them to be dynamic to enable our first behavior: blinking.
We call a drawEllipse
function for each eye at the end of the redraw
function. We want them on top of all the other body part images.
function redraw() { ...drawEllipse(x + 47, y - 68, 8, 14); // Left Eye
drawEllipse(x + 58, y - 68, 8, 14); // Right Eye
}
The drawEllipse
takes four parameters specifying the position and dimensions of the ellipse. For more information on the drawEllipse
function see the brief: How to draw an ellipse on HTML5 Canvas.
function drawEllipse(centerX, centerY, width, height) { context.beginPath(); context.moveTo(centerX, centerY - height/2); context.bezierCurveTo( centerX + width/2, centerY - height/2, centerX + width/2, centerY + height/2, centerX, centerY + height/2); context.bezierCurveTo( centerX - width/2, centerY + height/2, centerX - width/2, centerY - height/2, centerX, centerY - height/2); context.fillStyle = "black"; context.fill(); context.closePath(); }
Our character matches our original digital version minus the shadow.
Shadow
We create the shadow with one oval at our characters feet.
function redraw() {
...
canvas.width = canvas.width; // clears the canvas
drawEllipse(x + 40, y + 29, 160, 6);
...
}
We want the shadow behind all the other image layers sp an ellipse is drawn at the beginning of the redraw
function.
Now we have on HTML5 canvas what we had with a drawing program. Remember, the HTML5 canvas is dynamic. Let's make use of that.
Breathe
var breathInc = 0.1; var breathDir = 1; var breathAmt = 0; var breathMax = 2; var breathInterval = setInterval(updateBreath, 1000 / fps);
function updateBreath() { if (breathDir === 1) { // breath in breathAmt -= breathInc; if (breathAmt < -breathMax) { breathDir = -1; } } else { // breath out breathAmt += breathInc; if(breathAmt > breathMax) { breathDir = 1; } } }
The updateBreath
function increases or decreases the breath amount. Once the breath reaches it maximum it changes the breath direction. Breath in, breath out.
The purpose of this process is to update the variable breathAmt
which we will use to represent the constant breathing of our character in the form of a subtle rise and fall of the head and arms.
To breath life into our static pile of images, we turn to our redraw
function. We the help or the variable breathAmt
we vary the vertical position of certain body part images.
function redraw() { canvas.width = canvas.width; // clears the canvas drawEllipse(x + 40, y + 29, 160- breathAmt
, 6); // Shadow context.drawImage(images["leftArm"], x + 40, y - 42- breathAmt
); context.drawImage(images["legs"], x, y); context.drawImage(images["torso"], x, y - 50); context.drawImage(images["head"], x - 10, y - 125- breathAmt
); context.drawImage(images["hair"], x - 37, y - 138- breathAmt
); context.drawImage(images["rightArm"], x - 15, y - 42- breathAmt
); drawEllipse(x + 47, y - 68- breathAmt
, 8, 14); // Left Eye drawEllipse(x + 58, y - 68- breathAmt
, 8, 14); // Right Eye }
We subtract the vertical location by value of the variable breathAmt
for all the pieces we want to oscillate. The shadow will reflect this vertical motion in a change in width.
Blink
The time has come for our first behavior of this series. After a given amount of time we will make our character blink.
var maxEyeHeight = 14; var curEyeHeight = maxEyeHeight; var eyeOpenTime = 0; var timeBtwBlinks = 4000; var blinkUpdateTime = 200; var blinkTimer = setInterval(updateBlink, blinkUpdateTime);
We added several globals:
maxEyeHeight
: Eye height when our character eyes are wide open.curEyeHeight
: Current eye height while blinking.eyeOpenTime
: Milliseconds since last blink.timeBtwBlinks
: Milliseconds between blinks.blinkUpdateTime
: Milliseconds before updating blink status.blinkTimer
: Calls theupdateBlink
function everyblinkUpdateTime
milliseconds.
Update the redraw
function so the height corresponds with the new variable curEyeHeight
function redraw() { ... drawEllipse(x + 47, y - 68 - breathAmt, 8,curEyeHeight
); drawEllipse(x + 58, y - 68 - breathAmt, 8,curEyeHeight
); }
Every few moments we check to see if its time to blink.
function updateBlink() { eyeOpenTime += blinkUpdateTime; if(eyeOpenTime >= timeBtwBlinks){ blink(); } } function blink() { curEyeHeight -= 1; if (curEyeHeight <= 0) { eyeOpenTime = 0; curEyeHeight = maxEyeHeight; } else { setTimeout(blink, 10); } }
When the updateBlink
function is called the eye open time is updated by increasing the variable eyeOpenTime
. If the eye open time is greater than the timeBtwBlinks
value we begin blinking by calling the blink
function. The blink
function will decrement the current eye height until reaching zero. When the eyes are closed we set the eye height to maximum and reset the eye open time.
Working Example
Here is a working example. The example code is available for download below.
Create Your Own Character
Now try creating your own character. Download the source code below and edit the six provided images to make them your own. For example, I tweaked the images just a bit and made our character a zombie:
What's Next
In Part 2 of the series will give our character the ability to jump.
Download Source Code
- Download HTML5 Canvas Game Character (zip format): html5-canvas-drawing-app-1.zip