こんにちは、あんみんどうふです。
今回はソフトドロップの実装です。というよりは、ミノを設置した時の”待機時間”を実装します。これにより回転入れの際に意図しない置きミスの発生率が下がります。
前回の記事はこちら。
全編見たい方はタグからどうぞ。
13-0.ソフトドロップとは?
以前、上キーを入力することで何かにぶつかるまで真下へ落下させる「ハードドロップ」を実装しました。
その対となる「ソフトドロップ」とは、下キーを入力することでミノの落下速度を速めるものです。
・・・ですが、これ自体は前回のDAS・ARRで既にできあがっています。左右の移動はDASが終了してからARRに移行しますが、下への移動はDAS無しで直接ARRを行います。
この処理はもう書いちゃってるので何もしなくてもオッケーですね。
で、本題です。
本家テトリスではソフトドロップや自由落下でミノを設置した時、約0.5秒の待機時間があります。この間に位置の微調整を行ったり、設置まで待つことができます。
今回はこれを実装します。
13-1.ソフトドロップ
待機時間が発生するタイミングは下にブロックがある時です。勿論、床も同様です。
待機時間は0.5秒で、経過後にミノが設置されます。
ただ、これだけだと一番下まで行って0.5秒経過した時点で強制設置となるため、Tスピンミニのような回転入れを行う時間が足りず難しくなってしまいます。
なので、これを遅延できるようにする必要があります。
ということで、ミノが落下・回転する度に待機時間をリセットさせるようにします。永遠に同じミノで操作できるのも困るので、リセットした回数が8回に到達した時点でその場に強制設置します。
(参考: Lock Delay – Hard Drop – Tetris Wiki)
時間経過で処理を行いたいので、デザイナーを開きTimerを1つ追加します。
nameは「softDropTimer」にしました。毎ミリ秒チェックするのでIntervalは1にしています。(処理がちょっと重くなりそうだけど…)
内容は後で書きますが、予めsoftDropTimerのTickイベントを作成しておきます。
Tips. softDropTimerのデバッグ
結構複雑なのでDAS・ARR同様デバッグ用のLabel「debugViewLockDelay」を置いてます。
待機時間を表示させてます。これが500になったら設置。
コードに戻り、ソフトドロップ関連の変数を追加します。いつものグローバルの所に追記する形で。
1 2 3 4 5 | /* ソフトドロップ関連 */ Stopwatch softDropWatch = new Stopwatch(); // ソフトドロップ経過時間 const int DROP_LOCK_DELAY = 500; // ミノ設置猶予時間(ミリ秒) const int TURN_DELAY_MAX = 8; // 遅延カウント上限 int dropDelay = 0; // 遅延カウント数 |
softDropWatchは0.5秒数えるためのストップウォッチです。
先程作成したsoftDropTimerは毎ミリ秒チェックするためのもので、softDropWatchで0.5秒経過したらsoftDropTimerから設置処理を呼び出すようにします。
DROP_LOCK_DELAYは待機時間です。500ミリ秒=0.5秒。
本家テトリスは作品によって異なります。
dropDelayが待機時間を遅延した回数で、その上限がTURN_DELAY_MAXです。
新規メソッド「softDropRun」を追加します。ここはソフトドロップによる設置の処理を書きます。
1 2 3 4 5 6 7 8 9 10 | void softDropRun() { softDropTimer.Stop(); softDropWatch.Stop(); softDropWatch.Reset(); debugViewLockDelay.Text += " (Stop)"; dropDelay = 0; minoDrop(); minoDrawing(next[0], dir); } |
タイマーを止め、ストップウォッチをリセットし、ミノの設置を行います。ソフトドロップの本体ですね。
更に新規メソッド「dropDelayCheck」を追加します。遅延回数をカウントし、上限に達したら強制設置させます。
1 2 3 4 5 6 7 8 | void dropDelayCheck() { // 実行時点で下に何かあれば遅延カウント+1 if (!minoMoveCheck(2, 1)) dropDelay++; // 遅延カウントが上限に達したら強制設置 if (dropDelay >= TURN_DELAY_MAX) softDropRun(); } |
先程作成したsoftDropTimer_Tickに以下のコードを書きます。
1 2 3 4 5 6 7 8 9 10 | private void softDropTimer_Tick(object sender, EventArgs e) { debugViewLockDelay.Text = "Delay Lock: " + softDropWatch.ElapsedMilliseconds.ToString(); // 下に何かあり、猶予時間を過ぎたら強制的に設置 if(softDropWatch.ElapsedMilliseconds > DROP_LOCK_DELAY && !minoMoveCheck(2, 1)) { softDropRun(); } } |
当たり前ですが、毎ミリ秒実行するので簡潔な処理にしました。正直毎ミリ秒実行させてる時点でもっといい方法があるように思いますが・・・。
処理時点で待機時間が0.5秒経過していればsoftDropRunを行います。
ここからは追記地獄です。
まずはmainForm_KeyDownのKeys.Up, Keys.Z, Keys.Xから。
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 | private void mainForm_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e) { if (gameStart) { switch (e.KeyCode) { case Keys.Up: int hDrop = minoGhost(true); // ハードドロップ移動量 int point = 2 * (hDrop + 1); // 獲得スコア minoMoving(2, hDrop); scoring(point); minoMoving(2, 1, true); break; ... case Keys.Z: softDropWatch.Stop(); softDropWatch.Reset(); minoTurn(0); break; case Keys.X: softDropWatch.Stop(); softDropWatch.Reset(); minoTurn(1); break; |
ハードドロップはフラグで管理するようにしました。minoMovingの第3引数がtrueであればハードドロップとみなしています。
回転は行う度に待機時間リセットさせてます。通常の回転は遅延回数にカウントされないので、dropDelayの増加はありません。
次にminoMovingです。
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | void minoMoving(int move, int pwr, bool hard = false) { ... if (minoMoveCheck(move, pwr)) { switch (move) { case 0: // 上 y += -pwr; break; case 1: // 右 x += pwr; break; case 2: // 下 y += pwr; if (!useSpin) dropDelay = 0; // 最後に行ったのが移動であればカウントリセット break; case 3: // 左 x += -pwr; break; } useSpin = false; // 移動できたので回転フラグはfalse if((move == 1 || move == 3) && !minoMoveCheck(2, 1)) { freeFallRestart(); softDropTimer.Stop(); softDropWatch.Stop(); softDropWatch.Reset(); } } else if(move == 2) { // ハードドロップ if(hard) softDropRun(); } // 下に何かあるときタイマー開始 if (!minoMoveCheck(2, 1)) { softDropWatch.Start(); softDropTimer.Start(); // 遅延回数が上限に達した場合強制設置 if(dropDelay >= TURN_DELAY_MAX) { softDropRun(); } } else { softDropWatch.Stop(); softDropWatch.Reset(); } // 新しく描画する minoDrawing(next[0], dir); } |
まあいろいろやってます。
bool型引数「hard」を追加し、これをハードドロップを使ったフラグとしています。ハードドロップを行うと設置処理に直接移行させてます。
最後にminoTurnです。
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | void minoTurn(int turn) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { // 現在位置に描画しているミノを一旦削除 int target = field[i + y + FIELD_SPACE - 1, j + x + FIELD_WALL]; if (target == mino[next[0], dir, i, j]) { field[i + y + FIELD_SPACE - 1, j + x + FIELD_WALL] = 0; } } } int dirTmp = dir; // 以前のdirを控えておく switch (turn) { case 0: // 左回転 dir--; break; case 1: // 右回転 dir++; break; } if (dir < 0) dir = 3; if (dir > 3) dir = 0; useSpin = true; // ソフトドロップ遅延リセット softDropTimer.Stop(); softDropWatch.Stop(); softDropWatch.Reset(); // 通常の回転ができるかチェック if (!minoDuplicateCheck()) { // 通常回転できないならスーパーローテーションを試す if(canSRS && minoSuperRotation(dirTmp)) { // スーパーローテーションが出来た場合自由落下をリセット freeFallRestart(); dropDelayCheck(); } else { // どちらもできないなら回転前の状態に戻す dir = dirTmp; } } // Tスピン判定 if(next[0] == 7) { minoTspinCheck(); } // 描画 minoDrawing(next[0], dir); } |
スーパーローテーションの回転である場合、遅延回数を1増やしています。
ソフトドロップ実装完了です。遅延もできてますね。
だいぶやりたいことやりきったので、そろそろ一区切りつけたいと思います。
次回は設定画面を作ります。オプションってやつですね。私はどのゲームでも始める前にオプションからいじる派です。
#14 設定画面編へ続く。