こんにちは、あんみんどうふです。
今回はゲームオーバーの実装…だけだとすぐ終わっちゃうので、ゲームが始まるときのカウントダウンも実装します。
前回の記事はこちら。
全編見たい方はタグからどうぞ。
9-1.自由落下
ゲームオーバーの前に先に実装しておきたかったのが「自由落下」です。実装するタイミングがなかなか掴めず、後へ後へと先延ばししていたらここまで来てしまったのでここでやります・・・。
自由落下についてですが、いわゆる「重力」のことです。
テトリスもぷよぷよも、放っておけば勝手に落下しますよね。これを実装します。
まずデザイナーを開き、ツールボックスからTimerを追加しnameを「freeFall」にし、Intervalを「500」にします。
これでfreeFallを動かしている間は500ミリ秒=0.5秒ごとに処理が行われます。
次にfreeFallのTickイベントを追加し、以下のコードを書きこみます。
1 2 3 4 5 6 7 | /** * 自由落下 */ private void freeFall_Tick(object sender, EventArgs e) { if(!DEBUG_MODE) minoMoving(2, 1); } |
自由落下の処理はこれだけです。0.5秒ごとに下へ移動するだけ。
(デバッグモードを有効にしている時は自由落下させないようにしてます)
ただ、自由落下が有効になっていないのでmainFormのLoadイベントの最後の方に追記します。
1 2 3 4 5 6 7 8 9 10 11 12 | private async void mainForm_Load(object sender, EventArgs e) { ... // 自由落下有効 if(!DEBUG_MODE) freeFall.Enabled = true; // デバッグモード if (DEBUG_MODE) this.Text += " - Debug Mode"; gameStart = true; } |
これでゲーム開始時から落下するようになります。
ちなみにデバッグモード中は自由落下しないようにしています。
また、挙動を本家テトリスに近づけるため少し手を加えます。
自由落下の経過時間を下移動する度にリセットされるようにしたいので、KeyDownイベントを修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | /* ミノ操作(KeyDown) */ private void mainForm_KeyDown(object sender, KeyEventArgs e) { if (gameStart) { switch (e.KeyCode) { case Keys.Up: while(minoMoveCheck(2, 1)) minoMoving(2, 1); minoMoving(2, 1); if (!DEBUG_MODE) { freeFall.Stop(); freeFall.Start(); } break; ... case Keys.Down: minoMoving(2, 1); if (!DEBUG_MODE) { freeFall.Stop(); freeFall.Start(); } break; |
上キー、下キーの処理の最後にfreeFallを再スタートさせます。
ちなみにデバッグモードの時は再スタートさせないようにしないと勝手に自由落下し始めてしまうので注意。
また、設置時にも同じようにしたいのでminoDropメソッドの最後に同じものを追記します。
1 2 3 4 5 6 7 8 9 10 11 12 13 | void minoDrop() { ... // ホールドフラグの初期化 usedHold = false; if (!DEBUG_MODE) { freeFall.Stop(); freeFall.Start(); } } |
これでテトリスに重力の概念が生まれました。
9-2.ゲームオーバー
ゲームオーバーの条件は、次のNEXTに進行した際にミノの生成位置が障害物と被っていることです。
NEXTはミノを設置したタイミングで進行するので、minoDropで実行することになります。
そしてゲームオーバーになると操作ができなくなるようにします。逆に、ゲームオーバーではない時のみ操作できるようにしたいわけですね。
ということで、まずゲームを操作できるフラグとしてグローバル変数に「gameStart」というbool型変数を追加します。
1 | bool gameStart = true; // ゲーム開始フラグ |
※初期値はtrueにしてますが、後々falseに変えます。それは後ほど
次にKeyDownイベントに追記し、処理をすべてif文で囲うことでgameStartがtrueの時のみ操作できるようにします。
1 2 3 4 5 6 7 8 9 10 11 | /* ミノ操作(KeyDown) */ private void mainForm_KeyDown(object sender, KeyEventArgs e) { if (gameStart) { switch (e.KeyCode) { ... } } } |
本題です。
新規メソッド「GameOver」を作成し、以下のコードを入力します。
ただ操作できなくなるだけだと地味なので、簡単なアニメーションを作ってます。
Locationを直接動かしているのでかなり無理矢理だけど。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /** * ゲームオーバー */ async void GameOver() { // ゲームの進行を止める gameStart = false; await Task.Delay(1000); // ぷよぷよみたいにフィールドが落下する int i = 1; while(view_field.Top < this.Height) { view_field.Top += i; if(i < 40) i += i; await Task.Delay(20); } } |
(アニメーションがいらないなら最初の「gameStart = false;」だけで十分です・・・)
最後にミノを設置した時にGameOverメソッドを呼び出したいので、minoDropに追記します。
座標初期化時に重複チェックを行い、重複していればゲームオーバーです。
また、gameStartがtrueの時のみ処理を行うようにし、自由落下を停止しています。これらがないとゲームオーバー後も自由落下し続けて不自然な挙動になってしまうので。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | /** * テトリミノ設置 */ void minoDrop() { if (gameStart) { // フィールドに描画 minoDrawing(next[0], dir); // NEXTを進める nextToNext(); // ライン消去 minoDelete(); // 座標・向きの初期化 x = INITIAL_X; y = INITIAL_Y; dir = 0; // この時点でミノが重複していればゲームオーバー if (!minoDuplicateCheck()) GameOver(); // ホールドフラグの初期化 usedHold = false; if (!DEBUG_MODE) { freeFall.Stop(); freeFall.Start(); } } } |
ぷよぷよみたいにフィールドが落っこちてゲームオーバーになります。
ばたんきゅ~。
おまけ
お好みでゲームオーバー後の画面とか作るのもいいですね。
gameOverTextというLabelにわかりやすく「GAME OVER」と表示させて、gameOverButtonというButtonでフォームが閉じられるようにしてみました。
1 2 3 4 5 6 7 8 9 | async void GameOver() { ... gameOverText.Visible = true; gameOverButton.Enabled = true; gameOverButton.Visible = true; gameOverButton.Focus(); } |
1 2 3 4 | private void gameOverButton_Click(object sender, EventArgs e) { this.Close(); } |
9-3.カウントダウン
3、2、1、START!と表示されてからゲームが始まるやつです。
カウントダウンを終えるまでは操作できないようにするため、先ほど初期値をtrueで作った変数gameStartを修正します。
1 | bool gameStart = false; // ゲーム開始フラグ |
カウントダウンを表示する領域を作るため、デザイナーを開き、わかりやすい場所にLabelを配置します。nameは「StartCountDown」にします。
私の場合はフィールドと同じ大きさに広げてフィールドのど真ん中に配置しています。仮テキストとして「—」と書いているやつです。
コードに戻り、Loadイベントのprivateの後に「async」を追加します。
これで一定時間待機させられる「await Task.Deray()」が使えるようになります。
1 2 | private async void mainForm_Load(object sender, EventArgs e) { |
そしてカウントダウンの処理をLoadイベントに追記します。初期座標を設定する部分の手前に書きます。
カウントダウンが終わり、描画まで終えたタイミングで操作が可能になるので、gameStartをtrueにするのを忘れずに。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | ... // カウントダウン StartCountDown.Parent = view_field; StartCountDown.Location -= (Size)view_field.Location; StartCountDown.Visible = true; string[] str = { "3", "2", "1", "START!" }; for (int i = 0; i < str.Length; i++) { StartCountDown.Text = str[i]; await Task.Delay(1000); } StartCountDown.Visible = false; x = INITIAL_X; // 初期X座標設定 y = INITIAL_Y; // 初期Y座標設定 nextDecide(); // NEXTの設定 nextDrawing(); // NEXTの描画 holdDrawing(); // ホールドの描画 minoDrawing(next[0], 0); // フィールド・ミノ描画 // 自由落下有効 if(!DEBUG_MODE) freeFall.Enabled = true; // デバッグモード if (DEBUG_MODE) this.Text += " - Debug Mode"; gameStart = true; } |
できました。ゲーム感増しましたね。
ゲームオーバーも出来上がったので、テトリスとして普通に遊べるレベルになりました。
ここからは更にゲーム性を高めるための開発を行う予定です。
とりあえず一段落、自分にお疲れ様。
次回はTスピンの判定を作ります。
これまでは回転入れができるだけで特に何かあるわけでもなかったので、Tスピンしたことをよりわかりやすく強調表示させたいと思います。
#10 Tスピン判定編へ続く。