TGB Resource: Bitmap Fonts
by David Higgins · 01/20/2007 (11:28 am) · 10 comments
Bitmap Font for Torque Game Builder
This resource is yet another goofy one, and many may find it 'useless' with the addition of the t2dTextObject in the latest 1.1.3 release of TGB. I created this resource because someone said they'd love to have this functionality, due to some limitations in the t2dTextObject (not sure what they were referring too).
This resource sounded like a fun thing to do, so I did it, and I'm giving it to the community to play around with, extend and use in there projects.
So, onto the resource, eh?
This resource takes a bitmap file, any image format supported by the engine, which contains a series of tiles (256 to be exact) of any set size -- the only requirement is that the image be 16x16 tiles --
Here's an example image:

This image, as you can see, contains a limited subset of characters (all your standard printable characters -- no extended ascii)
You'll also notice, this image contains TWO (2) fonts. Each font is different, the first font, for example is not case-sensitive so the letters 'A' and 'a' are both drawn as an 'A' -- this is a limitation of the font itself, not the resource.
The second font, is case-sensitive, and draws 'A' and 'a' appropriately.
These bitmap files were generated using a tool called 'Bitmap Font Builder v1.9.9' -- it's free for non-commercial use (has a nag screen too, but for quick one-off's is great). For commercial registration, they simply ask for a donation of some sort.
The application has a "Character Set" menu, and the image above used the "NeHe Two-Font Texture (32-159 x2)" option.
I saved the bitmap as a BMP file, using 255,0,255 as my background color, then opened it in Photoshop and used the Wand tool to select the Cyan background, told it to select all other pixels with similiar color and then removed them to make the image transparent. I then saved the image as a PNG and imported it into TGB as "Untitled2ImageMap".
The use of this resource is quite simple, you load the bitmapFont.cs in your project somewhere, preferably in a location that will be loaded when the project loads (in your game.cs -- outside of a function call?) so that the BitmapFontDataBlock datablock is accessible in the Level Builder.
Create a new TileLayer, assign it the "BitmapFontDataBlock" datablock, set your defaults, such as Tile Size, Location, etc.
If you want to have a general idea as to how much space will be consumed by your strings, you can set the Tile Count X/Y values and position the Tile Layer in your Level so that it draws in the appropriate location -- you can even fill the TileLayer with tiles to help with visualizing the level layout.
Once you've placed the object, jump into your favorite code editor, I prefer Torsion, and add your logic to fill the Tile Layer with "words".
The resource supports multi-line text, and so you can populate the tilelayer's character grid from a text file or user input from a multi-line textbox as well. The only requirement is that multi-line text contain "\n" as the line seperator (does not support passing in arrays/sim[sets/groups] of strings, yet).
Here's some example code;
function updateScore(%score)
{
scoreObject.setText(%score);
}Sorry, was that too simple? Here's something more complex:
function updateScoreAndPlayerInfo(%player, %score)
{
scoreObject.setFont(0); // big fancy font for large numbers
scoreObject.setText(%score);
playerObject.setFont(1); // fancier script-like font
playerObject.setText(%player.Name);
echo("PLAYER NAME : " @ playerObject.getText());
echo("PLAYER SCORE: " @ scoreObject.getText());
}Ok, so I lied, it's not really more complex, just more repetitive.
That's it, there's a few functions exposed by the bitmapFont class, and all your default t2dTileLayer functions are also available.
The functions exposed by the bitmapFont class are as follows:
[b]Datablock[/b] BitmapFontDataBlock -- Datablock that assigns Defaults [b]Functions/Methods[/b] setImageMap(%this, %imageMap); getImageMap(%this); setText(%this, %text, %font); getText(%this); setFont(%this, %font); getFont(%this); %save(%this, %file); // wrapper to saveTileLayer sizeObjectToLayer(%this); // internal getCharacterFrame(%this, %char, %offset); // internal
Here are some additional screenshots:



And last, but not least, here's the code for the bitmapFont class, and the BitmapFontDataBlock:
/*
ONE QUICK NOTE: It appears that there is a small bug, and I don't think it is in the script
Description:
When you call 'setText('Aa')' with a Case-Sensitive font selected, it performs the strcmp()'s
properly and you see 'Aa' drawn on the screen
If you then call 'setText('aA')', after calling 'setText('Aa')' you will see 'Aa' drawn to the
screen -- this appears to be a logical flaw in the way the engine handles strings --
I have not dug around too much, but it appears as though the engine uses a String Table, and
does not use case-sensitive matching to determine if a string is already in the table or not.
Unfortuneately, this is an issue I can not resolve through TorqueScript -- or at least, I do
not know how -- Hopefully either GG or someone else in the community can supply a Patch for this.
*/
// bitmapFont.cs
new t2dSceneObjectDatablock(BitmapFontDataBlock)
{
// this defines the 'text' property
text = "";
// defines the class, so you can call "setText" and "getText"
class="bitmapFont";
characterMap = " !\"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_'abcdefghijklmnopqrstuvwxyz{|}~";
font = 0;
fontImageMap = Untitled2ImageMap;
};
function bitmapFont::setImageMap(%this, %imageMap)
{
%this.fontImageMap = %imageMap;
}
function bitmapFont::getImageMap(%this)
{
return %this.fontImageMap;
}
function bitmapFont::setText(%this, %text, %font) // %font is 0 or 1
{
%this.text = %text;
if(%font $= "") { %font = 0; } // default to first font
else { %this.font = %font; }
if(%this.isMemberOfClass("t2dTileLayer"))
{
//echo("Displaying Text");
//echo("Layer File: " @ %this.layerFile);
%this.clearLayer(); // clear all tiles
if(strpos(%text, "\n") >= 0) %multiLine = true;
else %multiLine = false;
if(%multiLine)
{
%lines = new SimSet() { }; // holds all of our "lines"
%prev = 0;
%next = strpos(%text, "\n", %prev);
while(%next >= 0)
{
//echo("Position: " @ %next);
%line = new ScriptObject() { text = getSubStr(%text, %prev, %next - %prev); };
%lines.add(%line);
%prev = %next + 1;
%next = strpos(%text, "\n", %next + 1);
}
if(strlen(%text) > %prev)
{
%line = new ScriptObject() { text = getSubStr(%text, %prev, strlen(%text) - %prev); };
%lines.add(%line);
}
%this.setTileCountY(%lines.getCount());
%lastx = 0;
%y = 0;
while(%lines.getCount() > 0)
{
%line = %lines.getObject(0);
%linex = strlen(%line.text);
if(%linex > %lastx) { %this.setTileCountX(%linex); %lastx = %linex; }
for(%x = 0; %x < %linex; %x++)
{
// go char by char and place on tilelayer
%frame = %this.getCharacterFrame(getSubStr(%line.text, %x, 1));
if(%frame < 0) %frame = 0;
%this.setStaticTile(%x, %y, %this.fontImageMap, %frame);
}
%lines.remove(%line);
%line.delete();
%y++;
}
%this.sizeObjectToLayer();
}
else
{
// handle a single line of text
%chars = strlen(%text);
%this.resizeLayer(%chars, 1); // set tile count
%this.sizeObjectToLayer(); // fit object to tiles
for(%x = 0; %x < %chars; %x++)
{
// go char by char and place on tilelayer
%frame = %this.getCharacterFrame(getSubStr(%text, %x, 1));
if(%frame < 0) %frame = 0;
%this.setStaticTile(%x, 0, %this.fontImageMap, %frame);
}
}
}
else
{
error("The object is not a member of the t2dTileLayer Class");
error("-- bitmapFont only works with t2dTileLayers");
}
}
function bitmapFont::setFont(%this, %font)
{
if(%font $= "") return;
if(%font > 1 || %font < 0) { error("bitmapFont only accepts 0 and 1 for 'font'"); return; }
%this.font = %font;
}
function bitmapFont::getFont(%this)
{
return %this.font;
}
function bitmapFont::getText(%this)
{
return %this.text;
}
function bitmapFont::save(%this, %file)
{
if(%this.isMemberOfClass("t2dTileLayer"))
{
%this.saveTileLayer(%file);
}
}
function bitmapFont::sizeObjectToLayer(%this)
{
if(!%this.isMemberOfClass("t2dTileLayer")) return;
// this was snagged from the TileLayerEditor code
%xSize = %this.getTileCountX() * %this.getTileSizeX();
%ySize = %this.getTileCountY() * %this.getTileSizeY();
%upperLeft = %this.getAreaMin();
%lowerRight = t2dVectorAdd( %upperLeft, %xSize SPC %ySize );
%this.setArea( %upperLeft, %lowerRight );
%this.setPosition( %this.getPosition() );
}
function bitmapFont::getCharacterFrame(%this, %char, %offset)
{
// character frames map directly to the charactermap string
//error("CHAR: " @ %char @ " --- OFFSET: " @ %offset);
if(%offset $= "") %offset = 0;
%frame = strpos(%this.characterMap, %char, %offset);
%chr = getSubStr(%this.characterMap, %frame, 1);
if(strcmp(%chr, %char) != 0)
{
// we grabbed the wrong case
return %this.getCharacterFrame(%char, %frame+1);
}
if(%frame < 0)
{
error("FRAME : " @ %frame);
error("CHAR : " @ %char);
error("OFFSET : " @ %offset);
}
return (%frame - 1) + (%this.font * 128);
}Simply copy the code, and save it as bitmapFont.cs somewhere in your project directory, and exec() it from whereever seems most appropriate to you.
Please note the 'disclaimer' in the code header about the case-sensitivity matching when passing strings you've used in the past with different case's --

About the author
#2
You're half right. It does use a string table. It is possible to have both case insensitive and case sensitive strings in the string table. The StringTable is used a *LOT* in Torque and the vast majority of those strings are case insensitive for reasons of memory usage. This includes everything that goes through script. In C++ you can specify whether the string is case sensitive with a second argument to insert() and lookup().
So, there is no "patch" and there likely isn't going to be. Firstly, it would cause the string table to eat significantly more memory and secondly it would likely break a number of things. In a networked game it could also cause unnecessary additional bandwidth usage in some circumstances.
So, your only option is to use C++. Arguably, doing something like bitmap fonts is far better done in C++ anyway. Doing it in script generally would have significantly more overhead then C++ in execution speed, rendering performance and memory usage.
T.
01/20/2007 (12:22 pm)
Dude, you should really be posting these as actual resources or on TDN, not as blogs.Quote:I have not dug around too much, but it appears as though the engine uses a String Table, and
does not use case-sensitive matching to determine if a string is already in the table or not.
Unfortuneately, this is an issue I can not resolve through TorqueScript -- or at least, I do
not know how -- Hopefully either GG or someone else in the community can supply a Patch for this.
You're half right. It does use a string table. It is possible to have both case insensitive and case sensitive strings in the string table. The StringTable is used a *LOT* in Torque and the vast majority of those strings are case insensitive for reasons of memory usage. This includes everything that goes through script. In C++ you can specify whether the string is case sensitive with a second argument to insert() and lookup().
So, there is no "patch" and there likely isn't going to be. Firstly, it would cause the string table to eat significantly more memory and secondly it would likely break a number of things. In a networked game it could also cause unnecessary additional bandwidth usage in some circumstances.
So, your only option is to use C++. Arguably, doing something like bitmap fonts is far better done in C++ anyway. Doing it in script generally would have significantly more overhead then C++ in execution speed, rendering performance and memory usage.
T.
#3
I post them as blogs, so people who may be interested in them see them as they are done, where as TDN Articles and 'Resources' tend to get lost in the mix of things. I do identify clearly in the subjects what the blog contains, and it's totally optional whether you wish to view it.
As far as the Bitmap Fonts in C++, your absolutely correct in the performance benefits of it, though this resource is intended for TGB, and simply utilizes the existing TileLayer and an ImageMap -- the resource is not intended to be used for drawing 'stories' to the screen, and the performance overhead is negligible in TGB as I don't expect anyone to update the tilelayer on an overly frequent basis -- though, it would be good to note that updating the tilelayer in the OnUpdateScene() callback would be DREADFUL and is NOT SUGGESTED (ie; for timers, etc -- only update it when you HAVE to).
I'd also like to mention, all the things I post are intended to be used as examples, and are usually not fully functional or read-to-use 'out of the box' without some work.
01/20/2007 (12:40 pm)
@Tom,I post them as blogs, so people who may be interested in them see them as they are done, where as TDN Articles and 'Resources' tend to get lost in the mix of things. I do identify clearly in the subjects what the blog contains, and it's totally optional whether you wish to view it.
As far as the Bitmap Fonts in C++, your absolutely correct in the performance benefits of it, though this resource is intended for TGB, and simply utilizes the existing TileLayer and an ImageMap -- the resource is not intended to be used for drawing 'stories' to the screen, and the performance overhead is negligible in TGB as I don't expect anyone to update the tilelayer on an overly frequent basis -- though, it would be good to note that updating the tilelayer in the OnUpdateScene() callback would be DREADFUL and is NOT SUGGESTED (ie; for timers, etc -- only update it when you HAVE to).
I'd also like to mention, all the things I post are intended to be used as examples, and are usually not fully functional or read-to-use 'out of the box' without some work.
#4
-Jeff Tunnell, GG
01/20/2007 (12:46 pm)
Please keep posting them as blogs, then add them to resources and TDN. Nice additions.-Jeff Tunnell, GG
#5
Editting Wiki documents, however, is quite time consuming, and though I may find a free hour here or there to whip together these oddball resources, I usually don't have the time, or the patience, to add them to TDN -- and I don't believe that the 'resources' section of the site is well organized, and most items placed there just disappear into the bottomless pit of resources.
I do, however, not mind one bit if someone wanted to create TDN articles for any of the code or resources I post to the community, and I would actually encourage it -- I only ask that if a TDN article is created from my works, that I be notified in some way of the articles existence so I can review it and link to it in future posts.
01/20/2007 (1:02 pm)
@Jeff, I've made a few additions to the TDN, for example, the High Scores object is on the TDN -- it was actually created because the TDN had it listed as a 'wishlist' item.Editting Wiki documents, however, is quite time consuming, and though I may find a free hour here or there to whip together these oddball resources, I usually don't have the time, or the patience, to add them to TDN -- and I don't believe that the 'resources' section of the site is well organized, and most items placed there just disappear into the bottomless pit of resources.
I do, however, not mind one bit if someone wanted to create TDN articles for any of the code or resources I post to the community, and I would actually encourage it -- I only ask that if a TDN article is created from my works, that I be notified in some way of the articles existence so I can review it and link to it in future posts.
#6
01/21/2007 (9:32 pm)
This is cool stuff. Good job David.
#7
01/22/2007 (2:59 am)
Nice one David :0)
#8
01/22/2007 (6:29 am)
Thanks
#9
With that, you could then create and store your maps in text format, rather then 'lyr' format, to allow for quick and easy modifications to your maps -- this would be useful for say something similiar to 'nethack' where the dungeons change constantly (nethack auto-generates its dungeons though) or for a game that allowed players to actually create there own maps -- as your not permitted to distribute the TGB Tile Map editor, you could write a custom program that used your games art resources and created simple text format maps from user input, and then stored them -- then used this resource to load and display your map.
Some tweaks here and there may be necessary to make it 'useful' for this purpose, just thought I'd open the door for the idea though.
01/28/2007 (8:55 am)
Something I realized the other day -- this resource can also be easily turned into a 'map creator' as well -- using a generic tile map, laid out in the same fashion as the font images, with actual game tiles for a dungeon, for example, each tile mapping to a specific character (using the font image as an overlay in photoshop or something could easily show you how to create your 'map legend').With that, you could then create and store your maps in text format, rather then 'lyr' format, to allow for quick and easy modifications to your maps -- this would be useful for say something similiar to 'nethack' where the dungeons change constantly (nethack auto-generates its dungeons though) or for a game that allowed players to actually create there own maps -- as your not permitted to distribute the TGB Tile Map editor, you could write a custom program that used your games art resources and created simple text format maps from user input, and then stored them -- then used this resource to load and display your map.
Some tweaks here and there may be necessary to make it 'useful' for this purpose, just thought I'd open the door for the idea though.

Associate David Higgins
DPHCoders.com
I might also note, a third or fourth font may also be possible, and again has been untested.
The logic for locating the second font is simply taking the character position and adding 128 to it.
So for example, the space (" ") character is frame 0, the space character for the second font is frame 128, so a third, fourth, fifth, etc font should be possible if you just keep the tiles in this arrangement.
However, there is a validation check in the 'setFont' function that checks to see if the font number is > 1 and if it is, sets it to 1, you would have to update the code and remove this check or update its logic to include the number of fonts in your image map --
You could probably add some additional logic, to determine the total number of fonts by dividing the imagemap frames by 128 -- if theres 256 of them, you've 2 fonts, for example, and your max font is 1 (0-based index).
Enjoy.