
マイケル
みなさんこんにちは!
マイケルです!
マイケルです!

エレキベア
こんにちクマ〜〜

マイケル
今日は前回に引き続き、GBDKを使ったゲームボーイソフト制作
を進めていくよ!
を進めていくよ!

マイケル
第2回ということで、お待ちかね ゲーム制作編 です!

エレキベア
楽しみクマ〜〜〜
どんなゲームを作るクマ??
どんなゲームを作るクマ??

マイケル
ゲームボーイのタイルを並べる画面構成に合わせて、
簡単な 倉庫番ゲーム を作ろうと思うよ!
完成品としては以下のような感じだ!
簡単な 倉庫番ゲーム を作ろうと思うよ!
完成品としては以下のような感じだ!

↑制作する倉庫番ゲーム

マイケル
頑張ってマップの自動生成処理も入れてみたんだけど、
そこは次回に回して、メイン処理の部分だけざっと紹介します!
そこは次回に回して、メイン処理の部分だけざっと紹介します!

エレキベア
わりと本格的クマね〜〜

マイケル
一通り遊べるようにはしたんだ!
ソースはGitHubにアップしてあるので、自由に使ってください。
ソースはGitHubにアップしてあるので、自由に使ってください。

エレキベア
(エンジニアっぽいクマ・・・)

マイケル
それでは早速やっていこう!
GBDKの基本操作

マイケル
ゲーム制作に入る前に、軽くゲームボーイの仕様と
GBDKの基本操作を確認しておきましょう!
GBDKの基本操作を確認しておきましょう!
画像の表示方法

マイケル
まず画像の表示ですが、ゲームボーイでは下記のように
8*8pxを1タイル として表示します。
そしてタイルを描画できる全体の領域は 256*256px ですが、
画面に表示できる範囲は 160*144px の範囲になります。
8*8pxを1タイル として表示します。
そしてタイルを描画できる全体の領域は 256*256px ですが、
画面に表示できる範囲は 160*144px の範囲になります。

↑8*8px を1タイルとして表示、画面表示領域は160*144px

エレキベア
このタイル単位でしか描画できないクマね

マイケル
用意した画像をタイルとして事前に登録しておくことで、
指定して描画できるようになるわけだね!
指定して描画できるようになるわけだね!
タイル画像の作成

マイケル
タイル画像を表示するには、16ビットのデータに変換してソースファイルに記述
しなければいけません。
GBTD というツールを使えば簡単に変換できるようなので、こちらを使用します。
しなければいけません。
GBTD というツールを使えば簡単に変換できるようなので、こちらを使用します。
※Windows専用

マイケル
ツールを使って下記のように適当に書いたら、
C言語のソースファイルとして出力します。
C言語のソースファイルとして出力します。

↑タイルを描く

↑C言語のソースファイルとして出力(ラベル名は任意)
unsigned char block_tile[] =
{
0xF7,0x08,0xF7,0x08,0xF7,0x08,0x00,0xFF,
0x7F,0x80,0x7F,0x80,0x7F,0x80,0x00,0xFF
};
↑出力されたファイル
マイケル
このように出力されればオーケーです!
このソースを使って表示していきましょう!
このソースを使って表示していきましょう!

エレキベア
やったるクマ〜〜〜
タイル画像の描画

マイケル
それではタイル画像を描画してみましょう!
手順としては タイル画像を登録して表示する 流れになります。
手順としては タイル画像を登録して表示する 流れになります。

マイケル
登録できるタイルには下記の3種類があります。
・BG(背景の表示に使用)
・ウィンドウ(メッセージ等の表示に使用。背景の上に表示できる。)
・スプライト(キャラクター等の表示に使用。重ねたりできる。)
・BG(背景の表示に使用)
・ウィンドウ(メッセージ等の表示に使用。背景の上に表示できる。)
・スプライト(キャラクター等の表示に使用。重ねたりできる。)
今回作るゲームは背景表示の切り替えだけでゲームが実現できそうなので、
BGのタイルとして登録して使用 しましょう。

エレキベア
いろんな種類があるクマね

マイケル
BGタイルとして登録するため、
まずは出力したファイルを下記のように修正しましょう!
まずは出力したファイルを下記のように修正しましょう!
/* BGタイル配列 */
unsigned char block_tile[] =
{
/* 0x00 白背景 */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
/* 0x01 ブロック */
0xF7,0x08,0xF7,0x08,0xF7,0x08,0x00,0xFF,
0x7F,0x80,0x7F,0x80,0x7F,0x80,0x00,0xFF
};
/* 白背景 */
unsigned char blank[] = { 0x00 };
/* ブロック */
unsigned char block[] = { 0x01 };
unsigned char block_16[] = { 0x01, 0x01, 0x01, 0x01 };
↑タイル情報の修正

マイケル
BGは 最初のタイルがデフォルトで全領域に表示される ようなので、
頭に 白背景(全て0x00)を挿入 します。
登録したタイルは、16ビットごとに0x00、0x01、0x02〜と番号が振られて使用できる ようになります。
頭に 白背景(全て0x00)を挿入 します。
登録したタイルは、16ビットごとに0x00、0x01、0x02〜と番号が振られて使用できる ようになります。

マイケル
ソースを修正して読み込んだら、下記のように記述してタイル画像を描画できます!
#include <gb/gb.h>
#include "block.c"
void main(void)
{
// 背景を有効化
SHOW_BKG;
// BGタイルを登録
set_bkg_data(0, sizeof(block_tile)/16, block_tile);
// タイルを表示(8x8)
set_bkg_tiles(2, 4, 1, 1, block);
// タイルを表示(16x16)
set_bkg_tiles(8, 10, 2, 2, block_16);
}
↑タイル画像の描画
↑実行結果

エレキベア
表示の仕方は大体分かったクマ〜〜

マイケル
続けてボタンの入力操作を見ていこう!
ボタン入力

マイケル
ボタンの入力は、 joypad() より取得します!
下記のように記述することで検知することができます!
下記のように記述することで検知することができます!
#include <gb/gb.h>
#include "block.c"
void main(void)
{
// 背景を有効化
SHOW_BKG;
// BGタイルを登録
set_bkg_data(0, sizeof(block_tile)/16, block_tile);
// タイルを表示(8x8)
set_bkg_tiles(2, 4, 1, 1, block);
// タイルを表示(16x16)
set_bkg_tiles(8, 10, 2, 2, block_16);
// ボタン押下情報
char button;
char pre_button;
// ブロック位置
int block_pos[2] = {2, 4};
while (1) {
// 入力検知
button = joypad();
if (button != pre_button) {
// 押下されたボタンに応じて移動処理を行う
if (button & J_UP ) block_pos[1]-=1;
if (button & J_DOWN ) block_pos[1]+=1;
if (button & J_LEFT ) block_pos[0]-=1;
if (button & J_RIGHT) block_pos[0]+=1;
// ブロックを移動させる
set_bkg_tiles(block_pos[0], block_pos[1], 1, 1, block);
}
pre_button = button;
}
}
↑ボタン入力の検知
↑ボタン入力に応じて移動する

エレキベア
動いたクマ〜〜〜

マイケル
これで基本操作は押さえたので、
ゲーム制作にとりかかっていきましょう!
ゲーム制作にとりかかっていきましょう!
倉庫番ゲームの制作

マイケル
それでは、冒頭でお見せした倉庫番ゲームを作っていきます!
こちらは全部で3つのソースファイルで構成されています!
こちらは全部で3つのソースファイルで構成されています!
.
├── map.c. マップ関連処理
├── sokoban.c メイン処理
└── tile.h. タイル関連処理
↑倉庫番ゲームのファイル構成
エレキベア
シンプルで分かりやすいクマ

マイケル
それぞれのファイルの内容について、
・BGタイルの定義
・マップの定義
・メイン処理
の順番で解説していきます!
・BGタイルの定義
・マップの定義
・メイン処理
の順番で解説していきます!
BGタイルの定義

マイケル
まずはBGタイルの定義!
こちらはGBTDツールで下記4枚の画像を作成し、定義しています!
こちらはGBTDツールで下記4枚の画像を作成し、定義しています!

↑ブロック(8*8px)

↑ボックス(16*16px)

↑プレイヤー(16*16px)

↑ポイント(16*16px)

マイケル
今回作るゲームは、基本的に16*16を1タイルとして扱う ため、
4つのデータを1つの配列としてまとめました!
4つのデータを1つの配列としてまとめました!
#ifndef TILE_H
#define TILE_H
/* BGタイル配列 */
unsigned char tile[] =
{
/* 0x00 白背景 */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
/* 0x01 ブロック */
0xF7,0x08,0xF7,0x08,0xF7,0x08,0x00,0xFF,
0x7F,0x80,0x7F,0x80,0x7F,0x80,0x00,0xFF,
/* 0x02-0x05 エレキベア */
0x00,0x0C,0x00,0x1E,0x01,0x1E,0x03,0x0C,
0x00,0x0F,0x02,0x0F,0x01,0x0E,0x03,0x0F,
0x00,0x1F,0x00,0x3E,0x00,0x3C,0x00,0x3C,
0x00,0x3C,0x38,0x04,0x00,0x0F,0x00,0x15,
0x40,0x30,0xC0,0x38,0xE0,0x18,0xC0,0x30,
0x80,0x70,0x40,0xF0,0x80,0x70,0xC0,0xF0,
0x00,0xF8,0x00,0x7C,0x00,0x3C,0x00,0x3C,
0x00,0x3C,0x1C,0x20,0x00,0xF0,0x00,0xA8,
/* 0x06-0x09 ポイント */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x01,0x00,0x03,0x00,0x06,
0x00,0x06,0x00,0x03,0x00,0x01,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x80,0x00,0xC0,0x00,0x60,
0x00,0x60,0x00,0xC0,0x00,0x80,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
/* 0x0a-0x0d 箱 */
0x00,0x00,0x7F,0x7F,0x7F,0x40,0x77,0x48,
0x70,0x4F,0x65,0x5A,0x78,0x47,0x7B,0x44,
0x60,0x5F,0x71,0x4E,0x71,0x4E,0x6A,0x55,
0x7B,0x44,0x7F,0x40,0x7F,0x7F,0x00,0x00,
0x00,0x00,0xFE,0xFE,0xFE,0x02,0xBE,0x42,
0x06,0xFA,0x4E,0xB2,0x86,0x7A,0xB6,0x4A,
0x86,0x7A,0xB6,0x4A,0x86,0x7A,0xB6,0x4A,
0x86,0x7A,0xFE,0x02,0xFE,0xFE,0x00,0x00
};
/* BGタイル番号配列 */
unsigned char blank_tile[] = { 0x00, 0x00, 0x00, 0x00 }; // ブランク(白背景)2x2
unsigned char block_tile[] = { 0x01, 0x01, 0x01, 0x01 }; // ブロック 2x2
unsigned char player_tile[] = { 0x02, 0x04, 0x03, 0x05 }; // プレイヤー 2x2
unsigned char point_tile[] = { 0x06, 0x08, 0x07, 0x09 }; // ポイント 2x2
unsigned char box_tile[] = { 0x0a, 0x0c, 0x0b, 0x0d }; // ボックス 2z2
unsigned char blank_single_tile[] = { 0x00 }; // ブランク(白背景)1x1
unsigned char block_single_tile[] = { 0x01 }; // ブロック 1x1
/* マップ用のタイル番号 */
const int BLANK_TILE_NO = 0; // ブランク(白背景)
const int BLOCK_TILE_NO = 1; // ブロック
const int PLAYER_TILE_NO = 2; // プレイヤー
const int POINT_TILE_NO = 3; // ポイント
const int BOX_TILE_NO = 4; // ボックス
const int WALK_TILE_NO = 9; // 歩行ポイント(自動生成用)
/* BGタイル番号配列取得処理 */
char* get_tile(int index)
{
if (index == BLOCK_TILE_NO) { // 1: ブロック
return block_tile;
} else if (index == PLAYER_TILE_NO) { // 2: プレイヤー
return player_tile;
} else if (index == POINT_TILE_NO) { // 3: ポイント
return point_tile;
} else if (index == BOX_TILE_NO) { // 4: ボックス
return box_tile;
} else { // その他: ブランク(白背景)
return blank_tile;
}
}
#endif // TILE_H
↑タイルの定義
エレキベア
作った画像をこんな感じの配列にまとめたら
番号が割り振られるクマね
番号が割り振られるクマね

マイケル
ちなみにマップ用のタイル番号 については、
この後解説するマップで扱う定数になります!
この後解説するマップで扱う定数になります!
マップの定義

マイケル
次はマップの定義についてです!
先ほど書いた通り今回のゲームでは 16*16を1タイルとして扱うため、
マップ全体を10*9の配列として定義 しています!
先ほど書いた通り今回のゲームでは 16*16を1タイルとして扱うため、
マップ全体を10*9の配列として定義 しています!
#include <stdio.h>
#include <rand.h>
#include "tile.h"
/* マップ情報(10x9) */
const int MAP_WIDTH = 10;
const int MAP_HEIGHT = 9;
// マップ配列
unsigned int map[9][10];
// 初期化用デフォルトマップ
unsigned int default_map[9][10] =
{
{1,1,1,1,1,1,1,1,1,1},
{1,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,1},
{1,1,1,1,1,1,1,1,1,1},
};
// タイトル画面表示用マップ
unsigned int title_map[18][20] =
{
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1},
{1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1},
{1,1,0,1,0,0,1,0,0,0,1,0,0,1,1,1,1,0,1,1},
{1,1,0,0,1,0,1,0,1,1,1,1,0,0,0,0,1,0,1,1},
{1,1,0,0,0,0,1,0,1,0,0,1,0,0,0,0,1,0,1,1},
{1,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,1,0,1,1},
{1,1,0,1,1,0,0,0,0,1,1,0,0,1,1,1,1,0,1,1},
{1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1},
{1,1,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,1,1},
{1,1,0,0,0,1,0,1,1,0,1,0,1,1,0,1,0,0,1,1},
{1,1,0,0,0,1,0,1,0,0,0,0,0,0,0,1,0,0,1,1},
{1,1,0,0,0,1,0,1,0,0,0,0,0,0,0,1,0,0,1,1},
{1,1,0,0,1,0,0,0,1,0,0,0,1,1,1,0,0,0,1,1},
{1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1},
{1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
};
// クリア文字表示用マップ
unsigned int clear_map[18][20] =
{
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1},
{1,1,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,1,1},
{1,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,1},
{1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1},
{1,1,0,0,1,0,0,1,0,0,1,0,0,0,0,0,0,0,1,1},
{1,1,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,1,1},
{1,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,1,1},
{1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1},
{1,1,0,0,0,0,0,1,1,1,0,0,1,1,1,1,1,0,1,1},
{1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1},
{1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,1},
{1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,1},
{1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1},
{1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
};
・・・以下略(マップ自動生成処理は次回解説)・・・

マイケル
この時、タイルの定義で記述した定数(ブロックは1、プレイヤーは2など)
を使って、マップを表現しています!
を使って、マップを表現しています!

マイケル
例えば、下記のようなマップだとこのような配列になります。

{1,1,1,1,1,1,1,1,1,1},
{1,0,1,1,0,1,1,1,0,1},
{1,1,1,0,0,0,0,1,1,1},
{1,1,0,0,0,3,0,0,1,1},
{1,1,1,0,4,4,0,1,0,1},
{1,1,0,0,2,0,0,0,0,1},
{1,0,0,1,0,0,0,3,0,1},
{1,0,0,0,0,0,0,0,1,1},
{1,1,1,1,1,1,1,1,1,1},
↑上記マップを表した配列
マイケル
なおマップ配列の自動生成処理の解説については、
長くなってしまうため次回に持ちこそうと思います…。
長くなってしまうため次回に持ちこそうと思います…。

エレキベア
そっちも楽しみクマね
メイン処理

マイケル
そして最後にメイン処理!
準備したタイル、マップの定義を使ってゲームを実装しています!
準備したタイル、マップの定義を使ってゲームを実装しています!

マイケル
全体の処理としては下記になります。
#include <gb/gb.h>
#include <string.h>
#include "tile.h"
#include "map.c"
/* プレイヤー */
typedef struct
{
int x;
int y;
} Player;
Player player;
/* ボタン押下情報 */
char button;
char pre_button;
/* プロトタイプ宣言 */
void disp_title(); // ゲームタイトル画面表示
void game_start(); // ゲーム開始処理
void init(); // ゲーム初期化処理
void update(); // ゲーム更新処理
void disp_clear(); // ゲームクリア画面表示
char* get_tile(int); // BGタイル番号配列取得処理
/* メイン処理 */
void main(void)
{
// 初期処理(背景有効化、BGタイルの設定)
SHOW_BKG;
set_bkg_data(0, sizeof(tile)/16, tile);
// タイトル画面の表示
disp_title();
// ゲームスタート
game_start();
}
/* タイトル画面表示 */
void disp_title()
{
// 乱数シードを保持
UWORD seed = DIV_REG;
// タイトル画面の表示
for (int y = 0; y < MAP_HEIGHT*2; y++) {
for (int x = 0; x < MAP_WIDTH*2; x++) {
if (title_map[y][x] == BLOCK_TILE_NO) {
set_bkg_tiles(x, y, 1, 1, block_single_tile);
} else {
set_bkg_tiles(x, y, 1, 1, blank_single_tile);
}
}
}
// ボタンの入力検知
button = joypad();
pre_button = button;
while (1) {
button = joypad();
if (pre_button != button) {
// スタートボタン押下で次処理へ
if (button & J_START) {
break;
}
}
pre_button = button;
}
// 乱数初期化
// ※ボタン押下までの時間を使用してランダム調整
seed |= (UWORD)DIV_REG << 8;
initrand(seed);
// ゲームスタート
game_start();
}
/* ゲーム開始処理 */
void game_start()
{
init();
update();
}
/* ゲーム初期化処理 */
void init()
{
// マップ配列生成
generate_map();
// マップの描画
for (int y = 0; y < MAP_HEIGHT; y++) {
for (int x = 0; x < MAP_WIDTH; x++) {
// 番号に紐づくオブジェクトを描画
set_bkg_tiles(x*2, y*2, 2, 2, get_tile(map[y][x]));
// プレイヤータイルの場合、変数に設定
if (map[y][x] == PLAYER_TILE_NO) {
player.x = x;
player.y = y;
}
}
}
}
/* ゲーム更新処理 */
void update()
{
int player_pos[2]; // 移動後のプレイヤー位置
int box_pos[2]; // 移動後のボックス位置
int is_clear = 0; // クリアフラグ
int is_retly = 0; // リトライフラグ
while(1) {
// ボタンの入力検知
button = joypad();
if (pre_button != button) {
// スタートボタン押下でリトライ
if (button & J_START) {
is_retly = 1;
break;
}
// プレイヤーの移動後位置を求める
player_pos[0] = player.x;
player_pos[1] = player.y;
if (button & J_UP ) player_pos[1]-=1;
if (button & J_DOWN ) player_pos[1]+=1;
if (button & J_LEFT ) player_pos[0]-=1;
if (button & J_RIGHT) player_pos[0]+=1;
// 以下の場合、移動処理を行わない
// ① プレイヤーの移動後位置がマップ領域を超えている場合
if (player_pos[0] < 0 || MAP_WIDTH-1 < player_pos[0]
|| player_pos[1] < 0 || MAP_HEIGHT-1 < player_pos[1]) {
continue;
}
// ② プレイヤーの移動後位置がブロックの場合
if (map[player_pos[1]][player_pos[0]] == BLOCK_TILE_NO) {
continue;
}
// プレイヤーの移動後位置がボックスの場合
if (map[player_pos[1]][player_pos[0]] == BOX_TILE_NO) {
// ボックスの移動後位置を求める
box_pos[0] = player_pos[0];
box_pos[1] = player_pos[1];
if (button & J_UP ) box_pos[1]-=1;
if (button & J_DOWN ) box_pos[1]+=1;
if (button & J_LEFT ) box_pos[0]-=1;
if (button & J_RIGHT) box_pos[0]+=1;
// 以下の場合、移動処理を行わない
// ① ボックスの移動後位置がマップ領域を超えている場合
if (box_pos[0] < 0 || MAP_WIDTH-1 < box_pos[0]
|| box_pos[1] < 0 || MAP_HEIGHT-1 < box_pos[1]) {
continue;
}
// ② ボックスの移動後位置がブロックの場合
if (map[box_pos[1]][box_pos[0]] == BLOCK_TILE_NO) {
continue;
}
// ボックス移動処理
if (map[box_pos[1]][box_pos[0]] == POINT_TILE_NO) {
// 移動後位置がポイントの場合、ボックスとポイントを消す
map[player_pos[1]][player_pos[0]] = BLANK_TILE_NO;
map[box_pos[1]][box_pos[0]] = BLANK_TILE_NO;
set_bkg_tiles(player_pos[0]*2, player_pos[1]*2, 2, 2, blank_tile);
set_bkg_tiles(box_pos[0]*2, box_pos[1]*2, 2, 2, blank_tile);
} else {
// ボックスを移動させる
map[player_pos[1]][player_pos[0]] = BLANK_TILE_NO;
map[box_pos[1]][box_pos[0]] = BOX_TILE_NO;
set_bkg_tiles(player_pos[0]*2, player_pos[1]*2, 2, 2, blank_tile);
set_bkg_tiles(box_pos[0]*2, box_pos[1]*2, 2, 2, box_tile);
}
}
// プレイヤー移動処理
// 現在位置がプレイヤーのタイル番号の場合、白背景に設定
if (map[player.y][player.x] == PLAYER_TILE_NO) {
map[player.y][player.x] = BLANK_TILE_NO;
}
// 元いた場所をマップ指定のオブジェクトに戻す
set_bkg_tiles(player.x*2, player.y*2, 2, 2, get_tile(map[player.y][player.x]));
// プレイヤーを移動させる
player.x = player_pos[0];
player.y = player_pos[1];
set_bkg_tiles(player.x*2, player.y*2, 2, 2, player_tile);
// マップにボックスが1つも無ければクリア
is_clear = 1;
for (int y = 0; y < MAP_HEIGHT; y++) {
for (int x = 0; x < MAP_WIDTH; x++) {
if (map[y][x] == BOX_TILE_NO) {
is_clear = 0;
}
}
}
if (is_clear == 1) {
break;
}
}
// 押下ボタンを保持
pre_button = button;
}
// 以下、ループから抜けた後の処理
// クリア処理
if (is_clear == 1) {
disp_clear();
return;
}
// リトライ処理
if (is_retly == 1) {
game_start();
return;
}
}
/* ゲームクリア画面表示 */
void disp_clear()
{
// クリア文字の表示
for (int y = 0; y < MAP_HEIGHT*2; y++) {
for (int x = 0; x < MAP_WIDTH*2; x++) {
if (clear_map[y][x] == BLOCK_TILE_NO) {
set_bkg_tiles(x, y, 1, 1, block_single_tile);
} else {
set_bkg_tiles(x, y, 1, 1, blank_single_tile);
}
}
}
// ボタンの入力検知
button = joypad();
pre_button = button;
while (1) {
button = joypad();
if (pre_button != button) {
// スタートボタンかAボタン押下で次処理へ
if ((button & J_START) || (button & J_A)) {
break;
}
}
pre_button = button;
}
// ゲーム再スタート
game_start();
}
↑倉庫番ゲームメイン処理
エレキベア
長くてわからんクマ・・・

マイケル
少しずつ分けて見ていきましょう!
タイトル画面の表示

マイケル
まずはタイトル画面の表示!
事前に用意したタイトル画面のマップを表示してボタンの入力検知を行なっています。
事前に用意したタイトル画面のマップを表示してボタンの入力検知を行なっています。
/* タイトル画面表示 */
void disp_title()
{
// 乱数シードを保持
UWORD seed = DIV_REG;
// タイトル画面の表示
for (int y = 0; y < MAP_HEIGHT*2; y++) {
for (int x = 0; x < MAP_WIDTH*2; x++) {
if (title_map[y][x] == BLOCK_TILE_NO) {
set_bkg_tiles(x, y, 1, 1, block_single_tile);
} else {
set_bkg_tiles(x, y, 1, 1, blank_single_tile);
}
}
}
// ボタンの入力検知
button = joypad();
pre_button = button;
while (1) {
button = joypad();
if (pre_button != button) {
// スタートボタン押下で次処理へ
if (button & J_START) {
break;
}
}
pre_button = button;
}
// 乱数初期化
// ※ボタン押下までの時間を使用してランダム調整
seed |= (UWORD)DIV_REG << 8;
initrand(seed);
// ゲームスタート
game_start();
}
↑タイトル画面表示処理
↑タイトル画面

マイケル
ほんとは文字も画像として用意した方がいいんだろうけど、
面倒臭いのでブロックで無理やり書きました・・・。
面倒臭いのでブロックで無理やり書きました・・・。

エレキベア
(適当クマ・・・・。)

マイケル
ちなみにマップの自動生成処理の際に乱数取得を行うため、
ボタン入力までの時間を利用して乱数シードの設定 も行なっています!
こちらは次回みていきましょう!
ボタン入力までの時間を利用して乱数シードの設定 も行なっています!
こちらは次回みていきましょう!

エレキベア
ゲームボーイだと時刻機能も無いから
そういう工夫が必要なのクマね・・・
そういう工夫が必要なのクマね・・・
マップの描画処理

マイケル
タイトル画面でボタンが押下されたら次はマップ生成処理!
・・・なのですが、こちらの自動生成処理は次回解説します。
・・・なのですが、こちらの自動生成処理は次回解説します。
/* ゲーム初期化処理 */
void init()
{
// マップ配列生成
generate_map();
// マップの描画
for (int y = 0; y < MAP_HEIGHT; y++) {
for (int x = 0; x < MAP_WIDTH; x++) {
// 番号に紐づくオブジェクトを描画
set_bkg_tiles(x*2, y*2, 2, 2, get_tile(map[y][x]));
// プレイヤータイルの場合、変数に設定
if (map[y][x] == PLAYER_TILE_NO) {
player.x = x;
player.y = y;
}
}
}
}
↑マップ生成処理
エレキベア
(気になるクマ・・・。)
プレイヤーを動かす処理

マイケル
そして次はメインとなるプレイヤー移動処理!
押下されたボタンに応じてマップを移動するようにしています!
押下されたボタンに応じてマップを移動するようにしています!
/* プレイヤー */
typedef struct
{
int x;
int y;
} Player;
Player player;
/* ボタン押下情報 */
char button;
char pre_button;
・・・略・・・
/* ゲーム更新処理 */
void update()
{
int player_pos[2]; // 移動後のプレイヤー位置
int box_pos[2]; // 移動後のボックス位置
int is_clear = 0; // クリアフラグ
int is_retly = 0; // リトライフラグ
while(1) {
// ボタンの入力検知
button = joypad();
if (pre_button != button) {
// スタートボタン押下でリトライ
if (button & J_START) {
is_retly = 1;
break;
}
// プレイヤーの移動後位置を求める
player_pos[0] = player.x;
player_pos[1] = player.y;
if (button & J_UP ) player_pos[1]-=1;
if (button & J_DOWN ) player_pos[1]+=1;
if (button & J_LEFT ) player_pos[0]-=1;
if (button & J_RIGHT) player_pos[0]+=1;
// 以下の場合、移動処理を行わない
// ① プレイヤーの移動後位置がマップ領域を超えている場合
if (player_pos[0] < 0 || MAP_WIDTH-1 < player_pos[0]
|| player_pos[1] < 0 || MAP_HEIGHT-1 < player_pos[1]) {
continue;
}
// ② プレイヤーの移動後位置がブロックの場合
if (map[player_pos[1]][player_pos[0]] == BLOCK_TILE_NO) {
continue;
}
・・・略・・・
// プレイヤー移動処理
// 現在位置がプレイヤーのタイル番号の場合、白背景に設定
if (map[player.y][player.x] == PLAYER_TILE_NO) {
map[player.y][player.x] = BLANK_TILE_NO;
}
// 元いた場所をマップ指定のオブジェクトに戻す
set_bkg_tiles(player.x*2, player.y*2, 2, 2, get_tile(map[player.y][player.x]));
// プレイヤーを移動させる
player.x = player_pos[0];
player.y = player_pos[1];
set_bkg_tiles(player.x*2, player.y*2, 2, 2, player_tile);
// マップにボックスが1つも無ければクリア
is_clear = 1;
for (int y = 0; y < MAP_HEIGHT; y++) {
for (int x = 0; x < MAP_WIDTH; x++) {
if (map[y][x] == BOX_TILE_NO) {
is_clear = 0;
}
}
}
if (is_clear == 1) {
break;
}
}
// 押下ボタンを保持
pre_button = button;
}
// 以下、ループから抜けた後の処理
// クリア処理
if (is_clear == 1) {
disp_clear();
return;
}
// リトライ処理
if (is_retly == 1) {
game_start();
return;
}
}
↑プレイヤー移動処理
マイケル
移動先がブロックの場合には移動しない等、
細かいチェックも入れています!
細かいチェックも入れています!

エレキベア
マップ配列を使って移動処理を行なっているクマね
ボックスを動かす処理

マイケル
そして倉庫番ゲームということで、移動後の位置にボックスがあったら動かさなければなりません。
そちらの判定は下記のように実装しています!
そちらの判定は下記のように実装しています!
/* プレイヤー */
typedef struct
{
int x;
int y;
} Player;
Player player;
/* ボタン押下情報 */
char button;
char pre_button;
・・・略・・・
/* ゲーム更新処理 */
void update()
{
int player_pos[2]; // 移動後のプレイヤー位置
int box_pos[2]; // 移動後のボックス位置
・・・略・・・
while(1) {
・・・略・・・
// プレイヤーの移動後位置がボックスの場合
if (map[player_pos[1]][player_pos[0]] == BOX_TILE_NO) {
// ボックスの移動後位置を求める
box_pos[0] = player_pos[0];
box_pos[1] = player_pos[1];
if (button & J_UP ) box_pos[1]-=1;
if (button & J_DOWN ) box_pos[1]+=1;
if (button & J_LEFT ) box_pos[0]-=1;
if (button & J_RIGHT) box_pos[0]+=1;
// 以下の場合、移動処理を行わない
// ① ボックスの移動後位置がマップ領域を超えている場合
if (box_pos[0] < 0 || MAP_WIDTH-1 < box_pos[0]
|| box_pos[1] < 0 || MAP_HEIGHT-1 < box_pos[1]) {
continue;
}
// ② ボックスの移動後位置がブロックの場合
if (map[box_pos[1]][box_pos[0]] == BLOCK_TILE_NO) {
continue;
}
// ボックス移動処理
if (map[box_pos[1]][box_pos[0]] == POINT_TILE_NO) {
// 移動後位置がポイントの場合、ボックスとポイントを消す
map[player_pos[1]][player_pos[0]] = BLANK_TILE_NO;
map[box_pos[1]][box_pos[0]] = BLANK_TILE_NO;
set_bkg_tiles(player_pos[0]*2, player_pos[1]*2, 2, 2, blank_tile);
set_bkg_tiles(box_pos[0]*2, box_pos[1]*2, 2, 2, blank_tile);
} else {
// ボックスを移動させる
map[player_pos[1]][player_pos[0]] = BLANK_TILE_NO;
map[box_pos[1]][box_pos[0]] = BOX_TILE_NO;
set_bkg_tiles(player_pos[0]*2, player_pos[1]*2, 2, 2, blank_tile);
set_bkg_tiles(box_pos[0]*2, box_pos[1]*2, 2, 2, box_tile);
}
}
・・・略・・・
}
・・・略・・・
}
↑ボックス移動処理
マイケル
プレイヤーとボックスのどちらも移動が可能かを
判定する必要があることに注意です!
判定する必要があることに注意です!

エレキベア
これで基本的な動きはできるようになったクマね
クリア画面の表示

マイケル
最後はゲームクリア処理!
マップ上にボックスが無くなったことを検知したら下記処理に遷移します!
マップ上にボックスが無くなったことを検知したら下記処理に遷移します!
/* ゲームクリア画面表示 */
void disp_clear()
{
// クリア文字の表示
for (int y = 0; y < MAP_HEIGHT*2; y++) {
for (int x = 0; x < MAP_WIDTH*2; x++) {
if (clear_map[y][x] == BLOCK_TILE_NO) {
set_bkg_tiles(x, y, 1, 1, block_single_tile);
} else {
set_bkg_tiles(x, y, 1, 1, blank_single_tile);
}
}
}
// ボタンの入力検知
button = joypad();
pre_button = button;
while (1) {
button = joypad();
if (pre_button != button) {
// スタートボタンかAボタン押下で次処理へ
if ((button & J_START) || (button & J_A)) {
break;
}
}
pre_button = button;
}
// ゲーム再スタート
game_start();
}
↑クリア画面の表示

マイケル
こちらもタイトル画面と同じく、
用意したマップ配列を表示しているだけですね!
用意したマップ配列を表示しているだけですね!

エレキベア
これで完成クマ〜〜〜!!!
おわりに

マイケル
というわけで以上が倉庫番ゲームの解説でした!
どうだったかな?
どうだったかな?

エレキベア
小さなところから作っていけば案外難しくなかったクマ

マイケル
使い方さえ覚えてしまえば案外いけるし、
制限がある分、逆に悩まなくて済むというのもあるよね。
制限がある分、逆に悩まなくて済むというのもあるよね。

マイケル
ただ個人的にはC言語が書き慣れておらず、
オブジェクト指向感覚で書いてるといろいろ苦戦しました・・・。
(コードが汚く申し訳ないです・・・。)
オブジェクト指向感覚で書いてるといろいろ苦戦しました・・・。
(コードが汚く申し訳ないです・・・。)

エレキベア
今ほど便利じゃないからいろいろ困るクマね・・・

マイケル
でもまあとりあえずゲームは完成したので、次回はいよいよ実機実行!
・・・の前に、マップの自動生成処理の解説だけちょこっと挟もうと思います。
・・・の前に、マップの自動生成処理の解説だけちょこっと挟もうと思います。

エレキベア
第2.5回ってところクマね

マイケル
それでは今日はこの辺で!
アデュー!!
アデュー!!

エレキベア
クマ〜〜〜〜〜〜
【GBDK】第二回 自作ゲームボーイソフトを作ろう! 〜ゲーム制作編「倉庫番」〜 〜完〜
※続きはこちら
コメント