Devlog 04: Writing text on the screen
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!
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.
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:
Arranged in an image, it looks like this:
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
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.bread and returns a byte at the requested memory location
&text$ + itakes the memory address of the string with
&and add the character number, giving us the location of that character in memory.
x, yis the location on the bitmap where we want the shape drawn
0is 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
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 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
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 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.
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.
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!