Tim Garcia
- Computer Science undergrad @ UC Riverside, masters @ Georgia Tech
- 5 years @ Amazon working on different kindle products (kindle readers, kindle fire table, kindle cloud reader, etc)
- 3 Years teaching at Web Development Bootcamps
- Co-founder and Lead Instructor at Rithm School.
Contact: Please get in touch!
- Why phaser?
- Understand how a canvas element works (phaser uses this to render your game).
- Create a phaser game that uses the physics engine
- Game architecture
Phaser is a 2d game library built on html5 standards.
Richard Davey created the project in 2012 as a weekend experiment. Over time, it exploded in popularity.
Phaser is the most popular javascript gaming framework on github (by stars) and continues to grow. You can see Richard Davey's 2016 year in review here.
You can see some games that were build with phaser here
There are some compelling reasons to choose phaser as a gaming platform:
- Easy installs. Most people have compatible browsers and internet access. No need to ship a binary and worry about system dependencies.
- Fun and fast paced development. You can quickly iterate and debug your game with all the tools available to you in Chrome.
- Community support. The html 5 game devs forum provides lots of assistance for beginners. Also there are many free tutorials available on phaser's website.
What it may not be best for:
- Games with lots of 3d rendering. Something like the Unity framework maybe a better choice.
Phase 2 (the current version of phaser), relies heavily on a modified version of PixieJS
Pixie is a library used to manipulate html5 canvas elements. In order to understand how these games are working, we'll take a moment to learn a bit about canvas.
A canvas is an html element that allows the developer to draw shapes, text, etc. It was originally introduced by Apple as an attempt to make the web more dynamic.
In 2012, there was already a standard that allowed you to draw, SVG (scalable vector graphics). When apple introduced canvas, people we skeptical about the need for a second element for drawing.
Apple also was toying with the idea of making canvas proprietary and making other companies pay for a license to use the technology. Lucky, canvas was not held hostage, and it has become a standard part of the browser. IE9+, Chrome, Safari and Firefox all support canvas.
A canvas element has some advantages that make it ideal for games:
- Canvas allows for direct pixel access. Often we need to directly manipulate pixels in games.
- The canvas element does not manage a DOM. Conversely, SVG is DOM based. It means that you have to do more work in a canvas to manage the state yourself but often you get the benefit of performance.
Here is a basic canvas tag:
<canvas height="200" width="200">
This text is shown to the user if the browser does not support canvas
</canvas>
Your imagination is the only thing holding you back with the canvas element. This tuts plus article, 21 Ridiculously Impressive HTML5 Canvas Experiments, gives some really cool examples of what is possible.
We have much more modest goals for this lesson. We are going to go over the basic canvas setup, drawing a few shapes, and then make our shapes move.
To create your first canvas element, let's start with this html:
<!DOCTYPE html>
<html>
<head>
<title>Canvas Intro</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<canvas id="my-canvas"></canvas>
</body>
</html>
And this code for style.css
:
#my-canvas {
background-color: blue;
height: 400px;
width: 400px;
}
If you open up your html page in a browser, you should see a blue html canvas on the screen!
Now, open up your javascript console, and get the canvas element by doing the following:
var canvas = document.getElementById("my-canvas");
Next, we'll start adding elements to the canvas, but first we need to understand the x and y coordinate system.
In the canvas coordinate system, the x coordinate grows from the left side of the element to the right and the y coordinate grows from the top of the canvas to the bottom.
This coordinate system can seem not very intuitive to some people since they are used to graphs from math where the y value gets larger from bottom to top, but once you get the hang of it, you'll be fine.
We also want to make sure that the width and height of our canvas is the same as the width and height on the page. Try doing the following in the console:
var canvas = document.getElementById("my-canvas");
console.log("width: ", canvas.width, "height: ", canvas.height);
The above code should return a width of 300 and a height of 150, but our canvas has a different dimension on the page. In fact, 300 and 150 are the default values of width and height for a canvas element. The difference of the canvas dimensions and the real world pixel dimensions will lead to confusion and problems, so the easiest thing to do is update the canvas size to the values from our style:
var canvas = document.getElementById("my-canvas");
canvas.width = canvas.scrollWidth;
canvas.height = canvas.scrollHeight;
When drawing on a canvas, you must choose which drawing context you want to use. While learning canvas, we'll use a 2D context which does not have 3D support.
WebGL is a 3D context that can take advantage of the graphics card on your computer (uses GPU rather than CPU for certain operations).
Even though we will not be using 3D features, often rendering in WebGL is faster than a 2D context because of the GPU.
Once you have the canvas element, the next thing you need is the context for drawing. The context is the object that actually let's you draw to the screen. To get the context, do the following:
var ctx = canvas.getContext('2d');
With the context, draw a rectangle:
var upperLeftX = 0;
var upperLeftY = 0;
var width = 50;
var height = 50;
ctx.fillRect(upperLeftX, upperLeftY, width, height);
The above code creates a black rectangle on our blue canvas. To draw with a different color, we need to specify the fillStyle
:
ctx.fillStyle = "orange";
ctx.fillRect(upperLeftX, upperLeftY, width, height);
Next, let's draw a triangle. There is no build in way to draw a triangle, but rather you can create a path that defines a shape. So our path will need 3 points:
ctx.fillStyle = "red";
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(40, 40);
ctx.lineTo(0, 80);
ctx.fill();
ctx.closePath();
Let's say you've drawn somethings, and now you want to clear the screen and start over. To do that, you can use clearRect
:
var canvasWidth = 400;
var canvasHeight = 400;
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
Exercise
Figure out how to draw text and a circle on the canvas.
To create movement in the canvas, you need to draw something, clear the screen, then draw something again that has moved slightly.
Before we get there, let's clean up our code a little. In the example below, the code for drawing a square is kept inside of the square object. The square object is responsible for drawing itself based on the properties that it has:
var canvas = document.getElementById("my-canvas");
canvas.width = canvas.scrollWidth;
canvas.height = canvas.scrollHeight;
var ctx = canvas.getContext('2d');
var square = {
corner: [0,0],
width: 50,
height: 50,
color: "red",
draw: function() {
ctx.fillStyle = this.color;
ctx.fillRect(this.corner[0], this.corner[1], this.width, this.height);
}
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
square.draw();
}
draw();
The first step to adding movement is to create a drawing loop using setInterval
. So rather than calling the draw
function directly, setInterval will call it. The more frequently we call draw, the higher the frame rate of our motion is. In other words, the frame rate is the number of screens drawn per second. If we draw a frame every second, we would have a frame rate of 1 frame per second. Typically, if you want the motion too look natural, you want a frame rate of at least 20 frames per second:
// Drawing every 50ms = 20 draws in 1 second or 20 fps
var intervalId = setInterval(draw, 50);
Now in the console if you do the following:
square.corner = [50, 50];
You should see the square being redrawn somewhere else.
Now to make the square move diagonally across the screen, modify the draw function to update the corner of the square slightly on each draw:
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
square.corner[0] += 2;
square.corner[1] += 2;
square.draw();
}
Now you should see a square moving diagonally across your screen!
EXERCISE
Once the square is off of the screen, move it back to a visible spot.
Although we are not directly manipulating canvas elements in phaser, understanding the mechanics here will be useful when we create our games.
Now that we have a solid understanding of what phaser is using underneath, let's build a game. We will be building a brain speed game. The goal is to press the correct arrow key before the shape falls to the bottom of the screen:
Let's start with the html. I've added bootstrap incase we want to style our pages :
<!DOCTYPE html>
<html>
<head>
<title>Falling Shapes</title>
<link rel="stylesheet" type="text/css" href="index.css">
</head>
<body>
<h2>Falling Shapes</h2>
<div id="game">
</div>
<div>
<p class='red'>RED SQ = ^</p>
<p class='red'>RED CIR = <</p>
<p>BLK CIR = ></p>
<p>BLK SQ = v</p>
</div>
<script src="//cdn.jsdelivr.net/phaser/2.6.2/phaser.js"></script>
<script type="text/javascript" src="index.js"></script>
</body>
</html>
Notice at the bottom of the body, we've added phaser.js
from a CDN and index.js
which we'll use to implement our game.
In index.js
, the first thing we need to do is create our Phaser game:
Initial Setup
The parameters to a Game are in the phaser docs
The first two arguments are the height and with of the canvas.
Next, Phaser.AUTO
tells phaser to use whichever context it thinks is best. (It will take WebGL if its available and fall back to 2d if WebGL is not there. This is almost always what you want).
Finally we are setting the state for phaser. These are the functions we'll use to implement our game:
const game = new Phaser.Game(500, 500,
Phaser.AUTO,
document.getElementById("game"),
{preload, create, update});
Next, create our 3 functions:
function preload() {
}
function create() {
}
function update() {
}
First, do any initial setup in preload (this is where you load images if you have any).
let score;
function preload() {
game.stage.backgroundColor = '#eee';
score = 0;
}
If you refresh the page, you should see a grey square with nothing happening. You've created your first game!
Show Score
Next, let's show our score on the screen:
let scoreText;
function create() {
let styles = {font: "20px Arial",fill: "#ff0044"}
scoreText = game.add.text(game.world.width-100, 10, `score: ${score}`, styles);
}
Notice that I'm not directly using the width of 500 here. That is important for later when we want to make our game scale.
Load Image
Next, let's create a sprite by grabbing an image from the web:
In preload, we'll load the image:
function preload() {
// ...
const img = 'img/rithm.png';
game.load.image('rithm', img);
}
Now in create, we need to put that image inside of a sprite. We want to use the physics engine for phaser and it knows how to deal with sprites.
let rithm;
function create() {
// ...
rithm = game.add.sprite(game.world.width * 0.5, game.world.height * 0.2, 'rithm');
rithm.anchor.set(0.5, 0.5);
game.physics.arcade.enable(rithm);
}
EXERCISE
The update method is similar to our draw method in the canvas example. Use rithm.body.x
and rithm.body.y
to change position.
The Power of Physics
Now that we are using a physics engine, we can do less manual updating of position:
let rithm;
function create() {
// ...
rithm = game.add.sprite(game.world.width * 0.5, game.world.width * 0.2, 'rithm');
rithm.anchor.set(0.5, 0.5);
game.physics.arcade.enable(rithm);
rithm.body.velocity.set(0, 100);
}
We can also add:
rithm.body.collideWorldBounds=true;
and then:
rithm.body.bounce.set(.70);
EXERCISE
Make two sprites on the screen. Have them collide!
Collision Detection
You may have gotten to the two sprites on the screen and moving, but there's a chance they didn't collide! That is because we aren't checking for that. We need to detect the collision in update:
function update() {
game.physics.arcade.collide(rithm, rithm2);
}
Now on each update we are checking if the two sprites collide.
Using Canvas Shapes
Sprites are one of the few things Phaser arcade physics knows how to work with. So if you want to create a canvas element, you must wrap it in a sprite:
shape = game.add.graphics(game.world.width*0.5, game.world.height* 0.5);
shape.beginFill(0xFF0000);
shape.drawCircle(0,30,60);
shapeSprite = game.add.sprite(0, 0);
shapeSprite.addChild(shape);
shapeSprite.anchor.set(0.5,0.5);
game.physics.enable(shape, Phaser.ARCADE);
shape.body.velocity.set(0, 70);
shape.body.collideWorldBounds=true;
Add A Group
rithms = game.add.physicsGroup();
rithms.enableBody = true;
rithm = rithms.create(game.world.width * 0.5, game.world.height * 0.2, 'rithm');
rithm2 = rithms.create(game.world.width * 0.5, game.world.height * 0.8, 'rithm');
rithm.body.velocity.set(0, 100);
rithm2.body.velocity.set(0, -100);
rithms.setAll('body.bounce.y', 1);
rithms.setAll('body.collideWorldBounds', true);
Create Random Shape
function createRandomShape() {
isCircle = Math.floor(Math.random() * 2) > 0;
color = [0xFF0000 /*red*/, 0x000000/*black*/][Math.floor(Math.random() * 2)];
let shapeSprite;
let shape;
let height = game.world.height * (Math.random() * 0.4 + .3);
let width = game.world.width * (Math.random() * 0.4 + .3);
if (isCircle) {
shape = game.add.graphics(width, height);
shape.beginFill(color);
shape.drawCircle(0,30,60);
} else {
shape = game.add.graphics(width, height);
shape.beginFill(color);
shape.drawRect(0,0,60,60);
}
shapeSprite = game.add.sprite(0, 0);
shapeSprite.addChild(shape);
shapeSprite.anchor.set(0.5,0.5);
return shape;
}
Make the Shape Fall
function setupFallingShape() {
game.physics.enable(fallingShape, Phaser.ARCADE);
fallingShape.body.velocity.set(0, 70);
fallingShape.checkWorldBounds = true;
fallingShape.events.onOutOfBounds.add(function(shape){
shape.kill();
score--;
updateScore();
fallingShape = createRandomShape();
setupFallingShape(fallingShape);
}, this);
}
Keyboard Events
function handleKeyUp(e) {
if (e.keyCode == Phaser.Keyboard.UP ||
e.keyCode == Phaser.Keyboard.DOWN ||
e.keyCode == Phaser.Keyboard.LEFT ||
e.keyCode == Phaser.Keyboard.RIGHT) {
let correct = false;
if (e.keyCode == Phaser.Keyboard.LEFT) {
if (color === 0xFF0000 && isCircle) {
correct = true;
}
} else if (e.keyCode == Phaser.Keyboard.RIGHT) {
if (color === 0x000000 && isCircle) {
correct = true;
}
} else if (e.keyCode == Phaser.Keyboard.UP) {
if (color === 0xFF0000 && !isCircle) {
correct = true;
}
} else if (e.keyCode == Phaser.Keyboard.DOWN) {
if (color === 0x000000 && !isCircle) {
correct = true;
}
}
if (correct) {
fallingShape.kill();
fallingShape = createRandomShape();
setupFallingShape(fallingShape);
score++;
} else {
score--;
}
updateScore();
}
}