RPG Player Types.

This is the third article in a series that takes the RPG from the "How to Make an RPG" project and adds a little more polish to one element.

In the previous polish phase we updated the numbers in the game to use bitmap fonts, so they're much sharper and easier to read.

In this polish phase we're going to:

  • Add chunkier damage and heal numbers
  • Use custom sprites for attack events (miss, dodge etc)

Here's a comparison of the changes.

Tweaked combat numbers.

It looks much crisper natively. I failed to reproduce it perfectly using a gif :(.

Reference

Here's a screenshot of the Final Fantasy 7 combat state.

Final Fantasy 7 Combat Font.

There are two different number fonts here: one on the battle field and one in the dialog box at the bottom.

The '4' numeral from each font is enlarged on the left.

Top '4'

The top '4' is a jumping number; a number that appears to jump out from the head of a character.

  • 8x11 pixels
  • Full black border
  • Easy to read on any background

Being easy to read on any background is important because this number appears on the field of combat and the background could be any color.

Bottom '4'

The bottom '4' font is used to display the current health and mana.

  • Smaller at 7x8 pixels
  • Drop shadow
  • Displayed inside a dialog box

The smaller size allows more data to be squeezed into the combat panel. The panel background is known so a drop shadow is fine.

Bitmap Font Engine

Dinodeck has a true-type text rendering library. It's hard to make true-type fonts render crisply at low resolutions therefore I wrote a second font rendering library called BitmapFont.lua.

The BitmapFont library uses the same interface as the Dinodeck library. I've been hacking on this code for a while and this layer of polish uses the most recent version. You can always get the cutting edge code here. Since the last update, non-monospaced fonts are now supported.

What Are We Going to Change?

We'll update all numbers on the field of combat as well some of the status text.

Here is how it looks right now.

The current combat number.

The current dodge indicator.

There are a couple of things to note in these images:

  • Numbers / Text isn't very readable
  • Numbers are monospaced, so the gap between the '1' digit is too big
  • Dropshadow instead of an outline
  • The top two enemies are slightly blurred as they're not aligned correctly to the pixel grid

Let's polish up these issues!

Sourcing a Replacement Font

The image below shows the combat font we'll be using. I drew this pixel by pixel but it's based very closely on the Final Fantasy font. (If you have a similar font with a permissive license, let me know and I'll be happy to promote it!).

The current dodge indicator.

Numbers spacing is handled in the definition file.

My pixeling skill aren't very advanced. It would be better with:

  • Nicer letter shapes
  • Larger glyphs to better suit the resolution.

Code Changes

Code changes are restricted to the combat state and a few classes used by the combat state.

Adding The Font

To add new combat damage numbers we need to add a new font.

Bitmap fonts are made up of two files - an image file and a Lua file that describes the glyphs. The two files we're adding are:

  • damage_font.png
  • DamageFontDef.lua

These files are added to /art/font and /code/font directories.

I've also updated BitmapText.lua with the most recent version of the code.

Storing Fonts

At this point we have two fonts; a general text font and this new combat font. Let's put the font objects somewhere together so they're easier to manage.

Here's how the current font is stored:

gNumberFont = BitmapText:Create(NumberFontDef)

function SetupNewGame()
    gStack = StateStack:Create()
    gWorld = World:Create()

We'll modify this to store all fonts under a global gGame.Font table. While we're there let's add the gStack and gWorld variables under the same table. This reduces the amount of noise in the global space.

gGame =
{
    Font =
    {
        default = BitmapText:Create(NumberFontDef),
        damage = BitmapText:Create(DamageFontDef)
    },
    Stack = {},
    World = {}
}

function SetupNewGame()
    gGame.Stack = StateStack:Create()
    gGame.World = World:Create()

These polish phases cater primarily to the end user but here I'm also doing a bit of code clean up.

Moving the gWorld and gStack variables isn't just a local change. I've updated all code that accesses these variables as well as the save system.

Both bitmap fonts are now easily accessible, so let's move on.

An Aside: Fixing up Blurry Enemies

In the screenshots the goblin sprites were blurred because they weren't on the pixel boundaries. This can be fixed by tweaking the CombatState code as below:

function CombatState:CreateCombatCharacters(side)

    -- code omitted
    local x = math.floor(pos:X() * System.ScreenWidth())
    local y = math.floor(pos:Y() * System.ScreenHeight())
    char.mEntity.mSprite:SetPosition(x, y)
    char.mEntity.mX = x
    char.mEntity.mY = y

The math.floor functions are new here.

Updating the Combat Font

The font for the combat numbers is set in JumpingNumbers.lua here's the current implementation.

function JumpingNumbers:Render(renderer)

    local x = self.mX
    local y = math.floor(self.mCurrentY)
    local n = tostring(self.mNumber)


    local font = gGame.Font.default
    font:AlignText("center", "center")
    font:DrawText2d(renderer, x + 1, y - 1, n, Vector.Create(0,0,0, self.mColor:W()))
    font:DrawText2d(renderer, x, y, n, self.mColor)

end

And the fix is to change this line from

local font = gGame.Font.default

To

local font = gGame.Font.damage

That's it for the numbers.

The improved font numbers in action.

This screenshot shows the nicer chunkier damage font, no longer monospaced.

Speed Tweek

The damage numbers move quickly, which is good; it gives a nice fast pace, but the number can be tricky to read.

To address this, I've added a 0.2 second pause when the number reaches the peak of it's ascent.

Attack Event Text

Numbers are done, it's time to move on to the attack events.

Text is controlled by CombatTextFx. It displays text using the Dinodeck text calls. We're going to replace the text with sprites cut from the damage_number.png file. To do this we'll make a new class: CombatSpriteFx.

The event sprites are stored in damage_font.png. We'll add a file called DamageSpriteDef.lua to describe how to cut the sprites out of this atlas. This file will live in the same directory as DamageFontDef.lua.

-- DamageSpriteDef.lua
DamageSpriteDef =
{
    texture = "damage_font.png",
    -- Given in pixels
    sprites =
    {
        ["miss"] =
        {
            x = 80,
            y = 0,
            width = 24,
            height = 14
        },
        ["counter"] =
        {
            x = 104,
            y = 0,
            width = 47,
            height = 14
        },
        ["dodge"] =
        {
            x = 152,
            y = 0,
            width = 37,
            height = 14,
        }
    }
}

We'll create and load these sprites under the Game.Font table as damageSprite. The definition contains the pixel coordinates of each sprite. We need a couple of util functions to transform the def into sprites.

function PixelCoordsToUVs(tex, def)
    local texWidth = tex:GetWidth()
    local texHeight = tex:GetHeight()

    local x = def.x / texWidth
    local y = def.y / texHeight
    local width = def.width / texWidth
    local height = def.height / texHeight

    return {x, y, x + width, y + height}

end

function CreateSpriteSet(def)
    local texture = Texture.Find(def.texture)
    local spriteSet = {}
    for k, v in pairs(def.sprites) do
        local sprite = Sprite.Create()
        sprite:SetTexture(texture)
        sprite:SetUVs(unpack(PixelCoordsToUVs(texture, v)))
        spriteSet[k] = sprite
    end
    return spriteSet
end

Here's how the sprites are loaded in the main file.

gGame =
{
    Font =
    {
        -- code omitted
        damageSprite = CreateSpriteSet(DamageSpriteDef)
    },

After creating the sprites we need to create a new special effect to use them in combat.

The text effects are created by AddTextEffect, we'll add a new function call AddSpriteEffect. Here it is:

function CombatState:AddSpriteEffect(actor, sprite)
    local character = self.mActorCharMap[actor]
    local entity = character.mEntity
    local x = entity.mX
    local y = entity.mY
    local effect = CombatSpriteFx:Create(x, y, sprite)
    self:AddEffect(effect)
end

Finally let's update the code so the new sprite effect is called.

function CombatState:ApplyMiss(target)
    self:AddSpriteEffect(target,
        gGame.Font.damageSprite['miss'])
end

function CombatState:ApplyDodge(target)
    -- code omitted

    self:AddSpriteEffect(target,
        gGame.Font.damageSprite['dodge'])
end

The improved font numbers in action.

Here's the end result! Definitely a step towards better combat events.

Source

There's a github with an updated project here.