Follow @SBC_Games
Previous Phaser tutorials and articles:
Phaser: Typescript defs for Phaser Box2D plugin
Phaser tutorial: Spriter Pro features added to Spriter player for Phaser
Phaser tutorial: Using Phaser signals
Phaser tutorial: Breaking the (z-order) law!
Phaser tutorial: Phaser and Spriter skeletal animation
Phaser tutorial: DronShooter - simple game in Typescript - Part 3
Phaser tutorial: DronShooter - simple game in Typescript - Part 2
Phaser tutorial: adding 9-patch image support to Phaser
Phaser tutorial: DronShooter - simple game in Typescript - Part 1
Phaser tutorial: custom easing functions for tweening and easing functions with parameters
Phaser tutorial: sprites and custom properties for atlas frames
Phaser tutorial: manage different screen sizes
Phaser tutorial: How to wrap bitmap text
Introduction
Everybody with at least short experience in game development came across sprite atlases. Its benefits are in merging multiple sprites into one large bitmap and so decreasing draw calls. We can simplify draw call as request to GPU to draw something with some set of parameters. One of these parameters is also texture. Changing any of parameters results into quite expensive setup on GPU side (flushing current work and doing setup for different set of parameters) - this is reason why to build larger textures made from individual sprites.
Currently there is lot of tools, that help you with creating sprite atlas from individual sprites. But, when it comes to fonts, the situation starts to get complicated.
For creating fonts I am using on-line tool Littera. This tool is great, but when you are exporting your font, you already get some atlas that contains your font characters. Alongside, you get file with characters metadata saying where in atlas is character positioned, how big it is, ...
Export from Littera can look like this:
<font> <info face="MapNormal" size="40" bold="0" italic="0" charset="" unicode="" stretchH="100" smooth="1" aa="1" padding="2,2,2,2" spacing="0,0" outline="0"/> <common lineHeight="75" base="24" scaleW="42" scaleH="252" pages="1" packed="0"/> <pages> <page id="0" file="MapNormal.png"/> </pages> <chars count="10"> <char id="48" x="2" y="2" width="18" height="24" xoffset="2" yoffset="3" xadvance="19" page="0" chnl="15"/> <char id="49" x="2" y="28" width="15" height="24" xoffset="0" yoffset="3" xadvance="13" page="0" chnl="15"/> <char id="50" x="2" y="54" width="21" height="25" xoffset="0" yoffset="2" xadvance="19" page="0" chnl="15"/> <char id="51" x="22" y="2" width="18" height="26" xoffset="1" yoffset="1" xadvance="17" page="0" chnl="15"/> <char id="52" x="2" y="81" width="20" height="25" xoffset="2" yoffset="3" xadvance="18" page="0" chnl="15"/> <char id="53" x="2" y="108" width="18" height="25" xoffset="1" yoffset="2" xadvance="16" page="0" chnl="15"/> <char id="54" x="2" y="135" width="20" height="27" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15"/> <char id="55" x="2" y="164" width="19" height="26" xoffset="1" yoffset="2" xadvance="12" page="0" chnl="15"/> <char id="56" x="2" y="192" width="19" height="28" xoffset="1" yoffset="1" xadvance="16" page="0" chnl="15"/> <char id="57" x="2" y="222" width="19" height="28" xoffset="1" yoffset="3" xadvance="18" page="0" chnl="15"/> <char id="32" x="0" y="0" width="0" height="0" xoffset="1" yoffset="3" xadvance="11" page="0" chnl="15"/> </chars> <kernings count="0"/> </font>
Problem
So, let's have some sprite atlas and above font created in Littera. Let's also imagine we have some object, that is made from sprites and text. This is exactly what map screen with map spots is in our upcoming game Pirates! - the match three game (graphics is made by Tomáš Kopecký):
Every single map spot is object made from sprites and text (level number). Scene graph for it looks like this:
Map has 65 level spots and drawing it takes 136 draw calls. 130 (65 * 2) of them is for map spots. It is very bad, but it is impact of switching between atlas with sprites and atlas with fonts. On above picture it is clear, that every time renderer renders single map spot, it has to switch texture twice.
What we need is somehow merge font into our sprite atlas and keep valid font metadata. Then we will get rid of that texture switching and we will be able to decrease draw calls.
Solution
Long time ago I made tool for creating sprite atlases. It is very old and it has messy code inside, not too good GUI and also name is messy - I call it sometimes PicOpt and more recently Spritor. Anyway, it has some nice features and I used it for all games I made. You can get it for free here. One of these nice features is, that it can do what we need. So, download it and follow next steps:
Open Spritor tool and pres Ctrl+N to create new project. Give it name (it is not saved, it only created new project):
Press Ctrl+A to add some sprites (individual .png images):
Your screen should look similar to this:
Now, pres Ctrl+A again and open you Littera font atlas. You need to have Littera .xml/.fnt file in the same directory. Also, do not forget to check "Is font" in bottom of dialog window:
This will add Littera font atlas into your project. It will read through Littera metadata in .xml/.fnt and cut Littera font atlas into individual sprites. The role of metadata is not finished yet. It will play important role once again during export. You can now select type of export. To have one compatible with TexturePacker export, select format in top bar as this:
(Btw, export just one line below ("JSON - TP + Properties") is adding custom properties, that you can add to your sprites in Spritor tool, into final export). You can now save your project by pressing Ctrl+S.
It's time to make atlas. Go to menu and select Optimize -> Best Place:
After some short time for optimiaztion, there will be created folder with name "export" and you will find several files there:
- your atlas image,
- your atlas metadata (in TexturePacker format),
- one Littera metadata file (.xml or .fnt) for every font you merged into atlas. This file already has all character specs adjusted to positions in new atlas.
Not finished yet - Phaser part
As we have our assets ready, we can load it into Phaser and see what happes.
Normally, you would load your font and atlas in preload method with code like:
this.load.atlas("Atlas", "assets/Atlas.png", "assets/Atlas.json"); this.load.bitmapFont("Font", "assets/Font.png", "assets/Font.xml");
But in our case we already have fonts merged into atlas, so we do not need to load "Font.png" file. So, we will first change this to:
this.load.atlas("Atlas", "assets/Atlas.png", "assets/Atlas.json"); this.load.xml("Font", "assets/Font.xml");
Then, when assets are loaded, in create method we have to build our font from loaded atlas and xml metadata. At first, it looks like easy task:
this.cache.addBitmapFont("MyFont", null, this.cache.getImage("Atlas"), this.cache.getXML("Font"), "xml");
If you do above and run your game, You will see, that you can use "MyFont" in your game. It is taking characters from atlas, that is common with other sprites, but number of draw calls did not decreased! Something went wrong.
Let's look into Phaser source. Method for addBitmapFont has this in the beginning:
addBitmapFont: function (key, url, data, atlasData, atlasType, xSpacing, ySpacing) { var obj = { url: url, data: data, font: null, base: new PIXI.BaseTexture(data) }; : : :
And if we dive a little bit deeper into PIXI.WebGLSpriteBatch.prototype.flush() method we find this (shortened and lots of code omitted for clarity):
: : nextTexture = sprite.texture.baseTexture; : : if ((currentBaseTexture !== nextTexture && !nextTexture.skipRender) || blendSwap || shaderSwap) { this.renderBatch(currentBaseTexture, batchSize, start); : :
Batches are flushed based not on underlaying atlas image, but on baseTexture. As we have one atlas we have to achieve somehow to have the same baseTexture for atlas as well as for font. For this we will create our custom method that will add functionality to Phaser Cache class. Let's call it addBitmapFontFromImage. Code for it is mostly copy of original addBitmapFont:
* update for Phaser 2.7.3 and later * - when using jsonBitmapFont and xmlBitmapFont in code below, you have to pass two additional parameters for frame (null) and resolution (1).
* update for Phaser 2.7.3 and later * - when using jsonBitmapFont and xmlBitmapFont in code below, you have to pass two additional parameters for frame (null) and resolution (1).
module Utils { export class PhaserUtils { // ------------------------------------------------------------------------- public static AddBitmapFontAddMethod(): void { Phaser.Cache.prototype["addBitmapFontFromImage"] = function addBitmapFont(key: string, url: string, imageName: string, atlasData: any, atlasType: string, xSpacing?: number, ySpacing?: number): void { var img = this.getImage(imageName, true); var obj = { url: url, data: img.data, font: null, base: img.base }; if (xSpacing === undefined) { xSpacing = 0; } if (ySpacing === undefined) { ySpacing = 0; } if (atlasType === 'json') { obj.font = Phaser.LoaderParser.jsonBitmapFont(atlasData, obj.base, xSpacing, ySpacing, null, 1); } else { obj.font = Phaser.LoaderParser.xmlBitmapFont(atlasData, obj.base, xSpacing, ySpacing, null, 1); } this._cache.bitmapFont[key] = obj; this._resolveURL(url, obj); } } } }
Our new method is wrapped in special PhaserUtils class. Btw, I have this class filled with other small engine tweaks and in the very beginning of the game I call one or more static methods to apply these tweaks. You have to do the same - do it before you create game object!:
Utils.PhaserUtils.AddBitmapFontAddMethod();
Now, replace line for creating font in create method with call to our new method:
this.cache["addBitmapFontFromImage"]("MyFont", null, "Atlas", this.cache.getXML("Font"), "xml");
Call is using square brackets as we did not add our method into phaser.d.ts file. If you used standard call, you would get Typescript complaints.
If you run your game now, you should see that number of draw calls decreased. In my case it went down from 136 to 6!!!
Conclusion
In our game we have in fact three different fonts for map spots - depends on whether spot is normal, finished, special battle spot... All three fonts contain only numbers. Instead of having three font atlases and one for sprites, we have only one for everything.
This helps us to save resources, reduce draw calls and as the process of creating atlas is easy we do not get any overhead.
Hello! I am looking for a way to generate Font XML from a PNG. I saw that you had an Excel file for this here:
ReplyDeletehttps://www.html5gamedevs.com/topic/21443-generating-bitmap-font-xml/
but it is no longer available. Could you share it?