Devlog 04: Writing text on the screen

Reading time: 8 min

Hello World!

While there are some rudimentary facilities to render text in BLITZ mode on the Amiga, they require a system library, it uses the system font, and you can’t have cool or crazy text effects on there (like multiple colors on each character). Even if we’re ok with what the system provides, we’re still on our own to provide word wrapping or some kind of frame around the text.

Let’s get started!

Bitmap Fonts

A bitmap font is just an image that contains every character you care to draw on the screen. Because the font is defined inside an image, it can look like whatever you want, have colors, etc. You can really get creative.

For NEONnoir, I’m using this font from VileR over at int10h.org.

To keep things simple, I created an image containing all the characters I was going to use. These would be from ASCII 32 (a space) to ASCII 127 (the DEL character). That last one I’m going to use for a special purpose. The whole character set is comprised of these symbols:

 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~

Arranged in an image, it looks like this:

My font image, note the subtle CRT scan lines baked into the image.

I broke the characters in 3 lines so I could easily work on it but it doesn’t matter so long as you know where to find every character. The image itself is indexed and uses a standard palette I use with every image (otherwise the text could change color). At this point I’d decided that color indices 1 and 2 were reserved for font rendering and nothing else will use those colors. The background is color index 0 which is reserved for things that need to be transparent.

You’ll notice that the very last character is an upside down pyramid, I’ll use this character to print out a blinking prompt indicating to the user to press a button to continue. This is the real power of bitmap fonts because you can have any character you don’t normally use look like something else. Perhaps your character buys things with credits and you want to replace the dollar sign with the your symbol for credits, you can easily do that.

From Images to Shapes

In order for us to use these characters in Blitz Basic, we’ll need to first turn them into Shapes. Shapes are what actually gets blitted around in Blitz instead of raw bitmaps.

In order to create a shape, you first create a bitmap, load a image from disk or draw on the bitmap, and then you use the GetaShape function to assign a rectangular portion of the bitmap to a shape id. At that point you no longer need the original bitmap and it can be freed.

Rather than do this every time we load the game, we can use SaveShapes to save those shapes to a file and LoadShapes to load them back in. This’ll save us some time when running the game.

So, how do we create these shapes? Well, here’s my tiny Blitz Basic program to do just that:

01| BitMap 0, 320, 200, 5
02| LoadBitMap 0, "data/font.iff", 0
03|
04| x.w = 0
05| y.w = -8
06| w = 6
07| h = 8
08|
09| For i = 32 To 127
10|   If i MOD 32 = 0
11|     x = 0
12|     y + h
13|   End If
14|
15|   GetaShape i , x, y, w, h
16|   x + w
17| Next
18|
19| SaveShapes 32, 127, "data/display.font"
20|
21| End

This code won’t win any awards and is more complicated because I broke my image into three lines of 32 characters. If they were all in a straight line, this code would be half the size.

Lines 01 and 02, create a 5 bitplate (32 color) bitmap and load my font image. IFF is the image format most commonly used by the Amiga and the only one that Blitz will load natively.

Lines 04 through 07 just set the start values and the width and height of each character, all characters are the same size and are 6x8.

Lines 09-13 is where we create the shapes. Every 32 characters we “go to the next line” and continue.

Finally, on line 19 we save all the shapes in file called “display.font”. We now no longer need the original font.iff image and won’t ship it as part of the game.

Note that the shapes are numbered 32 to 127 (inclusive), this is not a coincidence.

From Shapes to Words on the Screen

Before we can use the shapes, we need to load them in our game, and that’s just one call to LoadShapes 32, 127, "data/display.font" away. The thing to remember is that when loaded this way, the shapes do not have any palette information, they’ll use whatever palette has been selected. All it knows is that some pixels are transparent (0), and others use index 1 and 2. It’s going to get the colors from the currently loaded palette. This is why we set these colors aside for the font so that we can make sure that every image we load will have the correct colors for the font.

Alternately, we can set those colors directly using PalRGB or AGAPalRGB. We can even get clever by changing or animating the color. The only thing to remember is that all text on the screen will get the new colors.

At its most basic (no pun intended), we iterate through a string, character by character and blitting the corresponding shape:

01| For i = 0 to Len(text$) - 1
02|     Blit Peek.b(&text$ + i), x, y, 0
03|     x + char_width
04| Next

On line 02, it looks like the cat crawled on my keyboards, but it’s just a slightly more efficient way of getting characters. Let’s break it down:

  • Peek.b read and returns a byte at the requested memory location
  • &text$ + i takes the memory address of the string with & and add the character number, giving us the location of that character in memory.
  • x, y is the location on the bitmap where we want the shape drawn
  • 0 is some magic to make sure that we use the palette entries at the beginning of the palette. I don’t fully understand it myself, but without it, everything draws with the wrong colors.

Being Less Destructive

So far, we got text to display on the screen. Unfortunately, we can’t erase it because it’s been blitted directly onto our primary bitmap. Fortunately Blitz Basic has a few other methods for blitting shapes that will allow us to erase the text without ruining the background.

QBlit and BBlit

QBlit and BBlit stand for Queue Blit and Buffer Blit respectively and serve the same purpose: draw a shape to the screen in a matter that can be erased.

QBlit

QBlit makes uses of a queue, every time something is QBlited it gets added to the queue. When you want to remove all the things you blitted, you call UnQueue and all items are removed. They’re gone, but replaced with black boxes where they used to be. Fortunately one of the optional parameters to UnQueue specifies a bitmap to use to erase the shapes. If we use a bitmap that contains a clean version of our background, the shapes (or text in this case) disappears.

We could set it up like so:

01| Bitmap 0, 320, 200, 8
02| Bitmap 1, 320, 200, 8   ; The "clean" bitmap
03|
04| Queue 0, 64
05| For i = 0 to Len(text$) - 1
06|     QBlit 0, Peek.b(&text$ + i), x, y, 0
07|     x + char_width
08| Next
09|
10| UnQueue 0, 1

The important bits are on lines 04, 06, and 10. We set up a queue with id 0 with a capacity for, in this case, 64 elements. This means that we can have only 64 characters since each QBlit operation will take one spot. We can play around with that number to find something that suits us better.

On line 06, we replaced Blit with QBlit and its first parameter is the queue id, the rest are the same.

Line 10, immediately erases everything that was queued using the clean bitmap.

BBlit

BBlit serves a similar purpose a QBlit but does things differently. Whereas QBlit used a queue, to use BBlit we first set up a memory buffer. When we use BBlit, the background is copied into this buffer before blitting the shapes. When we call UnBuffer, it copies things back.

The beauty of BBlit is that we don’t need a clean bitmap to copy from, however, it’s a lot slower on account that it’s making a copy with every blit.

Setting up for BBlit is also very simple:

01| Bitmap 0, 320, 200, 8
03|
04| Buffer 0, 32768
05| For i = 0 to Len(text$) - 1
06|     BBlit 0, Peek.b(&text$ + i), x, y, 0
07|     x + char_width
08| Next
09|
10| UnBuffer 0

Here we reserve a 32Kb buffer (in the very precious chip RAM) for our blits. BBlit’s first parameter is the buffer we want to use, and UnBuffer copies everything back.

Unlike QBlit where we know the number of blits we’re allowed before we run out of queue space (since we set it ourselves) with BBlit it’s a little more nebulous, we just know how much space we have, not how much we’ve used.

Next Time

Now that we can draw and erase text from the screen, we need to take it to the next level by implementing word wrapping, a nice looking frame around the text, and a way to get around the limitation to the number of characters we can draw.

See you next game!