SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

動体視力をテストする

乱数とTimerコントロールを使ったゲームサンプル


  • このエントリーをはてなブックマークに追加

Timerコントロールと乱数を発生するRandomクラスを利用して、ランダムに赤丸を表示し動体視力をテストするゲームを作成してみました。

  • このエントリーをはてなブックマークに追加
動体視力テストゲーム
動体視力テストゲーム

はじめに

 最近テレビでちょくちょく見かける、動体視力をチェックする機械のソフトウェア版を作ってみました。1秒間隔で、フォーム内にランダムに赤いランプ(●印)が表示されるので、そのときにボタンを押し1分間で何個ボタンを押せるかを測るゲームです。フォームに配置した35個のLabelコントロールのうち、どれか1つに「●」を表示させます。そのコントロールの選択に乱数を使用し、TimerコントロールのTickイベントハンドラを組み合わせ1秒おきに違う位置のLabelコントロールに表示させます。

対象読者

 Visual C#を利用してプログラムを作りたいという初心者の方を対象としています。

必要な環境

 .NET Framework 1.1が必要です。またサンプルコードのプロジェクトを開くにはVisual Studio .NET 2003が必要です。

ユーザーインターフェイスの作成

 ユーザーインターフェイスの作成は簡単にしました。LabelコントロールとButtonコントロールを35個ばかり配置し、Timerコントロールを1つ配置するだけです。LabelButtonコントロールの配置は、コードから各クラスのコンストラクタを使って配置してもかまわないのですが、説明が煩雑になるので今回はフォームデザイナ上でコピー・ペーストを使って配置しました。できあがりは、サンプルプログラムの画面を見てください。

 各コントロールのプロパティ設定値は表のようになります。

各コントロールのプロパティ設定値
コントロールプロパティ設定値
Button1Button35Text(空白)
Label1Label35Text
TabIndex1 ~ 35
Label36Namescoretitle
Textスコア
Label37Namescoretitle
Text0
BorderStyleFixed3D
TextAlignMiddleCenter
Label38NameTimetitle
Text残り時間
Label39Namescoretitle
Text60
BorderStyleFixed3D
TextAlignMiddleCenter
BackColor192, 255, 255
Button36TextStart
BackColorRed
TimerInterval1000

 ポイントは、Labelコントロールの名前を番号順になるように設定し、TabIndexプロパティをコントロールの名前と同じ数字にしている点です。たとえば、Label1TabIndexプロパティの値が「1」、Label2は「2」となります。これは、TabIndexプロパティの値を使って、赤丸を付ける際のLabelコントロールの識別に使用するためです。

TabIndexプロパティ
TabIndexプロパティ

Timerコントロールについて

 このコントロールは、ユーザーの操作を受け付けるのではなく、設定した時間間隔でTickというイベントを発生します。このTickイベントハンドラに処理を記述しておけば、その処理を一定間隔でずっと繰り返すというわけです。アニメーション処理や時刻表示などによく使われるコントロールで、EnabledプロパティでTimerコントロールのオン・オフを切り替えます(Timerコントロールをフォームに配置した時点では、Enabledプロパティはデフォルトで「False」になっています)。

 プログラム起動時からTimerコントロールを動作させたい場合は、プロパティウィンドウでこのEnabledプロパティを「True」にします。プログラム起動後にTimerをオンにしたい場合は、Buttonコントロールやメニューのイベントハンドラで、このEnabledプロパティを「True」にします。Tickイベントを発生させる時間間隔は、Intervalプロパティを使います。設定値はミリ秒です。

Intervalプロパティ
Intervalプロパティ

プログラムの動作内容

 このプログラムは、次のように動作するようにしました。

  1. スタートボタンを押すと、Timerコントロールが作動します。
  2. TimerコントロールのTickイベントハンドラでは、まずすべてのLabelコントロールの表示を「○」にセットします。
  3. 次に、赤丸を表示するLabelコントロールを指定するために、1から35までの乱数を発生させます。
  4. 発生した乱数を使って、その番号のLabelコントロールで「●」を表示します。
  5. 乱数は、TimerコントロールのIntervalプロパティに設定した1秒ごとに作成されますので、その時間間隔で赤丸の点滅が始まります。
    と同時に、フォームに表示している秒数のカウントダウンを開始します。
  6. ユーザーが赤丸の下のボタンを押すと、Labelコントロールに赤丸が表示されているときだけ、ヒットカウントを1つ増やします。
  7. ゲーム動作時間60秒が経過すると、Timerコントロールを停止し、次のスタートに備えます。

プログラムの開始

 まずは、プログラムを開始する準備です。ゲームは、[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メソッドを使う。

参考資料

  1. MSDNライブラリ Timerクラス』
  2. MSDNライブラリ Randomクラス』

この記事は参考になりましたか?

  • このエントリーをはてなブックマークに追加
特集記事連載記事一覧

もっと読む

この記事の著者

瀬戸 遥(セト ハルカ)

8ビットコンピュータの時代からBASICを使い、C言語を独習で学びWindows 3.1のフリーソフトを作成、NiftyServeのフォーラムなどで配布。Excel VBAとVisual Basic関連の解説書を中心に現在まで40冊以上の書籍を出版。近著に、「ExcelユーザーのためのAccess再...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/46 2006/06/26 19:09

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング