I’ve done a number of these walk through type tutorials using many different languages/libraries and there is one common traffic trend. People LOVE reading about graphics. In every single example the post I do about graphics always seems to draw the most traffic. I guess we just love drawing stuff on screen. Now for the good part, Phaser is good at it and makes it really all quite easy.
Loading and adding a sprite
Back in the previous post I actually jumped the gun a bit and showed preloading and rendering sprites. Since so many people jump ahead straight to the graphics post, I’ll review the process.
/// <reference path="phaser.d.ts"/> class SimpleGame { game: Phaser.Game; titleScreenImage: Phaser.Sprite; constructor() { this.game = new Phaser.Game(800, 600, Phaser.AUTO, 'content', { create: this.create, preload: this.preload }); } preload() { this.game.load.image("title", "TitleScreen.png"); } create() { this.titleScreenImage = this.game.add.sprite(0, 0, "title"); } } window.onload = () => { var game = new SimpleGame(); };
The key concepts to be aware of here is preloading assets using game.load methods, where you pass in the filename as well as a unique string key that you will use to access the asset. Then in create you can see this in action, where we add a sprite to the game using game.add.sprite, using the key “title” to access it. In this case our “sprite” was a full screen image. Now let’s look at how you can work with a sprite, this time using a slightly smaller image.
Working with sprites
For this section I am going to work with this sprite ( created in this tutorial series ):
Add it to your project’s root directory. In my case I’ve called it jet.png. Using the above code, simply replace “TitleScreen.png” with “jet.png” and “title” with “jet” and you should see:
As you can see, our sprite is drawn at the top left corner of the screen. That is because the value (0,0) in Phaser refers to the top left corner of the screen by default. Let’s instead center our sprite using the following code:
/// <reference path="phaser.d.ts"/> class SimpleGame { game: Phaser.Game; jetSprite: Phaser.Sprite; constructor() { this.game = new Phaser.Game(800, 600, Phaser.AUTO, 'content', { create: this.create, preload: this.preload }); } preload() { var loader = this.game.load.image("jet", "jet.png"); } create() { var image = <Phaser.Image>this.game.cache.getImage("jet"); this.jetSprite = this.game.add.sprite( this.game.width / 2 - image.width / 2, this.game.height / 2 - image.height / 2, "jet"); } } window.onload = () => { var game = new SimpleGame(); };
Run this code and:
We are now nicely centered, both to the window and sprite.
We have one major challenge with centering the image. Until the sprite is created, it doesn’t have a width or height. However, when you create the sprite you can set it’s position. Of course it would be possible to create then move the sprite but that is hackish and could have unintended graphical problems. Instead we can get the image we loaded using this.game.cache.getImage() then access the images dimensions. One line of code might stand out for you here:
var image = <Phaser.Image>this.game.cache.getImage("jet");
This is TypeScript’s way of typecasting. If you’ve worked in Java, C# or C++ you’ve no doubt encountered typescasting. If your experience was mostly in JavaScript ( a mostly typeless language ), this might be new to you. Basically what you are saying is “we promise the value returned by getImage() is of the type <Phaser.Image>, so make image a Phaser.Image”. If you try to access a value or method in image that doesn’t exist in Phaser.Image, TypeScript will give you an error.
Positioning items in Phaser
When using a sprite, by default, transformations happen relative to the top left corner of the sprite. This is why we had to subtract half to the width and height of the sprite when positioning it in the center of the screen. Otherwise the top left corner of the sprite would be centered to the screen like this:
Sometimes however you would rather transform the sprite relative to a different point, commonly the very middle or occasionally the bottom left corner. Fortunately there is an option for this, the anchor. The anchor tells Phaser where to draw your Sprite relative to. Here we set the anchor to the center of the sprite then draw it at (0,0) like so:
create() { var image = <Phaser.Image>this.game.cache.getImage("jet"); this.jetSprite = this.game.add.sprite( this.game.width / 2 - image.width / 2, this.game.height / 2 - image.height / 2, "jet"); this.jetSprite.anchor.set(0.5,0.0) this.jetSprite.position.x = this.jetSprite.position.y = 0.0; }
And the result:
As you can see, draw calls for the sprite now position relative to it’s center. Positioning sprites relative to their center is incredibly handy when it comes to rotation, while anchoring at the bottom is useful for platformers where you are aligning the sprite’s feet to the ground. What you chose is entirely up to you. The values passed in to anchor might be a bit confusing, as they are normalized, meaning they go from 0 to 1. The values are all relative to the sprite itself, while (0,0) is the top left corner of the sprite, while (1,1) is the bottom right corner. (1,0) would be the bottom left, while (0,1) would be the top right.
There is one important thing to be aware of here. Anchor works relative to the source image, not the sprite itself. Therefore if you intend to scale your sprites, instead of using anchor, you are going to want to use pivot instead. (Until recently pivot was broken, but it appears to work now). Pivot sets the center point of the sprite, not the image that composes the sprite. Setting the pivot looks like this:
this.jetSprite.pivot.x = this.jetSprite.width / 2; this.jetSprite.pivot.y = this.jetSprite.height / 2;
Again, you don’t have to set the anchor at all, but it can be useful. Unlike anchor, pivot uses relative pixel coordinates within the sprite itself. Therefore the mid-point is at (width/2,height/2). Once again, (0,0) is the top left corner.
Simple Graphics
Sometimes you just want to draw primitive graphics on screen… lines, circles, boxes, that kind of stuff. Fortunately Phaser has that built in as well in the form of the Graphics object.
/// <reference path="phaser.d.ts"/> class SimpleGame { game: Phaser.Game; jetSprite: Phaser.Sprite; constructor() { this.game = new Phaser.Game(800, 600, Phaser.AUTO, 'content', { create: this.create, preload: this.preload }); } preload() { var loader = this.game.load.image("jet", "jet.png"); } create() { // Add a graphics object to our game var graphics = this.game.add.graphics(0, 0); // Create an array to hold the points that make up our triangle var points: Phaser.Point[] = []; // Add 4 Point objects to it points.push(new Phaser.Point()); points.push(new Phaser.Point()); points.push(new Phaser.Point()); // Position one top left, top right and botto mmiddle points[0].x = 0; points[0].y = 0; points[1].x = this.game.width; points[1].y = 0; points[2].x = this.game.width/2; points[2].y = this.game.height; // set fill color to red in HEX form. The following is equal to 256 red, 0 green and 0 blue. // Do at 50 % alpha, meaning half transparent graphics.beginFill(0xff0000, 0.5); // Finally draw the triangle, false indicates not to cull ( remove unseen values ) graphics.drawTriangle(points, false); // Now change colour to green and 100% opacity/alpha graphics.beginFill(0x00ff00, 1.0); // Draw circle about screen's center, with 200 pixels radius graphics.drawCircle(this.game.width / 2, this.game.height / 2, 200); } } window.onload = () => { var game = new SimpleGame(); };
The code is pretty heavily commented so should be self explanatory. When you run it you should see:
A look behind the scenes
Let’s take a quick look at how graphics drawing works in Phaser. That involves going back to this line:
this.game = new Phaser.Game(800, 600, Phaser.AUTO, 'content', {});
Here you are passing in a lot of important information. First (and second) are the resolution of your game. Next is the type of Renderer that Phaser should use. We mentioned this briefly in the prior tutorial. You have the option of WEBGL or Canvas rendering ( or headless, which means no rendering at all and is used for server side programming ). Which you chose depends heavily on the device you are supporting. For example, currently no iOS devices support WebGL and only the most recent version of Internet Explorer work. By selecting AUTO you let Phaser decide based on the device you are running on. Finally ‘content’ is the HTML ID of the DIV to render our game in.
You may notice scattered throughout Phaser’s code/documentation are references to PIXI. Pixi.js is a popular WebGL 2D renderer that is able to fallback on Canvas rendering when WebGL is unavailable. Pixi is the renderer that Phaser uses, so you will occasionally see Pixi classes inside Phaser code.
There is one final thing to cover about graphics before moving on, full screen and handling multiple resolutions.
Going Full Screen
Now let’s take a look at an application that can go full screen:
/// <reference path="phaser.d.ts"/> class SimpleGame { game: Phaser.Game; jetSprite: Phaser.Sprite; constructor() { this.game = new Phaser.Game(640, 480, Phaser.AUTO, 'content', { create: this.create, preload: this.preload }); } preload() { var loader = this.game.load.image("jet", "jet.png"); } // This function is called when a full screen request comes in onGoFullScreen() { // tell Phaser how you want it to handle scaling when you go full screen this.game.scale.fullScreenScaleMode = Phaser.ScaleManager.EXACT_FIT; // and this causes it to actually do it this.game.scale.refresh(); } goFullScreen() { } create() { var image = <Phaser.Image>this.game.cache.getImage("jet"); // Draw the jet image centered to the screen this.jetSprite = this.game.add.sprite( this.game.width / 2 - image.width / 2, this.game.height / 2 - image.height / 2, "jet"); // Set background to white to make effect clearer this.game.stage.backgroundColor = 0xffffff; // Add a function that will get called when the game goes fullscreen this.game.scale.enterFullScreen.add(SimpleGame.prototype.onGoFullScreen, this); // Now add a function that will get called when user taps screen. // Function declared inline using arrow (=>) function expression // Simply calls startFullScreen(). True specifies you want anti aliasing. // Unfortunately you can only make full screen requests in desktop browsers in event handlers this.game.input.onTap.add( () => { this.game.scale.startFullScreen(true); }, this); } } window.onload = () => { var game = new SimpleGame(); };
The comments cover most of what’s going on, but I thought I would touch on a couple things in the above example. First you cant simply request to go fullScreen in Desktop browsers for security reasons. This means your game can’t simply start in full screen, you need to make the call to startFullScreen() inside an event handler. Most commonly this will be in the form of a “Click here for FullScreen” button or link.
Next is the ()=> syntax, known in TypeScript as an arrow function expression (if you’ve used C#, this syntax is going to look awfully familiar to you!) and is something that should be coming in the next JavaScript version (ECMAScript 6). It is simply a more compact form of a function expression ( no need for the word function ) that is automatically scoped to “this”. You could have created a function like onGoFullScreen like we did for enterFullScreen. ( Coincidentally we could have also handled enterFullScreen using an arrow function.
The last thing to look at is the scale mode. In this example we used Phaser.ScaleManager.EXACT_FIT, which scales the scene up to match your resolution. There are two other options, SHOW_ALL and NO_SCALE. Here is the result of running the code above using each setting:
Phaser.ScaleManager.EXACT_FIT
Phaser.ScaleManager.NO_SCALE
Phaser.ScaleManager.SHOW_ALL
If you have an HDTV, you can think about them this way. EXACT_FIT is the same as Stretch mode. It scales the scene to use as much of the screen as possible, but can result in some distortion. NO_SCALE does nothing, it simply shows the scene un-altered, centered to the screen. SHOW_ALL is about the equivalent of Letterbox. Basically it fits as well as it can while maintaining the aspect ration of your original scene.
Don’t worry, that’s not it for graphics, we have all kinds of things coming up… spritesheets, effects, particles, animation, etc… That’s just it for the foundations.