GB ゲーム開発覚え書き: バックグラウンドを設定する2
Game, C, GameBoy前回の続き。
おさらい
前回は、バックグラウンドのために 2 つのデータを作成した。
- タイルデータ(Backgrounds.c)
- タイルマップ(StageMap.c)
これを GBDK で読み込み、ゲームのステージマップとして使うのが目的。
今回は、第 1 回で書いたコードに、ステージマップを実装する
※コードの変更点
スプライトのタイルデータがプレイヤーとフレンド 2 つになったので、タイルの番号や読み込み数が変わってます。
src/main.c
// 前回
set_sprite_data(0, 6, Charcters);
set_sprite_tile(1, 5); // フレンドのタイル割り当て
// 今回
set_sprite_data(0, 2, Charcters);
set_sprite_tile(1, 1); // フレンドのタイル割り当て
バックグラウンドの設定
スプライトの set_sprite_data
同様、バックグラウンドも set_bkg_data
を使ってセットする。
スプライトは $8000
以降、バックグラウンドは $9000
にタイルデータが読み込まれる。
src/main.c
#include <gb/gb.h>
#include <stdio.h>
#include "Characters.c"
#include "Backgrounds.c" // タイルデータ
#include "StageMap.c" // タイルマップ
/* 省略 */
void main()
{
set_sprite_data(0, 2, Charcters); // $8000〜$8001
set_bkg_data(0, 2, Backgrounds); // $9000〜$9001
/* 省略 */
}
さらに、タイルマップをセットする必要がある。
それは set_bkg_tiles(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *tiles)
で行う。
x と y は 0 のままで OK。20×18 のマップを作ったので w は 20 で h は 18。
最後の引数に StagaMap.c
で作成した "StageMap" を渡す。
src/main.c
#include <gb/gb.h>
#include <stdio.h>
#include "Characters.c"
#include "Backgrounds.c"
#include "StageMap.c"
/* 省略 */
void main()
{
set_sprite_data(0, 2, Charcters);
set_bkg_data(0, 2, Backgrounds);
set_bkg_tiles(0, 0, 20, 18, StageMap); // タイルマップ読み込み
/* 省略 */
}
あとは set_bkg_submap
という関数もある。これは GBDK-2020 バージョン 4.0.3 で追加されたもの。
タイルマップのサブマップ(9C00
のこと?)を利用して、32×32 マスを越えるタイルマップを設定できる。
GB で横スクロールなど、32 マスを越えるマップを表現するには、画面外のタイルマップを書き換えないといけないので、そこの処理が楽になりそう。今回は使用しない。
バックグラウンドもスプライト同様、初期状態は非表示になっているので、LCDC の bit 0 を 1 にする。
GBDK では SHOW_BKG
マクロを使う。
src/main.c
#include <gb/gb.h>
#include <stdio.h>
#include "Characters.c"
#include "Backgrounds.c"
#include "StageMap.c"
/* 省略 */
void main()
{
set_sprite_data(0, 2, Charcters);
set_bkg_data(0, 2, Backgrounds);
set_bkg_tiles(0, 0, 20, 18, StageMap); // タイルマップ読み込み
/* 省略 */
SHOW_BKG;
SHOW_SPRITES;
/* 省略 */
}
あとは、第 1 回で指定したキャラクターのスタート位置を、壁の内側に変えておく。
src/main.c
/* 省略 */
void main()
{
/* 省略 */
// 主人公
set_sprite_tile(0, 0);
player_pos[0] = 16; // 8 -> 16
player_pos[1] = 24; // 16 -> 24
move_sprite(0, player_pos[0], player_pos[1]);
// フレンド
set_sprite_tile(1, 1);
friend_pos[0] = 152; // 160 -> 152
friend_pos[1] = 144; // 152 -> 144
move_sprite(1, friend_pos[0], friend_pos[1]);
/* 省略 */
}
これで OK。ただし、当たり判定を設定していないので、壁をすり抜けられてしまう。
バックグラウンドの当たり判定
配列として出力されているタイルマップの配列を、そのまま当たり判定マップとして使えば OK。
GBDK-2020 には一応 get_bkg_tile_xy(uint_8 x, uint_8 y)
という関数がある。
x, y のタイル番号を出してくれるというもの。
ただ、現行バージョンだとエラーが出て使用できない。
あと、VRAM にアクセスするので、パフォーマンスからあまり推奨されていないらしい。
Gameboy Development Forum / ?ASlink-Warning-Byte PCR relocation error for symbol .set_tile_xy
今回のゲームだと player_pos[x, y]
からタイルマップ配列の index を求められる。
計算方法としてはこういうのがある。
RPG みたいに 4 方向 1 マスずつの移動ならこれでよさそう。
src/main.c
#define STAGE_WIDTH 20
BOOLEAN is_colliding(UINT8 next_player_x, UINT8 next_player_y) {
UINT16 grid_X = (next_player_x - 8) / 8;
UINT16 grid_Y = (next_player_y - 16) / 8;
UINT16 tile_index = STAGE_WIDTH * grid_Y + grid_X;
return stageMap[tile_index];
}
今回のマップは横幅 20 で作成しているので、さっきのコードだと乗算が最適化されない。
32×32 で作っておいた方が良かった…。
第 1 回で作った is_colliding_x
と is_colliding_y
を消して is_colliding
を作成する。
src/main.c
BOOLEAN is_colliding(UINT8 next_player_x, UINT8 next_player_y)
{
// 画面端
if (next_player_x - 1 >= 160 || next_player_x - 1 <= 1 || next_player_y - 1 >= 152 || next_player_y - 1 <= 16) {
return TRUE;
}
UINT16 grid_X;
UINT16 grid_Y;
UINT16 tile_index;
BOOLEAN result = FALSE;
UINT8 i;
for (i = 0; i < 8; i++) {
grid_X = (next_player_x - 8 + i) / 8;
grid_Y = (next_player_y - 16 + i) / 8;
tile_index = STAGE_WIDTH * grid_Y + grid_X;
if (StageMap[tile_index] != 0x00) {
result = TRUE;
}
}
return result;
}
そして while 内を変更した。
src/main.c
while (!is_end)
{
UINT8 joypad_control = joypad();
if (joypad_control & J_UP)
{
if (!is_colliding(player_pos[0], player_pos[1] - 1))
{
player_pos[1]--;
move_sprite(0, player_pos[0], player_pos[1]);
}
}
else if (joypad_control & J_DOWN)
{
if (!is_colliding(player_pos[0], player_pos[1] + 1))
{
player_pos[1]++;
move_sprite(0, player_pos[0], player_pos[1]);
}
}
if (joypad_control & J_RIGHT)
{
if (!is_colliding(player_pos[0] + 1, player_pos[1]))
{
player_pos[0]++;
move_sprite(0, player_pos[0], player_pos[1]);
}
}
else if (joypad_control & J_LEFT)
{
if (!is_colliding(player_pos[0] - 1, player_pos[1]))
{
player_pos[0]--;
move_sprite(0, player_pos[0], player_pos[1]);
}
}
is_end = is_colliding_friend();
wait_vbl_done();
}
終わり
とりあえずバックグラウンドを置いて、簡素な当たり判定を追加できた。
ウィンドウ編 に続く。