ポンクソフト

ぷよぷよの作成 - C言語とelで様々なゲームを作ろう

前ページ C言語とelで様々なゲームを作ろう TOP 

目次

  1. C言語とelで様々なゲームを作ろう
  2. Visual C++ .NET での設定
  3. テンプレートファイルの解説
  4. シューティングゲームの作成(チュートリアル)
  5. パックマン的ゲームの作成(チュートリアル)
  6. ブロック崩しの作成
  7. 15パズルの作成
  8. 横スクロールジャンピングゲームの作成
  9. オセロの作成
  10. 神経衰弱の作成
  11. 7ならべの作成
  12. テトリスの作成
  13. ぷよぷよの作成

ソース・プロジェクトファイル

ソースファイル
プロジェクトファイル(Visual C++ 6.0)

ゲームの説明

いわゆるぷよぷよです。
カーソルキーの左右でぷよを動かし、スペースキーで回転、カーソルキーの下で落下します。同色のぷよを4つ以上繋げると消すことができます。だんだん落下速度が速くなって難しくなります。

プログラムの解説

グローバル変数

DDOBJ puyo;
各色のぷよを表わすスプライトオブジェクトです。ひとつのぷよは 32 × 32 ピクセルで、それが5色と、ぷよを消したときに一時的に表示される灰色のぷよが1つ並んだ画像です。
puyopuyo_puyo.gif
int field[15][8];
画面の状態を表わす配列です。中に入る値として、-1が空白、0~4が緑・黄・青・赤・紫のぷよ、5が壁を表わします。配列のどの値が画面のどこを表わすかを以下に示します。壁は赤色の部分で、画面上には表示されません。
puyopuyo_grid.gif
int cmb[15][8];
ぷよの結合チェックに使う配列です。
bool elist[30];
ぷよが結合されているかを一時的に保存しておくリストです。

ウインドウ生成関数

puyo = elDraw::LoadObject("puyo.bmp");
ぷよの画像をスプライトオブジェクトに読み込んでいる部分です。
for (int y = 0; y < 15; y++) {
  for (int x = 0; x < 8; x++) {
    if (x == 0 || x == 7 || y == 14) field[y][x] = 5;
    else field[y][x] = -1;
  }
}
画面データ配列 field を初期化しています。画面の左右端と下端は見えない壁 (5) とし、その他は空白 (-1) とします。

ぷよ結合チェック関数 CheckCombine

int CheckCombine(int x, int y, int pno, int cno)
画面上の (x, y) の位置に pno (0 ~ 4) の種類のぷよが存在するか調べ、さらにそのぷよに繋がったぷよを調べていき、繋がったぷよの合計を戻値として返す関数です。cno は結合番号で、既に結合チェック済みのぷよを再度調べないために使用し、ひとつ調べるたびにインクリメントされます。
if (field[y][x] != pno || cmb[y][x] != 0) return 0;
まず、調べようとする位置のぷよの種類が違う、またはすでにチェック済みならば結合数 0 を戻値として返します。結合チェック配列 cmb には引数として渡された結合番号 cno が入ります。0 ならば未チェックです。
int ret = 1;
ret に戻値として返すためのぷよ結合数が入ります。
cmb[y][x] = cno;
現在の結合番号を結合チェック配列にセットします。
if (y > 0) ret += CheckCombine(x, y - 1, pno, cno);
ret += CheckCombine(x + 1, y, pno, cno);
ret += CheckCombine(x, y + 1, pno, cno);
ret += CheckCombine(x - 1, y, pno, cno);
ぷよの繋がりを調べるために、再帰関数として自分自身を呼び出します。現在の座標の上下左右の座標をセットして呼び出すことによって、再帰的に全ての繋がりを調べることができます。上部以外は壁があるのでそこでチェックが止まります。

メイン画面 変数

static int pnext1, pnext2;
次に出てくるぷよ(ネクストぷよ)の種類です。5種類のぷよがあるので値は0~4を取り、一度に2つのぷよが出るので変数が2つあります。
static int pno1, pno2;
現在画面上に出ているぷよの種類です。5種類のぷよがあるので値は0~4を取り、一度に2つのぷよが出るので変数が2つあります。
static int px1, py1, px2, py2;
現在画面上に出ているぷよの座標です。これも2つ分あります。
static int keyLeft = FREE_KEY;
static int keyRight = FREE_KEY;
static int keyDown = FREE_KEY;
static int keySpace = FREE_KEY;
カーソルキー左・右・下・スペースキーの状態を保持する変数です。
static bool overFlag = false;
ゲームオーバーフラグです。これが true になるとゲームオーバーです。
static int score = 0;
得点です。
static DWORD rpt = 0;
ぷよを動かすときに連続で動くと速すぎるので、どのくらいの速さで動くかを表わすキーリピート時間を保持します。
static DWORD downTime = 1000;
ぷよが強制的に一段落下する時間をミリ秒で表わします。ゲームが進むにつれてこの時間が短くなります。
static DWORD downCount;
ぷよが強制的に落下する時間を計測するカウンタです。
static enum {
  NEXT,
  NORMAL,
  FALL,
  ERASE1,
  ERASE2,
} status = NEXT;
現在の状態を表わす列挙型変数です。それぞれの値は次のような意味です。

メイン画面 初期処理

if (elChangeScreen()) {pnext1 = rand() % 5; pnext2 = rand() % 5;}
elChangeScreen 関数はゲーム開始時に一度だけ true になります。ここでは、ゲーム開始時にネクストぷよを2つランダムで選んでいます。
DWORD nowTime = timeGetTime();
メイン処理を行なう前に、現在時刻を nowTime に代入しておきます。

メイン画面 ネクストぷよ出現

メイン処理では、状態変数 status の値に応じてそれぞれの処理を行ないます。
px1 = 3; py1 = 1; px2 = 3; py2 = 0;
pno1 = pnext1; pno2 = pnext2;
pnext1 = rand() % 5; pnext2 = rand() % 5;
ぷよの各変数を初期化している部分です。ぷよの座標(2つ)を画面最上部の中央にセットし、ぷよの種類をネクストぷよの値にし、ネクストぷよをランダムで2つ選びます。
downCount = timeGetTime();
強制落下用のカウンタを現在時刻にします。
if (downTime > 100) downTime -= 10;
else downTime--;
if (downTime < 10) downTime = 10;
ネクストぷよが出るたびに、強制落下時間を減らします。強制落下時間が 100 ミリ秒より多ければ 10 ミリ秒ずつ減らし、100 ミリ秒以下なら 1 ミリ秒ずつ減らしています。さらに強制落下時間が 10 ミリ秒未満にならないようにします。
status = NORMAL;
最後に status を通常の状態にします。

メイン画面 通常処理

kx1 = px1; ky1 = py1;
kx2 = px2; ky2 = py2;
まず仮の座標にぷよの座標を入れておきます。
elSystem::GetKey(VK_LEFT, &keyLeft);
elSystem::GetKey(VK_RIGHT, &keyRight);
elSystem::GetKey(VK_DOWN, &keyDown);
elSystem::GetKey(VK_SPACE, &keySpace);
カーソルキーとスペースキーの値を読み取り変数に格納します。
flag = false;
カーソルキーの下を押したときや、時間による強制落下などで、ぷよが落下中なら true になるフラグです。
if (keySpace == PUSH_KEY) {
  if (kx2 > kx1) {kx2 = kx1; ky2 = ky1 + 1;}
  else if (kx2 < kx1) {kx2 = kx1; ky2 = ky1 - 1;}
  else if (ky2 > ky1) {ky2 = ky1; kx2 = kx1 - 1;}
  else {ky2 = ky1; kx2 = kx1 + 1;}
スペースキーが押されている場合は、仮のぷよ座標を回転します。GetKey 関数はキーが押された瞬間に PUSH_KEY、押し続けている間は HOLD_KEY の値を取ります。つまり、続けて回転するためには一度キーを離さないといけません。
} else if (keyDown == PUSH_KEY || keyDown == HOLD_KEY && nowTime - rpt > 10) {
  ky1++; ky2++; flag = true; rpt = nowTime;
カーソルキーの下が押されたときは、ぷよの y 座標を +1 し、落下フラグをセットします。押した瞬間 (PUSH_KEY) はすぐに落下、押し続けているなら (HOLD_KEY) キーリピート時間 10 ミリ秒を過ぎたときだけ落下します。
} else if (keyLeft == PUSH_KEY || keyLeft == HOLD_KEY && nowTime - rpt > 150) {
  kx1--; kx2--; rpt = nowTime;
} else if (keyRight == PUSH_KEY || keyRight == HOLD_KEY && nowTime - rpt > 150) {
  kx1++; kx2++; rpt = nowTime;
カーソルキーの左右を押したときも同じくぷよの座標を左右に動かします。キーリピート時間は 150 ミリ秒にします。
} else if (nowTime - downCount > downTime) {
  ky1++; ky2++; flag = true; downCount = nowTime;
}
どのキーも押されていないとき、強制落下時間を過ぎていればぷよの y 座標を +1 し、落下フラグをセットします。
if (field[ky1][kx1] == -1 && field[ky2][kx2] == -1) {
  px1 = kx1; py1 = ky1;
  px2 = kx2; py2 = ky2;
もしぷよを動かした先が空ならば、実際の座標に仮の座標を入れます。
} else if (flag) {
  field[py1][px1] = pno1;
  field[py2][px2] = pno2;
  status = FALL;
}
もしぷよを動かしたさきが空でなく、つまり壁か他のぷよに当たったときで、なおかつ落下中ならば、現在のぷよを画面配列に入れ、状態変数をぷよ落下中にします。

メイン画面 ぷよ落下

Sleep(50);
毎回処理を行なうと一瞬で落下してしまうので、まずここで 50 ミリ秒時間待ちをします。
flag = false;
ぷよがひとつでも落下中ならば、このフラグが true になります。
for (y = 12; y >= 0; y--) {
  for (x = 1; x < 7; x++) {
    if (field[y][x] != -1 && field[y + 1][x] == -1) {
      field[y + 1][x] = field[y][x];
      field[y][x] = -1;
      flag = true;
    }
  }
}
画面の一番下から上へ順次調べて行き、現在調べている座標にぷよがあり、さらにそのひとつ下が空間ならば、落下処理を行ないます。ぷよをひとつ下に移動し、落下中フラグをセットします。
if (flag == false) status = ERASE1;
ぷよがひとつも落下中でなければ、状態をぷよ消しチェックにします。

メイン画面 ぷよ消しチェック

flag = false;
ひとつでも連結ぷよ(4つ以上繋がったぷよ)があればこのフラグが true になります。
for (y = 0; y < 15; y++) for (x = 0; x < 8; x++) cmb[y][x] = 0;
for (i = 0; i < 30; i++) elist[i] = false;
結合チェック配列と結合リストを全てクリアしておきます。
for (y = 13, i = 0; y >= 0; y--) {
  for (x = 1; x < 7; x++) {
画面の一番下から上へ、左から右へ順次結合チェックして行きます。
if (cmb[y][x] == 0 && field[y][x] != -1) {
まだ結合チェックを行なっていなくて、なおかつそこが空間でなければ以下の処理を行ないます。
i++;
i は結合番号です。チェック配列 cmb にセットされる値です。
int ret = CheckCombine(x, y, field[y][x], i);
CheckCombine 関数を呼び出し、現在の座標にあるぷよから繋がっているぷよの数を ret に代入します。
if (ret >= 4) {
  flag = true;
  elist[i] = true;
  score += ret * 10;
}
繋がっているぷよの数が4以上ならば連結フラグをセットし、結合リストをセットします。さらに得点を繋がった数× 10 点追加します。
if (flag) {
  for (y = 13; y >= 0; y--) {
    for (x = 1; x < 7; x++) {
      if (elist[cmb[y][x]]) field[y][x] = 5;
    }
  }
  status = ERASE2;
結合ぷよが存在する場合、結合ぷよを灰色の消しぷよに入れ替え、状態をぷよ消し中にします。
} else {
  for (y = 0; y < 2; y++) {
    for (x = 1; x < 7; x++) {
      if (field[y][x] != -1) overFlag = true;
    }
  }
  status = NEXT;
結合ぷよが存在しない場合、上部2段のどこかにぷよが存在するか調べ、あったならばゲームオーバーフラグをセットします。さらに状態をネクストぷよ出現にします。

メイン画面 ぷよ消し中

Sleep(500);
for (y = 13; y >= 0; y--) {
  for (x = 1; x < 7; x++) {
    if (field[y][x] == 5) field[y][x] = -1;
  }
}
status = FALL;
灰色の消しぷよが表示されている状態です。まず 500 ミリ秒待ち、その後消しぷよを空間にし、状態をぷよ落下中にします。

メイン画面 画面表示処理

elDraw::Box(192, 0, 256, 448, RGB(150, 150, 150), RGB(150, 150, 150), 0);
elDraw::Line(0, 64, 192, 64, RGB(255, 255, 255), 1);
画面右側の灰色の四角と、これより上にぷよが置かれたらゲームオーバーになる線を書きます。
for (y = 0; y < 14; y++) {
  for (x = 1; x < 7; x++) {
    if (field[y][x] != -1) {
      elDraw::Layer((x - 1) * 32, y * 32, puyo,
        field[y][x] * 32, 0, field[y][x] * 32 + 32, 32);
    }
  }
}
画面配列を見て、ぷよを描いている部分です。
if (status == NORMAL) {
  elDraw::Layer((px1 - 1) * 32, py1 * 32,
    puyo, pno1 * 32, 0, pno1 * 32 + 32, 32);
  elDraw::Layer((px2 - 1) * 32, py2 * 32,
    puyo, pno2 * 32, 0, pno2 * 32 + 32, 32);
}
現在のぷよを表示しています。状態が通常のときだけ表示します。
elDraw::Layer(208, 144, puyo, pnext2 * 32, 0, pnext2 * 32 + 32, 32);
elDraw::Layer(208, 144 + 32, puyo, pnext1 * 32, 0, pnext1 * 32 + 32, 32);
ネクストぷよを表示している部分です。
if (overFlag) {
  elSystem::Message("ゲームオーバー");
  elDraw::Exit();
}
ゲームオーバーフラグが立っていた場合、メッセージを表示してゲームを終了します。
前ページ C言語とelで様々なゲームを作ろう TOP 
このエントリーをはてなブックマークに追加 そっか0

このページに関するコメントをどうぞ

お名前:


しお
vectorではなくlistを使っているのはなぜなのでしょうか?
2017/05/07 19:06

surach
なぜ弾のX座標が sx = px + 24;で時機の中央になるんですか?
2017/05/05 11:09

何度やってもできないー^^
ファイルが見つかりませんとなるのですが・・・
どうすれば?
2017/01/20 17:23

ww
dxlibは外部から自分で導入するものです。しっかり導入しましたか?
2016/12/26 12:29

ww
IncludePathとLibraryPathはdxライブラリを導入したときのフォルダパスが間違っているため、dxlib.hがあるフォルダパスを設定しなおせば動きます。
2016/12/26 12:28

ww
MTdは、プロジェクトのプロパティの設定項目から変更できる。
Debugモードの設定が、違うようですね↓
2016/12/26 12:26

笑子
実行したところ、白い画面が出てきてしまいます。解決策は、あるのでしょうか?
2016/11/10 23:38


背景を挿入したいのですが,どこにどんな風にプログラムを加えればいいのでしょうか?
2016/09/24 21:44

華仙 学
オセロプログラムに関してです。
Visual Studio 2015を使ってます。このプログラムを打ち込んだところ、「'RuntimeLibrary'の不一致が検出されました。値'MTd_StaticDebug'がMDd_DynamicDebugの値'othello.obj'と一致しません」と出てしまいます。いろいろ調べていますがなかなか解決しません。何か解決策はあるでしょうか?
2016/08/17 21:12

あたる
さらにいいゲームを教えてほしいですね。
2016/08/11 13:00

残りを読む »