※ブログ内の日付は2013/6/18になっていますが(表示階層を合わせるため)
本当の更新日は2015/8/20です
実は最初のゲームを作った後にもう一つ作ったのですが
色々と準備が面倒になって放置してしまいました。
今回は釣りゲームです。
こんな感じの画面で、ペンギンの乗った船を左右に動かし
Aボタンを押すとエサの付いた釣り糸を垂らします。
(Aボタンを押している間は海底へ落ちていき、離すと引き上げます)
エサを魚たちの口あたりに触れさせると魚がくっついて釣れます。
エサは10個しかありませんが、海底のカニを釣り上げるとエサが増えます。
(根気さえあれば永遠に釣り続けられます)
まずはグラフィックデータの準備です。
GBTDで開いて下さい。
背景用 SeaBkg.gbr
文字用 ascii_misaki.gbr
スプライト用 Penguin2.gbr
そしてマップデータです。
GBMBで開いて下さい。
背景用 SeaBkgMap.gbm
これらのデータをエクスポートしてCソースファイルにしてincludeします。
ソース一式はここからダウンロードできます。
メインのソースプログラムは以下の通りです。
長いといえば長いですが、これだけといえばこれだけなのです。
PenguinFishing.c
#include <stdio.h>
#include <gb.h>
// タイルデータ
#include "SeaBkg.c"
#include "Penguin2Spr.c"
#include "Ascii_misaki.c"
// マップデータ
#include "SeaBkgMap.c"
#define GAMEMODE_INIT 0U
#define GAMEMODE_TITLE 2U
#define GAMEMODE_START 3U
#define GAMEMODE_PLAY 4U
#define GAMEMODE_END 5U
#define SPR_ESA 0U
#define SPR_FISH 1U
#define SPR_ITO 21U
#define SPR_HERO 34U
#define SPR_MAX 40U
#define HERO_ACT_NONE 0U
#define HERO_ACT_MOVE 1U
#define HERO_ACT_DOWN 2U
#define HERO_ACT_UP 3U
#define ENE_ACT_NONE 0U
#define ENE_ACT_MOVE 1U
#define ENE_ACT_UP 2U
#define ENE_ACT_DEAD 3U
#define ENE_MAX 5U
UBYTE intcnt;// カウンタ
UBYTE first;// ファーストフラグ
UBYTE game_mode;// ゲームモード
UBYTE modecnt;// カウンタ
UBYTE input_on, input_old, input_edge, input_trg;// キー入力用
fixed seed;// 乱数シード
UBYTE scrx;// スクロール用X座標
UBYTE scry;// スクロール用Y座標
UWORD score;// スコア
UWORD hiscore;// ハイスコア
UBYTE esa;// エサ
// プレイヤ用構造体
struct st_hero {
UBYTE act; // アクションno
UBYTE x; // X座標
UBYTE y; // Y座標
UBYTE ex; // エサX座標
UBYTE ey; // エサY座標
UBYTE cnt; // カウンタ
} hero;
// 敵用構造体
struct st_ene {
UBYTE act; // アクションno
UBYTE knd; // 種類
UBYTE x; // X座標
UBYTE y; // Y座標
BYTE dir; // エサY座標
UBYTE cnt; // カウンタ
} ene[ENE_MAX];
// 波で上下に揺れる
BYTE wavetbl[]={0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,-1,0,0,0,0,-1};
// 敵というか魚のスプライト番号
UBYTE ene_spr[]={0,0,16,17,18,19,20,21,22,23,24,0,25,0};
// 移動スピード(小さいほど速い)
UBYTE ene_wait[]={1,1,2,4,6,3,5,1};
// 点数
UBYTE ene_score[]={0,100,80,50,10,20,30,0};
// 敵というか魚のアタッチ確率
UBYTE ene_att[]={1,1,1,2,2,2,3,4,4,4,4,4,4,4,5,6};
void hero_ctrl();
void hero_disp();
void ene_touch();
void ene_attach();
void ene_mvdisp();
void bg_num_disp(UWORD n, UBYTE x, UBYTE y, UBYTE d);
void sprite_init();
// タイトル画面
void init_title()
{
DISPLAY_OFF;// 画面全体を非表示
scrx = 0;
scry = 184;
hero.x = 88;
hero.y = 64;
intcnt = 0;
seed.b.l = DIV_REG;
sprite_init();// スプライト座標初期化
// タイトル文字
gotoxy(2, 28);
print("FISHING PENGUIN");
gotoxy(5, 2);
print("PUSH START");
gotoxy(5, 3);
print(" ");
gotoxy(1, 1);
print(" ");
DISPLAY_ON;// 画面全体を表示
}
void proc_title()
{
// 入力待ち
if (input_trg & J_START) {
seed.b.h = DIV_REG;
initrand(seed.w);// 乱数初期化
game_mode = GAMEMODE_START;// ゲームスタート処理
first = 0;// ファーストフラグ
}
}
// スタート・画面を下へスクロール
void init_start()
{
score = 0;
esa = 10;
set_bkg_tiles(0, 0, 20, 3, SeaBkgMapLabel);// マップデータを設定する
// エサやスコア
bg_num_disp(esa, 3, 1, 2);// BGへ数字描画
bg_num_disp(score, 12, 1, 5);// BGへ数字描画
}
void proc_start()
{
if (scry != 0) scry++;
else {
game_mode = GAMEMODE_PLAY;// ゲーム処理
first = 0;// ファーストフラグ
}
}
// プレイ
void init_game()
{
hero.act = HERO_ACT_MOVE;
}
void proc_game()
{
hero_ctrl();
ene_touch();
ene_attach();
ene_mvdisp();
}
// ゲームオーバー
void init_end()
{
gotoxy(5, 3);
print("GAME OVER");
if (hiscore < score) hiscore = score;
}
void proc_end()
{
// 入力待ち
if (input_trg & J_START) {
game_mode = GAMEMODE_TITLE;// タイトル処理
first = 0;// ファーストフラグ
}
}
// ペンギンコントロール
void hero_ctrl()
{
if (hero.act == HERO_ACT_MOVE) {
if (esa == 0) {
game_mode = GAMEMODE_END;// ゲームエンド処理
first = 0;// ファーストフラグ
}
if (intcnt & 1) {
if (hero.x > 24U && (input_on & J_LEFT)) hero.x--;
if (hero.x < 152U && (input_on & J_RIGHT)) hero.x++;
}
if (input_trg & J_A) {
hero.act = HERO_ACT_DOWN;
esa--;
bg_num_disp(esa, 3, 1, 2);// BGへ数字描画
}
}
if (hero.act == HERO_ACT_DOWN) {
hero.cnt++;
if (hero.cnt == 96U || !(input_on & J_A)) hero.act = HERO_ACT_UP;
}
if (hero.act == HERO_ACT_UP) {
if (hero.cnt != 0) hero.cnt--;
else hero.act = HERO_ACT_MOVE;
}
}
// ペンギン表示
void hero_disp()
{
UBYTE i, chk;
// 揺れる
if (intcnt & 1) hero.y += wavetbl[((intcnt>>1) & 0x1fU)];
// ペンギン&船表示
for (i = 0; i != 2; i++) {
set_sprite_tile( SPR_HERO+i, 2+i);// スプライトを設定
move_sprite( SPR_HERO+i, hero.x-8+(i<<3), hero.y-scry-8);// スプライトの座標を設定
set_sprite_prop( SPR_HERO+i, S_PRIORITY);
}
for (i = 0; i != 4; i++) {
set_sprite_tile( SPR_HERO+2+i, 4+i);// スプライトを設定
move_sprite( SPR_HERO+2+i, hero.x-16+(i<<3), hero.y-scry);// スプライトの座標を設定
set_sprite_prop( SPR_HERO+2+i, S_PRIORITY);
}
// エサ表示
hero.ex = hero.x-8;
hero.ey = hero.y + hero.cnt - scry;
set_sprite_tile( SPR_ESA, 1);// スプライトを設定
move_sprite( SPR_ESA, hero.ex-4, hero.ey-4);// スプライトの座標を設定
// 糸表示
set_sprite_tile( SPR_ITO, 15);// スプライトを設定
move_sprite( SPR_ITO, hero.ex-4, hero.y-scry-8);// スプライトの座標を設定
// のびる糸
chk = (hero.cnt >> 3);
for (i = 0; i != 12; i++) {
if (chk == i) {
set_sprite_tile( SPR_ITO+i+1, 8 + (hero.cnt & 0x07U));// スプライトを設定
move_sprite( SPR_ITO+i+1, hero.ex-4, hero.y-scry+(i<<3));// スプライトの座標を設定
} else {
if (chk > i) {
set_sprite_tile( SPR_ITO+i+1, 15);// スプライトを設定
move_sprite( SPR_ITO+i+1, hero.ex-4, hero.y-scry+(i<<3));// スプライトの座標を設定
} else {
move_sprite( SPR_ITO+i+1, 0, 0);// スプライトの座標を設定
}
}
}
}
// 敵というか魚との当たり判定
void ene_touch()
{
UBYTE i;
if (hero.act == HERO_ACT_MOVE) return;
for (i = 0; i != ENE_MAX; i++) {
if (ene[i].act == ENE_ACT_MOVE) {
if (hero.ex >= ene[i].x - 4U) {
if (hero.ex <= ene[i].x + 4U) {
if (hero.ey >= ene[i].y - 4U) {
if (hero.ey <= ene[i].y + 4U) {
ene[i].act = ENE_ACT_UP;
hero.act = HERO_ACT_UP;
}
}
}
}
}
}
}
// 敵というか魚のアタッチ
void ene_attach()
{
UBYTE i, r, spr;
r = rand();// 乱数取得
if ((r & 0xf8) != 0) return;
for (i = 0; i != ENE_MAX; i++) {
if (ene[i].act == ENE_ACT_NONE) {
spr = SPR_FISH+(i<<1);
if (r & 1) {
ene[i].dir = 1;
ene[i].x = 248;
set_sprite_prop( spr, S_FLIPX);
set_sprite_prop( spr+1, S_FLIPX);
} else {
ene[i].dir = -1;
ene[i].x = 176;
set_sprite_prop( spr, 0);
set_sprite_prop( spr+1, 0);
}
ene[i].act = ENE_ACT_MOVE;
ene[i].cnt = 0;
r = rand() & 0x0f;// 乱数取得
ene[i].knd = ene_att[r];
set_sprite_tile( spr, ene_spr[(ene[i].knd<<1)]);// スプライトを設定
set_sprite_tile( spr+1, ene_spr[((ene[i].knd<<1)+1)]);// スプライトを設定
if (ene[i].knd == 3) {
ene[i].y = 148;
} else {
r = rand() & 0x3fU;// 乱数取得
ene[i].y = r + 80;
}
break;
}
}
}
// 敵というか魚表示
void ene_mvdisp()
{
UBYTE i, spr;
BYTE xofs;
for (i = 0; i != ENE_MAX; i++) {
if (ene[i].act != ENE_ACT_NONE) {
spr = SPR_FISH+(i<<1);
move_sprite( spr, ene[i].x-4, ene[i].y-4);// スプライトの座標を設定
if (ene[i].dir == -1) {
xofs = 8;
} else {
xofs = -8;
}
if (get_sprite_tile(spr) != 0) {
move_sprite( spr+1, ene[i].x-4+xofs, ene[i].y-4);// スプライトの座標を設定
}
ene[i].cnt++;
// 通常移動
if (ene[i].act == ENE_ACT_MOVE) {
if (ene[i].cnt == ene_wait[(ene[i].knd)]) {
ene[i].cnt = 0;
ene[i].x += ene[i].dir;
if (ene[i].x >= 192U && ene[i].x <= 232U) {
ene[i].act = ENE_ACT_DEAD;
}
}
}
// 釣られた
if (ene[i].act == ENE_ACT_UP) {
ene[i].y--;
if (hero.cnt == 1) {
ene[i].act = ENE_ACT_DEAD;
score += ene_score[(ene[i].knd)];// スコア加算
if (ene[i].knd == 3) esa += 2;// カニだったらエサ加算
if (esa > 99U) esa = 99U;
bg_num_disp(esa, 3, 1, 2);// BGへ数字描画
bg_num_disp(score, 12, 1, 5);// BGへ数字描画
}
}
// デタッチ
if (ene[i].act == ENE_ACT_DEAD) {
ene[i].act = ENE_ACT_NONE;
move_sprite( spr, 0, 0);// スプライトの座標を設定
move_sprite( spr+1, 0, 0);// スプライトの座標を設定
}
}
}
}
// BGへ数字描画
// n=表示する数値 x=描画X座標(左端) y=描画Y座標 d=描画桁数
// 10で除算をやってるので処理が重いかも
void bg_num_disp(UWORD n, UBYTE x, UBYTE y, UBYTE d)
{
UBYTE f = 1;// 最初の0は表示する
do {
d--;
gotoxy(x+d, y);
if (f == 0 && n == 0)
setchar(0);// 空白
else
setchar(0x30U+(n%10));// 数字
f = 0;
if (n != 0) n /= 10;
} while(d != 0);
}
// game_mode毎の初期化(ファースト)
void init()
{
if (first == 0) {
switch (game_mode) {
case GAMEMODE_INIT:
game_mode = GAMEMODE_TITLE;// タイトルへ
case GAMEMODE_TITLE:
init_title();
break;
case GAMEMODE_START:
init_start();
break;
case GAMEMODE_PLAY:
init_game();
break;
case GAMEMODE_END:
init_end();
break;
}
first = 1;
modecnt = 0;
}
}
// game_mode毎の処理
void proc()
{
switch (game_mode) {
case GAMEMODE_INIT:
break;
case GAMEMODE_TITLE:
proc_title();
break;
case GAMEMODE_START:
proc_start();
break;
case GAMEMODE_PLAY:
proc_game();
break;
case GAMEMODE_END:
proc_end();
break;
}
set_bkg_data(15, 1, &SeaBkgTileLabel[240+(intcnt & 0x30U)]); // BGアニメ
intcnt++;
modecnt++;
}
// スプライト座標初期化
void sprite_init()
{
UBYTE i;
for (i = 0; i != SPR_MAX; i++) {
set_sprite_tile( i, 0);
move_sprite( i, 0, 0);
set_sprite_prop( i, 0);
}
}
// VBL割込で呼ばれる
void vbl_isr(void)
{
// キー入力
input_old = input_on;
input_on = joypad();
input_edge = input_on ^ input_old;
input_trg = input_on & input_edge;
// モード別ファースト処理
init();
// モード別実行処理
proc();
// BGの描画開始XY座標を設定
move_bkg(scrx, scry);
// プレイヤー描画
hero_disp();
}
// メイン
void main()
{
DISPLAY_OFF;// 画面全体を非表示
HIDE_BKG;// BGを非表示
HIDE_SPRITES;// スプライトを非表示
print(" ");// ダミー
// 先にスクロール座標を設定しておく
init_title();
move_bkg(scrx, scry);
set_bkg_data(0, 16, SeaBkgTileLabel);// タイルデータをBGとして設定
set_bkg_tiles(0, 2, 20, 16, SeaBkgMapLabel+40);// マップデータを設定する
set_bkg_data(32, 64, AsciiBgLabel);// アスキー文字
SPRITES_8x8;// スプライトモードを8*8に設定
set_sprite_data(0, 26, Penguin2SpTileLabel); // タイルデータをスプライトとして設定(0番から26個)
SHOW_SPRITES;// スプライトを表示
SHOW_BKG;// BGを表示
DISPLAY_ON;// 画面全体を表示
hiscore = 0;//ハイスコア初期化
disable_interrupts();// 割込無効
add_VBL(vbl_isr);// VBL割込に追加
enable_interrupts();// 割込有効
set_interrupts(VBL_IFLAG);// VBL割込セット
// メインループ(無くても良いけど何となく)
while(1) {
delay(1000UL);
}
}
前回はmain()関数の中で無限ループさせていましたが
これだと処理速度が一定になりません。
Vブランク割込で呼ばれるためのvbl_isr(void)関数を作り、main()関数内で登録します。
disable_interrupts();// 割込無効
add_VBL(vbl_isr);// VBL割込に追加
enable_interrupts();// 割込有効
set_interrupts(VBL_IFLAG);// VBL割込セット
Vブランク割込とは、画面の描画タイミングに合わせて発生する
垂直同期割込とか言われるやつです。興味のある方はググって下さい。
そんなこんなでゲームができました。
ゲーム本体はここからダウンロードできます。
![]() |
タイトル画面 |
では、機会がありましたら、またお会いしましょう。