こんにちは、あんみんどうふです。
今回はDASとARRの実装です。今まで結構テトリスやってきている私も単語自体は初めて聞きました。
前回の記事はこちら。
全編見たい方はタグからどうぞ。
12-0.DAS・ARRとは
DASは「Delayer Auto Shift」の頭文字を取った略語で、左右へ入力し続けた時の溜め時間のことです。日本のテトラーからは「横タメ」と呼ばれているそうですが、私は聞いたことないです。
説明だけだとわかりづらいので試してみましょう。Windowsのメモ帳を開いて、どのキーでもいいので押し続けてみてください。
押し続け始めたとき、少し溜めてから同じ文字が入力されましたよね?
DASはこの溜め時間の部分を指します。
で、文字が連続で入力される間隔がARRです。「Auto Repeat Rate」の略語で、この時間が短ければ短いほど短い間隔で入力されます。
設置した時、既に他の方向へ入力されていればDASを挟むことなくARRの状態を継続して移動できるテクニックがあり、上級プレイヤーはこれを利用して高速でミノを積み上げています。
Windowsには内部的にDASとARRが標準で設定されているらしく、何もしなくてもそれっぽくなっていました。
ですが、ここまでテトリス開発しているとどうも本家に寄せたくなってしまいます。なんなら、この開発も「なるべく本家に寄せる」というコンセプトでやってきていますし。
要するにただの拘りであり自己満足に過ぎないですが、本家らしさを出すためにDASとARRを実装します。
12-1.DAS・ARRの仕様
方向キーを押しっぱなしにしているとその方向に1マス移動し、DASが開始されます。
キーが押されたままDASが終了すると、今度はARRが開始されます。
キーが押されたままARRが終了すると入力方向にもう1マス移動し、再度ARRが開始されます。
キーを離すと、DASとARRを終了します。
こんな流れです。
DASしたら後はずっとARRで、キー離したらどっちも止めます。
また、対象であるすべてのキーが離されない限りARRは継続されます。
12-2.DAS・ARR用Timer
DASとARR、どちらも時間関連のものなのでデザイナーを開いてTimerを追加します。nameは「dasTimer」「arrTimer」にしました。
Intervalはコード内で設定するのでそのまま。
コードに戻り、DASとARRを設定します。
本家テトリスではDASやARRの時間が作品ごとに異なるようなので、今回も「ぷよぷよテトリス」を基準にすることにしました。一番この作品が馴染みあるので…。
というわけで、いつものグローバル変数の所に追加します。
ぷよぷよテトリスのDASは11F、ARRは2Fとなってます。
(F(フレーム)は1/60秒を基準とした単位です。格闘ゲーム等でよく聞くやつです)
1 2 3 4 5 | /* DAS・ARR関連 */ const int DAS_RATE = 11; // DAS必要フレーム const int ARR_RATE = 2; // ARR必要フレーム bool useDas = false; // DAS使用フラグ bool[] controlStatus = new bool[4]; // どの方向キーが押されているか |
ARRを開始するにはDASが終了したことを知る必要があるため、フラグを用意しています。
また、方向キーの入力フラグは上下左右4キーのどれかが押されていたら、各要素の値をtrueにします。これにより、ミノ設置後もARRを継続したまま操作できます。
最初は配列でなく普通のint型でもいけると思ったのですが、押されたキーを1つしか判定できないのでやめました。
Tips. 入力デバッグ機能
作るの割と大変だったので、方向キーの入力状況を可視化できるようにデバッグ機能を追加していました。
「debugViewControl」というlabelを追加し、配列controlStatusの値を数値で表示させてます(コードは後ほど)。
12-3.TimerのInterval設定
次にTimerのIntervalを設定します。mainForm_Loadイベントに追記します。
Loadイベント内ならどこに書いても構いませんが、各コントロールの値を変更している所にまとめておくとわかりやすいです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | private async void mainForm_Load(object sender, EventArgs e) { ... view_field.BackColor = Color.White; // フィールド背景色 statusMessageInitialX = statusMessage.Location.X; statusMessageInitialY = statusMessage.Location.Y; dasTimer.Interval = 1000 / 60 * DAS_RATE; arrTimer.Interval = 1000 / 60 * ARR_RATE; ... } |
1F(フレーム)=1/60秒なので1000ミリ秒÷60×フレーム数で計算しています。
12-4.DAS・ARRの処理
DAS・ARRで繰り返し実行される処理として新規メソッド「controlRepeat」を作成し、以下を入力します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | /** * DAS・ARRで繰り返される処理 */ void controlRepeat() { string controlPreview = ""; for (int i = 0; i < controlStatus.Length; i++) { // どの方向キーが押されているかチェック if (controlStatus[i]) { minoMoving(i, 1); if (i == 2) { freeFallRestart(); if (minoMoveCheck(2, 1)) scoring(2); } } // [debug]押されたキーを表示 if (controlStatus[i]) controlPreview += "1"; else controlPreview += "0"; } debugViewControl.Text = controlPreview; } |
配列controlStatusを順にチェックし、入力されているキーに応じて移動させてます。
最後の方にあるのはさっきのデバッグ用(debugViewControl)です。
Tips. 自由落下初期化のメソッド化
見慣れないメソッド「freeFallRestart」が紛れていますね・・・。
自由落下のリセット処理なんですけど書くのが面倒なのでメソッド化しました。それだけです。
1 2 3 4 5 6 7 8 9 10 11 | /** * 自由落下再スタート */ void freeFallRestart() { if(gameStart && !DEBUG_MODE) { freeFall.Stop(); freeFall.Start(); } } |
また、Timerで処理を行うため、DASとARRのTimer_Tickイベントを作ります。
1 2 3 4 5 6 7 8 9 10 | private void dasTimer_Tick(object sender, EventArgs e) { // DASが終了したらARRを開始する arrTimer.Start(); } private void arrTimer_Tick(object sender, EventArgs e) { // ARR継続中は同じ処理を続ける controlRepeat(); } |
Tickイベントで行う処理はこれだけ。
今まで移動処理(minoMoving)はmainForm_keyDownでキーが押されたときに行っていましたが、DAS・ARRの実装に伴いcontrolRepeatメソッドにお引越しすることにしました。
というわけで、mainForm_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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | private void mainForm_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e) { if (gameStart) { switch (e.KeyCode) { case Keys.Up: if (canHardDrop) { int hDrop = minoGhost(true); // ハードドロップ移動量 int point = 2 * (hDrop + 1); // 獲得スコア minoMoving(2, hDrop); scoring(point); minoMoving(2, 1, true); } break; case Keys.Left: controlStatus[3] = true; break; case Keys.Right: controlStatus[1] = true; break; case Keys.Down: controlStatus[2] = true; break; ... } // DAS・ARR if (controlStatus[0] || controlStatus[1] || controlStatus[2] || controlStatus[3]) { if (!useDas) { useDas = true; controlRepeat(); if (controlStatus[2]) arrTimer.Start(); else dasTimer.Start(); } } } ... |
キー入力処理後、配列内要素のどれかがtrueであればDASを開始します。
同時にdasTimerが開始し、11F経過後ARRを開始します。
ただ、このままだとキーを離してもARRが継続されたままになっているので、キーを離した時に配列controlStatusをfalseにさせ、Timerをストップさせる必要があります。
というわけで「mainForm_KeyUp」の登場です。mainFormのイベントから追加します。
このイベントは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 27 28 29 30 31 32 33 34 | private void mainForm_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e) { switch(e.KeyCode) { case Keys.Up: controlStatus[0] = false; break; case Keys.Right: controlStatus[1] = false; break; case Keys.Down: controlStatus[2] = false; break; case Keys.Left: controlStatus[3] = false; break; } // [debug]押されているキーを表示 string controlPreview = ""; for(int i = 0; i < controlStatus.Length; i++) { if (controlStatus[i]) controlPreview += "1"; else controlPreview += "0"; } debugViewControl.Text = controlPreview; if(!controlStatus[0] && !controlStatus[1] && !controlStatus[2] && !controlStatus[3]) { dasTimer.Stop(); arrTimer.Stop(); useDas = false; } } |
配列controlStatusの要素全てがfalseの時にタイマー停止しています。
即ち、DASとARRを終了させています。
【実装前】
【実装後】
DAS・ARR、無事に実装できました!
動きがスムーズになり、ぷよぷよテトリスに近い操作感になりました。いや~満足満足。
次回は更に本家へ近づけるべく、保留していた「ソフトドロップ」の実装をします。
#13 ソフトドロップ編へ続く。