はじめに
本稿では、第1回で解説した「TETRA」とは別の落ち物ゲーム「聖夜の落とし物」を作ります。「聖夜の落とし物」も、ある有名な落ち物ゲームを模しています。ルールを簡単に説明すると、次の通りです。
このゲームでは、落ちてくるブロックを積み重ねて、同じ種類のブロックを4個以上つなげることを目標とします。縦横に4個以上つながるとブロックが消え、点数が入ります。上まで積み重ねてしまうと、ゲームオーバーです。
他に「TETRA」と違うところは、浮いているブロックが個別に落下すること(ピース単位ではなく)、それによって続けざまにブロックが消える「連鎖」という動作が起こりうること、です。連鎖するほど得られる点数は増えるので、大連鎖を狙う楽しみがあります。
本稿のプログラムは、第1回のサンプルプログラム「TETRA」を基にしています。「聖夜の落とし物」に特有な処理として、4個以上つながっているかの判定、妨害ブロック(後述)の取り扱い、などがあります。
対象読者
前作『落ち物ゲームの作り方 第1回:「TETRA」編』を読んだ方。重複する内容は解説しません。
必要な環境
Visual C++ .NET 2002で開発し、Windows XP/98で動作確認しています。
データ構造の諸変更
PIECE
構造体とFIELD
構造体にあった、ブロックの有無を表す変数bool block[][]
の代わりに、BYTE image[][]
メンバ変数を利用します。ブロックが無いときはNONE
を代入します。他にも、ここではわかりませんが、フィールドの最上段は表示しないように変更しています。
// ピースのセル数 #define PW 3 #define PH 3 // フィールドのセル数 #define FW 6 #define FH 13 // セルのピクセル数 #define CW 32 #define CH 32 // ピース管理構造体 typedef struct tagPIECE { BYTE image[PH][PW]; // ブロックのビットマップ番号 char x,y; // 左上のセル座標 }PIECE; // フィールド管理構造体 typedef struct tagFIELD { BYTE image[FH][FW]; // ブロックのビットマップ番号 bool vanish[FH][FW]; // 消滅フラグ }FIELD; // ブロックのビットマップ番号 enum { COLOR1,COLOR2,COLOR3,COLOR4,COLOR5,COLOR6,JAMA,KATA,NONE };
ピースを作る
ピースの形は次に示す1種類だけですが、ブロックの色はランダムです。この色ブロックはCOLOR1
からCOLOR6
までの最大6種類を用意していますが、実際に出現させる種類はg_colors
グローバル変数で調節します。
BYTE g_colors=6; // 色ブロックの種類 // ピース作成 static void CreatePiece(PIECE *piece) { for(BYTE y=0;y<PH;y++) for(BYTE x=0;x<PW;x++) piece->image[y][x]=NONE; piece->image[1][1]=(BYTE)(rand()%g_colors); piece->image[2][1]=(BYTE)(rand()%g_colors); piece->x=1; piece->y=-2; }
いくつ繋がっているかを調べる
このゲームの肝である、同じ色のブロックが一定数(例えば4個)以上つながったら消す、という処理の下請けとして、いくつ繋がっているのかを調べます。調べる方向は上下左右の四方です。これは再帰処理で簡単に書くことができます。
// 四方に隣接している同色ブロックの個数を調べる // TETRA から追加 static void Count(FIELD *pField,int y,int x,DWORD *n) { BYTE c=pField->image[y][x]; // 自分の色 pField->image[y][x]=NONE; // 調べたブロックは消す (*n)++; // 個数 if(x+1<FW && pField->image[y][x+1]==c) Count(pField,y,x+1,n); // 右 if(y+1<FH && pField->image[y+1][x]==c) Count(pField,y+1,x,n); // 下 if(x-1>=0 && pField->image[y][x-1]==c) Count(pField,y,x-1,n); // 左 if(y-1>=0 && pField->image[y-1][x]==c) Count(pField,y-1,x,n); // 上 }
これだけです。でも本当にこれで大丈夫なの、と感じた方もいると思うので、図3に示す例で、同じ色のブロックを追跡する軌跡をたどってみましょう。○は空セル、●は色ブロック、◎は現在の座標です。
- 初期状態です。
- ◎を起点に走査を開始します。右を調べると色ブロックがあるので移動します。
- 右を調べると色ブロックがあるので移動します。
- 右、下、左、上の順に調べますが、色ブロックがないので、再帰を抜けて、3.の座標に戻ります。そして下を調べると色ブロックがあるので移動します。
- 右下左上に色ブロックがないので、3.の座標に戻ります。そして左、上の順に調べますが、色ブロックがないので、2.の座標に戻ります。下、左の順に調べると、左に色ブロックがあるので移動します。
以下同様です。
消滅フラグを立てる
Count
関数で一定数以上つながっていることがわかったら、そのセル座標をVanish
関数に渡して、隣接しているブロックに消滅フラグを立てます。
ところで、このゲームには色ブロックの他に、妨害ブロックというものがあります。妨害ブロックは、いくつ繋げても消えることが無いやっかいな存在です。JAMA
とKATA
の2種類があり、これらが消える条件は、「一定数以上つながった色ブロックに隣接している」です。JAMA
とKATA
の違いは後で説明します。
// 四方に隣接している同色ブロックと // その四方に隣接している妨害ブロックに消滅フラグを立てる static void Vanish(int y,int x) // TETRA から追加 { if(g_field.vanish[y][x]) return; BYTE c; // 妨害ブロックに消滅フラグを立てる if(x+1<FW) { c=g_field.image[y][x+1]; if(c==JAMA || c==KATA) g_field.vanish[y][x+1]=true; } if(y+1<FH) { c=g_field.image[y+1][x]; if(c==JAMA || c==KATA) g_field.vanish[y+1][x]=true; } if(x-1>=0) { c=g_field.image[y][x-1]; if(c==JAMA || c==KATA) g_field.vanish[y][x-1]=true; } if(y-1>=0) { c=g_field.image[y-1][x]; if(c==JAMA || c==KATA) g_field.vanish[y-1][x]=true; } // 同色ブロックに消滅フラグを立てる g_field.vanish[y][x]=true; c=g_field.image[y][x]; if(x+1<FW && g_field.image[y][x+1]==c) Vanish(y,x+1); if(y+1<FH && g_field.image[y+1][x]==c) Vanish(y+1,x); if(x-1>=0 && g_field.image[y][x-1]==c) Vanish(y,x-1); if(y-1>=0 && g_field.image[y-1][x]==c) Vanish(y-1,x); }