GB ゲーム開発覚え書き: バックグラウンドを設定する1

Game, C, GameBoy

前回のシリーズではスプライトを動かしたので、今回はバックグラウンドレイヤーを学ぶ。
今回も実装を C に甘えつつ、ASM で書く時も参考にできるようなレイヤーから学んでいく。

バッググラウンドレイヤーについて

バックグラウンドレイヤーは、その名の通り背景にあたる部分。
1 マスには 8 ビット(1 バイト)の「タイルデータの参照」が含まれる。

バックグラウンドのタイルデータ

タイルデータは、一部スプライトと共有される部分がある。

まず、タイルデータ自体は $8000–$87FF, $8800–$8FFF, $9000–$97FF の 3 ブロックある。
スプライトに使えるタイルデータは $8000–8800 の 0〜127 番、 $8800–$8FFF の 128〜255 番の範囲。$9000 以降は使えない。
一方で、バックグラウンドレイヤーは、$9000 スタート。そして、符号付きなので、$8800–$8FFF に -128〜-1,$9000–$97FF に 0〜127 が入る。
(LCDC の bit 4 を 1 にすると、アドレス範囲がスプライトと同じになる)

エミュレーターの VRAM Viewer でアドレスの範囲を確認している。構造は先の説明と同じ。今回使用するバックグラウンドのアドレスは$9000からなので、その箇所に矢印を置いて強調している

タイルマップの場所

タイルマップは $9800-$9BFF にある。
(LCDC の bit 3 または 6 が 1 だと 9C00-9FFF になる)

タイルマップの各データ(1 マス)は 8 ビットで、タイルデータの番号が入る。
GBC のゲームのみ追加の 8 ビットがある。詳細は後述。

GBTD でタイルデータを作る

まず、スプライト同様タイルデータの作成から始める。

以前の記事で「GBTD でタイルデータを作る」 を書いたので、ここは省略。
空白のタイル 0 番と、ブロックのタイル 1 番を描いてエクスポートする。
ファイル名は "Backgrounds.c" でラベルは "Backgrounds" とする。

GBTDでバックグラウンドのタイルデータを作成する。0番は空白、1番は正方形のブロックを描く。

出力は以下のコード。

src/Backgrounds.c

const unsigned char Backgrounds[] =
{
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 0(空白)
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0xFF,0xFF,0xFD,0x83,0xFD,0x83,0xFD,0x83, // 1(ブロック)
  0xFD,0x83,0xFD,0x83,0x81,0xFF,0xFF,0xFF
};

前回書き忘れていたこととして、 1 タイルデータの情報は 16 バイトで構成される。
タイルマップは、この 16 バイトを 1 グループとした 1 バイトの情報を持てる。つまり、上記の配列をタイルマップに当てはめると Backgrounds[0〜15] が空白の部分 0x00 として、Backgrounds[16〜31] がブロックの部分 0x01 として使えることになる。

GBMB でタイルマップの作成

GBMB は GBTD で作成したタイルデータをインポートして、タイルマップを作成できるツール。

まず、起動したら "File" を選択し、"Map properties" を選ぶ。

GBMBの File メニューを展開し、Map properties を選択している。

"Map properties" ウィンドウが開く。 "Size" フィールドの "Width" に 20 を、"Height" に 18 を指定。
"Tileset" の "Filename" には、GBTD で作成した .gbr ファイルを指定する。

Map properties ウィンドウ。設定内容は先ほどの説明と同じなので割愛する。

すると、先ほど読み込んだタイルデータが右のパレットに読み込まれる。
1 番タイルを選び、右ドラッグでマップを書く。

先ほど作成したタイルデータ 1 番のタイルを使って、ゲームに使用するマップを作成している。

GBMB のエクスポート

GBTD 同様エクスポートが必要。
"File" から "Export to" を選ぶと、"Export Options" 画面が表示される。
"Standard" と "Location format" の 2 つのタブがある。

GBMB のエクスポートは GBTD と同じく、配列で出力される。ただし、今回は各ドットの情報ではなく、各マスのタイル番号「など」が格納される関係で、色々と知っておかないといけない。

エクスポートの設定: Standard

ここでの設定は、 GBTD のエクスポートとほぼ同じ。

"File" フィールドには、GBTD と同じくファイル名と、"Type" に "GBDK C file" を選択する。
"Settings" フィールドの "Label" も StageMap へ。"Bank" は 0 のまま。
"Split data" を使って、マップデータを分割できるけども、今回はいじらない。

Export Options の画面。先ほど説明した内容と全く同じなので割愛

エクスポートの設定: Location format

続いて、"Location format" タブに移動する。
ここでプロパティを 1 つ以上設定しないと、エクスポートできない。

"Location format" については現存する資料がほとんどなくて、何もわからなかった。
本体のコードと、WarioCraft というサンプル ROM のコメントを読んでも何もわからなかったので、できる限りのことをわかるつもり。

プロパティの設定

まず、左カラムの番号付きリストは、プロパティとビット数を指定するところになる。
プルダウンメニューから、"[Tile number]" を指定する。
隣の欄に表示される "7" はビット数。今回はこのまま。
プロパティの追加ができるけども、やらなくて OK。

Export options の Location format 画面。内容は説明済みなので割愛

ふいんきだけで触るのもいけないので、どんなプロパティがあるのかを知っておく。

プロパティ名
Tile number
Tile number: Low 8
Tile number: High 9
Vertical flip
Horizontal flip
GBC pallete
SGB pallete
GBC BG Attribute
0 filter
1 filter

そもそも、「プロパティ」がなんなのかわからない。
そこで、GBMB が出力できるデータは、タイルの番号だけじゃないことを知る必要がある。

例えば GBC では BG Map Attributes という追加の 8 ビットを持つ。ここにはカラーパレットや、反転(GBC ではタイルの反転ができる)などの情報が含まれている。
それらをコードに含めるには、パレットの情報とかも持つ必要があり、それらの情報を出力に含めるための設定が「プロパティ」になる。

今回出力に含めたいのは「タイルの番号」なので、"Tile number" を指定すればいい。
では "Tile number: Low 8" と "High 9" はなんなのか。
それは「8 ビット以下か、9 ビット以上か」を示しているらしい。
先程の BG Map Attributes は 9 ビット以上に属するものなので、タイルのデータが 9 ビット以上でエクスポートされるといけない。
また、今回指定する "Tile number" は 7 ビットなので、例えば GBC のカラーパレット用に必要なプロパティ "GBC Palette" を追加すると、8 ビット目にデータが食い込んでしまう。
そもそもなんで "Tile number" が 7 ビットなのか、それはわからなかったので宿題にする。

あとは、"High 9" をバンクセレクタとして使用することもあるらしい。
GBC は 512 のタイルデータを持てるけど、実際は GB との互換性を保つために 256 + 256 のバンク切り替え方式になっている。
BG Map Attributes に、「どっちのバンクか」の情報が入っているので、"High 9" をバンクセレクタとして使用し、"Low 8" をタイル番号として使用することで、バンク 0,1 のタイル番号を指定できる。GBMB だと 256 番目以降のタイルは bit 9 が 1 になるらしい(実際にやってみないと何もわからない)。

最後の方のプロパティ "0 filter" はマップを "0x00" で埋め、"1 filter" は "0x01" で埋める。これは何に使うのかわかっていない…。

Map Layout, Plane の設定

次に、右カラムの設定をする。
ここでは "Plane Count" を "1 Plane(8 bits)" に変えるだけ。

ここもふいんきだけで触ってはいけないので、それぞれの役割を見てみる。

項目名 説明
Map layout Row は横方向、Cols は縦方向に 1 マスずつ配列に入れる
Plane count 1 マスの領域。8 ビットで 1 Plane とされる
Plane order Plane を 1 つの配列にまとめるか、分割して出力するか。
Tile offset ここに設定した数、実際のタイル番号にプラスして出力される

Map layout は Row のままで OK。横スクロールゲームなど、追加のマップデータを 1〜列ずつ書き換える時に、Cols 単位でタイルマップを分割しておくと便利なことがある。

Plane を分割できるのは BG Map Attributes のためだと思う。
BG Map Attributes は GB にはないので、互換性のため VRAM バンク 1 に入る。
「1 マスごとのタイル番号を格納した配列」と「1 マスごとの属性を格納した配列」の 2 つをそれぞれ用意しておいて、バックグラウンドの実装時にそれぞれ使用する、という事情があるのだと思う。

今回は GB のゲームなので、1 マスごとには「タイルの番号」という 8 ビットの情報があればいい。そのため "Plane count" は "1 Plane(8 bits)" にする。
また、Plane は 1 つなので "Plane order" の設定を変える必要はない。
"Tile offset" は 0 のままで OK。

エクスポートして確認

この状態で "OK" ボタンを押すと、StageMap.c が出力される。
コードを見て、ただしくマップされているか確認してみる。

src/StageMap.c

#define StageMapWidth 20
#define StageMapHeight 18
#define StageMapBank 0

const unsigned char StageMap[] =
{
  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x01,
  0x01,0x01,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
  0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
  0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
  0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x00,
  0x00,0x01,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01
};

先程説明したように GB の 1 マスは 1 バイトなので、この配列は 20×18=360 バイトを持つ。
0x00 が空白、0x01 がブロック部分。

実際に表示される幅に合わせて 20 ずつで区切ってみれば、わかりやすい。
GBMB で作成したマップの画像と比べれば、正しくマッピングされていることがわかると思う。

src/StageMap.c

#define StageMapWidth 20
#define StageMapHeight 18
#define StageMapBank 0

// 0x01 はブロック、0x00 は空白
const unsigned char StageMap[] =
{
  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x00,0x00,0x01,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01
};

ゲームに使われるマップ。出力されたタイルマップの配列と見比べるために用意した

続く

その2 に続く