Who is this for?
This intro is for anyone who wants a free tool for making web-based HTML5 games. I am assuming you have a basic familiarity with ES6 JavaScript, but any decent JavaScript knowledge should be good enough.
You can view the finished game we build in this tutorial here, and download the full source here.
What is Phaser?
Phaser is a JavaScript-based framework for building HTML5 web games. It was written by @PhotonStorm and has been in active development since 2013 (well before most of us even thought about switching from Flash to HTML5).
It's also one of my personal favorite game development frameworks. There are tutorials and examples for damn near anything you want to do, an their community is always active and helpful.
Why I prefer Phaser over other game frameworks
I think it mostly comes down to two things. I have a history with Flash and ActionScript that's shared by Phaser, and I like having as much control as possible in how I build things. I really loved the Starling framework fro ActionScript 3 games, and Phaser has a very similar workflow.
I've never been a big fan of programming game objects with drag and drop widgets or clicking baked in behaviors. I'm a bit of a control freak. Tools like Unity and Construct WILL let you code things manually, but if I'm going to be doing everything that way anyhow, I prefer to work lean.
With Phaser, you are working in pure JavaScript (or TypeScript if you prefer), so you don't have to worry about what kind of bloat some outside IDE is going to add. A simple game, like Flappy Bird, would be much smaller in size and memory use, while probably running better on lower-end hardware than the exact same game built with Unity's WebGL export.
There are drawbacks to choosing Phaser. It's a pure code library, not an IDE. You need 3rd party tools for art, animation and level design. Of course, that's not always a drawback. Because Phaser is just code, it can't crash and cause you to lose hours of work. Each tool you use may crash for whatever reason, but it will only cause you to lose maybe a single drawing or one level layout at most. It keeps you from having all your eggs in one basket.
The biggest reason I prefer Phaser is it has, hands down, one of the fastest rendering engines I've seen for web-based HTML5 games. I've had some pretty ambitious demos working at 60fps on my old low-end laptop without even using WebGL.
Getting Started
Before we get too far ahead of ourselves, there's a few things you should know before you get started. Because we are targeting web browsers with our Phaser games, we will need to run a web server.
Most modern browsers have built-in security measures that prevent local files from doing things that could be considered risky. Trying to run your game by loading it directly will almost certainly fail.
I'm a bit fan of MiniWeb since it doesn't require any real setup or server admin skills, but you could use any web server you prefer.
If you DO use MiniWeb, you need to save it to whatever directory your game will be in; let's say C:\PhaserTutorial. Next you'll need to add an 'htdocs' directory so the server knows where to find your files.
In the htdocs folder, you can add a file called "index.html". In this file, just write "Hello World" or something.
If everything is set up right, you should be able to run miniweb.exe and it will have a line saying "host" (it'll look something like 123.45.678.910:8000). Type that exact host address in your browser, and you should see your "Hello World" text.
We are also going to need to add directories for our assets (sound, art, etc) and JavaScripts. Let's go ahead and add 'lib' (asset library) and 'js' (JavaScripts) directories inside our 'htdocs' folder.
To review, you should currently have a file setup something like this:
C:\PhaserTutorial C:\PhaserTutorial\miniweb.exe C:\PhaserTutorial\htdocs C:\PhaserTutorial\htdocs\index.html C:\PhaserTutorial\htdocs\lib C:\PhaserTutorial\htdocs\js
Now that we have a working server, and our directories are all set up, we can install Phaser. At the time this was written, you could grab the latest version of Phaser 3 here. For this tutorial, we'll be using the 'js' version. When you build a game and are ready to release it, you'll probably want to use the min.js version to get a smaller download size.
Make sure your file is saved to C:\PhaserTutorial\js\phaser.js (or wherever your 'js' directory happens to be).
Now we need to update that 'index.html' file we made earlier so it's a proper HTML5 file, and loads the Phaser library. Replace your "Hello World" text with the following:
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>My Phaser Game</title> <meta name="description" content="This is a Phaser Game!"> <meta name="author" content="Your Name Here"> <style> body { background-color: black; margin: 0px; padding: 0px; } </style> </head> <body> <!-- This is where we load our scripts --> <script src="js/phaser.js"></script> </body> </html>
This is a simple HTML5 template. The <head> tag contains our title and some other meta info, along with a style tag that makes the background black, and gets rid of margins. Newgrounds runs HTML5 games in an Iframe, so we don't want any gutters making our game look goofy.
We are now ready to start building a game! (If you test your app in the browser now, you should just see a black screen)
Organizing Your Project
Note: There's no real right or wrong way to set up your Phaser project. If you've done other tutorials, you'll have seen examples where you create a Phaser instance and use functions to handle everything. I prefer to put most of my logic in Phaser scenes, and use some utility scripts to pull them all together.
This will add another layer of learning that most introductory tutorials don't add on, but I think learning how to organize your project is just as important as actually coding it.
Phaser uses scenes and Sprites for most of it's magic. These are classes that have baked in events like 'preload' and 'update' that come in very handy (we'll get into all that soon).
Before Phaser can use any scenes, you have to tell it what scene objects are available. This is done in the config object Phaser uses when it is instantiated.
And before any of your scene files can even be used, we have to load their js files. Wouldn't it be nice if we could just handle keeping track of these files in one place?
Let's create a new file in our 'js' folder named 'main.js' (C:\PhaserTutorial\js\main.js), and add the following code:
var MainConfig = (()=>{ // loose reference let config = this; // path to our scene scripts let scene_path = "js/scenes/"; // class/file names of all our scenes config.scene_names = [ 'InitScene', 'PreloaderScene', 'GameScene' ]; // This will be called when all our scene files have loaded and we are ready to start the Phaser game. function startGame() { config.game = new Phaser.Game({ width: 860, height: 640, type: Phaser.AUTO, // Render mode, could also be Phaser.CANVAS or Phaser.WEBGL scene: config.scene_classes // the code below will set this for us }); } //---------- You shouldn't need to edit anything below here ----------\\ // this will store references to our scene classes as they are loaded. config.scene_classes = []; // get the body tag in the HTML document let body = document.getElementsByTagName('body')[0]; // keep track of number of loaded scenes let scenes_loaded = 0; // Loads a scene file by adding a script tag for it in our page HTML function loadScene(scene_name) { let script_tag = document.createElement('script'); script_tag.src = scene_path + scene_name + ".js"; // wait for the scene file to load, then check if we have loaded them all script_tag.addEventListener('load', ()=>{ scenes_loaded++; eval("config.scene_classes.push("+scene_name+")"); // looks like we can start the game! if (scenes_loaded === config.scene_names.length) startGame(); }); body.appendChild(script_tag); } // start loading all of our scene files config.scene_names.forEach((scene_name)=> { loadScene(scene_name); }); return this; })();
Next, edit your 'index.html' file so it knows to load your main.js file.
<!-- This is where we load our scripts --> <script src="js/phaser.js"></script> <script src="js/main.js"></script>
This script gives you a handy array of any scenes your game will use. In this example, we'll be adding scenes called 'InitScene', 'PreloaderScene' and 'GameScene'. All of these scenes will be extensions of the Phaser.scene class.
InitScene is a very basic scene that preloads the absolute minimum assets we will need to render our proper preloader.
PreloadScene is where we will load everything else and render a proper preloader. It's also where we'll make sure sound is enabled in our game.
GameScene will be the class that controls our actual game. In a real game, you might have a TitleScene and OptionsScene, and multiple variations of GameScene as well, but we won't be doing anything that polished in this guide.
As you may have noticed in the 'main.js' file, we will be storing our scenes in "js/scenes/". You will need to add the 'scenes' directory in your js folder, then create a file for each scene, using the exact scene name as it's filename, like so:
C:\PhaserTutorial\htdocs\js\scenes\InitScene.js C:\PhaserTutorial\htdocs\js\scenes\PreloaderScene.js C:\PhaserTutorial\htdocs\js\scenes\GameScene.js
Add the following code to your files:
InitScene.js
class InitScene extends Phaser.Scene { init() { console.log('InitScene Started'); } constructor () { // Sets the string name we can use to access this scene from other scenes super({key:'InitScene'}); } preload() { // This is where you would preload the elements you need to render the preloader scene } create() { console.log('InitScene Created'); // Once the preload phase is done, we can switch to our preloader scene this.scene.start('PreloaderScene'); } }
PreloaderScene.js
class PreloaderScene extends Phaser.Scene { constructor () { // Sets the string name we can use to access this scene from other scenes super({key:'PreloaderScene'}); } init() { console.log('PreloaderScene Started'); } preload() { // each of these events can update your preloader! this.load.on('progress', function (progress) { console.log("total progress: " + (progress * 100) + "%"); }); this.load.on('fileprogress', function (file) { console.log("loading asset: " + file.src); console.log("file progress: " + (file.percentComplete * 100) + "%"); }); this.load.on('complete', function () { console.log("everything is loaded!"); }); // Load your assets here } create() { console.log('PreloaderScene Created'); // When our bulk assets have been preloaded, we can start our main game scene this.scene.start('GameScene'); } }
GameScene.js
class GameScene extends Phaser.Scene { constructor () { // Sets the string name we can use to access this scene from other scenes super({key:'GameScene'}); } init() { console.log('GameScene Started'); } create() { console.log('GameScene Created'); // You could set a background color here... this.cameras.main.setBackgroundColor(0x00AAFF); // add whatever else you need to the scene here } update() { // This is your main game loop. Code in here runs on every frame tick. } }
If you test your project now, you should see an 860x640 blue box. It's not fancy, but you officially have a working Phaser app.
Okay, but how does this all actually work?
Let's look at our new scripts.
main.js
This file is kind of our staging area for our Phaser game. It loads all of our scene files so we only have to update the config.scene_name array whenever we add new scenes.
This file is also responsible for instantiating our Phaser game. In the new Phaser.Game(...) call, we pass any configuration options our game needs.
We are making our game 860px x 640px, and letting Phaser automatically determine the best rendering mode to use on the user's end machine.
When the Phaser game starts, the first scene in the config.scene_name array is started by default. In this case, InitScene.
The way this script is set up, it creates a global MainConfig object. You could access the main Phaser.Game object from anywhere by using MainConfig.game.
This file is a handy place to put any other data you would like to be globally available.
InitScene.js
This is our first Phaser scene, and it is a proper ES6 subclass of Phaser.Scene (as are all of our scene classes).
The first thing to note is that we call the parent class constructor to pass along a key. This is a string that Phaser uses to label each scene internally, and you should have this in all of your scene classes. Without it, you won't be able to switch between scenes, and your game will likely crash.
The 'init' method is the first thing that's called when this scene is started. It's a good place to initialize any data you may need later in the class.
The 'preload' method gets called next. In this method, you can tell the scene what assets to load before the scene is actually created. In this scene we would load any artwork we want our PreloaderScene to use.
The 'create' method gets called when everything has been loaded and the scene is ready for action. Because this scene won't be doing anything after our preloader art is loaded, it simply tells Phaser to go to the next scene...
So, let's go ahead and tell it to load our preloader images. You can grab them here:
Save these files to your 'lib' folder (C:\PhaserTutorial\htdocs\lib\).
Update InitScene's 'preload' function:
preload() { // Load the images for our preloader this.load.image('preloader_background', 'lib/ng-simple-preloader-bg.png'); this.load.image('preloader_button', 'lib/ng-simple-play-btn.png'); }
Once these are loaded, we can grab them using the 'preloader_background' and 'preloader_button' labels we have set.
And now we're done with this file!
PreloaderScene.js
This is the scene where we'll load the rest of our game assets and show the user a nicer preloader. As you can see, this scene has all the same methods InitScene.js had, but does a few more things within the 'preload' method.
During the preload phase of a scene, the load object (we'll get into that more later) can fire events letting us know what it is currently loading, and how far along we are.
The 'progress' event will give is a number between 0 and 1, indicating how far along the overall preload phase has gotten.
The 'fileprogress' event will update us on how far each individual file has gotten along, returning one of Phaser's file objects. This object has a bunch of properties, but you'll be most interested in file.src (the filename) and file.percentComplete (a number between 0 and 1 indicating how much has downloaded).
The 'complete' event will fire when all your files have been loaded, but before the 'create' method is called.
We'll use these events later to make a nice preloader.
First, we need a large enough file to actually see our preloader. Go ahead and download this familiar song:
Save the song to your 'lib' folder (C:\PhaserTutorial\htdocs\lib\306544_Four_Brave_Champions__FULL.mp3).
Update the preload function in PreloaderScene.js to:
preload() { // set our background color this.cameras.main.setBackgroundColor(0x42484E); // add our preloader image and center it on the camera this.bg_image = this.add.image(this.cameras.main.centerX, this.cameras.main.centerY, 'preloader_background'); // note where our dynamic/interactive stuff should go this.interact_point = {x: this.bg_image.x, y: this.bg_image.y + (this.bg_image.height/2) - 42}; let loadbar_width = 224; let loadbar_height = 8; let loadbar_pad = 4; let loadbar_x = this.interact_point.x - loadbar_width/2; let loadbar_y = this.interact_point.y - loadbar_height/2; // create a bg for the loading bar (doing this dynamically saves us having to add more image files!) let loadbar_bg = this.add.graphics(); // color and alpha loadbar_bg.fillStyle(0x525961, 1); // position and dimensions (not position here does NOT set origin point, thats why we're using 0,0) loadbar_bg.fillRect(0, 0, loadbar_width + loadbar_pad*2, loadbar_height + loadbar_pad*2); // move the bar where we want it loadbar_bg.x = loadbar_x - loadbar_pad; loadbar_bg.y = loadbar_y - loadbar_pad; // do the same for the loading bar itself let loadbar_bar = this.add.graphics(); loadbar_bar.fillStyle(0xD2D8E3, 1); loadbar_bar.fillRect(0, 0, loadbar_width, loadbar_height); loadbar_bar.x = loadbar_x; loadbar_bar.y = loadbar_y; // start the bar at 0% width loadbar_bar.scaleX = 0; // update the progress bar width with each progress event this.load.on('progress', function (progress) { loadbar_bar.scaleX = progress; }); // remove the progress bar when everything has loaded this.load.on('complete', function () { loadbar_bar.destroy(); loadbar_bg.destroy(); }); // Load your assets here this.load.audio('castle_crashers_theme', 'lib/306544_Four_Brave_Champions__FULL.mp3'); }
Now when you test the game, you'll see our preloader just before GameScene is started.
Now we can make our game play the Castle Crashers theme!
GameScene.js
This part is simple. Just update the 'create' method to:
create() { // add the Castle Crasher theme to the scene this.cc_theme = this.sound.add('castle_crashers_theme'); // and play it on a loop this.cc_theme.play({loop: true}); // Lets change the background again too so you can see something happened this.cameras.main.setBackgroundColor(0x00AAFF); }
Now when you test your game, you'll see the preloader, then our blue screen and hear the Castle Crashers theme! Or wait, will you?
Most modern browsers actually won't let your game play sound without some sort of user interaction. So it would appear we need to make the user click on our game before we need to play any sounds. The easiest place to do that is back in our preloader.
So, let's edit PreloaderScene.js again. This time, replace the 'create' method so adds a play button instead of auto-starting the GameScene.
create() { let play_btn = this.add.image(this.interact_point.x, this.interact_point.y, 'preloader_button'); // make the button interactive play_btn.setInteractive(); // add some alpha for the default state play_btn.alpha = 0.9; // remove alpha on hover play_btn.on('pointerover', ()=>{ play_btn.alpha = 1; }); // add alpha on roll out play_btn.on('pointerout', ()=>{ play_btn.alpha = 0.9; }); // start the GameScene when the button is clicked. Bonus - this will enable audo playback as well! play_btn.on('pointerup', ()=>{ this.scene.start('GameScene'); }); }
Now when you test your game, you should see the preloader, have to click 'PLAY', and THEN you'll hear the music!
This is all great, but when do we actually make a game?
I realize we've done a lot of stuff here and haven't really even made anything resembling a game now, but there's a pretty good reason for that.
First of all, you now know how to create and switch scenes. You should be able to import images, sound files and add them to your screen. You should be able to make a basic button, and loop music. That's a lot of stuff you'll be needing to make any sort of game!
Second, you know how to make a pretty preloader so your users won't think your game is broken because it's a just blank screen for several minutes.
Third, you could save what we have done so far and use it as a boiler plate for any future projects and not have to build a preloader or scene manager from scratch ever again!
You're welcome.
Thanks, I Guess... But how about the game part?
Okay, you've made it this far, so I guess we can bang out super basic game.
Let's grab some more assets and put them in our 'lib' folder (C:\PhaserTutorial\htdocs\lib\).
Open up PreloaderScene.js again, and add these lines to the end of the preload function:
this.load.image('cat_head', 'lib/cat-head.png'); this.load.image('cheezeburger', 'lib/cheezeburger.png');
And finally, replace all the code in GameScene.js with this:
class GameScene extends Phaser.Scene { constructor () { super({key:'GameScene'}); } create() { // add the Castle Crasher theme to the scene this.cc_theme = this.sound.add('castle_crashers_theme'); // and play it on a loop this.cc_theme.play({loop: true}); // Lets change the background again too so you can see something happened this.cameras.main.setBackgroundColor(0x00AAFF); // add the player sprite this.player = this.add.image(this.cameras.main.centerX, this.cameras.main.centerY, 'cat_head'); // add the cheeseburger sprite somewhere off-screen this.cheezeburger = this.add.image(-9999, 0, 'cheezeburger'); // set a cool time for cheezeburger respawn this.cheezeburger_cooldown = 80; // set the initial cool time for the cheezeburger this.cheezeburger_cooltime = this.cheezeburger_cooldown; // set how fast the player can move this.player_speed = 20; // handy container for our keyboard keys this.controls = { up: this.input.keyboard.addKey('UP'), // up arrow down: this.input.keyboard.addKey('DOWN'), // down arrow left: this.input.keyboard.addKey('LEFT'), // left arrow right: this.input.keyboard.addKey('RIGHT') // right arrow }; // this is our score counter this.score = 0; // and this is our score text this.scoretext = this.add.text(5, 5, "Score: "+this.score); this.scoretext.setStyle({ fontSize: "26px", fontFamily: "Arial Black" }); } update() { // up and down movement if (this.controls.up.isDown) { this.player.y -= this.player_speed; // screen wrap if (this.player.y < 0) this.player.y = this.cameras.main.height; } else if (this.controls.down.isDown) { this.player.y += this.player_speed; // screen wrap if (this.player.y > this.cameras.main.height) this.player.y = 0; } // up and down movement if (this.controls.left.isDown) { this.player.x -= this.player_speed; // screen wrap if (this.player.x < 0) this.player.x = this.cameras.main.width; } else if (this.controls.right.isDown) { this.player.x += this.player_speed; // screen wrap if (this.player.x > this.cameras.main.width) this.player.x = 0; } // check our cheezeburger cool time if (this.cheezeburger_cooltime > 0) { this.cheezeburger_cooltime--; if (this.cheezeburger_cooltime < 1) { this.spawnBurger(); } } // check for collision with cheezeburger // (note, we aren't using physics here, just checking distance with pythagorean theorem) let cheezeburger_distance = Math.sqrt( Math.pow(this.player.x - this.cheezeburger.x, 2) + Math.pow(this.player.y - this.cheezeburger.y, 2) ); if (cheezeburger_distance < 60) this.eatBurger(); } //------------------- custom methods -------------------\\ // handles "spawning" the burger spawnBurger() { // put the burger somweher random within camera bounds this.cheezeburger.x = Math.round(Math.random() * this.cameras.main.width); this.cheezeburger.y = Math.round(Math.random() * this.cameras.main.height); } // handles "eating" the burger eatBurger() { // update the score this.score++; // move the burger off-screen this.cheezeburger.x = -9999; // reset the cool time this.cheezeburger_cooltime = this.cheezeburger_cooldown; this.scoretext.setText("Score: "+this.score); } }
In this version of the file, we're adding all our assets to the scene in the 'create' function, and setting a bunch of instance values that other functions will have access to. You are also adding text for the first time here!
In our 'update' function, we're handling all the player movement, collision detection and burger spawn time.
At the end of the file, we've added some custom methods that get called by our game loop. We could have put this code right in the 'update' function, but I wanted to illustrate how you can expand the core abilities of the scene object and organize your code a bit more.
If you test the game now, you should be able to control the cat and have him haz cheezeburgers.
This is an extremely basic game, but you should have a pretty good grasp on how to start your own Phaser project by now.
As you dive a bit deeper, you'll earn how to use the physics models to handle proper collisions and things like that. You'll learn how to make sprite sheets (or better yet, texture atlases) so you aren't loading as many image files. You'll learn how to use Sprites and animate them, and so much more.
Be sure to check out all the great resources at http://phaser.io/learn and follow me on Newgrounds for any further Phaser stuff I may post about!
ninjamuffin99
gonna be digging through this later, god bless ya PG