GB ゲーム開発覚え書き: スプライトを動かす1

Game, C, GameBoy

前回でゲームボーイの基本的なことを知った。今回は GBDK(C 言語)での覚え書き。

実際にキャラクターを表示して、動かすゲームを作る。
画面の左上にプレイヤーがいて、十字キーで上下左右に動かし、画面右下にいるフレンドに接触すればゲームクリア。そんな大作。
最終的なコードはこちら。

これから作成するゲームの内容となる画面

※注意

C 言語特有の説明(型、配列、ポインタ、構造体、マクロ、ヘッダなど…)は省きます。
また、あくまで「ゲームボーイのゲーム」を作るための覚え書きなので、ゲームのロジックについては説明を割愛する場合があります。

タイルのおさらい

まず、キャラクターのグラフィックを描くために、「タイル」のおさらいをする。

タイルについて

前回で触れたように、グラフィックは、ピクセル単位でなく「タイル」という単位で保存される。
具体的には 8×8 ピクセルのひとかたまりが、 1 タイルとして扱われる。
これを本体の表示サイズで割ると、1 つの画面に 20×18 マス分のタイルを表示できることになる。

実際のゲーム画面を8×8の間隔で分割し、マス目に数字を振った。横は20タイルまで、縦は18タイルまである。

各タイルでは、最大 4 色が使用できる。
ID0〜3 の 4 種類あり、数字が高くなるにつれ濃度が高くなる。

スプライトについて

スプライトはバックグラウンドの上のレイヤーで、オブジェクトとも呼ばれる。 ID0 の色を透過色にできるので、自機などの移動するキャラクターなどに使われる。
スプライトは、8×8 か 8×16 のどちらかを 1 単位で表示できる。

ゲームボーイの画面で 8×8 は小さすぎるので、複数のスプライトを組み合わせて…例えば 4 つのスプライトで 1 つのキャラクターを表現する手法もある(メタスプライト)。

ゲーム内では一度に最大 40 個のスプライトが置ける。しかし、画面 1 行ごとに最大 10 個のスプライトしか置けない。

スプライトのタイルマップ

タイルデータを格納するのは VRAM。アドレスで言うと 8000〜97FF の範囲。
その中で、スプライトのタイルマップは 8000-8FFF に割り当てられる。
開発では、まずタイルとなるデータを、スプライトのタイルマップに割り当てないといけない。

スプライトの属性(OAM)

スプライトは FE00-FE9F の範囲で、それぞれオブジェクト属性メモリ(Object Attribute Memory, 略して OAM)という 4 バイトぶんの情報を持っている。 FE00-FE9F の 160 バイト ÷ OAM 4 バイトで、最大スプライト数が 40 個というわけだった。 4 バイトに何の情報が入るのかは、次回触れる。GBDK で開発する分には気にしなくても OK。

GBTD でタイルデータを作る

タイルデータの作成に便利なツールとして GBTD がある。
このツール自体は 1999 年の頃に作られた Delphi 製のオーパーツで、リンク元のライブラリは GBDK メンテナーがバグ修正したもの。
これで作成したデータは ASM, C などにエクスポートできる。
macOS では動かず、wine-crossover が必要。 詳しくは「macOS でゲームボーイ用ゲームの開発環境を構築する」の記事を参照。

タイルを C 向けにエクスポートする

GBTD を使い、タイル 0〜5 までの 6 タイルを作成した。
0 は主人公の正面顔、1〜4 は各向き、5 はフレンドキャラのタイル。
※ 1〜4 は存在を忘れていたので、これから開発するゲームでは使ってません。

GBTDで主人公の正面顔のドット絵を描いて表示しているところ

メニューから export to を選択する。
Export のメニューが開く。

まず、"File" フィールドの設定。
"Filename" はファイルの出力先。あとで include する。今回は Characters.c にした。
"Type" は出力形式。"GBDK C file(*.c)" を指定する。

"Settings" フィールドはほとんどいじらない。
"Label" はとりあえず Characters で。"Bank" は 0。
"From" と "To" は、出力するタイル番号の範囲。今回はタイル 0〜5 をエクスポートするので、"To" に "5" を指定する。

GBTD の出力ファイルを設定する画面。入力内容はそのまま、本文に書かれているので省略。

これで "OK" を押せば、Filename で指定されたパスに C ファイルが出力される。 そのファイルでは、タイルデータを格納した unsigned char の配列ができる。

src/Characters.c

const unsigned char Charcters[] =
{
  0x7E,0x7E,0xFF,0x81,0xFF,0x81,0xFF,0xA5, // タイル 0
  0xFF,0x81,0xFF,0x81,0xFF,0x81,0x7E,0x7E,
  0x7E,0x7E,0xFF,0x81,0xFF,0xA5,0xFF,0x81, // タイル 1
  0xFF,0x81,0xFF,0x81,0xFF,0x81,0x7E,0x7E, 
  0x7E,0x7E,0xFF,0x81,0xFF,0x81,0xFF,0x81, // タイル 2
  0xFF,0x81,0xFF,0xA5,0xFF,0x81,0x7E,0x7E,
  0x7E,0x7E,0xFF,0x81,0xFF,0x81,0xFF,0x8B, // タイル 3
  0xFF,0x81,0xFF,0x81,0xFF,0x81,0x7E,0x7E,
  0x7E,0x7E,0xFF,0x81,0xFF,0x81,0xFF,0xD1, // タイル 4
  0xFF,0x81,0xFF,0x81,0xFF,0x81,0x7E,0x7E,
  0x00,0x24,0x00,0x18,0x7E,0x7E,0xFF,0x81, // タイル 5
  0xFF,0xA5,0x99,0xE7,0xFF,0x81,0x7E,0x7E  
};

これをスプライトのタイルマップに設定するのが最初の目標。

スプライトの読み込み

プロジェクトの準備

ワークスペースとするフォルダ内に srcreleasebuild フォルダを作る。
その後、src/main.c を作成する。

タイルマップを設定する

まずは、スプライトに使うタイルマップを設定する必要がある。
GBDK では set_sprite_data(開始番号, 読み込むタイル数, タイルマップ); を使う。

今回は、GBTD でエクスポートした Characters.c のタイル番号 0〜5 まで、6 つのタイルを設定する。

src/main.c

#include <gb/gb.h>
#include "Characters.c"

void main() {
  set_sprite_data(0, 6, Characters);
}

これで make してみる。ちなみに今回使用する Makefile はこちら(gbdk がホームディレクトリにあるとして)。

Makefile

CC	= $${HOME}/gbdk/bin/lcc -Wa-l -Wl-m -Wl-j -DUSE_SFR_FOR_REG
SRC = src/
BUILD = build/
RELEASE = release/

all:
	$(CC) -c -o $(BUILD)main.o $(SRC)main.c
	$(CC) -o $(RELEASE)rom.gb $(BUILD)main.o

releaserom.gb ができるが、この段階ではタイルマップを設定しただけなので、起動しても何も表示されない。
でも、BGB の VRAM ビューアーを見ると、0〜5 にタイルが設定されていることがわかる。

BGB の VRAM ビューアー。GBTD で作成した0〜5の6タイルが反映されている。

つづく

その2へ。