講義メモ:ゲーム開発演習

:自機と敵機の衝突判定とゲームオーバー画面 他

演習34 自機と敵機の衝突判定とゲームオーバー画面

・自機と敵機が衝突したら自機とその敵機を消そう
・そして、ゲームオーバーとし、ゲームオーバー画面に遷移しよう
・ゲームオーバー画面でも背景のスクロールや敵機の出現は続くものとする
・しかし、自機の移動や自弾の発射はできないようにする
・Enterキーが押されたら、リプレイできるようにしよう

手順と仕様

①各データをゲーム開始時の値にするメソッド void initData() を追加する
 対象はコンストラクタにあった下記を移動:
        player.i = playeri;  //自機の画像
        player.x = 320; //自機の中心X座標
        player.y = 410; //自機の中心Y座標
        player.hv = 0;  //自機の左右方向の速度
 加えて下記を追加:
        player.v = 1;  //自機を表示
        waitpb = 0; //自弾発射の待ち時間
        waitpb = 0; //自弾発射の待ち時間
②描画処理:プレイ画面において表示していたものはゲームオーバー画面でも表示する
③描画処理:自機の向きによって分岐する処理と、自機を描画する処理は、自機が表示状態の時のみにする
④描画処理:ゲームオーバー時に「GAME OVER」と(210,150)から25ポイントで表示し、
 「Hit Enter Key」と(200,300)から25ポイントで表示
⑤キー入力時処理:タイトル画面でEnterキーが押されていたら、①を呼んでからゲームモードを1にする
⑥キー入力時処理:ゲームオーバー画面でEnterキーが押されていたら、①を呼び、全敵機と全自弾を非表示にし、
 ゲームモードを1にする処理を追加
⑦タイマーイベント処理:←→キーのチェックはプレイ画面の時のみにする
⑧タイマーイベント処理:スペースキーのチェックはプレイ画面の時のみにする
⑨タイマーイベント処理:全敵機について自弾との衝突判定をする前に、自機との衝突判定を行い、衝突していたら自機を消し、
 ゲームモードを9にして抜ける処理を追加

作成例

//演習34 自機と敵機の衝突判定とゲームオーバー画面
using System; //汎用的に利用
using System.Windows.Forms; //フォームアプリケーションに必須
using System.Drawing; //Size、Image用
struct Item { //アイテムを表す構造体
    public Image i; //画像
    public int x;  //中心X座標
    public int y;  //中心Y座標
    public int hv; //左右方向の速度(左向きは負の数、右向きは正の数)
    public int vv; //上下方向の速度(上向きは負の数、下向きは正の数)
    public int v;  //表示状態(0:非表示、1以上:表示)
}
class Program : Form { //Formクラスの派生クラス
    [System.Runtime.InteropServices.DllImport("user32.dll")] //DLLインポート
    private static extern short GetKeyState(int nVirtKey); //外部定義指定
    int gamemode = 0; //モード(0:タイトル画面,1:プレイ画面,9:終了画面)
    int score = 0; //スコア
    Image backi = Image.FromFile("backb.bmp"); //背景画像を読込む
    Image playeri = Image.FromFile("player.gif"); //自機通常画像を読込む
    Image playerl = Image.FromFile("playerl.gif"); //自機左寄画像を読込む
    Image playerr = Image.FromFile("playerr.gif"); //自機右寄画像を読込む
    Image bulleti = Image.FromFile("bullet.gif"); //自弾画像を読込む
    Image bullet2i = Image.FromFile("bullet2.gif"); //自弾画像2を読込む
    Image enemyi = Image.FromFile("enemy.gif"); //敵機画像を読込む
    Pen pen1 = new Pen(Color.Red, 2); //赤色太さ2のペン
    Brush brush1 = new SolidBrush(Color.FromArgb(63, 255, 0, 0)); //透明赤いブラシ
    Font font1 = new Font("メイリオ", 20, FontStyle.Bold); //フォントを生成
    Font fontt = new Font("メイリオ", 80, FontStyle.Bold); //フォントを生成
    Font fontm = new Font("メイリオ", 25, FontStyle.Bold); //フォントを生成
    Brush brushs = new SolidBrush(Color.Yellow); //黄色のブラシ
    Timer timer = new Timer(); //タイマーの生成
    int backy = 0; //1枚目の背景描画開始Y座標
    Item player; //自機の構造体オブジェクト
    const int maxpb = 10; //自弾の最大数
    Item[] pba = new Item[maxpb]; //自弾の構造体オブジェクト配列
    const int cold = 10; //自弾発射の冷却時間
    int waitpb = 0; //自弾発射の待ち時間
    const int maxenemy = 20; //敵機の最大数
    Item[] enemya = new Item[maxenemy]; //敵機の構造体オブジェクト配列
    int waitenemy = 0; //敵機の出現待ち時間
    int enemyint = 50; //敵機の出現間隔
    //【以下追加】各データをゲーム開始時の値にする
    void initData() {
        player.i = playeri;  //自機の画像
        player.x = 320; //自機の中心X座標
        player.y = 410; //自機の中心Y座標
        player.hv = 0;  //自機の左右方向の速度
        player.v = 1;  //自機を表示
        waitpb = 0; //自弾発射の待ち時間
        waitpb = 0; //自弾発射の待ち時間
    }
    //中央座標を用いる画像描画処理
    private void DrawItem(PaintEventArgs e, Item it) {
        int xx = it.x - it.i.Width / 2; //左上X座標を得る
        int yy = it.y - it.i.Height / 2; //左上Y座標を得る
        e.Graphics.DrawImage(it.i, xx, yy);
    }
    //楕円形の衝突判定(アイテムaとアイテムbが衝突しているかどうかを返す)
    private bool isHit(Item a, Item b) {
        int dest = (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y); //距離2乗
        double ra = (a.i.Width < a.i.Height) ? a.i.Width / 2.0 : a.i.Height / 2.0; //内径a
        double rb = (b.i.Width < b.i.Height) ? b.i.Width / 2.0 : b.i.Height / 2.0; //内径b
        return dest < (ra + rb) * (ra + rb); //距離2乗と内径の和の2乗を比較
    }
    //描画処理のオーバライド
    protected override void OnPaint(PaintEventArgs e) {
        base.OnPaint(e); //基本クラスの描画処理を呼ぶ
        e.Graphics.DrawImage(backi, 0, backy); //背景画像を描画
        e.Graphics.DrawImage(backi, 0, backy - backi.Height); //背景画像を描画
        if (gamemode == 0) { //スタート画面?
            e.Graphics.DrawString("GAME1", fontt, brushs, 100, 150); //タイトル表示
            e.Graphics.DrawString("Hit Enter Key", fontm, brushs, 200, 300); //メッセージ表示
        } else { //【変更】プレイ画面かゲームオーバー画面?
            string s = String.Format("SCORE:{0:000,000}", score); //スコア文字列を作る
            e.Graphics.DrawString(s, font1, brushs, 400, 10); //スコア表示
            if (player.v != 0) { //【追加】自機が表示中?
                switch (player.hv) { //自機の向きによって分岐
                    case 0: player.i = playeri; break; //通常画像にする
                    case -1: player.i = playerl; break; //左寄画像にする
                    case 1: player.i = playerr; break; //右寄画像にする
                }
                DrawItem(e, player); //自機を描画
            }
            foreach (var pb in pba) { //全自弾について繰返す
                if (pb.v == 1) { //自弾がある?
                    DrawItem(e, pb); //自弾を描画
                }
            }
            foreach (var enemy in enemya) { //全敵機について繰返す
                if (enemy.v == 1) { //敵機がある?
                    DrawItem(e, enemy); //敵機を描画
                }
            }
        }
        if (gamemode == 9) { //【以下追加】ゲームオーバー画面?
            e.Graphics.DrawString("GAME OVER", fontm, brushs, 210, 150); //メッセージ表示
            e.Graphics.DrawString("Hit Enter Key", fontm, brushs, 200, 300); //メッセージ表示
        }
    }
    //キー入力時処理
    void OnKeyDown(object o, KeyEventArgs e) { 
        if (e.KeyCode.ToString() == "Escape") { //Escキーが押されていたら
            Close(); //フォーム終了
        }
        //タイトル画面でEnterキーが押されていたら
        if (gamemode == 0 && e.KeyCode.ToString() == "Return") {
            initData(); //【追加】各データをゲーム開始時の値にする
            gamemode = 1; //プレイ動画に遷移
            timer.Start(); //タイマー開始                  
        }
        //【以下追加】ゲームオーバー画面でEnterキーが押されていたら
        else if (gamemode == 9 && e.KeyCode.ToString() == "Return") {
            initData(); //各データをゲーム開始時の値にする
            for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
                enemya[i].v = 0; //無しにする
            }
            for (int i = 0; i < maxpb; i++) { //全自弾について繰返す
                pba[i].v = 0; //無しにする
            }
            gamemode = 1; //プレイ動画に遷移
        }
        Invalidate(); //画面再描画を依頼
    }
    //タイマーイベント処理
    void Play(object o, EventArgs e) { 
        backy = (backy + 1) % backi.Height; //1枚目の背景描画開始Y座標を下げる
        if (gamemode == 1) { //【追加】プレイ画面?
            player.hv = 0; //自機の向きを無しにしておく
            if (player.x > playeri.Width / 2 && GetKeyState((int)Keys.Left) < 0) { //範囲内で←キーが押されている?
                player.x -= 10; //自機を左へ
                player.hv = -1; //左向き
            }
            if (player.x < backi.Width - playeri.Width / 2 && GetKeyState((int)Keys.Right) < 0) { //範囲内で→キーが押されている?
                player.x += 10; //自機を右へ
                player.hv = 1; //右向き
            }
        }
        for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
            if (enemya[i].v != 0) { //敵機が存在?
                if (player.v != 0 && isHit(enemya[i], player)) { //【以下追加】自機が存在し衝突? 
                    player.v = 0; //自機を消す
                    gamemode = 9; //ゲームオーバーにする
                    break; //抜ける
                }
                for (int j = 0; j < maxpb; j++) { //全自弾について繰返す
                    if (pba[j].v != 0 && isHit(enemya[i], pba[j])) { //自弾が存在し衝突?
                        enemya[i].v = 0; //この敵機を消す
                        pba[j].v = 0;  //この自弾を消す
                        break; //次の敵機へ進む
                    }
                }
            }
        }
        if (gamemode == 1) { //【追加】プレイ画面?
            if (GetKeyState((int)Keys.Space) < 0) { //スペースキーが押されている?
                if (waitpb <= 0) { //自弾発射待ち時間がゼロ?
                    for (int i = 0; i < maxpb; i++) { //全自弾について繰返す
                        if (pba[i].v == 0) { //自弾が非表示?
                            pba[i].v = 1; //表示にする
                            pba[i].i = bulleti; //画像
                            pba[i].x = player.x; //X座標は自機と同じ
                            pba[i].y = player.y - player.i.Height / 2 - pba[i].i.Height / 2; //Y座標は自機の直上
                            pba[i].vv = -5; //上移動速度
                            waitpb = cold; //自弾発射待ち時間をセット
                            break; //1発発射できればOK
                        }
                    }
                }
            } else { //スペースキーが押されていない?
                waitpb = 0; //自弾発射待ち時間をゼロにして発射可能にする
            }
        }
        if (waitenemy <= 0) { //敵機出現待ち時間がゼロ?
            for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
                if (enemya[i].v == 0) { //敵機が非表示?
                    enemya[i].v = 1; //表示にする
                    enemya[i].i = enemyi; //画像
                    enemya[i].x = player.x; //X座標は自機と同じ
                    enemya[i].y = -enemyi.Height; //Y座標は上端の直上
                    enemya[i].vv = 5; //下移動速度
                    waitenemy = enemyint; //自弾発射待ち時間をセット
                    break; //1機出現できればOK
                }
            }
        }
        for (int i = 0; i < maxpb; i++) { //全自弾について繰返す
            if (pba[i].v != 0) { //自弾が存在?
                pba[i].i = (pba[i].i == bulleti) ? bullet2i : bulleti; //画像を交互変更
                pba[i].y += pba[i].vv; //上へ移動
                if (pba[i].y + pba[i].i.Height / 2 < 0) { //画面上端より上に出たら
                    pba[i].v = 0; //自弾を消す
                }
            }
        }
        for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
            if (enemya[i].v != 0) { //敵機が存在?
                enemya[i].y += enemya[i].vv; //下へ移動
                if (enemya[i].y - enemya[i].i.Height / 2 > backi.Height) { //画面下端より下に出たら
                    enemya[i].v = 0; //敵機を消す
                }
            }
        }
        if (waitpb > 0) { //自弾発射待ち時間がセットされていたら
            waitpb--; //カウントダウンする
        }
        if (waitenemy > 0) { //敵機出現待ち時間がセットされていたら
            waitenemy--; //カウントダウンする
        }
        Invalidate(); //画面再描画を依頼
    }
    //コンストラクタ
    Program() { 
        DoubleBuffered = true; //ダブルバッファリングを有効化
        KeyDown += new KeyEventHandler(OnKeyDown); //キー入力イベント登録
        timer.Tick += new EventHandler(Play); //タイマーイベント登録
        timer.Interval = 10; //タイマーインターバル(ミリ秒)
        //player.i = playeri;  //【移動】自機の画像
        //player.x = 320; //【移動】自機の中心X座標
        //player.y = 410; //【移動】自機の中心Y座標
        //player.hv = 0;  //【移動】自機の左右方向の速度
    }
    public static void Main() {
        Program f = new Program(); //自分のオブジェクトを生成
        f.Size = new Size(660, 520); //フォームのサイズを設定
        f.Text = "Game"; //フォーム名を設定
        f.ControlBox = false; //コントロールボックスを非表示に
        f.FormBorderStyle = FormBorderStyle.Fixed3D; //サイズ変更を抑止
        Application.Run(f); //フォームを現出
    }
}

演習35 スコアアップ

・敵機に自弾が当たったら+10点としよう
・スコアはゲームオーバー画面でEnterキーが押されたらゼロにしよう
・ハイスコアを保持しておき、GAME OVER画面でハイスコアを超えたら「HiSCORE!」と表示しよう
・ハイスコア以下なら「HiSCORE is 000,000」と表示しよう

提出:演習35 スコアアップ(演習34や未完成でもOK)

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です