Phaser 3: Add sprites into font as characters


In this tutorial I will show you, how to add sprites into font definition as characters and then simply print it along with other text like this:



last checked with Phaser  3.11.0

Goal

Our goal is to take small sprite and add it into existing font as new character. Then we will be able to print this sprite as text along with other characters.
What is it good for? It is very handy whenever you have icon followed with text or any text with icons inside of it. Look at image bellow:

Pirates! - level task list

Picture was take from our game Pirates! – the match-3 made in Phaser 2. It is level task list and normally it would take four sprites and two bitmap texts to print both lines. With solution, presented shortly, it takes only two bitmap texts as we will turn sprites into characters.

Loading and parsing font from atlas

First, we need to load font. We cannot load font with this.load.bitmapFont() – there is limitation, that font characters are in the same atlas as sprites we will add into it. So, we have to load atlas containing various sprites and also font characters. For font we will load only .XML metadata. When loading is finished we will parse font from atlas based on .XML metadata. In your preload() method add these lines:

// atlas
this.load.atlas("Sprites", "Sprites.png", "Sprites.json");
// font metadata
this.load.xml("FontXML", "Font.xml");

There are two possibilities how font characters are stored in atlas image:

  1. font creation tool (like Littera) exported .PNG with characters and .XML with metadata. Exported .PNG was added as single big frame into atlas. X and Y positions of characters defined in metadata are relative to top-left corner of this frame.
  2. font creation tool exported .PNG and .XML with metadata. But your tool for creating atlases can take these, mix it into atlas character by character and output not only final atlas, but also updated font metadata. X and Y positions of characters in updated metadata are absolute.

Difference is shown here:

1. Font as single frame

2. Characters mixed into atlas

Second case is better in my opinion, because characters are usually small sprites and can fill small empty areas in atlas, while in first case atlas tool has to place big block somewhere.
So, now we have atlas and .XML metadata, but not bitmapfont. We have to ask Phaser to parse it with ParseFromAtlas() method. If you are using Typescript like me along with official Typescript defs, then you will find this method missing. To overcome this, you have to write:

Phaser.GameObjects.BitmapText["ParseFromAtlas"](this, "Font", "Sprites", "__BASE", "FontXML");

Complete list of parameters is here. Shortly, we are passing current scene as first parameter, followed with name for our new bitmapfont. Then we pass name of atlas, name of frame and XML metadata. As I am using individual characters mixed into atlas, I have to pass “__BASE” as frame. This is frame every texture has and it is as big as whole texture. If you are using atlas with single big frame for all characters, you will have to pass name of that frame. Phaser will calculate absolute position of each character in atlas internally for you.

Adding sprite as character into font

For adding sprites into font, we will create static method in FontUtils class. We can add other static methods working with fonts into it in future. Whole class will be part of namespace Helper. I named the method addSpriteIntoFont() and to make use of it convenient for me, I added some additional features to it. First, here is full listing:

namespace Helper {
    
    export class FontUtils {

        public static readonly ALIGN_TOP = 0;
        public static readonly ALIGN_CENTER = 1;
        public static readonly ALIGN_BOTTOM = 2;

        // -------------------------------------------------------------------------
        /** 
         * Add sprite into bitmap font, position it and assign it character code.
         * Then you can print it along with other font characters.
         * 
         * @param {Phaser.Game} game - Phaser game
         * @param {string} fontName - name of font (the same as used in cache as key)
         * @param {(string|number)} frame - new sprite character frame
         * @param {number} newCharCode - char code to assign to sprite character
         * @param {(number|string)} [referenceChar = "0"] - reference character to position sprite character against
         * @param {number} [align = FontUtils.ALIGN_CENTER] - align to top, center or bottom of reference character
         * @param {number} [originY = 0.5] - origin of sprite character on y axis
         */
        public static addSpriteIntoFont(game: Phaser.Game, fontName: string, frame: string | number, newCharCode: number,
            referenceChar: number | string = "0",
            align: number = FontUtils.ALIGN_CENTER, originY: number = 0.5): void {

            // if reference char is string, convert it to number
            if (typeof referenceChar === "string") {
                referenceChar = referenceChar.charCodeAt(0);
            }

            // get font characters and reference character
            let font = game.cache.bitmapFont.get(fontName);
            let fontChars = (font.data as BitmapFontData).chars;
            let refChar = fontChars[referenceChar];

            if (refChar == null) {
                throw new Error(`Reference character ${String.fromCharCode(referenceChar)} with code ${referenceChar} is mssing in font. Try another.`);
            }

            // get frame of new sprite character
            let f = game.textures.getFrame(font["texture"], frame);
            let fWidth = f.customData["sourceSize"]["w"];
            let fHeight = f.customData["sourceSize"]["h"];

            // calculate y offset of sprite chracter
            let refY = refChar.yOffset +
                (align === FontUtils.ALIGN_CENTER ? refChar.height / 2 :
                    align === FontUtils.ALIGN_BOTTOM ? refChar.height : 0);
            let yOffset = Math.round(refY - fHeight * originY);

            // add new sprite character
            fontChars[newCharCode] = {
                x: f.cutX,
                y: f.cutY,
                width: f.cutWidth,
                height: f.cutHeight,
                centerX: Math.floor(fWidth / 2),
                centerY: Math.floor(fHeight / 2),
                xOffset: 0,
                yOffset: yOffset,
                xAdvance: fWidth + 2,
                data: {},
                kerning: {}
            };
        }
    }
}

First four parameters are mandatory. Method must get reference to Phaser.Game, so it can ask Phaser for cache and texture data. We have to pass name of the font we want our sprite add into, and also frame of sprite we are adding. We do not have to pass atlas name, because method can extract it from font data in cache. Last mandatory parameter is character code we want to assign to sprite character. It is good to choose some character that is not defined in font, otherwise you are in danger of overwriting existing character.

Next three parameters are optional and deserve some explanation:

  • referenceChar (=”0″) – diferent characters have different heights. Letter “r” is smaller than “R” and it is smaller than letter with accent “Ř”. referenceChar selects character we will position our new character sprite against. Other two parameters will make it clear. By default, “0” (zero) is used as reference. Of course, it has to be present in font. If not, choose another character or you will get error,
  • align (=FontUtils.CENTER) – possible values are FontUtils.TOP | FontUtils.CENTER | FontUtils.BOTTOM. This parameter says how new sprite character will be aligned agians reference character. If set to CENTER, then top of added sprite character will start in the middle of reference character. If set to BOTTOM, it will start at bottom of reference character,
  • originY (=0.5) – this works as standard Phaser origin – it says where is frame anchored. By default, value 0.5 says, it is anchored in center.

Here is image with various combinations of align and originY:

Usage

To add sprite into font we call addSpriteIntoFont():

// with defaults
Helper.FontUtils.addSpriteIntoFont(this.sys.game, "Font", "skeleton", 0xBC);
Helper.FontUtils.addSpriteIntoFont(this.sys.game, "Font", "spider", 0xBD,);

// with all parameters
Helper.FontUtils.addSpriteIntoFont(this.sys.game, "Font", "skeleton", 0xBC, "0", Helper.FontUtils.ALIGN_BOTTOM, 1);
Helper.FontUtils.addSpriteIntoFont(this.sys.game, "Font", "spider", 0xBD, "0", Helper.FontUtils.ALIGN_BOTTOM, 1);

TypeScript

In code above, we added character code 0xBC to skeleton sprite and 0xBD to spider sprite. Later when we want to print it, we create BitmapText object like this:

this.add.bitmapText(320, 512, "Font", "There is \u00BC and \u00BD in dungeon!");
TypeScript

In code above, we added character code 0xBC to skeleton sprite and 0xBD to spider sprite. Later when we want to print it, we create BitmapText object like this:

<span class="token keyword">this</span><span class="token punctuation">.</span>add<span class="token punctuation">.</span><span class="token function">bitmapText</span><span class="token punctuation">(</span><span class="token number">320</span><span class="token punctuation">,</span> <span class="token number">512</span><span class="token punctuation">,</span> <span class="token string">"Font"</span><span class="token punctuation">,</span> <span class="token string">"There is \u00BC and \u00BD in dungeon!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

Here is result:

Conclusion

With created class printing things like icon with text is easy. If you are using Phaser 2, I wrote similar article on my old blog.


Leave a Reply

Your email address will not be published. Required fields are marked *