GBAにはフォント表示の機能がないので自前に用意しなくてはいけません。加えてフォント自体を自作するのは大変な労力です。そこで着目したのがインターネット上に存在するフリーフォントです。以下に紹介したフォントはライセンスがかなり緩いものとなっています。本来なら使用料などの金銭的な問題がありますのでライセンスを確認せずに使うことは避けてください。
bdf形式はフォントを構成しているドットの集まりを記述したものです。内容はテキストのままなのでエディタを使って開いてみてください。ここでは漢字12x10ドットフォントのk6x10.bdfを例に見ていきます。
STARTCHAR 21 ENCODING 33 SWIDTH 960 0 DWIDTH 6 0 BBX 6 10 0 -1 BITMAP 20 20 20 20 20 20 00 60 60 00 ENDCHAR
注意してほしい部分はBITMAP~ENDCHARの間です。数字が並んでいるだけでよくわからないと思いますが、実はこれが1文字分のフォントを表したものです。この数値は16進数で表されていて、ビットが立っている個所は色あり、立っていない個所は色なしと考えます。
00100000 00100000 00100000 00100000 00100000 00100000 00000000 01100000 01100000 00000000
2進数に変換してみたのが上記です。この文字は良く見ると「!」(ビックリマーク)の形をしていることがうっすらとわかると思います。bdfファイルには複数のBITMAP~ENDCHARがありますが、何文字あるかをカウントしているのはヘッダ部のCHARSです。このbdfファイルは、160文字入っていることになっています。
CHARS 160
bdfの内容はどのように文字が並んでいたり、字体がさっぱりわからないと思います。そこでグラフィカルに出力してくれるツール(bdf2bmp)を使って確認してみましょう。使用方法はプロンプト上で入力します。
bdf2bmp.exe -s 0 -c 160 k6x10.bdf k6x10.bmp Total glyphs = 160 BMP width = 960 pixels BMP height = 10 pixels BMP filesize = 10678 bytes
-sN: 文字仕切り(spacing)の幅を Nピクセルにします。 -cN: 文字の折り返しカウントをN個で指定します。
今回の例では文字の間に余白(ドット)を0にして、文字160個分の列を折り返しなし、1行で表示するという意味になります。
最初の字体は「 」(空白スペース)、次に「!」(ビックリマーク)、「”」(ダブルクォーテーション)。この順番には決まり事があり、俗に文字コード表と呼ばれています。ホームページを見ていたら文字化けしている現象を目にするのはブラウザが変換を間違えてるからです。1文字6x10ドット、160文字あるので画像全体の大きさは960x10ドットとなります。
0x20 (空白スペース) 0x21 ! 0x22 " 0x23 # 0x24 $ 0x25 % (以下略・・・)
さて、k6x10.bmpをgritでmode4用に変換をします。変換されたデータは1ドットが1バイトで表されていて、文字の表示処理を作るときにラクができます。データ容量としては無駄かもしれませんがサンプルコードということでご了承ください。
.section .rodata .align 2 .global k6x10Bitmap @ 9600 unsigned chars .hidden k6x10Bitmap k6x10Bitmap: .byte 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x01,0x00,0x01 .byte 0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00 (以下略・・・)
縦960×横10=9600バイト。仮に2文字目の「!」(ビックリマーク)をmode3でGBA上に表示したい場合を考えてみます。ビックリマークの最初のドット座標はx=6,y=0で、そこから横に6ドット分が1行目ということになります。次に2行目のドット座標はx=6,y=1で、そこから横に6ドット分が2行目となります。という具合に疑似的なコードに落としてみるとこういう形になります。
index_x=6; index_y=0; s32 x, y; for(y=0; y<10; y++) { for(x=0; x<6; x++) { if(data[(index_y+y)*960 + index_x+x] == 0x01) { GBA VRAM = 黒色; } else { GBA VRAM = 白色; } } }
今回の例ではトップダウンで読んでいった方がわかりやすいのでmain関数から説明します。
int main(void) { SetMode(MODE_3 | BG2_ENABLE); ST_FONT f; f.pDat = (u8*) &k6x10Bitmap; // データのポインタ f.imgCx = 960; // データの横サイズ f.cx = 6; // 1文字の横サイズ f.cy = 10; // 1文字の縦サイズ Mode3DrawFontChr(&f, 0, 0, '!', RGB5(31,31,31)); Mode3DrawFontStr(&f, 16, 16, "ABCDEF #$%&", RGB5(31,31,31)); Mode3DrawFontStr(&f, 16, 32, "123456 +-*/", RGB5(31,31, 0)); for(;;) { WaitForVsync(); } }
この中で出てくるST_FONT構造体は、フォントを表示するために必要なパラメータを設定します。
typedef struct { u8* pDat; u16 imgCx; u16 cx; u16 cy; } ST_FONT;
Mode3DrawFontChrは先ほど説明した内容をコードに落としたものです。
void Mode3DrawFontChr(ST_FONT* p, s32 sx, s32 sy, u16 chr, u16 col) { // 書き込む起点位置を求めます u16* pScreen = (u16*)VRAM + (sy * 240) + sx; // 書き込むフォントデータの位置を求めます u8* pDat = p->pDat + (chr - 0x20) * p->cx; s32 x, y; for(y=0; y<p->cy; y++) { for(x=0; x<p->cx; x++) { if(pDat[y*p->imgCx + x] == 0x00) { continue; } pScreen[y*240 + x] = col; } } }
最後にMode3DrawFontStr関数ですが、これは文字列を1つ1つMode3DrawFontChr関数に渡しているだけです。
void Mode3DrawFontStr(ST_FONT* p, s32 sx, s32 sy, char* str, u16 col) { u16 chr; s32 i=0, x=0, y=0; for(;;) { chr = str[i++]; if(chr == '\0') { return; } // 改行処理 if(sx + x >= 240) { x = 0; y += p->cy; if(sy + y + p->cy >= 160) { return; } } Mode3DrawFontChr(p, sx + x, sy + y, chr, col); x += p->cx; } }