Developing Multiplatform Game with Libgdx, part 10: adding fonts!

Adding a Font

So, our player has lives and he can lose the game. But he has no way to find out the lives left and when the game ends – everything just closes. Not cool. We’ll fix it today.

The simplest thing to fonts libgdx has to offer is BitmapFont class. Without paramters, it will simply show arial 12 font. The problem? Resizing that font. Our game cannot open ttf files, so the texture with font needs to be generated first. BitmapFont is going to use that texture, but it needs to be prepared beforehand. To do this, we are going to use Hiero. I’m downloading it here: https://libgdx.badlogicgames.com/tools.html

Make sure to pick a ttf font you want first. For pixel-art styled games, I normally use http://www.pentacom.jp/pentacom/bitfontmaker2/gallery/ to get a freely available font. I’m picking up Megaman10 by YahooXD: http://www.pentacom.jp/pentacom/bitfontmaker2/gallery/?id=528 (public domain). Download it to your computer, save it wherever you want to (but I usually save it in the project assets folder, separate from the one that is being packed).

Run the downloaded runnable Hiero. Open your font file, and pick the size you want. For out case, since the game is really low-resolution, I think 16px will be more than enough. I usually set padding to 2 (to have some extra space between letters) and use rendering type: java.

After that – go to “File” -> Save BMFont files. Name it gamefont and put the files into android/assets folder, since this is the one that is used for resource loading by default. There should now be two files: fnt file (which is a font descriptor) and png file, which is actually a texture of the font.

If you are going to use the same font throughout the game – I think it’s a good idea to put it into Resources class too:

public BitmapFont gamefont;

Initialize it in Resources constructor:

gamefont = new BitmapFont(Gdx.files.internal("gamefont.fnt"),
        Gdx.files.internal("gamefont.png"), false);

To test it, go to our GameScreen and add DrawUI function, which will draw our font at top of the screen.

private void DrawUi()
{
    batch.begin();
    game.res.gamefont.draw(batch,
            "LIVES: " + player.getLives(),
            5,
            gameStage.getViewport().getScreenY() + gameStage.getHeight());
    batch.end();
}

The thing with font is… It’s drawn from top to bottom, a complete opposite of what we have when drawing textures. I can’t explain why it is (if you can – please comment!). So in our case, when we want to draw it on top of the screen, we get the screen Y position (in case not all height is covered) and add an actual stage height. Add the DrawUI call inside our render function, right before our gamestage.draw() call.

@Override
public void render (float delta) {
// …
// old code here, not copying it
// …
    DrawUi();
    gameStage.draw();
}

Run the game. Great, now we can actually see how our lives are being deducted. What about the endgame? What I think is important – it to show the message that the player lost instead of quitting the game (sure, that’s the only thing left for our player to do now, but not for long).

Remove this part from our GameLogic:

if (player.getLives() <= 0)
{
    Gdx.app.exit();
}

Our OnEffectOver(WarningEffect effect) should look like this:

public void OnEffectOver(WarningEffect effect) {
    if (effect.getFieldY() == player.getFieldY() &&
        effect.getFieldX() == player.getFieldX())
    {
        player.takeDamage(1);
    }
}

Good. Now let’s get back to our GameScreen. We need to do two things: disable update and player controls after player died (so that player cannot move / monsters cannot attack).

Alter our AttemptMove function:

public void AttemptMove(int dx, int dy)
{
    if (player.getLives() > 0 &&
            logic.CheckMove(player.getFieldX() + dx, player.getFieldY() + dy))
    {
        logic.AssignPlayerPosition(player.getFieldX() + dx, player.getFieldY() + dy);
    }
}

After that – check our update call. Stop updating logic on player death, it is unnecessary.

private void update(float delta)
{
    gameStage.act(delta);
    if (player.getLives() > 0)
    {
        logic.update(delta);
    }
}

Finally, modify our DrawUI function to write “DEFEAT” after player has died.

if (player.getLives() <= 0)
{
    game.res.gamefont.setColor(1, 0, 0, 1);
    game.res.gamefont.draw(batch,
            "DEFEAT",
            0,
            gameStage.getViewport().getScreenY() + gameStage.getWidth() / 2,
            gameStage.getWidth(), Align.center, false);
    game.res.gamefont.setColor(1, 1, 1, 1);
}

We set font color to red, then we show 0 as X coordinate and take stageWidth as targetWidth because the writing will automatically be aligned to center. After that, we switch font color back to white (don’t forget this step!)

If you run the game now, you can see that both ‘lives’ and ‘defeat’ text are poorly visible. How can we fix this? One way to do this is to add a black border. Let’s make a new function: drawShadowed(x, y, width, align, color)

Our function will draw the background of our word in black and then will overlap the background with the word of chosen color.

private void DrawShadowed(String str, float x, float y, float width, int align, Color color)
{
    game.res.gamefont.setColor(Color.BLACK);
    for (int i = -1; i < 2; i++)
    {
        for (int j = -1; j < 2; j++)
        {
            game.res.gamefont.draw(batch, str, x + i, y + j, width, align, false);
        }
    }

    game.res.gamefont.setColor(color);
    game.res.gamefont.draw(batch, str, x, y, width, align, false);
    game.res.gamefont.setColor(Color.WHITE);
}

After that, change our DrawUI to actually draw shadowed text:

private void DrawUi()
{
    batch.begin();
    DrawShadowed("LIVES:" + player.getLives(),
            5,
            gameStage.getViewport().getScreenY() + gameStage.getHeight() - 3, // add some vertical padding
            gameStage.getWidth(),
            Align.left,
            Color.WHITE);

    if (player.getLives() <= 0)
    {
        DrawShadowed("DEFEAT!",
                0,
                gameStage.getViewport().getScreenY() + gameStage.getWidth() / 2,
                gameStage.getWidth(),
                Align.center,
                Color.RED);
    }

    batch.end();
}

Run the game! Everything should be bordered now. Now we can lose with style!

Lose with style!

Relevant commit: https://github.com/vladimirslav/dodginghero/commit/14d7cf194a9d992ea7a6753662f0ac4685879911

Leave a Reply

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