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

Game, C, GameBoy

前回は GBTD でスプライトのタイルマップを作り、読み込むところまでやった。
今回はこれを使って、スプライトを画面に表示するところまでやる。

スプライトのタイルを設定

全部で 40 あるスプライトのどれかに、タイルを設定しないといけない。

前回「それぞれのスプライトは 4 バイトの属性(OAM)を持っている」ことを知った。
この OAM の 3 バイト目には、タイルの番号が入る。
つまり、プレイヤーにしたいスプライト番号に、GBTD で作成した 0 番のタイル(プレイヤーの正面顔)を設定すればいい。
GBDK では set_sprite_tile(スプライトの番号, タイル番号) を使用する。

src/main.c

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

void main() {
  set_sprite_data(0, 6, Characters);
  set_sprite_tile(0, 0); // スプライト番号 0 にタイル 0 (プレイヤーのタイル)を設定
}

スプライトの座標を指定

次に、スプライトの表示場所を指定する必要がある。
OAM では、1 バイト目に Y 座標、2 バイト目に X 座標を入れている。
座標の数値はピクセル単位。

注意点として、実際の画面だと 1 マス目の X 座標は 8 で、 1 マス目の Y 座標は 16 になる
また、X, Y どちらかに 0 を指定すると非表示にできる。ただし PPU の仕様により、 X を 0 にしても「1 行ごとに 10 スプライトまで」の制限に含まれてしまうので、非表示にするなら Y を 0 に指定することが望ましいとされる。

GBDK では、タイルの移動を move_sprite(index, x, y) で行う。
第一引数の index は、スプライトの番号を指す。先ほど 0 番目のスプライトに主人公のタイルを登録しているので、これを x8, y16 つまり画面左上に移動させてみる。

src/main.c

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

void main() {
  set_sprite_data(0, 6, Characters);
  set_sprite_tile(0, 0);
  move_sprite(0, 8, 16); // 0 番目のスプライトを x8, y16(画面左上) に移動する
}

これで OK と思いきや、ゲームボーイ側でスプライトの表示が有効になっていないので、まだ表示されない。
ゲームボーイには LCD Control(LCDC) というレジスタがあり、ここの bit 1 を 1 にすることでスプライトの表示が有効になる。
GBDK では SHOW_SPRITES というマクロがそれをやる。

src/main.c

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

void main() {
  set_sprite_data(0, 6, Characters);
  set_sprite_tile(0, 0);
  move_sprite(0, 8, 16);
  
  SHOW_SPRITES; // スプライトの表示を ON
}

ここまでやると、画面の左上に主人公が設置される。

ゲーム画面に主人公を設置した

座標を配列に入れる

この後プレイヤーの操作を実装するので、座標をどこかに記録しておかないといけない。
プレイヤーの座標を格納する [x, y] のタプル的な配列 player_pos を用意しておく。

src/main.c

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

// [x, y] が入る
UINT8 player_pos[2] = {0};

void main() {
  set_sprite_data(0, 6, Charcters);
  set_sprite_tile(0, 0);

  // プレイヤーの初期座標を設定
  player_pos[0] = 8; 
  player_pos[1] = 16;
  move_sprite(0, player_pos[0], player_pos[1]);

  SHOW_SPRITES;
}

UINT8 型は、unsidned 8-bit char のことで、0〜255。
座標はバックグラウンドレイヤーの 32×32 マスでループするので、8 かけで最大 256 ぶん確保できていればいい。

フレンドの設置

フレンドも同じように設置する。

src/main.c

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

UINT8 player_pos[2] = {0};
UINT8 friend_pos[2] = {0}; // フレンドの座標

void main() {
  set_sprite_data(0, 6, Charcters);

  // 主人公
  set_sprite_tile(0, 0);
  player_pos[0] = 8; 
  player_pos[1] = 16;
  move_sprite(0, player_pos[0], player_pos[1]);

  // フレンド
  set_sprite_tile(1, 5); // 1 番目のスプライトに、5 番目のタイルを指定する
  friend_pos[0] = 160; 
  friend_pos[1] = 152;
  move_sprite(1, friend_pos[0], friend_pos[1]); // 1 番目のスプライトを x160, y152 に移動

  SHOW_SPRITES;
}

これで、フレンドが画面右下に設置される。

ゲーム画面に主人公とフレンドを設置した

(余談)乗算と除算、剰余演算について

例えば、x 座標を直接指定するのではなく マス数 * 8 のように計算したい時がある。
でも、ゲームボーイの CPU には乗算と除算命令がない。

そのような計算がコードにあっても、コンパイラがいい感じにしてくれる。
でも、どういい感じにしてくれるかは、意識しておいた方がいいかもしれない。
例えば、20 マス × 8 で実際の座標 160 を求める時の計算。

src/main.c

friend_pos[0] = 20 * 8;

詳細はおいといて、 2 の累乗の乗算・除算は、かわりにシフト演算で表現できる。
今回の例だと 8 は 2 の 3 乗なので、シフト演算に置き換えられる。
乗算は、左シフト演算を用いる。

src/main.c

friend_pos[0] = 20 << 3;

もし 20 ÷ 8 なら、右シフト演算に置き換えることができる。

src/main.c

friend_pos[0] = 20 >> 3;

剰余も 2 の累乗であれば、ビットマスクで求めることができる。

src/main.c

UINT8 mod4 = 26 & 0x3; // 2
UINT8 mod8 = 26 & 0x7; // 2

SDCC(コンパイラ)では 2 の累乗の乗算・除算・剰余がこのように最適化されるらしい。
それ以外での演算はなるべく避けるよう、GBDKのコーディングガイドラインで推奨されている。

つづく

その3へ。