はじめに
最近テレビでちょくちょく見かける、動体視力をチェックする機械のソフトウェア版を作ってみました。1秒間隔で、フォーム内にランダムに赤いランプ(●印)が表示されるので、そのときにボタンを押し1分間で何個ボタンを押せるかを測るゲームです。フォームに配置した35個のLabel
コントロールのうち、どれか1つに「●」を表示させます。そのコントロールの選択に乱数を使用し、Timer
コントロールのTick
イベントハンドラを組み合わせ1秒おきに違う位置のLabel
コントロールに表示させます。
対象読者
Visual C#を利用してプログラムを作りたいという初心者の方を対象としています。
必要な環境
.NET Framework 1.1が必要です。またサンプルコードのプロジェクトを開くにはVisual Studio .NET 2003が必要です。
ユーザーインターフェイスの作成
ユーザーインターフェイスの作成は簡単にしました。Label
コントロールとButton
コントロールを35個ばかり配置し、Timer
コントロールを1つ配置するだけです。Label
、Button
コントロールの配置は、コードから各クラスのコンストラクタを使って配置してもかまわないのですが、説明が煩雑になるので今回はフォームデザイナ上でコピー・ペーストを使って配置しました。できあがりは、サンプルプログラムの画面を見てください。
各コントロールのプロパティ設定値は表のようになります。
コントロール | プロパティ | 設定値 |
Button1 ~Button35 | Text | (空白) |
Label1 ~Label35 | Text | ○ |
TabIndex | 1 ~ 35 | |
Label36 | Name | scoretitle |
Text | スコア | |
Label37 | Name | scoretitle |
Text | 0 | |
BorderStyle | Fixed3D | |
TextAlign | MiddleCenter | |
Label38 | Name | Timetitle |
Text | 残り時間 | |
Label39 | Name | scoretitle |
Text | 60 | |
BorderStyle | Fixed3D | |
TextAlign | MiddleCenter | |
BackColor | 192, 255, 255 | |
Button36 | Text | Start |
BackColor | Red | |
Timer | Interval | 1000 |
ポイントは、Label
コントロールの名前を番号順になるように設定し、TabIndex
プロパティをコントロールの名前と同じ数字にしている点です。たとえば、Label1
はTabIndex
プロパティの値が「1」、Label2
は「2」となります。これは、TabIndex
プロパティの値を使って、赤丸を付ける際のLabel
コントロールの識別に使用するためです。
Timerコントロールについて
このコントロールは、ユーザーの操作を受け付けるのではなく、設定した時間間隔でTick
というイベントを発生します。このTick
イベントハンドラに処理を記述しておけば、その処理を一定間隔でずっと繰り返すというわけです。アニメーション処理や時刻表示などによく使われるコントロールで、Enabled
プロパティでTimer
コントロールのオン・オフを切り替えます(Timer
コントロールをフォームに配置した時点では、Enabled
プロパティはデフォルトで「False」になっています)。
プログラム起動時からTimer
コントロールを動作させたい場合は、プロパティウィンドウでこのEnabled
プロパティを「True」にします。プログラム起動後にTimer
をオンにしたい場合は、Button
コントロールやメニューのイベントハンドラで、このEnabled
プロパティを「True」にします。Tick
イベントを発生させる時間間隔は、Interval
プロパティを使います。設定値はミリ秒です。
プログラムの動作内容
このプログラムは、次のように動作するようにしました。
- スタートボタンを押すと、
Timer
コントロールが作動します。 Timer
コントロールのTick
イベントハンドラでは、まずすべてのLabel
コントロールの表示を「○」にセットします。- 次に、赤丸を表示する
Label
コントロールを指定するために、1から35までの乱数を発生させます。 - 発生した乱数を使って、その番号の
Label
コントロールで「●」を表示します。 - ユーザーが赤丸の下のボタンを押すと、
Label
コントロールに赤丸が表示されているときだけ、ヒットカウントを1つ増やします。 - ゲーム動作時間60秒が経過すると、
Timer
コントロールを停止し、次のスタートに備えます。
Timer
コントロールのInterval
プロパティに設定した1秒ごとに作成されますので、その時間間隔で赤丸の点滅が始まります。プログラムの開始
まずは、プログラムを開始する準備です。ゲームは、[Start]ボタンを押すと開始しますので、Click
イベントハンドラで処理を行います。
ブロックの外で変数を2つ宣言します。1つはゲーム時間のカウントダウンに使用する「i」で、もう1つはヒットした数のカウントに使う「hitcount」です。そして、Start
ボタンのClick
イベントハンドラを作成し、Timer
コントロールのEnabled
プロパティを「True」にし、スコアと残り時間の表示を設定します。また、2つの変数をそれぞれ初期化しておきます。変数i
に「60」ではなく「59」を代入しているのは、ゲーム時間は59から0までだからです。
int i; int hitcount; private void StartButton_Click(object sender, System.EventArgs e) { this.timer1.Enabled = true; this.scorelabel.Text = "0"; this.countdown.Text = "60"; i=59; hitcount = 0; }
プログラムの実行中
Timer
コントロールが動作し始めるので、赤丸の表示処理をします。
赤丸の初期化
最初に、ゲーム開始前の処理です。ポイントは、一度ゲームを実行し終えた場合に、すでに「●」が表示されていたら、それを「○」に変えます。どのLabel
コントロールに「●」が表示されているのかが分かりませんから、foreach
ステートメントですべてのLabel
コントロールを「○」に設定しました。
foreach
ステートメントは、配列やコレクションオブジェクト内の要素1つ1つにアクセスする処理を繰り返すステートメントです。下記のように、対象コレクションから指定した型でオブジェクトにアクセスし、そこへの参照を変数var
に格納します。これを用いて、フォーム上のLabel
コントロールを1つ1つアクセスし、Text
プロパティを「○」に設定します。
foreach(指定した型 変数var in 対象コレクション)
あとは、変数var
からオブジェクトのプロパティやメソッドを操作します。コレクション内のすべてのオブジェクトにアクセスし終えると、ループ処理は終了します。ここでは、フォームのControls
コレクション内から、Control
オブジェクト1つ1つにアクセスします。
foreach(System.Windows.Forms.Control lb in this.Controls)
当然、フォームにはLabel
コントロール以外にButton
コントロールも配置されていますから、この中からLabel
コントロールだけを選別します。コントロールのName
プロパティを参照し、コントロール名の先頭が「label」で始まる文字であるかどうかを、StartsWith
メソッドを使って判断します。StartsWith
メソッドは、String
クラスのメソッドで、引数に指定した文字列(String
)が、操作対象の文字列の先頭と一致するかどうかを判断するメソッドです。
if (lb.Name.StartsWith("label"))
これで、フォーム上のコントロール群の中から、Label
コントロールだけを探し出すことができます。
private void timer1_Tick(object sender, System.EventArgs e) { int c, rnd; // Tickイベントが1回発生するごとに1秒差し引く c = i--; // ゲームが始まるとStartボタンを無効にする this.StartButton.Enabled = false; //まず、すでにあるLabelコントロールの赤丸を消す foreach(System.Windows.Forms.Control lb in this.Controls) { if (lb.Name.StartsWith("label")) { lb.Text = "○"; lb.ForeColor = System.Drawing.Color.Black; } }
ゲーム終了判断
次に、ゲーム終了時の処理です。変数c
が0未満になっていれば、ゲーム時間がなくなったことになりますから、Timer
コントロールを停止し、[Start]ボタンを有効にし、次のゲーム開始に備えます。なお、ブロック内にreturn
ステートメントを記述すると、この時点でコードの実行を終了させることができます。
// もし、ゲーム時間が0になったら、 if(c <0) { this.timer1.Enabled = false; this.countdown.Text = "60"; this.StartButton.Enabled = true; return; }
乱数の発生
ゲームが実行できる状況であれば、乱数を発生させます。これは、Random
クラスのインスタンスを作成し、Next
メソッドを実行します。Next
メソッドはオーバーロードなので、ここでは乱数を発生させる範囲を指定できるNext
メソッドを使うことにしました。引数は2つで、発生させたい乱数の下限値と上限値を指定します。
// 1から35までの乱数を発生 Random rd = new Random(); rnd = rd.Next(1,35);
乱数が作成できたら、再びforeach
ステートメントでフォーム上のコントロールにアクセスし、Label
コントロールを探し出してTabIndex
プロパティの値を調べます。そして、この値が乱数と一致していたら、そのText
プロパティを「●」に設定し、ForeColor
プロパティを赤(System.Drawing.Color.Red
)にします。
foreach(System.Windows.Forms.Control lb in this.Controls) { // 取得した乱数でLabelコントロールに赤丸を付ける if(lb.TabIndex == rnd) { lb.Text = "●"; lb.ForeColor = System.Drawing.Color.Red; } }
経過時間の表示
最後に、1秒減らしたゲームの経過時間を表示しますが、C#では暗黙のデータ型変換というものがありません。したがって、整数である変数c
の値を、ToString
メソッドで文字列に変換する作業が必要になります。
// 秒数のカウントダウンを表示 this.countdown.Text = c.ToString(); }
Interval
プロパティの値を小さくしてください。ただし、その場合はTimer
コントロールをもう1つ配置し、ゲーム時間のカウントを別プロセスで処理しないと、ゲームが進行する速度まで速くなってしまいます。ヒットしたかどうかの判定
「●」が付いている時にボタンを押すと、ヒットカウンタを1つ増やす処理は、各Label
コントロールの下に配置したButton
コントロールで行います。この処理は、35個配置したButton
コントロールすべてのClick
イベントハンドラに作成します。処理は、Click
イベントが発生したときに、ボタンの上に位置するLabel
コントロールに「●」が表示されていればヒットカウンタを1つ増やし、そうでなければ何もしません。
private void button1_Click(object sender, System.EventArgs e) { if (this.label1.Text == "●") { hitcount++; this.scorelabel.Text = hitcount.ToString(); } } private void button2_Click(object sender, System.EventArgs e) { if (this.label2.Text == "●") { hitcount++; this.scorelabel.Text = hitcount.ToString(); } } private void button3_Click(object sender, System.EventArgs e) { if (this.label3.Text == "●") { hitcount++; this.scorelabel.Text = hitcount.ToString(); } } // ************** 中略 ************** private void button35_Click(object sender, System.EventArgs e) { if (this.label35.Text == "●") { hitcount++; this.scorelabel.Text = hitcount.ToString(); } }
まとめ
今回は、Timer
コントロールと乱数、foreach
ステートメントを使った、簡単なゲームプログラムを作成しました。
Random
クラスとNext
メソッドを使うと乱数を発生できる。foreach
ステートメントは、コレクションや配列内の要素すべてにアクセスし、各要素への参照を取得することができるステートメント。- 数値データの文字列への変換は、変数がもつ
ToString
メソッドを使う。
参考資料
- MSDNライブラリ 『
Timer
クラス』 - MSDNライブラリ 『
Random
クラス』