New phaser-bitmapfont-generator with .png and .xml output available

A few months ago I started to incorporate Phaser game engine in one of my biggest projects, a new Educational webapp that teaches math to young children. Phaser is an amazingly fast engine and I am glad I have given it a try. There were however some annoying performance problems with text fonts. Since there were only limited tools available, I decided to write a custom solution to generate bitmap fonts (.png + .xml) from the command line. It is a node.js package that is available at:

phaser-bitmapfont-generator

github: https://github.com/stephanvermeire/phaser-bitmapfont-generator#readme
npm: https://www.npmjs.com/package/@rtpa/phaser-bitmapfont-generator

The tool is inprobably very accessable for Phaser developers since it is able to convert a Phaser 3 textStyle configuration object to the accoring .png + xml files.

In short, this small script:

const generator = require('@rtpa/phaser-bitmapfont-generator');

(async () => {

//generate textures
await generator.TextStyle2BitmapFont(
    {
        path: './mydir',
        fileName: 'myBitmapfont',
        textStyle: {
            fontFamily: 'Impact',
            fontSize: '50px',
            color: '#fde5c0',
            shadow: {
                offsetX: 4,
                offsetY: 4,
                blur: 0,
                fill: true,
                stroke: true,
                color: '#000000'
            },
        }
    }
);

//exit node
return process.exit(1);

})();

generates the following files from the command line:

and:

        <?xml version="1.0"?>
    <font>
        <info face="Impact" size="100"/>
        <common lineHeight="86" base="19"/>
        <pages>
            <page id="0" file="myBitmapfont.png"/>
        </pages>
        <chars>
            <char id="32" x="0" y="0" width="22" height="105" xoffset="0" yoffset="0" xadvance="18" page="0"/>
            <char id="33" x="26" y="0" width="31" height="105" xoffset="0" yoffset="0" xadvance="27" page="0"/>
            <char id="34" x="61" y="0" width="41" height="105" xoffset="0" yoffset="0" xadvance="37" page="0"/>
            <char id="35" x="106" y="0" width="67" height="105" xoffset="0" yoffset="0" xadvance="63" page="0"/>
            <char id="36" x="177" y="0" width="59" height="105" xoffset="0" yoffset="0" xadvance="55" page="0"/>
            <char id="37" x="240" y="0" width="73" height="105" xoffset="0" yoffset="0" xadvance="69" page="0"/>
            <char id="38" x="317" y="0" width="62" height="105" xoffset="0" yoffset="0" xadvance="58" page="0"/>
            <char id="39" x="383" y="0" width="23" height="105" xoffset="0" yoffset="0" xadvance="19" page="0"/>
            <char id="40" x="410" y="0" width="35" height="105" xoffset="0" yoffset="0" xadvance="31" page="0"/>
            <char id="41" x="449" y="0" width="35" height="105" xoffset="0" yoffset="0" xadvance="31" page="0"/>
            <char id="42" x="488" y="0" width="32" height="105" xoffset="0" yoffset="0" xadvance="28" page="0"/>
            <char id="43" x="524" y="0" width="57" height="105" xoffset="0" yoffset="0" xadvance="53" page="0"/>
            <char id="44" x="585" y="0" width="21" height="105" xoffset="0" yoffset="0" xadvance="17" page="0"/>
            <char id="45" x="610" y="0" width="33" height="105" xoffset="0" yoffset="0" xadvance="29" page="0"/>
            <char id="46" x="647" y="0" width="22" height="105" xoffset="0" yoffset="0" xadvance="18" page="0"/>
            <char id="47" x="673" y="0" width="44" height="105" xoffset="0" yoffset="0" xadvance="40" page="0"/>
            <char id="48" x="721" y="0" width="58" height="105" xoffset="0" yoffset="0" xadvance="54" page="0"/>
            <char id="49" x="783" y="0" width="42" height="105" xoffset="0" yoffset="0" xadvance="38" page="0"/>
            <char id="50" x="829" y="0" width="54" height="105" xoffset="0" yoffset="0" xadvance="50" page="0"/>
            <char id="51" x="887" y="0" width="57" height="105" xoffset="0" yoffset="0" xadvance="53" page="0"/>
            <char id="52" x="948" y="0" width="54" height="105" xoffset="0" yoffset="0" xadvance="50" page="0"/>
            <char id="53" x="0" y="109" width="58" height="105" xoffset="0" yoffset="0" xadvance="54" page="0"/>
            <char id="54" x="62" y="109" width="58" height="105" xoffset="0" yoffset="0" xadvance="54" page="0"/>
            <char id="55" x="124" y="109" width="43" height="105" xoffset="0" yoffset="0" xadvance="39" page="0"/>
            <char id="56" x="171" y="109" width="57" height="105" xoffset="0" yoffset="0" xadvance="53" page="0"/>
            <char id="57" x="232" y="109" width="58" height="105" xoffset="0" yoffset="0" xadvance="54" page="0"/>
            <char id="58" x="294" y="109" width="24" height="105" xoffset="0" yoffset="0" xadvance="20" page="0"/>
            <char id="59" x="322" y="109" width="24" height="105" xoffset="0" yoffset="0" xadvance="20" page="0"/>
            <char id="60" x="350" y="109" width="57" height="105" xoffset="0" yoffset="0" xadvance="53" page="0"/>
            <char id="61" x="411" y="109" width="57" height="105" xoffset="0" yoffset="0" xadvance="53" page="0"/>
            <char id="62" x="472" y="109" width="57" height="105" xoffset="0" yoffset="0" xadvance="53" page="0"/>
            <char id="63" x="533" y="109" width="56" height="105" xoffset="0" yoffset="0" xadvance="52" page="0"/>
            <char id="64" x="593" y="109" width="81" height="105" xoffset="0" yoffset="0" xadvance="77" page="0"/>
            <char id="65" x="678" y="109" width="55" height="105" xoffset="0" yoffset="0" xadvance="51" page="0"/>
            <char id="66" x="737" y="109" width="59" height="105" xoffset="0" yoffset="0" xadvance="55" page="0"/>
            <char id="67" x="800" y="109" width="59" height="105" xoffset="0" yoffset="0" xadvance="55" page="0"/>
            <char id="68" x="863" y="109" width="59" height="105" xoffset="0" yoffset="0" xadvance="55" page="0"/>
            <char id="69" x="926" y="109" width="46" height="105" xoffset="0" yoffset="0" xadvance="42" page="0"/>
            <char id="70" x="976" y="109" width="44" height="105" xoffset="0" yoffset="0" xadvance="40" page="0"/>
            <char id="71" x="0" y="218" width="59" height="105" xoffset="0" yoffset="0" xadvance="55" page="0"/>
            <char id="72" x="63" y="218" width="60" height="105" xoffset="0" yoffset="0" xadvance="56" page="0"/>
            <char id="73" x="127" y="218" width="33" height="105" xoffset="0" yoffset="0" xadvance="29" page="0"/>
            <char id="74" x="164" y="218" width="37" height="105" xoffset="0" yoffset="0" xadvance="33" page="0"/>
            <char id="75" x="205" y="218" width="58" height="105" xoffset="0" yoffset="0" xadvance="54" page="0"/>
            <char id="76" x="267" y="218" width="42" height="105" xoffset="0" yoffset="0" xadvance="38" page="0"/>
            <char id="77" x="313" y="218" width="76" height="105" xoffset="0" yoffset="0" xadvance="72" page="0"/>
            <char id="78" x="393" y="218" width="58" height="105" xoffset="0" yoffset="0" xadvance="54" page="0"/>
            <char id="79" x="455" y="218" width="59" height="105" xoffset="0" yoffset="0" xadvance="55" page="0"/>
            <char id="80" x="518" y="218" width="54" height="105" xoffset="0" yoffset="0" xadvance="50" page="0"/>
            <char id="81" x="576" y="218" width="59" height="105" xoffset="0" yoffset="0" xadvance="55" page="0"/>
            <char id="82" x="639" y="218" width="58" height="105" xoffset="0" yoffset="0" xadvance="54" page="0"/>
            <char id="83" x="701" y="218" width="56" height="105" xoffset="0" yoffset="0" xadvance="52" page="0"/>
            <char id="84" x="761" y="218" width="50" height="105" xoffset="0" yoffset="0" xadvance="46" page="0"/>
            <char id="85" x="815" y="218" width="59" height="105" xoffset="0" yoffset="0" xadvance="55" page="0"/>
            <char id="86" x="878" y="218" width="56" height="105" xoffset="0" yoffset="0" xadvance="52" page="0"/>
            <char id="87" x="938" y="218" width="85" height="105" xoffset="0" yoffset="0" xadvance="81" page="0"/>
            <char id="88" x="0" y="327" width="51" height="105" xoffset="0" yoffset="0" xadvance="47" page="0"/>
            <char id="89" x="55" y="327" width="51" height="105" xoffset="0" yoffset="0" xadvance="47" page="0"/>
            <char id="90" x="110" y="327" width="44" height="105" xoffset="0" yoffset="0" xadvance="40" page="0"/>
            <char id="91" x="158" y="327" width="32" height="105" xoffset="0" yoffset="0" xadvance="28" page="0"/>
            <char id="92" x="194" y="327" width="44" height="105" xoffset="0" yoffset="0" xadvance="40" page="0"/>
            <char id="93" x="242" y="327" width="32" height="105" xoffset="0" yoffset="0" xadvance="28" page="0"/>
            <char id="94" x="278" y="327" width="52" height="105" xoffset="0" yoffset="0" xadvance="48" page="0"/>
            <char id="95" x="334" y="327" width="59" height="105" xoffset="0" yoffset="0" xadvance="55" page="0"/>
            <char id="96" x="397" y="327" width="37" height="105" xoffset="0" yoffset="0" xadvance="33" page="0"/>
            <char id="97" x="438" y="327" width="54" height="105" xoffset="0" yoffset="0" xadvance="50" page="0"/>
            <char id="98" x="496" y="327" width="56" height="105" xoffset="0" yoffset="0" xadvance="52" page="0"/>
            <char id="99" x="556" y="327" width="53" height="105" xoffset="0" yoffset="0" xadvance="49" page="0"/>
            <char id="100" x="613" y="327" width="56" height="105" xoffset="0" yoffset="0" xadvance="52" page="0"/>
            <char id="101" x="673" y="327" width="55" height="105" xoffset="0" yoffset="0" xadvance="51" page="0"/>
            <char id="102" x="732" y="327" width="33" height="105" xoffset="0" yoffset="0" xadvance="29" page="0"/>
            <char id="103" x="769" y="327" width="56" height="105" xoffset="0" yoffset="0" xadvance="52" page="0"/>
            <char id="104" x="829" y="327" width="56" height="105" xoffset="0" yoffset="0" xadvance="52" page="0"/>
            <char id="105" x="889" y="327" width="31" height="105" xoffset="0" yoffset="0" xadvance="27" page="0"/>
            <char id="106" x="924" y="327" width="31" height="105" xoffset="0" yoffset="0" xadvance="27" page="0"/>
            <char id="107" x="959" y="327" width="52" height="105" xoffset="0" yoffset="0" xadvance="48" page="0"/>
            <char id="108" x="0" y="436" width="31" height="105" xoffset="0" yoffset="0" xadvance="27" page="0"/>
            <char id="109" x="35" y="436" width="81" height="105" xoffset="0" yoffset="0" xadvance="77" page="0"/>
            <char id="110" x="120" y="436" width="56" height="105" xoffset="0" yoffset="0" xadvance="52" page="0"/>
            <char id="111" x="180" y="436" width="55" height="105" xoffset="0" yoffset="0" xadvance="51" page="0"/>
            <char id="112" x="239" y="436" width="56" height="105" xoffset="0" yoffset="0" xadvance="52" page="0"/>
            <char id="113" x="299" y="436" width="56" height="105" xoffset="0" yoffset="0" xadvance="52" page="0"/>
            <char id="114" x="359" y="436" width="40" height="105" xoffset="0" yoffset="0" xadvance="36" page="0"/>
            <char id="115" x="403" y="436" width="51" height="105" xoffset="0" yoffset="0" xadvance="47" page="0"/>
            <char id="116" x="458" y="436" width="34" height="105" xoffset="0" yoffset="0" xadvance="30" page="0"/>
            <char id="117" x="496" y="436" width="56" height="105" xoffset="0" yoffset="0" xadvance="52" page="0"/>
            <char id="118" x="556" y="436" width="48" height="105" xoffset="0" yoffset="0" xadvance="44" page="0"/>
            <char id="119" x="608" y="436" width="71" height="105" xoffset="0" yoffset="0" xadvance="67" page="0"/>
            <char id="120" x="683" y="436" width="47" height="105" xoffset="0" yoffset="0" xadvance="43" page="0"/>
            <char id="121" x="734" y="436" width="49" height="105" xoffset="0" yoffset="0" xadvance="45" page="0"/>
            <char id="122" x="787" y="436" width="39" height="105" xoffset="0" yoffset="0" xadvance="35" page="0"/>
            <char id="123" x="830" y="436" width="41" height="105" xoffset="0" yoffset="0" xadvance="37" page="0"/>
            <char id="124" x="875" y="436" width="31" height="105" xoffset="0" yoffset="0" xadvance="27" page="0"/>
            <char id="125" x="910" y="436" width="41" height="105" xoffset="0" yoffset="0" xadvance="37" page="0"/>
            <char id="126" x="955" y="436" width="56" height="105" xoffset="0" yoffset="0" xadvance="52" page="0"/>
        </chars>
    </font>

The bitmap font .png file is compressed automatically and you can ajust various settings from the configuration object that is passed into the generator script. There are several examples available at github and npm to help you get started.

Happy coding and enjoy!
Stephan :grin:

6 Likes

This is great! Definitely going to be really useful, thanks so much.

Does it grab the font from my system? If I’ve got a custom font installed, can I use its name for the font family?

Yes, you can use all fonts that are installed locally on your own system.

Oh, great! I’ve been using Bitmap Font Generator from AngelCode, but as it is Windows only I have had to boot up the virtual machine every time I needed to do a font :slight_smile:

Thank you for this, will definitely give it a try.

I haven’t had a chance to play with it yet, can we disable anti-aliasing so we can use it for pixel fonts for pixel art projects?

antiAlias is not yet implemented but I am happy to add this feature for you. Can you send me a copy of your GameConfig file so can have a look at the settings that you are using for pixel art?

Here’s a typical one, but how does my gameconfig help?

but how does my gameconfig help?

Well, I have no experience with pixelart so I wanted to make sure I am using the right settings during testing.

Unfortunately it appears to be a bit more complicated to implement than I expected. I cannot find an easy way to render text in Phaser without antialias. Am I overlooking something?

That’s what makes pixel perfect games so frustrating in Phaser - you have to use bitmap text ( https://photonstorm.github.io/phaser3-docs/Phaser.GameObjects.BitmapText.html ) to render text without anti aliasing, but you need a bitmap font to do so!

I just had a look at a possible solution based on the npm jimp and image-pixelizer packages. I managed to convert the following bitmap font:

from (zoom: 400%):
image

to:
image

It is not perfect but at the very least it is an improvement.
The result of a single color bitmap font is quite reasonable:

from (zoom 100%):
example3

(zoom 800%):

to:
example3-out

How many colors do you intent to use for the bitmapfont?

2 Likes

Very cool! I’ll explain the problem I’m trying to solve. I create a game with a very small canvas, 128x128 px for example. The game is for a jam, so I don’t have the time (or expertise, honestly) to build a decent bitmap font.

I’d like to take a font like “press start 2p” ( https://www.fontspace.com/press-start-2p-font-f11591 ) which has a very permissive license, and convert it to use for my tiny pixel art toys and games. “Press start 2p” is probably a better candidate for testing conversion without anti aliasing, because I’m pretty confident that the font has properly scaled psuedo-pixel(?) widths and heights. I mean, I think all the characters are 8 squares tall and wide.

My intention was to create a pixel font with white characters, and to use Phaser’s tint methods to change their colors (possibly dynamically). For my own purposes, I don’t plan to use multiple colors on a single character, for instance.

I think it is indeed a good idea to use a white pixel perfect bitmapfont and use the Phaser tint option to spice things up a bit.

Good news about the bitmapfont-generator, I managed to implement two new options that you can use to generate your personal pixel art bitmapfont.

for single color bitmapfonts you can use the antialias=false option.

The old situation:

now with antialias turned off:

await generator.TextStyle2BitmapFont(
    {
        textStyle: {
            fontFamily: 'Impact',
            fontSize: '30px',
            color: '#ffffff',
        },
        antialias: false
    }
);

generates:

I also added an option to reduce the maximum number of colors that is allowed in the final .png. (This is useful for multi-colored pixel art bitmap fonts).

an example:

await generator.TextStyle2BitmapFont(
    {
        textStyle: {
            fontFamily: 'Impact',
            fontSize: '50px',
            color: '#ffffff',
            shadow: {
                offsetX: 1,
                offsetY: 1,
                blur: 0,
                fill: true,
                stroke: true,
                color: '#000000'
            },
        },
        antialias: false,
        maxNumberOfColours: 3
    }
);

generates:
image

nice and crisp! :grin:

The updated phaser-bitmapfont-generator packages is available at npm and github

1 Like

Gorgeous, I love it. I play with it a bit this evening, thank you for this little feature now I don’t have to use a scary unsigned executable tool for this going forward!

Love it! Got exactly what I needed on the first try!