ポンクソフト

テトリスの作成 - 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)

用語説明

以下の説明では、落ちてくる物体をピース、ピースを形作るものをブロックと呼びます。
さらに、ピースに番号を付けます。

ゲームの説明

いわゆるテトリスです。
カーソルキーの左右でピースを動かし、スペースキーで回転、カーソルキーの下で落下します。だんだんピースの落下速度が速くなります。

プログラムの解説

グローバル変数

DDOBJ block;
ブロック画像を表わすスプライトオブジェクトです。16 × 16 ピクセルのブロックが 8 個横に並んだ画像で、左から順にピース1~ピース7で使用されるブロック、壁のブロックとなります。全体の画像の大きさは 128 × 16 ピクセルです。
tetris_block.jpg
int field[21][12];
画面の状態を表わす配列です。中に入る値として、0が空白、1~7がそれぞれピース1~ピース7のブロック、8が壁を表わします。配列のどの値が画面のどこを表わすかを以下に示します。
tetris_grid.gif
struct Piece {
  int x, y;
} piece[7][4][4] = {
  ・・・
};
ピースの形を表わす構造体です。全てのピースは4×4の大きさに収まっているので、その中のどこにブロックがあるかをこの構造体で示しています。
「右下がり階段」型のピースで説明すると、一番最初の行が
1, 1, 2, 1, 2, 2, 3, 2,
となっています。以下の画像の (1, 1)、(2, 1)、(2, 2)、(3, 2)の座標の場所にブロックがあるわけです。
tetris_piece1.gif
さらに回転が4種類あるので上の座標データを各ピースにつき4種類持ち、さらにピースが7種類あるので、3次元の配列となっているわけです。

ウインドウ生成関数

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

メイン画面 変数

bool downFlag = false;
ピース落下フラグです。下矢印キーを押したときや、時間によって強制落下するときに true になります。
static int pnext;
画面の上部に表示されている、次に落ちるピース(ネクストピース)の番号です。0~6の値を取り、それぞれピース1~ピース7を表わします。
static int pno;
現在画面上に出ているピースの番号です。0~6の値を取り、それぞれピース1~ピース7を表わします。
static int pr;
ピースの回転状態を表わします。反時計回りに0度・90度・180度・270度の回転をそれぞれ0・1・2・3で表わします。
static int px, py;
現在のピースの座標です。
static int keyLeft = FREE_KEY;
static int keyRight = FREE_KEY;
static int keyDown = FREE_KEY;
static int keySpace = FREE_KEY;
カーソルキーの左・右・下、スペースキーの状態を保持する変数です。
static bool nextFlag = true;
ネクストピースを出すときに true になります。ゲーム開始時や、現在のピースが一番下まで落ちたときに true となります。
static bool overFlag = false;
ゲームオーバーになったときに true となります。
static int score = 0;
スコアです。
static DWORD rpt = 0;
ピースを動かしたときに、連続で動くと速すぎるので、どのくらいの速さで動くかを表わすキーリピート時間を保持します。
static DWORD downTime = 1000;
ピースが強制的に一段落下する時間をミリ秒で表わします。ゲームが進むにつれてこの時間が短くなります。
static DWORD downCount;
ピースが強制的に落下する時間を計測するカウンタです。

メイン画面 初期処理

if (elChangeScreen()) pnext = rand() % 7;
elChangeScreen 関数はゲーム開始時に一度だけ true になります。ここでは、ゲーム開始時にネクストピースをランダムで選んでいます。

メイン画面 ネクストピース出現処理

if (nextFlag) {
ゲーム開始時や、現在のピースが下まで落ちたとき、このフラグがセットされてネクストピース出現処理を行ないます。
px = 4; py = 0; pr = 0;
ピースの座標を画面の一番上の中央に、回転を0度にセットします。
pno = pnext;
pnext = rand() % 7;
ネクストピースの番号を現在のピースの番号とし、ネクストピースをランダムでセットします。
nextFlag = false;
ネクストピース出現フラグをクリアします。
downCount = timeGetTime();
強制落下用のカウンタを現在時刻にします。
if (downTime > 100) downTime -= 10;
else downTime--;
if (downTime < 10) downTime = 10;
ネクストピースが出るたびに、強制落下時間を減らします。強制落下時間が 100 ミリ秒より多ければ 10 ミリ秒ずつ減らし、100 ミリ秒以下なら 1 ミリ秒ずつ減らしています。さらに強制落下時間が 10 ミリ秒未満にならないようにします。
for (i = 0; i < 4; i++) {
  if (field[py + piece[pno][pr][i].y][px + piece[pno][pr][i].x]) overFlag = true;
}
このゲームでゲームオーバーになる条件は、ネクストピースが出た瞬間に、そこにブロックがあったときだけです。そのチェックをここで行なっています。画面データからピースの中のブロックがある座標4つをループで調べ、そこにブロックが存在したときにゲームオーバーフラグをセットしています。

メイン画面 ピース移動処理

int kx, ky, kr;
kx = px; ky = py; kr = pr;
仮にピースの座標と回転の情報を保持しておく変数です。
elSystem::GetKey(VK_LEFT, &keyLeft);
elSystem::GetKey(VK_RIGHT, &keyRight);
elSystem::GetKey(VK_DOWN, &keyDown);
elSystem::GetKey(VK_SPACE, &keySpace);
カーソルキーとスペースキーの値を読み取り変数に格納します。
DWORD nowTime = timeGetTime();
キーリピートとピースの強制落下チェック用に現在時刻を nowTime に格納します。
if (keySpace == PUSH_KEY) {
  if (++kr > 3) kr = 0;
スペースキーが押されたときは回転なので、仮の回転用の変数を +1 (90 度回転)します。GetKey 関数はキーが押された瞬間に PUSH_KEY、押し続けている間は HOLD_KEY の値を取ります。つまり、続けて回転するためには一度キーを離さないといけません。
} else if (keyDown == PUSH_KEY || keyDown == HOLD_KEY && nowTime > rpt) {
  ky++; downFlag = true; rpt = nowTime + 10;
下矢印キーが押されたときは、ピースの y 座標を +1 し、落下フラグをセットします。押した瞬間 (PUSH_KEY) はすぐに落下、押し続けているなら (HOLD_KEY) キーリピート時間 rpt を過ぎたときだけ落下します。落下後はキーリピート時間を現在時間 + 10 ミリ秒にセットします。
} else if (keyLeft == PUSH_KEY) {
  kx--; rpt = nowTime + 180;
} else if (keyLeft == HOLD_KEY && nowTime > rpt) {
  kx--; rpt = nowTime + downTime / 10;
左矢印キーが押されたときは、ピースの x 座標を -1 します。押した瞬間はすぐに左へ移動しキーリピート時間を 180 ミリ秒にセット、押し続けているときはキーリピート時間が過ぎたら左へ移動し、キーリピート時間を強制落下時間÷10 にセットします。これでゲームが進んで落下スピードが速くなったときにキーリピートも速くなり、動きやすくなります。
} else if (keyRight == PUSH_KEY) {
  kx++; rpt = nowTime + 180;
} else if (keyRight == HOLD_KEY && nowTime > rpt) {
  kx++; rpt = nowTime + downTime / 10;
右矢印キーが押されたときも同様の処理を行ないます。
} else if (nowTime - downCount > downTime) {
  ky++; downFlag = true; downCount = nowTime;
}
どのキーも押されていないとき、強制落下時間を過ぎていればピースの y 座標を +1 し、落下フラグをセットします。

メイン画面 当たり判定処理

for (i = 0; i < 4; i++) {
  if (field[ky + piece[pno][kr][i].y][kx + piece[pno][kr][i].x]) break;
}
現在のピースの各ブロックが置かれている場所をチェックし、他のブロックや壁にぶつかっていたら即座にループを抜けます。
if (i >= 4) {
  px = kx; py = ky; pr = kr;
ループを4回通った、つまりどこのブロックにも当たっていない場合は仮のピース座標と回転の変数を本当のピース座標と回転の変数にセットします。
} else if (downFlag) {
ループを4回通っていないときはどこかのブロックに当たったときですが、その場合さらに落下フラグをチェックし、落下しているのなら以下のピースを固めるチェックと、ブロックを消すチェックを行ないます。
for (i = 0; i < 4; i++) {
  field[py + piece[pno][pr][i].y][px + piece[pno][pr][i].x] = pno + 1;
}
nextFlag = true;
まずピースを固める処理を行ないます。現在のピースのブロックの情報を画面データにセットし、ネクストピースフラグをセットして次のピースを出現させるようにします。
int sc = 0;
ここからはラインを埋めたときのブロックを消すチェックを行なっています。まず仮のスコア sc を 0 にします。
for (y = 19; y >= 0; y--) {
画面データの一番下から一番上に向かって、チェックを行なっていきます。
for (x = 1; x < 11; x++) if (field[y][x] == 0) break;
if (x == 11) {
現在チェック中のラインを左から右へ見ていき、ひとつでも空白があればチェックを終了します。
for (int y2 = y - 1; y2 >= 0; y2--) {
  for (int x2 = 1; x2 < 11; x2++) field[y2 + 1][x2] = field[y2][x2];
}
y++;
sc = sc ? sc * 4 : 10;
ラインが全てブロックで埋まっていればこの処理を行ないます。ひとつ上のラインから一番上のラインまでループし、全てのブロックを一段下へ下げます。その後、得点を計算します。1ライン目は 10 点、それ以降1ライン消える毎に 4 倍になります。
score += sc;
Sleep(400);
得点を追加し、次のブロックが出るまで 400 ミリ秒ストップします。

メイン画面 画面表示

for (y = 0; y < 21; y++) {
  for (x = 0; x < 12; x++) {
    if (field[y][x]) {
      elDraw::Layer(x * 16, y * 16 + 80, block,
        (field[y][x] - 1) * 16, 0, field[y][x] * 16, 16);
    }
  }
}
画面データに従って、画面上にブロックや壁を表示しています。
for (i = 0; i < 4; i++) {
  elDraw::Layer((px + piece[pno][pr][i].x) * 16,
    (py + piece[pno][pr][i].y) * 16 + 80,
    block, pno * 16, 0, pno * 16 + 16, 16);
  elDraw::Layer(piece[pnext][0][i].x * 16 + 64,
    piece[pnext][0][i].y * 16,
    block, pnext * 16, 0, pnext * 16 + 16, 16);
}
現在のピースとネクストピースを表示している部分です。
SHOW2(0, 0, "SCORE : %d", score);
スコアを表示しています。
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

残りを読む »