Tutorial: BMFont rendering with MonoGame

bmfont_rendering.png

The next step in my CraftworkGUI project is to implement text rendering. I’ve chosen to manually generate a bitmap font texture using the Bitmap Font Generator tool by AngelCode.com and write my own simple text renderer for MonoGame. The process is fairly easy and most of the code required already exists if you use the C# XML serializer for font loading by DeadlyDan linked from the AngelCode.com website.

Step 1: Generate the texture and font file using BMFont

If you haven’t already done so, download and install the BMFont tool. When you start the tool you’ll be presented with a screen that looks like this:

bmfont.jpg

Setup your Font options, they can be found in the Font Settings dialog from the options menu. You can choose whatever you settings you prefer here. Next, you need to select the characters you want to generate. Typically this will include all of the top half of the character set as shown in the screenshot.

Change the output format to XML in the Export Options dialog. This is important because we are going to be using the XML serializer to load our font. While your in the Export Options dialog you might also want you change your texture format, I prefer png. To keep things simple, I also like to make sure my font is going to fit on one texture. You might need to play around with the texture width and height a little to get something you’re comfortable with.

Output your font file and texture using the Save bitmap font as option.. You should end up with a .fnt file containing some XML and an image with the same name followed by _0. The font generator tightly packs the glyphs into the texture atlas to minimize the texture size.

Step 2: Add the BmFont XML Serializer code to your project

You can find the BmFont Serializer code on pastebin. It makes me feel a little uncomfortable that the code doesn’t live in a proper code repository, but I’ll keep a copy of it on my server in case it ever disappears.

The code consists of a handful of classes used for serialization including:

  • FontChar
  • FontCommon
  • FontFile
  • FontInfo
  • FontKerning
  • FontLoader
  • FontPage

For the most part, you won’t need to change the code at all. If you’re like most programmers you’ll probably fiddle with it a little and change the namespaces to your liking. I used the refactoring tools in MonoDevelop to split each class into it’s own file.

At the most basic level, loading a font file is easy. If you’ve put your font file and texture in the Content folder you can use the following code on Windows.

var fontFilePath = Path.Combine(Content.RootDirectory, "CourierNew32.fnt");
var fontFile = FontLoader.Load(fontFilePath);
var fontTexture = Content.Load<Texture2D>("CourierNew32_0.png");

But this won’t work when your using MonoGame to deploy to other platforms like Android or iOS. The solution is to modify or create a new FontLoader.Load method that takes a Stream instead of a file path like this:

public static FontFile Load(Stream stream)
{
        XmlSerializer deserializer = new XmlSerializer(typeof(FontFile));
        FontFile file = (FontFile) deserializer.Deserialize(stream);
        return file;
}

Now the code to load the font can use a TitleContainer.OpenStream method to load the file in a more generic way. MonoGame supports loading files on any platform using the same path through the content manager or the TitleContainer.

var fontFilePath = Path.Combine(Content.RootDirectory, "CourierNew32.fnt");
using(var stream = TitleContainer.OpenStream(fontFilePath))
{
    var fontFile = FontLoader.Load(stream);
    var fontTexture = Content.Load<Texture2D>("CourierNew32_0.png");
    // textRenderer initialization will go here
    stream.Close();
}

Step 4: Implementing a basic text renderer

At this stage you probably just want to get some text on the screen as fast as possible. Fortunately, a basic text renderer isn’t hard to implement. Here’s one I prepared earlier:

public class FontRenderer
{
        public FontRenderer (FontFile fontFile, Texture2D fontTexture)
        {
                _fontFile = fontFile;
                _texture = fontTexture;
                _characterMap = new Dictionary<char, FontChar>();

                foreach(var fontCharacter in _fontFile.Chars)
                {
                        char c = (char)fontCharacter.ID;
                        _characterMap.Add(c, fontCharacter);
                }
        }

        private Dictionary<char, FontChar> _characterMap;
        private FontFile _fontFile;
        private Texture2D _texture;
        public void DrawText(SpriteBatch spriteBatch, int x, int y, string text)
        {
                int dx = x;
                int dy = y;
                foreach(char c in text)
                {
                        FontChar fc;
                        if(_characterMap.TryGetValue(c, out fc))
                        {
                                var sourceRectangle = new Rectangle(fc.X, fc.Y, fc.Width, fc.Height);
                                var position = new Vector2(dx + fc.XOffset, dy + fc.YOffset);

                                spriteBatch.Draw(_texture, position, sourceRectangle, Color.White);
                                dx += fc.XAdvance;
                        }
                }
        }
}

The FontRenderer class takes a FontFile from the BMFont loader and a MonoGame/XNA Texture2D as input. It creates a dictionary mapping for each character in the font for fast lookup during rendering. The DrawText method takes a MonoGame/XNA SpriteBatch, a postion and string to render. It loops through each character in the string and looks up the info for each character. It creates a source rectangle on the texture to find the appropriate glyph and offsets the character position because of the tight texture packing. The font also specifies how many pixels to advance the cursor for each character.

You can create a new instance of the FontRenderer in your LoadContent method.

_fontRenderer = new FontRenderer(fontFile, fontTexture);

And call the DrawText method from your main Draw method.

_fontRenderer.DrawText(_spriteBatch, 50, 50, "Hello World!");

This implementation is not very sophisticated, it doesn’t take into account any kerning, multiple lines or word wrapping. However, all of these features can be implemented from the data loaded from the BMFont XML file.

  • Ferret ChereFeF

    Very helpful, thanks!
    I’m putting together a ‘quickstart’ project and set of tutorials for Monogame 3. Would you mind if this is included (with credit to you of course)?

    • http://www.craftworkgames.com glue

      I’m glad you found it useful. You’re welcome to use it in the quickstart project and tutorials. I just ask that you put the URL somewhere and let me know when you’re done so I can take a look.

  • Pansoft

    Nice article, never tough about doint it that way : I’m currently still using a ‘dummy’ VS true XNA content project to compile my fonts to .xnb and SpriteFont tool to create them then I just add the .xnb file as an asset to my monogame project. cheers

    • http://www.craftworkgames.com craftworkgames

      Yeah, that’s certainly one way to do it. Personally I find it a little cumbersome though. I heard that it was a little inefficient (but that may not be the case anymore, not sure)

  • http://www.sumosoftware.com Sean Dunford

    Can you post the using statements you….used. I can’t seem to get _fontfile.Chars to work on my Visual Studio. Thanks, great post by the way.

    • http://www.sumosoftware.com Sean Dunford

      FurtherMore I am attempting to use this fontfile class http://sharpdx.org/documentation/api/t-sharpdx-directwrite-fontfile are you using the same?

      • http://www.craftworkgames.com craftworkgames

        You don’t need to load the font file directly in your game. The BMFont tool loads the font file and saves the prerendered characters to a texture.

        You don’t need anything from the SharpDX library. All of the source code required is available here. Check the link to pastebin just under step 2.

  • http://marcelbehrmann.de EldoranDev

    First: Thank You for this Awesome Tutorial ;)
    Second:
    If I start my programm every thing works fine, but if I close my programm I get an AccessVialationException any idea what this could be ?

    • http://www.craftworkgames.com craftworkgames

      An AccessViolationException is pretty generic but since it’s happening when you close the program I’m going to have a stab in the dark and say you might not be disposing of an unmanaged resource correctly. Just a guess though, good luck.

      • EldoranDev

        I did not add anything but your code =/ I even added the Content.Unload(); to the UnloadContent Method.

        And the Problem just appears if I use the FontRenderer.DrawText Method, and there is nothing to dispose or ?

        • http://www.craftworkgames.com craftworkgames

          Not sure man. I’ve never experienced that problem with it before. Perhaps there’s something different about your setup. I’ve been developing for Android using MonoDevelop.

          If you can reproduce the issue in an example app or something perhaps I can take a closer look. It’s probably worth taking it to the MonoGame forums, they are more likely to know what’s going on here.

          • EldoranDev

            Okay thanks for the tip,

            found Error, in the ProjektTemlpate I used the line “game.Dispose()” was missing. It is just needed if you draw some thing manualy like this text so the error just came if i used the textRenderer

            thanks ;)

  • http://www.danielnordmark.se Daniel

    Thanks for a great tutorial! :-)

  • Kenneth

    Thanks for the nice tutorial, I just have one problem.
    When I write my text, it doesn’t correct. There are some black boxes around the letters.
    It looks like this:

    http://imageshack.us/photo/my-images/818/77249056.png/

    Maybe it has something to do with my settings in the BMF font tool.

    • http://www.craftworkgames.com craftworkgames

      In the Export options you’ll see a Presets drop down. You’ll probably want it set to ‘White text with alpha’ and your texture format as ‘png – Portable Network Graphics’

      If that doesn’t work let me know and I’ll look into it some more.

  • Kenneth

    Thx for the quick answer. It doesn’t help to change the Presets to “White text with alpha”. If I set it and press ok, and go back into export setting its set as Custom again.
    It still has the black outline.

  • Kenneth

    The front you provided works fine. So It must be a problem with my BMFont, No matter how my setting are I only get png’s with black background.

  • Jared

    Most excellent, thank you! Been having quite a bit of trouble getting DrawText to work until I stumbled upon this gem.

    • http://www.craftworkgames.com craftworkgames

      Thanks. I’ve been thinking about doing some more tutorials. Any suggestions?

  • Bullock86

    Thanks for the post, I have one question, how are you handling big fonts ? (for example 64px). It will create more than one texture for the same font. Is there a way to output all in a single texture?

    • http://www.craftworkgames.com craftworkgames

      Yes, that’s right. Currently the font loader only supports one texture. You can change the texture output size in the export options of BMFont to make sure it all fits. It can take a little tweaking to get right.

      You can also exclude characters you don’t need to save space by clicking them in the BMFont main interface. The grayed out characters will not be exported.

      • Bullock86

        Yes I understand, thanks for the info, this post helped me a lot.

  • Moses

    There is error popped to [Serializable] with the “type or namespace can’t be found” message. Any idea?

    • Moses

      Thanks for the post btw.

    • http://www.craftworkgames.com craftworkgames

      The Serializable attribute is used to mark a class as serializable. According to the documentation says that “The common language runtime throws SerializationException if any type in the graph of objects being serialized does not have the SerializableAttribute attribute applied.”. However, the comments at the bottom indicate that the XML Serializer doesn’t use the attribute and I can confirm that removing the attribute doesn’t break the code.

      So just remove it and it should work as expected.

  • http://beyondmindless.com/bmd/ Noctys

    Awesome; thanks so much – I really did not want to have to use the XNA pipline in my game; this saved me!

    • http://www.craftworkgames.com craftworkgames

      Your welcome. That’s exactly why I created it in the first place. Probably should have said that in the original post :)

  • Ben Ranson

    Thanks for this. I was earlier using Monogame’s SpriteFont which was looking utterly horrible. Now thanks to your guide, I have beautifully rendered text in my Windows game!

    • Dylan Wilson (@craftworkgames)

      You’re welcome.

  • Bjarne Pedersen

    I were just wondering if you have a more complete FontRendere?
    If you dont, then ill just expand it by myself :)

    And it sure is nice to work with, MonoGame surely need som font rendering

  • Eko Suprapto Wibowo

    I am pretty much prepare that building a game will be very complicated, even for a –seems to be– simple stuff like printing text.

    Thanks to this tutorial, I made it.
    But, how do you make the background of the font transparent?
    I end up with this dark background text.

    • Dylan Wilson (@craftworkgames)

      There are a couple of things involved in making the text transparent. First, you’ll want to make sure the PNG font texture file has transparency enabled. Open it up in Paint.NET or something to confirm.

      Next, check the parameters to the _spriteBatch.Begin call. The BlendState is most important here, I’ve set mine to BlendState.NonPremultiplied because I’m using raw PNG files (rather than pre-compiled XNB files).

  • Jesse

    If you’re still watching this post, my BMFont seems to be defaulting to “Custom”, everytime I select “White text with alpha”. Why is this? I am having the white text but with black background symptoms.

    • Dylan Wilson (@craftworkgames)

      I have seen it default to custom before. I don’t think it’s a problem. Here are my settings, hopefully that will help.