シンプルなワンキーゲーム

こんにちは、きうちです。

朝晩はだいぶ涼しくなり、過ごしやすくなってきましたね!

でも、季節の変わり目は体調を崩さないように、より一層注意が必要ですね。

私も「食欲の秋」とか言って食べ過ぎて健康を損ねないように留意していきたいと思っています。

なんたって健康診断もありますし(笑)

★ ★ ★

ここでお知らせです。

弊社すぎやまから度々このブログなどでお知らせをさせていただいておりますが、
GEEXは来る10/4(土),10/5(日)に東京ビッグサイトにて行われる「メーカーフェア」に、昨年に引き続き出展します!

詳細はこちらをどうぞ。

私もお手伝いに行ってきます。今年はどんなイベントになるか、楽しみです^^

まあ、私のブログの内容とは全然関係ないんですが^^;

★ ★ ★

今回は、「シンプルなワンキーゲーム」と題してお送りします。

ワンキーゲームというのは、ゲームそのものにキーを1つしか使わないゲームの総称ですね。

ここでは、昔からよくある以下のようなルールのゲームを作ってみたいと思います。

  • 画面の中に自機が存在する
  • 勝手に前(右)に進む
  • キーを押すと上昇し、放っておくと下降する
  • 画面左端からスタートし、右端までいくとクリア
  • 画面上端または下端に突入、または障害物に衝突するとゲームオーバー

言語は例によってC#にします。Windowsフォームアプリとして作成します。

画面サイズは800×600にします。

背景は黒にします。

ちらつきを抑えるため、DoubleBufferedをtrueにします。

「勝手に前(右)に進む」や「放っておくと下降する」を実装するため、タイマーを1つ追加します。
名前は timMove にします。プロパティはそのまま。

自機の座標や速度、加速度を示すフィールドを宣言します。

    private int mx = 0; // 自機のX座標

    private int my = 0; // 自機のY座標

    private int vx = 0; // 自機のX方向速度(移動増分)

    private int vy = 0; // 自機のY方向速度(移動増分)

    private int ax = 0; // 自機のX方向加速度

    private int ay = 0; // 自機のY方向加速度

ゲームを開始するためのメソッドを追加します。名前をStartGameとし、以下のような内容します。

    private void StartGame() // ゲーム開始
    {
        mx = 0;
        my = Height / 2;
        vx = 4;
        vy = 8;
        ax = 0;
        ay = 1;

        timMove.Start();
    }

自機の初期座標は X が 0 (左端)、Y は 画面の半分の位置。

X方向の速度は4ピクセル、Y方向の速度は8ピクセルにします。

加速度はX方向は0(加速しない)、Y方向は1ピクセルとします。

コンストラクタにこのメソッドを呼び出す処理を追加します。

    public Form1() // コンストラクタ
    {
        InitializeComponent();

        StartGame();
    }

画面描画処理は以下のようにします。
(フォームデザイナ側で Form1 の Paint イベントにこのメソッドを設定してあげてください。)

    private void Form1_Paint(object sender, PaintEventArgs e) // 描画処理
    {
        e.Graphics.FillEllipse(Brushes.LightYellow, mx - 4, my - 4, 8, 8); // 自機(薄黄色の塗りつぶしの円)を描く
    }

自機は8×8の薄黄色の塗りつぶしの円で表現します。

続いて、タイマーのTICK時処理を実装します。

    private void timMove_Tick(object sender, EventArgs e) // 移動と判定
    {
        mx += vx;
        my += vy;
        vx += ax;
        vy += ay;
        Refresh();

        if (mx >= Width) // 自機が画面右端まで到達したら
        {
            // クリア時処理
            timMove.Stop();
            MessageBox.Show("Clear!");
            StartGame(); // メッセージボックスでOKボタンを押したら次のゲーム開始
        }
        else if (my < 0 || my >= Height) // 自機が画面上端または下端まで到達したら
        {
            // ゲームオーバー時処理
            timMove.Stop();
            MessageBox.Show("Game Over!");
            StartGame(); // メッセージボックスでOKボタンを押したら次のゲーム開始
        }
    }

自機のX座標にはX方向速度が足しこまれます。Y座標も同じくです。

X方向速度にはX方向加速度が足しこまれます(0なので変わらない予定ですが)。Y方向速度も同じくです(こちらは1ずつ変化していきます)。

これで、自機は移動しつつ、速度も変換(加速)していきます。

そのあと画面を再描画して・・・

あとは、
「自機が画面右端まで到達したか判定し、していたらクリア」
「自機が画面上端または下端まで到達したか判定し、していたらゲームオーバー」
という風に処理します。

続いて、フォームのキー押下時処理を追加します。
(フォームデザイナ側で Form1 の KeyDown イベントにこのメソッドを設定してあげてください。)

    private void Form1_KeyDown(object sender, KeyEventArgs e) // キー押下イベント処理
    {
        if(e.KeyCode == Keys.Space) // スペースキーが押されたら
        {
            vy -= 1; // Y方向速度減らす(=上方向に加速)
        }
    }

スペースキーが押されたら、Y方向速度を1減らします。

ここまでで、障害物は存在しないですがひとまずゲームとして成立しているはずなので、実行してみます。

ゲームオーバーの状態から始まっていますが、これは動画キャプチャする上でその方が楽だったのと、いきなり始まるよりはわかりやすいかなと思ったからです、ご容赦のほどを…

動画だと自機が上下にふわふわ動いていますが、これは私が適宜スペースキーを押しているからです。

自機が勝手に右に動いて、そして勝手に下降、スペースキーを押すと上昇。

下端または上端にいくとゲームオーバー、右端に行くとクリア。

試してみてください。

あと、StartGameメソッドで設定した値たちをすこしずつ変えてみると、動きが変わって面白いと思います。

★ ★ ★

続いて
「障害物を置く」
という要素を足してみたいと思います。

以下のようなフィールドと、メソッドを足します。

    private List obstacles = new List(); // 障害物の座標リスト

    private Random random = new Random(); // 乱数生成用

    private void PutObstacles() // 障害物を置く
    {
        obstacles.Clear();
        for (int i = 0; i < 50; i++) // とりあえず50個くらい
        {
            obstacles.Add(new Point(random.Next(0, Width), random.Next(0, Height))); // 座標はランダムに決定
        }
    }

    private void DrawObstacles(Graphics g) // 障害物を描画する
    {
        foreach (Point p in obstacles)
        {
            g.FillEllipse(Brushes.LightCyan, p.X - 8, p.Y - 8, 16, 16); // 障害物(薄水色の塗りつぶしの円、サイズ16x16[半径8])を描く
        }
    }

障害物の座標はPointで表し、そのリストをフィールドで持つことにします。

PutObstaclesメソッドでランダムに50個の障害物を置く(座標を定義する)処理をします。

DrawObstaclesは、画面描画イベントメソッド(Form1_Paint)から呼び出されたら障害物を描画する、という処理です。

サイズは 16x16(半径8) とします。

StartGameには、次のように timMove.Start() の前に追加をします。

    private void StartGame() // ゲーム開始
    {
        mx = 0;
        my = Height / 2;
        vx = 4;
        vy = 8;
        ax = 0;
        ay = 1;

        PutObstacles(); // 追加

        timMove.Start();
    }

Form1_Paintには、次のように追加をします。

    private void Form1_Paint(object sender, PaintEventArgs e) // 描画処理
    {
        DrawObstacles(e.Graphics); // 追加

        e.Graphics.FillEllipse(Brushes.LightYellow, mx - 4, my - 4, 8, 8); // 自機(薄黄色の塗りつぶしの円)を描く
    }

障害物の描画を自機の描画よりも先にしているのは、自機と障害物が重なったときに自機の方が上に描画されるようにです。

ここまでで実行してみましょう。

・・・障害物がある世界を自機が移動するようになりましたね!

★ ★ ★

ただ、このままだと障害物をすり抜けて先へ進めてしまいますね。

「障害物に衝突した場合もゲームオーバー」

を追加しましょう。

以下のような判定処理のメソッドを追加します。

    private bool Judge() // 判定
    {
        bool hit = false;

        foreach (Point p in obstacles)
        {
            int xx = p.X - mx;
            int yy = p.Y - my;
            double distance = Math.Sqrt(xx * xx + yy * yy); // 自機と障害物の距離を計算する
            if (distance <= 8) // 距離が8(=障害物の半径)以下だったら
            {
                hit = true; // 「ぶつかった」と判定する
                break;
            }
        }

        return hit; // 結果を返却する
    }

やっていることは、自機と各障害物との距離を計算し、それが障害物の半径である「8」以下であればぶつかったと判定する、というものです。

厳密には障害物の半径の8と自機の半径4を足した「12」が判定に使用すべき値ですが、ちょっとそこは衝突判定を甘くします。
かすっただけならセーフとなるように。

さらに、タイマーTICK時処理に以下のようにこのメソッドの呼び出しと、衝突していた場合の処理を追加します。

    private void timMove_Tick(object sender, EventArgs e) // 移動と判定
    {
        mx += vx;
        my += vy;
        vx += ax;
        vy += ay;
        Refresh();

        if (mx >= Width) // 自機が画面右端まで到達したら
        {
            // クリア時処理
            timMove.Stop();
            MessageBox.Show("Clear!");
            StartGame(); // メッセージボックスでOKボタンを押したら次のゲーム開始
        }
        else if (my < 0 || my >= Height) // 自機が画面上端または下端まで到達したら
        {
            // ゲームオーバー時処理
            timMove.Stop();
            MessageBox.Show("Game Over!");
            StartGame(); // メッセージボックスでOKボタンを押したら次のゲーム開始
        }
        else if (Judge()) // 追加
        {
            // ゲームオーバー時処理
            timMove.Stop();
            MessageBox.Show("Game Over!");
            StartGame(); // メッセージボックスでOKボタンを押したら次のゲーム開始
        }
    }

else if を追加しています。
余談ですが、やっている内容はその前の「自機が画面上端または下端まで到達したら」と同じなので、1つにまとめることもできますね。

        else if (my < 0 || my >= Height || Judge()) {
            // ゲームオーバー時処理
            timMove.Stop();
            MessageBox.Show("Game Over!");
            StartGame(); // メッセージボックスでOKボタンを押したら次のゲーム開始
        }

という風に。

さて、これで実行してみますと・・・

・・・障害物にぶつかった場合もゲームオーバーとなるようになりましたね!

★ ★ ★

というわけで、今回は短いですがこのへんで。

キーを押したときの速度変化を大きくしてみるとか、
クリアするごとに障害物が増えたり障害物のサイズが変わるようにしてみたり、
障害物の色をカラフルにするなど、いろいろ変更を加えてみても面白いです。
是非おためしを^^

これから秋が深まってきたらどんどんおいしいものが増えていきますね!

よーし、この秋はおいしいマロンケーキ食べに行っちゃおうかな~

おっといけない、健康診断を忘れていた(笑)

ではまた!

追伸

ソースはこちらで公開しています。

https://github.com/YasuhikoKiuchi/OneKey