こんにちは、あんみんどうふです。
今回は設定画面の作成です。操作するキーやミノの色を変えたり、ゲームの仕様をいじったりできるようにします。
前回の記事はこちら。
全編見たい方はタグからどうぞ。
14-1.画面(UI)作成
一般的にどのゲームにも設定画面が存在し、キーやボタンの割り当てだったり、難易度の変更、画質・音声の調整など、プレイヤーが遊びやすいように自分で調整できます。
テトリスにはそういった要素は少ないものの、最低限キーコンフィグぐらいは必要だな~と思ったので作ります。
現に、ガイドライン上でハードドロップはSpaceキーに対しうちのテトリスは上キーですから、変えたい人もいるでしょうし。
また、ハードドロップやスーパーローテーション、ホールドの有無も切り替えできるようにします。これは回転と移動しかなかったクラシックテトリスを再現するためです。
こっちの方が馴染み深い人が多いかも。
後は各ミノの色ですね。気分転換で変えたり、全部同じ色にして縛りプレイみたいにしたり、色覚に障害がある方に合わせたり。
というわけで早速作ります。「SettingForm」というフォームを作って、Labelやらなんやらいろいろ配置します。
それで、出来上がったのがこれです。↓
「ゲーム設定」には落下速度、NEXT数、DAS・ARRの間隔の項目があります。
本家テトリスでは作品によっていろいろ違うので、馴染んだテトリスに合わせた設定ができるようにします。
「表示設定」にはミノの色、ゴーストの透明度を調整する項目があります。
(本当はこれらのプリセット機能も作る予定でしたが面倒でやめました)
「操作設定」は各操作のキーを設定する項目があります。
エミュレーターでよく見るようなやつですね。
「その他」にはデバッグモードの切り替えの項目があります。
積み方の練習をしたり、適当に遊んだりできます。
ずっと隣にある「プレビュー」ですが、これは表示設定用に仮のフィールドを表示して色を確認する所ですね。これは後程。
とりあえず画面だけは作りました。次はコーディング・・・の前にちょっとだけやることがあります。
14-2.コントロールの値
各コントロールの値ですが、これはプロジェクトの設定をいじらないとゲーム画面に反映させることができません。
というわけで、プロジェクト側に各設定項目の変数をずらっと書いていきます。
まず「ソリューションエクスプローラー」からプロジェクトを右クリックして「プロパティ」を選択します。
右の項目から「設定」を選び、変数の名前、型、デフォルト値をそれぞれ入力します。
これらはプロジェクト内での共通変数として扱うことができるので、設定画面とゲーム画面で値を共有することができます。
14-3.プレビュー描画
まずはLoadイベントから。ロード時にさっきの変数の値をコントロールに適用します。
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 | /* ロード時 */ private void SettingForm_Load(object sender, EventArgs e) { // ゲーム設定 numDropInterval.Value = Properties.Settings.Default.gameDropInterval; numNextCount.Value = Properties.Settings.Default.gameNextCount; numDAS.Value = Properties.Settings.Default.gameDAS; numARR.Value = Properties.Settings.Default.gameARR; checkHardDrop.Checked = Properties.Settings.Default.gameFuncHardDrop; checkHold.Checked = Properties.Settings.Default.gameFuncHardDrop; checkSRS.Checked = Properties.Settings.Default.gameFuncSRS; // 表示設定 colorBG.BackColor = Properties.Settings.Default.viewColorBG; colorLine.BackColor = Properties.Settings.Default.viewColorLine; colorMinoI.BackColor = Properties.Settings.Default.viewColorMinoI; colorMinoO.BackColor = Properties.Settings.Default.viewColorMinoO; colorMinoS.BackColor = Properties.Settings.Default.viewColorMinoS; colorMinoZ.BackColor = Properties.Settings.Default.viewColorMinoZ; colorMinoJ.BackColor = Properties.Settings.Default.viewColorMinoJ; colorMinoL.BackColor = Properties.Settings.Default.viewColorMinoL; colorMinoT.BackColor = Properties.Settings.Default.viewColorMinoT; numGhostAlpha.Value = Properties.Settings.Default.viewGhostAlpha; // キー設定 keySpinL.Text = Properties.Settings.Default.ctrlSpinL.ToString(); keySpinR.Text = Properties.Settings.Default.ctrlSpinR.ToString(); keyHold.Text = Properties.Settings.Default.ctrlHold.ToString(); keyMoveL.Text = Properties.Settings.Default.ctrlMoveL.ToString(); keyMoveR.Text = Properties.Settings.Default.ctrlMoveR.ToString(); keyHardDrop.Text = Properties.Settings.Default.ctrlHardDrop.ToString(); keySoftDrop.Text = Properties.Settings.Default.ctrlSoftDrop.ToString(); // その他設定 checkDebugMode.Checked = Properties.Settings.Default.otherDebugMode; previewMino(); // 配置 previewDraw(); // 描画 editFlag(false); // 編集フラグリセット } |
続けてメソッド「previewMino」です。
プレビューに表示するフィールド・ミノを配置します。ちなみにこの時点で描画は行ってません。
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 | int[,] field = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 5, 5, 5, 0, 0, 0, 0 }, { 6, 0, 0, 4, 4, 5, 3, 0, 0, 0 }, { 6, 0, 0, 0, 4, 4, 3, 3, 2, 2 }, { 6, 6, 0, 1, 1, 1, 1, 3, 2, 2 } }; const int INITIAL_X = 3; const int INITIAL_Y = 0; int preX = INITIAL_X; int preY = INITIAL_Y; int[,] preMino = { { 0, 0, 0, 0 }, { 0, 7, 0, 0 }, { 7, 7, 7, 0 }, { 0, 0, 0, 0 } }; /* 表示設定_プレビューミノ配置 */ void previewMino() { int ghostY = 0; bool initial = false; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (preMino[i, j] != 0) { // Tミノ配置 field[preY + i - 1, preX + j] = preMino[i, j]; // ゴースト配置 (初回のみ位置チェック) if (!initial) { while (field[preY + ghostY + 1, preX] == 0) ghostY++; if (ghostY > 0) initial = true; } if (initial) field[ghostY - 2 + i, preX + j] = 8; } } } } |
次に描画メソッド「previewDraw」です。
先程配置した通りにプレビューへ描画します。
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 | /* 表示設定_プレビュー描画 */ void previewDraw() { // 描画 Bitmap canvas = new Bitmap(previewField.Width, previewField.Height); Graphics g = Graphics.FromImage(canvas); previewField.BackColor = colorBG.BackColor; for (int i = 0; i < field.GetLength(0); i++) { Pen lPen = new Pen(Color.FromArgb(80, colorLine.BackColor)); int square = 20; for (int j = 0; j < field.GetLength(1); j++) { SolidBrush[] mBrush = { new SolidBrush(colorMinoI.BackColor), new SolidBrush(colorMinoO.BackColor), new SolidBrush(colorMinoS.BackColor), new SolidBrush(colorMinoZ.BackColor), new SolidBrush(colorMinoJ.BackColor), new SolidBrush(colorMinoL.BackColor), new SolidBrush(colorMinoT.BackColor), new SolidBrush(Color.FromArgb((byte)numGhostAlpha.Value, colorMinoT.BackColor)) // ゴースト }; // 四角形描画 if (field[i, j] != 0) { g.FillRectangle(mBrush[field[i, j] - 1], j * square, i * square, square, square); } // 縦線(x10) if(j > 0) g.DrawLine(lPen, j * square, 0, j * square, field.GetLength(0) * square); } // 横線(x20) if(i > 0) g.DrawLine(lPen, 0, i * square, field.GetLength(0) * square, i * square); } g.Dispose(); previewField.Image = canvas; } |
ゲーム画面で似たようなことを散々やってきたので簡単ですね。
見慣れたフィールドがプレビューされます。
14-4.OK・キャンセル・適用
適用ボタンの処理です。
ゲームに限らず、様々なアプリケーションで「OK」「キャンセル」「適用」みたいな並びを目にしますが、その「適用」です。
まずグローバル変数として編集フラグを作ります。
1 | bool editing = false; // 編集フラグ |
このフラグと適用ボタンの押せる押せないを同時に切り替えるメソッド「editFlag」を作ります。
1 2 3 4 5 6 | /* 編集フラグ・適用ボタン操作 */ void editFlag(bool b) { editing = b; buttonApply.Enabled = b; } |
引数の値で切り替えてます。
次に保存処理として「saveSettings」メソッドを作成します。
「適用」と「OK」を押した時にここで保存されます。
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 | /* 設定保存 */ void saveSettings() { // ゲーム設定 Properties.Settings.Default.gameDropInterval = (int)numDropInterval.Value; Properties.Settings.Default.gameNextCount = (int)numNextCount.Value; Properties.Settings.Default.gameDAS = (int)numDAS.Value; Properties.Settings.Default.gameARR = (int)numARR.Value; Properties.Settings.Default.gameFuncHardDrop = checkHardDrop.Checked; Properties.Settings.Default.gameFuncHold = checkHold.Checked; Properties.Settings.Default.gameFuncSRS = checkSRS.Checked; // 表示設定 Properties.Settings.Default.viewColorBG = colorBG.BackColor; Properties.Settings.Default.viewColorLine = colorLine.BackColor; Properties.Settings.Default.viewColorMinoI = colorMinoI.BackColor; Properties.Settings.Default.viewColorMinoO = colorMinoO.BackColor; Properties.Settings.Default.viewColorMinoS = colorMinoS.BackColor; Properties.Settings.Default.viewColorMinoZ = colorMinoZ.BackColor; Properties.Settings.Default.viewColorMinoJ = colorMinoJ.BackColor; Properties.Settings.Default.viewColorMinoL = colorMinoL.BackColor; Properties.Settings.Default.viewColorMinoT = colorMinoT.BackColor; Properties.Settings.Default.viewGhostAlpha = (byte)numGhostAlpha.Value; // キー設定 KeysConverter kc = new KeysConverter(); Properties.Settings.Default.ctrlSpinL = (Keys)kc.ConvertFromString(keySpinL.Text); Properties.Settings.Default.ctrlSpinR = (Keys)kc.ConvertFromString(keySpinR.Text); Properties.Settings.Default.ctrlHold = (Keys)kc.ConvertFromString(keyHold.Text); Properties.Settings.Default.ctrlMoveL = (Keys)kc.ConvertFromString(keyMoveL.Text); Properties.Settings.Default.ctrlMoveR = (Keys)kc.ConvertFromString(keyMoveR.Text); Properties.Settings.Default.ctrlHardDrop = (Keys)kc.ConvertFromString(keyHardDrop.Text); Properties.Settings.Default.ctrlSoftDrop = (Keys)kc.ConvertFromString(keySoftDrop.Text); // その他設定 Properties.Settings.Default.otherDebugMode = checkDebugMode.Checked; Properties.Settings.Default.Save(); editFlag(false); } |
処理部分を書き終えたので、各ボタンを押した時のイベントで実行させます。
「キャンセル」・・・押したら閉じる
「OK」・・・押したら保存して閉じる
「適用」・・・押したら保存のみ行う
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /* キャンセルボタン */ private void buttonCancel_Click(object sender, EventArgs e) { this.Close(); } /* 保存ボタン */ private void buttonSave_Click(object sender, EventArgs e) { saveSettings(); editing = false; this.Close(); } /* 適用ボタン */ private void buttonApply_Click(object sender, EventArgs e) { saveSettings(); } |
また、保存せずに閉じようとしたときに確認画面を出すようにします。
この処理はフォームのFormClosingイベントで発生させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /* 閉じる時に編集フラグがTrueなら確認 */ private void SettingForm_FormClosing(object sender, FormClosingEventArgs e) { if (editing) { DialogResult result = MessageBox.Show("変更を保存しますか?", "", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning); if (result == DialogResult.Yes) { saveSettings(); } else if(result == DialogResult.Cancel) { e.Cancel = true; } } } |
このための編集フラグですね。
編集フラグがtrueの時に確認画面を出し、「はい」なら保存して閉じる、「いいえ」なら保存せず閉じる、「キャンセル」ならそのままにします。
14-5.既定値に戻す
ボタンのClickイベントで各コントロールに値を入れ直してるだけです。
上から順にゲーム設定、表示設定、操作設定です。
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 | /* 既定値に戻す */ private void buttonGameReset_Click(object sender, EventArgs e) { numDropInterval.Value = 500; numNextCount.Value = 6; numDAS.Value = 11; numARR.Value = 2; checkHardDrop.Checked = true; checkHold.Checked = true; checkSRS.Checked = true; } private void buttonViewReset_Click(object sender, EventArgs e) { colorBG.BackColor = Color.White; colorLine.BackColor = Color.Silver; colorMinoI.BackColor = Color.Cyan; colorMinoO.BackColor = Color.Yellow; colorMinoS.BackColor = Color.FromArgb(0, 221, 0); colorMinoZ.BackColor = Color.Red; colorMinoJ.BackColor = Color.FromArgb(30, 128, 255); colorMinoL.BackColor = Color.DarkOrange; colorMinoT.BackColor = Color.Magenta; numGhostAlpha.Value = 80; previewDraw(); } private void buttonKeyReset_Click(object sender, EventArgs e) { KeysConverter kc = new KeysConverter(); keySpinL.Text = kc.ConvertToString(Keys.Z); keySpinR.Text = kc.ConvertToString(Keys.X); keyHold.Text = kc.ConvertToString(Keys.C); keyMoveL.Text = kc.ConvertToString(Keys.Left); keyMoveR.Text = kc.ConvertToString(Keys.Right); keyHardDrop.Text = kc.ConvertToString(Keys.Up); keySoftDrop.Text = kc.ConvertToString(Keys.Down); } |
14-6.(表示設定)色を指定するダイアログを開く
表示設定にあるすべてのPictureBoxに同じメソッド名を指定します。
clickイベントに「viewChangeColor」という名前を付け、以下のコードを入力します。
1 2 3 4 5 6 7 8 9 10 11 12 13 | /* 表示設定_色変更 */ private void viewChangeColor(object sender, EventArgs e) { ColorDialog cd = new ColorDialog(); cd.Color = ((PictureBox)sender).BackColor; if (cd.ShowDialog() == DialogResult.OK) { ((PictureBox)sender).BackColor = cd.Color; editFlag(true); previewDraw(); } } |
PictureBoxをクリックするとWindows標準の色を変更するダイアログが開かれます。
ペイントを触ったことがある人なら馴染みあると思います。
14-7.(表示設定)TrackBarとNumericUpDownを同期
TrackBarとNumericUpDown両方で個別にValueChangedイベントを作成し、以下のコードを入力します。
1 2 3 4 5 6 7 8 9 10 11 12 13 | /* 表示設定_数値とバーを同期 */ private void numGhostAlpha_ValueChanged(object sender, EventArgs e) { barGhostAlpha.Value = (byte)numGhostAlpha.Value; editFlag(true); previewDraw(); } private void barGhostAlpha_ValueChanged(object sender, EventArgs e) { numGhostAlpha.Value = barGhostAlpha.Value; editFlag(true); previewDraw(); } |
同期しました。
ただ、ValueChangedイベントは値が変わるたびに実行されるので、バーを高速で移動させると若干重くなってしまいますね・・・。
14-8.(操作設定)入力したキーをTextBoxに表示
14-6と同じ要領で、操作設定にあるすべてのTextBoxのイベントに同じメソッド名を指定します。
キー入力時に実行するので、KeyDownに「ctrlChangeKey」、KeyPressに「ctrlHandleTrue」という名前を付け以下のコードを入力します。
1 2 3 4 5 6 7 8 9 10 | /* キー設定 */ private void ctrlChangeKey(object sender, KeyEventArgs e) { KeysConverter kc = new KeysConverter(); ((TextBox)sender).Text = kc.ConvertToString(e.KeyCode); } private void ctrlHandleTrue(object sender, KeyPressEventArgs e) { e.Handled = true; } |
KeyDownイベントであるctrlChangeKeyは先程の通りですが、KeyPressのctrlHandleTrueはこれ何してるんだっけ・・・。
結構前に書いたコードなので忘れちゃいました。まあ、これで問題なく動いてるので大丈夫でしょう。
入力したキーの名前が表示されます。
14-9.ありとあらゆる場面で編集フラグをTrueにする
値を変えれるコントロールならどれでもいいので、ValueChangedイベントに「valChanged」と名前を付けて以下のコードを入力します。
※既にValueChangedイベントを使っているコントロール以外で!
1 2 3 4 5 | /* numeric系_編集フラグON */ private void valChanged(object sender, EventArgs e) { editFlag(true); } |
編集フラグをTrueにしているだけですが、これを各コントロールと紐付けることで保存のし忘れを完璧に防げます。
値を変えれるコントロールすべてのValueChangedイベントに「valChanged」と入力すればOKです。
14-10.ゲーム画面に反映
設定画面(SettingForm)で設定した値をゲーム画面(mainForm)に反映させます。
各項目に対応する変数に「Properties.Settings.Default.プロパティで決めた変数名」で代入すれば反映されます。
(例)NEXT数の場合
1 | int NEXT_DISPLAY = Properties.Settings.Default.gameNextCount; // NEXT表示数(1~6) |
ミノや背景の色に関しては#7.5で作ったsetColorというメソッドを使います。
1 2 3 4 5 6 7 8 | view_field.BackColor = Properties.Settings.Default.viewColorBG; MinoColor.setColor(1, Properties.Settings.Default.viewColorMinoI); MinoColor.setColor(2, Properties.Settings.Default.viewColorMinoO); MinoColor.setColor(3, Properties.Settings.Default.viewColorMinoS); MinoColor.setColor(4, Properties.Settings.Default.viewColorMinoZ); MinoColor.setColor(5, Properties.Settings.Default.viewColorMinoJ); MinoColor.setColor(6, Properties.Settings.Default.viewColorMinoL); MinoColor.setColor(7, Properties.Settings.Default.viewColorMinoT); |
流石に全部書くと長すぎるので割愛します。
黒と青緑を基調としたテトリスにしてみました。なかなかイケてますね。
ちなみにこの積み方は「DTTKI」と呼ばれる技です。DT砲の派生技で、Tスピンダブル→トリプル→トリプル→ダブルを連続で放てるすごい技です。
対戦では非常に強力で、Tスピンダブルは4列分、Tスピントリプルは6列分のダメージを与えます。連続で行うとBackToBackなので+1列分加算され、4+(6+1)+(6+1)+(4+1)で合計23列分のダメージになります。テトリスのフィールドは縦20列なので、抵抗されなければ勝ち、抵抗してもそれなりにダメージが入るので撃ち得ですね。
元々長い記事なのに余談をぶちかましてしまいました。
とりあえずはこれでテトリス完成です!満足!
バグは残ってるけど・・・。