This tutorial series has been updated for PixiJS v4.
Ever played endless runner games such as Canabalt and Monster Dash, and wondered how to build their scrolling game maps? In this tutorial we’ll take our first steps towards building a similar parallax scroller using JavaScript and the pixi.js 2D rendering engine.
What you will learn…
- The fundamentals of pixi.js
- How to work with textures and tiling sprites
- How to implement simple parallax scrolling
What you should know…
- A basic understanding of JavaScript or ActionScript
JavaScript is everywhere. Thanks to ever increasing browser maturity and the plethora of JavaScript libraries that are out there we’re really starting to see HTML5 games development flourish. But with so many libraries available, part of the challenge has become picking the right ones to work with.
This series of tutorials will introduce you to the basics of JavaScript games development and focus on pixi.js. Pixi.js is a new 2D rendering framework which supports both WebGL and HTML5 Canvas. By the end you will have built the following horizontal parallax scrolling game map:
Clicking on the image above will launch and run the final version of the scrolling map that you will be working towards. Notice that it contains three parallax layers: a far layer, a mid layer, and a foreground layer. In this first tutorial we’ll implement some basic parallax scrolling by concentrating on just the far and mid layers. And of course, to do that we’ll cover the basics of pixi.js. Also, if you’re new to JavaScript then you should find that this is a good place to start learning the basics of HTML5 games programming.
Before we proceed, click on the image above to see a running demonstration of what you’ll actually build during this tutorial. You can also download this tutorial’s source code from GitHub.
Getting Started
For development you’ll need a suitable code editor or IDE. I’ll be using Sublime Text 2, a trial version of which can be downloaded from www.sublimetext.com/2.
You’ll also need a web browser to test your work. Any modern browser should do but I’ll be using Google Chrome and covering some of its developer tools during the course of these tutorials. If you don’t have Chrome installed then get it from www.google.com/chrome.
To test your work, you’ll need to run a local web server on your development computer. Microsoft Windows users can set-up IIS: www.howtogeek.com/howto/windows-vista/how-to-install-iis-on-windows-vista, while Mac OS X users can configure and run the built-in Apache web server: http://macdevcenter.com/pub/a/mac/2001/12/07/apache.html. If you have OS X Mountain Lion installed then setting up your web server is a little less straightforward than it used to be. Check out this resource to get Apache up and running on Mountain Lion.
Once your web server is set up, create a new folder within its root and name it parallax-scroller
. If you’re using Windows then the path to your web server’s root folder will be C:\inetpub\parallax-scroller
. If you’re using OS X then the path to your personal web folder will be /Users/your_user_name/Sites
. Remember to replace your_user_name
with your actual username.
Finally, we’ll be working with a couple of graphics assets during the course of this tutorial. Rather than have you create your own, I’ve provided a zip file at www.yeahbutisitflash.com/pixi-parallax-scroller/tutorial-1/resources.zip. Download this file and extract it within your parallax-scroller
folder.
Here’s how your parallax-scroller
folder should now look on Windows:
and if you’re a Mac OS X user then your folder structure should look like this:
Now we’re ready to begin coding. Launch Sublime Text 2 or your own favourite code editor.
Setting up the Canvas
Every pixi.js project starts from an HTML file. From here we’ll create an HTML5 Canvas element and also include the pixi.js library. The canvas element represents the area on the HTML page where our scroller will be rendered.
Create a new file within Sublime Text 2 by selecting File | New File from the drop-down menu. Now before we start, select File | Save As… and save it within the root of your parallax-scroller
folder as index.html
.
Okay, let’s start with a minimal HTML page. Add the following to your index.html
file:
<html> <head> <meta charset="UTF-8"> <title>Parallax Scrolling Demo</title> </head> <body> </body> </html>
It’s all fairly straight forward at the moment. We have a basic HTML page with a <head>
and <body>
element.
Now let’s add our HTML5 Canvas element to the page. Simply add the following lines between the <body>
element and then save your file:
<body> <div align="center"> <canvas id="game-canvas" width="512" height="384"></canvas> </div> </body>
We’ve specified a canvas that’s 512 pixels wide and 384 pixels tall. It’s this region that the pixi.js library will render our game’s visuals into. Notice also that we assigned an ID to our canvas and named it game-canvas
. This will let us easily access this particular canvas element, which is required when initialising pixi.js.
Now go ahead and load your index.html
page into your web browser. If you’re using Windows then the URL to your HTML page will be http://localhost/parallax-scroller/index.html
while for Mac OS X enter http://localhost/~your_user_name/parallax-scroller/index.html
into your browser’s address bar.
If you go ahead and load index.html
into your web browser you’ll notice that you can’t actually see the canvas region. That’s because it’s currently the exact same colour as the page. Let’s rectify that with a style sheet, which will specify different background colours for the page and its canvas. Add the following lines within your file’s <head>
element:
<html> <head> <meta charset="UTF-8"> <title>Endless Runner Game Demo</title> <style> body { background-color: #000000; } canvas { background-color: #222222; } </style> </head> <body> </body> </html>
Save the current version of index.html
and refresh your browser. This time you should clearly see your canvas region: it will appear grey on top of a black page and will sit in the horizontal centre of the page.
Including the pixi.js JavaScript library
Now that our canvas is set up, let’s include the pixi.js library. Simply add the following line near the end of the page’s body:
<body> <div align="center"> <canvas id="game-canvas" width="512" height="384"></canvas> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.0.0/pixi.min.js"></script> </body>
Pixi.js is hosted on a CDN. Note that the URL includes 4.0.0
, which is the version of the library we’ll be using. You can replace this with any released version you wish to use in the future.
Save what we have and let’s check that everything runs as expected within Chrome. Chrome’s Developer Tools can come in handy here. Simply press F12 (Cmd + Opt + i on Mac) to open the Developer Tools window then click the Console tab to open its JavaScript Console window.
For example, if Chrome was unable to load the pixi.js library due to a mistyped path in your page then you’d see an error similar to this within the console window:
x GET file:///Users/ccaleb/Documents/javascript/tutorial/part1/parallax-scroller/pixi.js-master/bin/pixie.js index.html:14
Chrome’s developer tools are essential when trying to debug your own JavaScript or finding errors reported from pixi.js. You can even send text to it from within your own JavaScript code. Keep the console window open during development and if you have enough screen real estate then I suggest docking the Developer Tools window to the bottom of your browser window. You can do this by simply clicking on the Dock to main window icon at the bottom-left corner of the developer tools window.
Adding a Main Entry Point
When the contents of the HTML page have completely loaded, its <body>
element will dispatch an onload
event. At this point we can be sure that our canvas has been initialised and that the pixi.js library has been fully loaded. We can trigger JavaScript of our choosing in response to this event and start working directly with pixi.js. Let’s do this by calling a function named init()
when the onload
event is triggered:
<body onload="init();"> <div align="center"> <canvas id="game-canvas" width="512" height="384"></canvas> </div> <script src="pixi.js-master/bin/pixi.dev.js"></script> </body>
Now we need to write the actual init()
function. Place the function at the bottom of the <body>
element and within it simply output some text to the JavaScript console to satisfy yourself that the page’s onload
event has been successfully captured:
<body onload="init();"> <div align="center"> <canvas id="game-canvas" width="512" height="384"></canvas> </div> <script src="pixi.js-master/bin/pixi.dev.js"></script> <script> function init() { console.log("init() successfully called."); } </script> </body>
Save your index.html
page and refresh your browser. If all is well then you should see the following message written to your JavaScript Console window:
> init() successfully called.
While your init()
function does very little at the moment it will eventually be responsible for initialising your scroller and kicking it off. Essentially everything will be run through this main entry point.
Initialising pixi.js
Now that we have an init()
function to work from, let’s go ahead and initialise pixi.js. It’s a simple two step process:
- Create your stage
- Select and instantiate a renderer
First we’ll create a stage object and then a renderer. If you’re a Flash developer then you’ll be familiar with the concept of a stage. Basically the stage represents all your game’s graphical content. The renderer on the other hand takes the stage and draws it to your HTML page’s canvas so that you work is actually visible to the user.
Let’s create a stage instance and associated it with a global variable named stage
. Also, while you’re at it, remove the log statement we added previously:
function init() {
console.log("init() successfully called.");stage = new PIXI.Container(); }
The pixi.js API consists of several classes and functions which exist within the PIXI
module. The PIXI.Container
class is used to represent a collection of display objects and also represent the stage, which is the root display object.
Now that we’ve created a stage we need to select a renderer. Pixi.js supports two renderers: WebGL and HTML5 Canvas. You can instantiate a renderer with PIXI.WebGLRenderer
or the PIXI.CanvasRenderer
class respectively. However it’s better to let Pixi interrogate the browser and automatically detect the correct renderer on your behalf. By default Pixi will attempt to use WebGL and will fall back to its canvas render if WebGL isn’t available. Let’s go ahead and let Pixi select the appropriate renderer using its PIXI.autoDetectRenderer()
function:
function init() { stage = new PIXI.Container(); renderer = PIXI.autoDetectRenderer( 512, 384, {view:document.getElementById("game-canvas")} ); }
The autoDetectRenderer()
function expects the width and height of the canvas that your stage is to be rendered to, plus a reference to the canvas itself. It returns either an instance of PIXI.WebGLRenderer
or PIXI.CanvasRenderer
, which we’ve stored in a global variable named renderer
.
In our code above, the canvas reference passed to autoDetectRenderer()
is actually provided within a JavaScript object and is associated with a view
property. It’s important you pass this object as the function’s third argument rather than just a reference to the canvas itself.
Save your work.
var width = document.getElementById("game-canvas").width;
Rendering
In order to see the contents of your stage you’ll need to instruct your renderer to actually draw the stage’s content to the canvas. That’s done by calling your renderer’s render()
method and passing it a reference to your stage. Here’s a simple example using your code:
function init() { stage = new PIXI.Container(); renderer = PIXI.autoDetectRenderer( 512, 384, {view:document.getElementById("game-canvas")} ); renderer.render(stage); }
This will successfully render your stage to the browser. Of course, we haven’t added anything to the stage yet so there’s nothing to see.
Adding Items to the Display List
Now that your stage is set up let’s go ahead and actually add some items to it. After all, we don’t want to be staring at blank screen forever.
Items are added to a tree-like structure known as the display list. Your stage acts as the root of that display list meaning everything added to your stage is rendered. There’s also a stacking order, meaning that some items will appear in-front of others depending on their designated depth index.
There are several display object types that you can add to the display list. The most common is PIXI.Sprite
which is used to add an image.
Since this tutorial is all about creating a parallax scrolling background, let’s try adding an image that represents the farthest back layer. We’ll begin by adding a line of code to actually load the bg-far.png
file that I provided within the resources
folder:
function init() { stage = new PIXI.Container(); renderer = PIXI.autoDetectRenderer( 512, 384, {view:document.getElementById("game-canvas")} ); var farTexture = PIXI.Texture.fromImage("resources/bg-far.png"); renderer.render(stage); }
Images are loaded and stored as textures, which can then be attached to one or more sprites. In the line above we called the static PIXI.Texture.fromImage()
method to create a PIXI.Texture
instance and loaded our bg-far.png
file into it. We assigned our texture to a local variable named farTexture
for further use.
Now let’s create a sprite and attach our texture to it. While we’re at it we’ll position the sprite at the top-left of the stage:
function init() { stage = new PIXI.Container(); renderer = PIXI.autoDetectRenderer( 512, 384, {view:document.getElementById("game-canvas")} ); var farTexture = PIXI.Texture.fromImage("resources/bg-far.png"); far = new PIXI.Sprite(farTexture); far.position.x = 0; far.position.y = 0; renderer.render(stage); }
The PIXI.Sprite
class is used to create a sprite. Its constructor takes as its only parameter a reference to the texture that you wish the sprite to represent. We used a global variable named far
and stored our newly created sprite instance within it.
Also note how we used the position
property to set the sprite’s x and y coordinates to the top-left of the stage. The stage’s coordinates run from left-to-right and top-to-bottom, meaning your stage’s top-left position is at (0,0) and the bottom right is at (512,384).
Sprites have a pivot point, which they can be rotated around. The pivot point is also used when positioning (think of it as a handle) a sprite. A sprite’s default pivot point is set to its top-left corner. That’s why when positioning our sprite at the top-left corner of the stage, we set its position at (0,0).
The final step is to actually add the sprite to the stage. That’s done using the addChild()
method of the PIXI.Stage
class. Go ahead and do that:
var farTexture = PIXI.Texture.fromImage("resources/bg-far.png"); far = new PIXI.Sprite(farTexture); far.position.x = 0; far.position.y = 0; stage.addChild(far); renderer.render(stage); }
Okay, save your work and refresh your browser. You were probably expecting to see your background layer but you’ll likely find that it’s missing. So why is that? After all, we are making a call to the renderer’s render()
method immediately after adding the background to the display list. Well that’s actually the problem. We’re actually attempting to render our sprite before its texture has actually had time to fully load.
For the time being we can rectify this problem by simply waiting a short period of time then manually forcing our content to render. That should give the texture enough time to load. We’ll use Chrome’s JavaScript console to achieve this. Simply enter the following into the JavaScript console window:
renderer.render(stage);
renderer
and stage
variables, and also call methods belonging to them.Congratulations! You should now see your background layer sitting snuggly at the top of the screen.
Now let’s go ahead and add our mid layer:
var farTexture = PIXI.Texture.fromImage("resources/bg-far.png"); far = new PIXI.Sprite(farTexture); far.position.x = 0; far.position.y = 0; stage.addChild(far); var midTexture = PIXI.Texture.fromImage("resources/bg-mid.png"); mid = new PIXI.Sprite(midTexture); mid.position.x = 0; mid.position.y = 128; stage.addChild(mid); renderer.render(stage); }
Save your file and refresh your browser. To see both layers you’ll once again need to manually render your content. Type the following into the JavaScript console:
renderer.render(stage);
Because the mid layer was added to the stage second it gets placed at a higher depth than the background layer. Each call to addChild()
adds its display object directly above the previous display object.
Also notice that we positioned the mid layer’s sprite at a y-position of 128. Doing so forces the mid layer to sit lower on the stage than the farthest back layer.
Main Loop
Now that we have two background layers I suppose we could try implementing some parallax scrolling, and also find a way of rendering our content without manually doing it from the JavaScript console.
For the avoidance of doubt let’s quickly clarify what exactly parallax scrolling actually is. It’s a scrolling technique used in video games where background graphics layers are moved across the screen slower than foreground layers. Doing so creates an illusion of depth in 2D games and provides an added sense of immersion for the player.
Given this information we can apply it to our two sprite layers to produce a horizontal parallax scroller where we move our background layer across the screen slower than the mid layer. In order to scroll each layer we’re going to have to create a main loop where we can continually change the position of each. To achieve that we’ll employ the help of requestAnimationFrame()
, which is a JavaScript function that determines an optimal frame rate for your browser and then calls a specified function when your canvas/stage can next be redrawn. We’ll also use this main loop to continuously render our content.
Go ahead and do that by first making a call to requestAnimationFrame()
:
var midTexture = PIXI.Texture.fromImage("resources/bg-mid.png"); mid = new PIXI.Sprite(midTexture); mid.position.x = 0; mid.position.y = 128; stage.addChild(mid); renderer.render(stage); requestAnimationFrame(update); }
The line you’ve just added states that you want to call a function named update()
the next time your stage’s contents can be redrawn. If you make continuous successive calls to requestAnimationFrame()
then this will typically result in your update()
function being called 60 times every second or what’s more commonly known as 60 frames-per-second (FPS). The update()
function that we’ve specified will act as your main loop.
We don’t yet have an update()
function but before we write one, remove the following line of code since our main loop will soon take care of rendering:
var midTexture = PIXI.Texture.fromImage("resources/bg-mid.png"); mid = new PIXI.Sprite(midTexture); mid.position.x = 0; mid.position.y = 128; stage.addChild(mid);
renderer.render(stage);requestAnimationFrame(update); }
Okay let’s write our main loop and have it change the position of both layers slightly and also render the stage’s content so we can see the difference on each frame redraw. Add your update()
function directly after your init()
function:
function update() { far.position.x -= 0.128; mid.position.x -= 0.64; renderer.render(stage); requestAnimationFrame(update); } </script>
The first two lines within main()
update the x-position of our far and mid sprites. Notice that we move the far layer to the left by 0.128 pixels while we move the mid layer to the left by 0.64 pixels. To move something to the left we use a negative value while a positive value moves it to the right. Also notice that we moved our sprites by a fraction of a pixel. Pixi’s renderer can store and work with positions using sub-pixel values. This is ideal when you want to nudge things across the screen very slowly.
At the end of our loop we once again call the requestAnimationFrame()
function to ensure that update()
gets called when our canvas can next be drawn again. It’s this function that ensures our main loop is continuously called, which in turn ensures that our parallax layers are steadily moved across the screen.
Save your work and refresh your browser to see how things are looking. You should see both layers being automatically rendered to the screen now. Also, while both layers are moving, the mid layer will actually be moving faster than the far layer, giving a sense of depth to your scene. However you should also spot a glaring issue with our current implementation: as each sprite moves out of the left-hand side of the screen it leaves a gap to the right. In other words, the graphics for both layers don’t wrap around to give the illusion of a continuous scrolling environment. Thankfully there’s a solution.
Using a TilingSprite
So far we’ve used the PIXI.Sprite
class to represent objects within our display list. However pixi.js actually provides several other display objects to suit different needs.
If you take a look at bg-far.png
and bg-mid.png
then you should notice that both images are designed to repeat horizontally. Examine the left and right edge of each image. You should notice that the far-right edge perfectly leads back onto the far-left edge. In other words, both images are designed to wrap around.
So rather than physically moving the position of our far and mid sprites, wouldn’t it be great if there was a way to simply shift each sprite’s texture to give the illusion that they were moving? Thankfully pixi.js provides the PIXI.extras.TilingSprite
class, which does just that.
So let’s take advantage of tiling sprites by making some adjustments to our code. We’ll focus on the far layer first. Go ahead and remove the following line from your setup()
function:
var farTexture = PIXI.Texture.fromImage("resources/bg-far.png");
far = new PIXI.Sprite(farTexture);far.position.x = 0; far.position.y = 0; stage.addChild(far); }
and replace it with this:
var farTexture = PIXI.Texture.fromImage("resources/bg-far.png"); far = new PIXI.extras.TilingSprite(farTexture, 512, 256); far.position.x = 0; far.position.y = 0; stage.addChild(far); }
Staying within your setup()
function, insert and set the following two properties:
var farTexture = PIXI.Texture.fromImage("resources/bg-far.png"); far = new PIXI.extras.TilingSprite(farTexture, 512, 256); far.position.x = 0; far.position.y = 0; far.tilePosition.x = 0; far.tilePosition.y = 0; stage.addChild(far); }
Before we continue let’s discuss the TilingSprite
class’ constructor and its tilePosition
property.
You’ll immediately notice that the TilingSprite
class’ constructor expects 3 parameters compared to the Sprite
class’ single parameter:
far = new PIXI.extras.TilingSprite(farTexture, 512, 256);
Its first parameter is the same as before: a reference to the texture you wish to use. The second and third parameters expect the width and height of the tiling sprite respectively. Typically you’ll set these two parameters to the width and height of your texture, which in the case of bg-far.png
is 512 x 256 pixels.
far = new PIXI.extras.TilingSprite( farTexture, farTexture.baseTexture.width, farTexture.baseTexture.height );
We also took advantage of the tiling sprite’s tilePosition
property, which is used to offset the position of the sprite’s texture. In other words, by adjusting the offset you can shift the texture horizontally and/or vertically and have the texture wrap around to boot. Essentially you can simulate scrolling without physically changing the position of the sprite.
We defaulted the sprite’s tilePosition
property to (0,0) meaning that there’s initially no visible change in the far layer’s appearance:
far.tilePosition.x = 0; far.tilePosition.y = 0;
Now all that’s left to do is to simulate scrolling by continuously updating the horizontal offset of the sprite’s tilePosition
property. To do that we’ll make a change to your update()
function. Start by removing the following line:
function update() {
far.position.x -= 0.128;mid.position.x -= 0.64; renderer.render(stage); requestAnimationFrame(update); }
and replace it with the following:
function update() { far.tilePosition.x -= 0.128; mid.position.x -= 0.64; renderer.render(stage); requestAnimationFrame(update); }
Now save index.html
and once again refresh your browser. You’ll see that the far layer now seamlessly scrolls and repeats just as expected.
Okay, let’s go ahead and make the same changes for the mid layer. Here’s how your init()
function should look after you’ve made the changes:
function init() { stage = new PIXI.Container(); renderer = PIXI.autoDetectRenderer( 512, 384, {view:document.getElementById("game-canvas")} ); var farTexture = PIXI.Texture.fromImage("resources/bg-far.png"); far = new PIXI.extras.TilingSprite(farTexture, 512, 256); far.position.x = 0; far.position.y = 0; far.tilePosition.x = 0; far.tilePosition.y = 0; stage.addChild(far); var midTexture = PIXI.Texture.fromImage("resources/bg-mid.png"); mid = new PIXI.extras.TilingSprite(midTexture, 512, 256); mid.position.x = 0; mid.position.y = 128; mid.tilePosition.x = 0; mid.tilePosition.y = 0; stage.addChild(mid); requestAnimationFrame(update); }
Now go ahead and make the following change to your update()
function:
function update() { far.tilePosition.x -= 0.128; mid.tilePosition.x -= 0.64; renderer.render(stage); requestAnimationFrame(update); }
Save and test your code. This time you should see both layers perfectly scrolling while wrapping around both the screen’s left and right boundaries.
Conclusion
We’ve covered some of the basics of pixi.js and seen how PIXI.extras.TilingSprite
can be used to create layers that scroll infinitely. We’ve also seen how to use addChild()
to stack tiling sprites together to produce convincing parallax scrolling.
I encourage you to keep experimenting with Pixi and look at the documentation and code samples that come with it. Both are available on the official PixiJS website.
Next Time
While we have a horizontal parallax scroller up and running, it’s still a little unsophisticated. Next time we’ll introduce the concept of a viewport and world positioning, both of which are important if you want to eventually add your scroller into a game. It will also put us in a good position for adding the foreground layer, which will represent a simple platform game map.
Some valuable time will also be spent re-factoring our existing codebase. We’ll adopt a more object-oriented architecture and rid ourselves of our current dependency on global variables. By the end of the next tutorial all your scrolling functionality will be neatly contained within its own classes.
I hope you found this introduction tutorial useful and I’ll hopefully see you next time for part two.
Hi. I suspect you aren’t running the code from a local web server. There are details in the “Getting Started” section showing you how to setup and run a web server on your development computer. Once it’s setup, place your code in the web server’s root folder and then direct your browser to localhost. Hope that helps.
Hi there, I’m still unable to get the background images to show up and I thought it had to do with the fact that I wasn’t linking the images correctly (replaced the link to this:parallax-scroller/resources/bg-far.png). Any idea what I’m doing wrong?
Hi. To check that the images are being loaded you can do the following from Google Chrome: 1. Open the Developer Tools (alt-cmd-i). 2. Then from the Developer Tools panel, click on the Network tab. 3. Now when you refresh your page you will see the network requests being made by your code. This will also highlight any images that are failing to load.
I hope that was of some help.