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へ。