2
\$\begingroup\$

I want to display 2D text in Unity using bitmap fonts (.fnt files). There are some solutions out there, but I'm writing my own. Each character is a game object with a SpriteRenderer.

It works. However, there is a small offset problem.

enter image description here

The o, g and 3 characters are badly positioned. This is the code that makes this:

    Vector3 cursor =    new Vector3 (0, 0, 0);  // Cursor for positioning the characters

    for (int i = 0; i < text.Length; ++i) {
        char    c       = text[i];
        Sprite  sprite  = spriteForCharacter(c);
        GameObject obj  = new GameObject();
        obj.AddComponent<SpriteRenderer>().sprite = sprite;
        obj.transform.localPosition     = new Vector3(cursor.x + xOffset(c), cursor.y, 0);
        cursor += new Vector3(xAdvance(c),0,0);
    }

spriteForCharacter(char) returns a Sprite object that is created by making a Texture2D with the dimensions of the character (according to the information in .fnt file), then copying the pixels of the .PNG bitmap file to it, and finally creating the sprite with Sprite.Create(). Here's the relevant part:

Texture2D tex = new Texture2D(characterWidth, characterHeight);
Color[] pixels = bitmap.texture.GetPixels(character.x, bitmap.texture.height - characterY - characterHeight,characterWidth,characterHeight);
tex.SetPixels(pixels);
tex.Apply();

xOffset(char) and yOffset(char) simply return the xOffset and yOffset values for this character as read in the .fnt file.


If you look at the code, you will notice that I never used yOffset(char). I obviously should use it, but look what happens:

If I add the offset:

obj.transform.localPosition = new Vector3(cursor.x + xOffset(c), cursor.y + yOffset(c), 0);

enter image description here

If I subtract the offset:

obj.transform.localPosition = new Vector3(cursor.x + xOffset(c), cursor.y - yOffset(c), 0);

enter image description here

I'm at a loss: I'm not sure what to do wit hthe yOffset property.


Yes, I've confirmed that the yOffset property is read from the .fnt file correctly. If anything, all the lines in the .fnt file were parsed appropriately.


Here I submit the .fnt file and its bitmap - both created with Hiero

enter image description here

info face="Arial-Black" size=18 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=1,1,1,1 spacing=0,0
common lineHeight=28 base=20 scaleW=256 scaleH=128 pages=1 packed=0
page id=0 file="SimpleFont.png"
chars count=94
char id=32   x=0     y=0     width=0     height=0     xoffset=0     yoffset=20    xadvance=8     page=0  chnl=0 
char id=125   x=0     y=0     width=10     height=21     xoffset=-1     yoffset=5    xadvance=9     page=0  chnl=0 
char id=123   x=10     y=0     width=10     height=21     xoffset=-1     yoffset=5    xadvance=9     page=0  chnl=0 
char id=41   x=20     y=0     width=10     height=21     xoffset=0     yoffset=5    xadvance=9     page=0  chnl=0 
char id=40   x=30     y=0     width=10     height=21     xoffset=0     yoffset=5    xadvance=9     page=0  chnl=0 
char id=64   x=40     y=0     width=18     height=20     xoffset=-1     yoffset=5    xadvance=15     page=0  chnl=0 
char id=124   x=58     y=0     width=6     height=20     xoffset=0     yoffset=6    xadvance=7     page=0  chnl=0 
char id=93   x=64     y=0     width=9     height=20     xoffset=-1     yoffset=6    xadvance=9     page=0  chnl=0 
char id=91   x=73     y=0     width=9     height=20     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 
char id=106   x=82     y=0     width=9     height=20     xoffset=-2     yoffset=6    xadvance=8     page=0  chnl=0 
char id=36   x=91     y=0     width=15     height=19     xoffset=-1     yoffset=5    xadvance=14     page=0  chnl=0 
char id=81   x=106     y=0     width=18     height=19     xoffset=0     yoffset=5    xadvance=17     page=0  chnl=0 
char id=38   x=124     y=0     width=18     height=18     xoffset=0     yoffset=5    xadvance=18     page=0  chnl=0 
char id=35   x=142     y=0     width=15     height=18     xoffset=0     yoffset=5    xadvance=14     page=0  chnl=0 
char id=37   x=157     y=0     width=21     height=18     xoffset=0     yoffset=5    xadvance=20     page=0  chnl=0 
char id=92   x=178     y=0     width=9     height=18     xoffset=-1     yoffset=5    xadvance=7     page=0  chnl=0 
char id=47   x=187     y=0     width=9     height=18     xoffset=-1     yoffset=5    xadvance=7     page=0  chnl=0 
char id=48   x=196     y=0     width=15     height=18     xoffset=0     yoffset=5    xadvance=14     page=0  chnl=0 
char id=57   x=211     y=0     width=15     height=18     xoffset=0     yoffset=5    xadvance=14     page=0  chnl=0 
char id=56   x=226     y=0     width=15     height=18     xoffset=0     yoffset=5    xadvance=14     page=0  chnl=0 
char id=54   x=0     y=21     width=15     height=18     xoffset=0     yoffset=5    xadvance=14     page=0  chnl=0 
char id=51   x=15     y=21     width=15     height=18     xoffset=0     yoffset=5    xadvance=14     page=0  chnl=0 
char id=83   x=30     y=21     width=16     height=18     xoffset=0     yoffset=5    xadvance=15     page=0  chnl=0 
char id=79   x=46     y=21     width=18     height=18     xoffset=0     yoffset=5    xadvance=17     page=0  chnl=0 
char id=71   x=64     y=21     width=17     height=18     xoffset=0     yoffset=5    xadvance=17     page=0  chnl=0 
char id=67   x=81     y=21     width=17     height=18     xoffset=0     yoffset=5    xadvance=16     page=0  chnl=0 
char id=59   x=98     y=21     width=7     height=17     xoffset=0     yoffset=9    xadvance=8     page=0  chnl=0 
char id=63   x=105     y=21     width=14     height=17     xoffset=0     yoffset=5    xadvance=13     page=0  chnl=0 
char id=53   x=119     y=21     width=15     height=17     xoffset=0     yoffset=6    xadvance=14     page=0  chnl=0 
char id=52   x=134     y=21     width=15     height=17     xoffset=-1     yoffset=5    xadvance=14     page=0  chnl=0 
char id=50   x=149     y=21     width=15     height=17     xoffset=-1     yoffset=5    xadvance=14     page=0  chnl=0 
char id=49   x=164     y=21     width=11     height=17     xoffset=0     yoffset=5    xadvance=14     page=0  chnl=0 
char id=121   x=175     y=21     width=15     height=17     xoffset=-1     yoffset=9    xadvance=13     page=0  chnl=0 
char id=116   x=190     y=21     width=11     height=17     xoffset=-1     yoffset=6    xadvance=10     page=0  chnl=0 
char id=113   x=201     y=21     width=14     height=17     xoffset=0     yoffset=9    xadvance=14     page=0  chnl=0 
char id=112   x=215     y=21     width=14     height=17     xoffset=0     yoffset=9    xadvance=14     page=0  chnl=0 
char id=103   x=229     y=21     width=14     height=17     xoffset=0     yoffset=9    xadvance=14     page=0  chnl=0 
char id=102   x=243     y=21     width=11     height=17     xoffset=-1     yoffset=5    xadvance=9     page=0  chnl=0 
char id=100   x=0     y=39     width=14     height=17     xoffset=0     yoffset=6    xadvance=14     page=0  chnl=0 
char id=98   x=14     y=39     width=14     height=17     xoffset=0     yoffset=6    xadvance=14     page=0  chnl=0 
char id=85   x=28     y=39     width=16     height=17     xoffset=0     yoffset=6    xadvance=17     page=0  chnl=0 
char id=74   x=44     y=39     width=14     height=17     xoffset=-1     yoffset=6    xadvance=14     page=0  chnl=0 
char id=33   x=58     y=39     width=7     height=16     xoffset=0     yoffset=6    xadvance=8     page=0  chnl=0 
char id=55   x=65     y=39     width=15     height=16     xoffset=0     yoffset=6    xadvance=14     page=0  chnl=0 
char id=108   x=80     y=39     width=7     height=16     xoffset=0     yoffset=6    xadvance=8     page=0  chnl=0 
char id=107   x=87     y=39     width=15     height=16     xoffset=0     yoffset=6    xadvance=14     page=0  chnl=0 
char id=105   x=102     y=39     width=7     height=16     xoffset=0     yoffset=6    xadvance=8     page=0  chnl=0 
char id=104   x=109     y=39     width=13     height=16     xoffset=0     yoffset=6    xadvance=14     page=0  chnl=0 
char id=90   x=122     y=39     width=16     height=16     xoffset=-1     yoffset=6    xadvance=15     page=0  chnl=0 
char id=89   x=138     y=39     width=18     height=16     xoffset=-1     yoffset=6    xadvance=16     page=0  chnl=0 
char id=88   x=156     y=39     width=18     height=16     xoffset=-1     yoffset=6    xadvance=16     page=0  chnl=0 
char id=87   x=174     y=39     width=23     height=16     xoffset=-1     yoffset=6    xadvance=20     page=0  chnl=0 
char id=86   x=197     y=39     width=18     height=16     xoffset=-1     yoffset=6    xadvance=16     page=0  chnl=0 
char id=84   x=215     y=39     width=16     height=16     xoffset=-1     yoffset=6    xadvance=15     page=0  chnl=0 
char id=82   x=231     y=39     width=17     height=16     xoffset=0     yoffset=6    xadvance=16     page=0  chnl=0 
char id=80   x=0     y=56     width=15     height=16     xoffset=0     yoffset=6    xadvance=15     page=0  chnl=0 
char id=78   x=15     y=56     width=16     height=16     xoffset=0     yoffset=6    xadvance=17     page=0  chnl=0 
char id=77   x=31     y=56     width=18     height=16     xoffset=0     yoffset=6    xadvance=19     page=0  chnl=0 
char id=76   x=49     y=56     width=14     height=16     xoffset=0     yoffset=6    xadvance=14     page=0  chnl=0 
char id=75   x=63     y=56     width=17     height=16     xoffset=0     yoffset=6    xadvance=17     page=0  chnl=0 
char id=73   x=80     y=56     width=8     height=16     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 
char id=72   x=88     y=56     width=16     height=16     xoffset=0     yoffset=6    xadvance=17     page=0  chnl=0 
char id=70   x=104     y=56     width=14     height=16     xoffset=0     yoffset=6    xadvance=14     page=0  chnl=0 
char id=69   x=118     y=56     width=15     height=16     xoffset=0     yoffset=6    xadvance=15     page=0  chnl=0 
char id=68   x=133     y=56     width=16     height=16     xoffset=0     yoffset=6    xadvance=16     page=0  chnl=0 
char id=66   x=149     y=56     width=16     height=16     xoffset=0     yoffset=6    xadvance=16     page=0  chnl=0 
char id=65   x=165     y=56     width=18     height=16     xoffset=-1     yoffset=6    xadvance=16     page=0  chnl=0 
char id=62   x=183     y=56     width=14     height=15     xoffset=0     yoffset=7    xadvance=14     page=0  chnl=0 
char id=60   x=197     y=56     width=14     height=15     xoffset=0     yoffset=7    xadvance=14     page=0  chnl=0 
char id=43   x=211     y=56     width=13     height=14     xoffset=0     yoffset=7    xadvance=14     page=0  chnl=0 
char id=117   x=224     y=56     width=13     height=14     xoffset=0     yoffset=9    xadvance=14     page=0  chnl=0 
char id=115   x=237     y=56     width=14     height=14     xoffset=-1     yoffset=9    xadvance=13     page=0  chnl=0 
char id=111   x=0     y=72     width=15     height=14     xoffset=0     yoffset=9    xadvance=14     page=0  chnl=0 
char id=101   x=15     y=72     width=15     height=14     xoffset=0     yoffset=9    xadvance=14     page=0  chnl=0 
char id=99   x=30     y=72     width=15     height=14     xoffset=0     yoffset=9    xadvance=14     page=0  chnl=0 
char id=97   x=45     y=72     width=15     height=14     xoffset=0     yoffset=9    xadvance=14     page=0  chnl=0 
char id=58   x=60     y=72     width=7     height=13     xoffset=0     yoffset=9    xadvance=8     page=0  chnl=0 
char id=122   x=67     y=72     width=13     height=13     xoffset=-1     yoffset=9    xadvance=12     page=0  chnl=0 
char id=120   x=80     y=72     width=15     height=13     xoffset=-1     yoffset=9    xadvance=14     page=0  chnl=0 
char id=119   x=95     y=72     width=21     height=13     xoffset=-1     yoffset=9    xadvance=19     page=0  chnl=0 
char id=118   x=116     y=72     width=15     height=13     xoffset=-1     yoffset=9    xadvance=13     page=0  chnl=0 
char id=114   x=131     y=72     width=11     height=13     xoffset=0     yoffset=9    xadvance=10     page=0  chnl=0 
char id=110   x=142     y=72     width=13     height=13     xoffset=0     yoffset=9    xadvance=14     page=0  chnl=0 
char id=109   x=155     y=72     width=19     height=13     xoffset=0     yoffset=9    xadvance=20     page=0  chnl=0 
char id=61   x=174     y=72     width=13     height=12     xoffset=0     yoffset=8    xadvance=14     page=0  chnl=0 
char id=94   x=187     y=72     width=13     height=12     xoffset=0     yoffset=5    xadvance=14     page=0  chnl=0 
char id=42   x=200     y=72     width=11     height=11     xoffset=1     yoffset=5    xadvance=12     page=0  chnl=0 
char id=44   x=211     y=72     width=7     height=11     xoffset=0     yoffset=15    xadvance=8     page=0  chnl=0 
char id=126   x=218     y=72     width=14     height=8     xoffset=0     yoffset=10    xadvance=14     page=0  chnl=0 
char id=39   x=232     y=72     width=8     height=8     xoffset=0     yoffset=6    xadvance=7     page=0  chnl=0 
char id=34   x=240     y=72     width=12     height=8     xoffset=-1     yoffset=6    xadvance=11     page=0  chnl=0 
char id=45   x=0     y=86     width=9     height=7     xoffset=-1     yoffset=12    xadvance=8     page=0  chnl=0 
char id=46   x=9     y=86     width=7     height=7     xoffset=0     yoffset=15    xadvance=8     page=0  chnl=0 
char id=96   x=16     y=86     width=8     height=7     xoffset=-1     yoffset=5    xadvance=8     page=0  chnl=0 
char id=95   x=24     y=86     width=14     height=5     xoffset=-1     yoffset=20    xadvance=11     page=0  chnl=0 

I couldn't help but notice that of the three o's, only one is off. Now I am even more puzzled. Thanks to Steven it seems like the three o's are actually aligned.

\$\endgroup\$
4
+100
\$\begingroup\$

The reason you are getting this behaviour is because of coordinate system. The base coordinate for your sprites is "left,bottom" (classic Cartesian) however all offsets in fnt files are assuming inverted Y axis (drawing from "left,top" of the sprite).

So instead of (you may notice this gives correct align, but on the top, i.e. g goes up instead of down):

obj.transform.localPosition = new Vector3(cursor.x + xOffset(c), cursor.y + yOffset(c), 0);

you should do:

var baseLine = 20; // you should get baseline from fnt file
var y = cursor.y;
// first put it into assumed by fnt position
y += baseLine - charHeight(c); // charHeight must return character height
// now apply offset, using '-' instead of '+' because of our coordinates
y -= yOffset(c);

obj.transform.localPosition = new Vector3(cursor.x + xOffset(c), y, 0);

I'm not 100% sure that I'm correct since I can't test it, but I think solution lies somethere close to this. Hope that helps!

| improve this answer | |
\$\endgroup\$
3
\$\begingroup\$

I can't add a comment because my reputation isn't high enough, so this isn't really an answer.

I thought the o's were offset differently as well, but when I used a ruler it turns out all three o's are on the same baseline. It is just an optical illusion from the dropped 'm'.

In you code sample for extracting the glyphs from the font texture you use the variable character.x, but then you use a variable characterY - what's the value of that, and why not just use character.y directly?

Let's look at the font data.

Here is the entry for 3: char id=53 x=119 y=21 width=15 height=17 xoffset=0 yoffset=6 xadvance=14 page=0 chnl=0

And here is the entry for 1: char id=49 x=164 y=21 width=11 height=17 xoffset=0 yoffset=5 xadvance=14 page=0 chnl=0

The two glyphs are in the same row, and vertically aligned with each other, but the offset is off by one. This seems to match your rendering.

Are you using the baseline and line height properties as well? I don't know about .fnt but you might want your Y offset to be equal to lineheight - baseline + yoffset. Maybe that's a better calculation?

What anchor points are you using for positioning the glyphs? Top right, bottom left? If all the glyph textures are different heights, you might need to subtract the height of the glyph first before applying the yoffset.

Just my observations.

| improve this answer | |
\$\endgroup\$
3
\$\begingroup\$

It seems that something is going wrong when you create the textures for the individual characters. First, if we look closely, we see that it's not just the o, g and 3 characters are badly positioned:

the first image from the OP with a red baseline inserted to illustrate jumping characters

As Petr Abdulin points out:

The base coordinate for your sprites is "left,bottom" (classic Cartesian)

which is correct. Your renderer assumes left,top coordinate system which is part of the problem here. However, something else must be wrong in your code in addition to that because otherwise you should actually get something like this with the code you pasted:

all characters are rendered top-aligned

Here, all letters are top-aligned. However, since yours doesn't look like that, I suspect that something is not quite right with your sprite/texture computation.

For instance, I don't quite understand what exactly you're doing here:

Color[] pixels = bitmap.texture.GetPixels(character.x, bitmap.texture.height - characterY - characterHeight,characterWidth,characterHeight);

If we look at the font spec file, we see that every character gives the coordinates of its sub-bitmap directly, e.g., for the lower-case g we have:

char id=103   x=229     y=21     width=14     height=17     xoffset=0     yoffset=9    xadvance=14     page=0  chnl=0

The coordinates x, y, width, height refer directly to the sub-region of the bitmap, so I would have expected your code to look something like this:

int x = x(c)
int y = y(c)
int width = width(c)
int height = height(c)

Color[] pixels = bitmap.texture.GetPixels(x, y, width, height);

Example of how character coordinates in font spec file reference subregions of the bitmap

Note that there is no subtraction from bitmap.texture.height.

My suggestion is that you try to find out why your first image looks different. Once you fixed that, it's easy to achieve the actually correct rendering by adding yoffset to the y-coordinate, like you tried before.

correctly rendered image

| improve this answer | |
\$\endgroup\$
0
\$\begingroup\$

I think it would be useful for you to try this program "bmglyph" http://www.bmglyph.com/ You can generate a character set with the font you like and export the font atlas in unity native format and in Cocos2D format (the format used by NGUI). Then you can look at how the program generate the .fnt and match it against what you already have and see if you are missing something. I used it in several project and it was plug and play. Hope it helps.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.