テーマ31 乱数

・Randomクラスは乱数を提供するもので、System名前空間にあるので「using System;」があればクラス名だけで利用可能
・Randomクラスのデフォルトコンストラクタを呼び出すと、内部的にシステムクロックを用いたシード処理(乱数系の初期設定)が行われて、
　乱数機能を提供するオブジェクトが生成される
　※ C/C++が提供する疑似乱数と同等だが、自前のシード処理は不要なので便利
・Randomクラスのインスタンスメソッド「int Next(int)」は0から引数値-1までのどれかの整数を返す
例：
　Random rnd = new Random(); //乱数用のRandomクラスのインスタンスを生成
　int hp = 10 + rnd.Next(10); //10～19の間のランダムな整数を得てHPの初期値とする
・乱数オブジェクトは使いまわしが可能なので、クラスのデータメンバとして定義・初期化しておくと良い

演習36 敵機の出現位置をランダムに

・敵機の出現時のX座標を自機直上固定からランダムに変更しよう
・左端から右端までのどこかから出現するとする

手順・仕様

① ProgramクラスのデータメンバとしてRandomクラスのインスタンスを生成
② タイマーイベント処理：敵機出現待ち時間がゼロならば敵機を出現させるところで、X座標の値を下記の式で決めるように変更
　X座標 = 敵機の幅÷2＋乱数(0から背景の幅-敵機の幅まで)

作成例

//演習36 敵機の出現位置をランダムに
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; //スコア
    int hiscore = 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; //敵機の出現間隔
    string hiscoremsg = ""; //ハイスコアメッセージ
    Random rnd = new Random(); //【追加】乱数用のRandomクラスのインスタンスを生成
    //各データをゲーム開始時の値にする
    void initData() {
        player.i = playeri;  //自機の画像
        player.x = 320; //自機の中心X座標
        player.y = 410; //自機の中心Y座標
        player.hv = 0;  //自機の左右方向の速度
        player.v = 1;  //自機を表示
        waitpb = 0; //自弾発射の待ち時間
        waitpb = 0; //自弾発射の待ち時間
        score = 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); //距離２乗
        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); //距離２乗と内径の和の２乗を比較
    }
    //描画処理のオーバライド
    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(hiscoremsg, fontm, brushs, 150, 200); //ハイスコアメッセージ表示
            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; //ゲームオーバーにする
                    if (score > hiscore) { //ハイスコア更新？
                        hiscore = score; //ハイスコア更新
                        hiscoremsg =   "HiSCORE Update !!"; //ハイスコア文字列
                    } else {
                        hiscoremsg = String.Format("HiSCORE is {0:000,000}", hiscore); //ハイスコア文字列を作る
                    }
                    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;  //この自弾を消す
                        score += 10; //スコアアップ
                        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 =  enemyi.Width / 2 + rnd.Next(backi.Width - enemyi.Width);  //【変更】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; //タイマーインターバル(ミリ秒)
    }
    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); //フォームを現出
    }
}

演習37 敵弾の発射

・準備時間(200カウント)が経過したら、出現中の敵機の最初の1機が敵弾を発射するようにしよう
・その後は、一定時間(75カウント)が経過する都度、出現中の敵機の最初の1機が敵弾を発射するようにしよう
　※ただし、画面の上半分に出現中の敵機に限るとする
・敵弾は同時に複数出現可能で最大20個とする
・発射位置は敵機中央の15ドット上にある砲口とする
・敵弾の画像は以下を利用可能
　https://rundog.org/si/ebullet.gif (20x20)
・敵弾の移動と衝突判定は後回し

手順と仕様

① データメンバ：敵弾の画像を読み込んでImage型変数enemybiで扱う
② データメンバ：敵弾の出現間隔をint型定数enemybintで定義し値を75とする
③ データメンバ：敵弾の出現待ち時間をint型変数waitenemybで定義し初期値を200とする
④ データメンバ：敵弾の最大数をint型定数maxenemybで定義し値を20とする
⑤ データメンバ：敵弾の構造体Item型配列enemybaを敵弾の最大数の分定義
⑥ 初期化処理：敵弾の出現待ち時間waitenemybを200に戻す
⑦ 描画処理：プレイ画面かゲームオーバー画面なら、全敵弾について、敵弾の配列enemybaの表示状態vが0以外なものを描画
⑧ キー入力時処理：ゲームオーバー画面でEnterキーが押されていたら、全敵弾の表示状態vを0にする
⑨ タイマーイベント処理：敵弾出現待ち時間がゼロであれば：
　・発射敵機を-1(未確定)にしておく
　・全敵機の中で出現中でY座標が画面上半分にいるものがあれば、発射敵機とする
　・発射敵機確定していたら、全敵弾の中で未出現のものがあれば、出現させ、X座標は発射敵機と同じに、Y座標は発射敵機-15とする
　　また、敵弾出現待ち時間を敵弾の出現間隔にする
⑩ タイマーイベント処理：敵弾出現待ち時間がセットされていたらカウントダウンする

作成例

//演習37 敵弾の発射
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; //スコア
    int hiscore = 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"); //敵機画像を読込む
    Image enemybi = Image.FromFile("ebullet.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; //敵機の出現間隔
    string hiscoremsg = "";  //ハイスコアメッセージ
    Random rnd = new Random(); //乱数用のRandomクラスのインスタンスを生成
    const int enemybint = 75; //【追加】敵弾の出現間隔
    int waitenemyb = 200; //【追加】敵弾の出現待ち時間
    const int maxenemyb = 20; //【追加】敵弾の最大数
    Item[] enemyba = new Item[maxenemyb]; //【追加】敵弾の構造体オブジェクト配列
    //各データをゲーム開始時の値にする
    void initData() {
        player.i = playeri;  //自機の画像
        player.x = 320; //自機の中心X座標
        player.y = 410; //自機の中心Y座標
        player.hv = 0;  //自機の左右方向の速度
        player.v = 1;  //自機を表示
        waitpb = 0; //自弾発射の待ち時間
        waitpb = 0; //自弾発射の待ち時間
        score = 0; //スコアクリア
        waitenemyb = 200; //【追加】敵弾の出現待ち時間
    }
    //中央座標を用いる画像描画処理
    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); //距離２乗
        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); //距離２乗と内径の和の２乗を比較
    }
    //描画処理のオーバライド
    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); //敵機を描画
                }
            }
            foreach (var enemyb in enemyba) { //【以下追加】全敵弾について繰返す
                if (enemyb.v == 1) { //敵弾がある？
                    DrawItem(e, enemyb); //敵弾を描画
                }
            }
        }
        if (gamemode == 9) { //ゲームオーバー画面？
            e.Graphics.DrawString("GAME OVER", fontm, brushs, 210, 150); //メッセージ表示
            e.Graphics.DrawString(hiscoremsg, fontm, brushs, 150, 200); //ハイスコアメッセージ表示
            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; //無しにする
            }
            for (int i = 0; i < maxenemyb; i++) { //【以下追加】全敵弾について繰返す
                enemyba[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; //ゲームオーバーにする
                    if (score > hiscore) { //ハイスコア更新？
                        hiscore = score; //ハイスコア更新
                        hiscoremsg = "HiSCORE Update !"; //メッセージ
                    } else {
                        hiscoremsg = String.Format("HiSCORE is {0:000,000}", hiscore); //スコア文字列を作る
                    }
                    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;  //この自弾を消す
                        score += 10; //スコアアップ
                        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 = enemyi.Width / 2 + rnd.Next(backi.Width - enemyi.Width); //X座標はランダム
                    enemya[i].y = -enemyi.Height; //Y座標は上端の直上
                    enemya[i].vv = 5; //下移動速度
                    waitenemy = enemyint; //敵機出現待ち時間をセット
                    break; //1機出現できればOK
                }
            }
        }
        if (waitenemyb <= 0) { //【以下追加】敵弾出現待ち時間がゼロ？
            int enemyi = -1; //発射敵機
            for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
                if (enemya[i].v == 1 &&  enemya[i].y > 0 && enemya[i].y < backi.Height / 2) { //敵機が画面上半分にいる？
                    enemyi = i; //発射敵機決定
                    break;
                }
            }
            if (enemyi != -1) { //発射敵機決定済？
                for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
                    if (enemyba[i].v == 0) { //敵弾が非表示？
                        enemyba[i].v = 1; //表示にする
                        enemyba[i].i = enemybi; //画像
                        enemyba[i].x = enemya[enemyi].x ; //X座標は発射敵機と同じ
                        enemyba[i].y = enemya[enemyi].y- 15; //Y座標は発射敵機の15ドット上
                        waitenemyb = enemybint; //敵弾発射待ち時間をセット
                        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--; //カウントダウンする
        }
        if (waitenemyb > 0) { //【以下追加】敵弾出現待ち時間がセットされていたら
            waitenemyb--; //カウントダウンする
        }
        Invalidate(); //画面再描画を依頼
    }
    //コンストラクタ
    Program() { 
        DoubleBuffered = true; //ダブルバッファリングを有効化
        KeyDown += new KeyEventHandler(OnKeyDown); //キー入力イベント登録
        timer.Tick += new EventHandler(Play); //タイマーイベント登録
        timer.Interval = 10; //タイマーインターバル(ミリ秒)
    }
    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); //フォームを現出
    }
}

テーマ32 ２座標の角度から移動量を得る

・座標(x0,y0)への(x1,y1)からの角度はMath.Atan2関数にそれぞれの差を渡すことでラジアン値として得られる。
　double rad = Math.Atan2(y0 - y1, x0 - x1);
・すると、物体の移動先の座標は、Cos(角度)、Sin(角度)、移動速度で得ることができる
　x座標移動量＝Math.Cos(rad)×移動速度
　y座標移動量＝Math.Sin(rad)×移動速度

演習38 敵弾の移動・消去・自機との衝突判定

・敵弾は敵機の砲口からその時点の自機中央に向かって斜めに移動するようにしよう
・移動速度は敵機速度の1.5倍とする
・敵弾が画面端より外に出たら消そう
・全敵弾と自機との衝突判定を行い、衝突していたらゲームオーバーに遷移しよう
・衝突していたら、その敵弾も消そう

手順と仕様

① データメンバ：敵機移動速度をint型定数enemyvvで定義し値を5とする
② タイマーイベント処理：全敵機について繰返す中で自機が存在し衝突したら、ゲームオーバーのメソッドGameOver()を呼ぶようにする
　 これに伴い、繰返しを抜けるbreakを除く各処理をメソッドGameOver()の中に移動する
③ タイマーイベント処理：全敵弾について敵弾と自機が存在し衝突していたら、メソッドGameOver()を呼び、その敵弾を消してから繰返しを抜ける処理を追加
④ タイマーイベント処理：敵機を出現させるときに与える下移動速度を敵機移動速度定数enemyvvで与えるように変更
⑤ タイマーイベント処理：敵弾を出現させるときに自機と敵弾の座標で角度を得て、X移動量とY移動量を設定する処理を追加
  double rad = Math.Atan2(player.y - enemyba[i].y, player.x - enemyba[i].x); //【追加】角度を得る
  enemyba[i].hv = (int)(Math.Cos(rad) * enemyvv * 1.5); //【追加】X移動量
  enemyba[i].vv = (int)(Math.Sin(rad) * enemyvv * 1.5); //【追加】Y移動量
⑥ タイマーイベント処理：全敵弾について、敵弾が存在したらXY座標にXY移動量を加算し、画面端より外に出たら敵弾を消す処理を追加

作成例

//演習38 敵弾の移動・消去
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; //スコア
    int hiscore = 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"); //敵機画像を読込む
    Image enemybi = Image.FromFile("ebullet.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; //敵機の出現間隔
    string hiscoremsg = "";  //ハイスコアメッセージ
    Random rnd = new Random(); //乱数用のRandomクラスのインスタンスを生成
    const int enemybint = 75; //敵弾の出現間隔
    int waitenemyb = 200; //敵弾の出現待ち時間
    const int maxenemyb = 20; //敵弾の最大数
    Item[] enemyba = new Item[maxenemyb]; //敵弾の構造体オブジェクト配列
    const int enemyvv = 5; //【追加】敵機移動速度
    //各データをゲーム開始時の値にする
    void initData() {
        player.i = playeri;  //自機の画像
        player.x = 320; //自機の中心X座標
        player.y = 410; //自機の中心Y座標
        player.hv = 0;  //自機の左右方向の速度
        player.v = 1;  //自機を表示
        waitpb = 0; //自弾発射の待ち時間
        waitpb = 0; //自弾発射の待ち時間
        score = 0; //スコアクリア
        waitenemyb = 200; //敵弾の出現待ち時間
    }
    //中央座標を用いる画像描画処理
    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); //距離２乗
        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); //距離２乗と内径の和の２乗を比較
    }
    //描画処理のオーバライド
    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); //敵機を描画
                }
            }
            foreach (var enemyb in enemyba) { //全敵弾について繰返す
                if (enemyb.v == 1) { //敵弾がある？
                    DrawItem(e, enemyb); //敵弾を描画
                }
            }
        }
        if (gamemode == 9) { //ゲームオーバー画面？
            e.Graphics.DrawString("GAME OVER", fontm, brushs, 210, 150); //メッセージ表示
            e.Graphics.DrawString(hiscoremsg, fontm, brushs, 150, 200); //ハイスコアメッセージ表示
            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; //無しにする
            }
            for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
                enemyba[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)) { //自機が存在し衝突？ 
                    GameOver(); //ゲームオーバーにする
                    //player.v = 0; /【移動】/自機を消す
                    //gamemode = 9; //【移動】ゲームオーバーにする
                    //if (score > hiscore) { //【移動】ハイスコア更新？
                    //    hiscore = score; //【移動】ハイスコア更新
                    //    hiscoremsg = "HiSCORE Update !"; //【移動】メッセージ
                    //} else {
                    //    hiscoremsg = String.Format("HiSCORE is {0:000,000}", hiscore); //【移動】スコア文字列を作る
                    //}
                    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;  //この自弾を消す
                        score += 10; //スコアアップ
                        break; //次の敵機へ進む
                    }
                }
            }
        }
        for (int i = 0; i < maxenemyb; i++) { //【以下追加】全敵弾について繰返す
            if (enemyba[i].v != 0 && player.v != 0 && isHit(enemyba[i], player)) { //敵弾と自機が存在し衝突？ 
                GameOver(); //ゲームオーバーにする
                enemyba[i].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 = enemyi.Width / 2 + rnd.Next(backi.Width - enemyi.Width); //X座標はランダム
                    enemya[i].y = -enemyi.Height; //Y座標は上端の直上
                    enemya[i].vv = enemyvv; //【変更】下移動速度
                    waitenemy = enemyint; //敵機出現待ち時間をセット
                    break; //1機出現できればOK
                }
            }
        }
        if (waitenemyb <= 0) { //敵弾出現待ち時間がゼロ？
            int enemyi = -1; //発射敵機
            for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
                if (enemya[i].v == 1 && enemya[i].y > 0 && enemya[i].y < backi.Height / 2) { //敵機が画面上半分にいる？
                    enemyi = i; //発射敵機決定
                    break;
                }
            }
            if (enemyi != -1) { //発射敵機決定済？
                for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
                    if (enemyba[i].v == 0) { //敵弾が非表示？
                        enemyba[i].v = 1; //表示にする
                        enemyba[i].i = enemybi; //画像
                        enemyba[i].x = enemya[enemyi].x; //X座標は発射敵機と同じ
                        enemyba[i].y = enemya[enemyi].y - 15; //Y座標は発射敵機の15ドット上
                        double rad = Math.Atan2(player.y - enemyba[i].y, player.x - enemyba[i].x); //【追加】角度を得る
                        enemyba[i].hv = (int)(Math.Cos(rad) * enemyvv * 1.5); //【追加】X移動量
                        enemyba[i].vv = (int)(Math.Sin(rad) * enemyvv * 1.5); //【追加】Y移動量
                        waitenemyb = enemybint; //敵弾発射待ち時間をセット
                        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; //敵機を消す
                }
            }
        }
        for (int i = 0; i < maxenemyb; i++) { //【以下追加】全敵弾について繰返す
            if (enemyba[i].v != 0) { //敵弾が存在？
                enemyba[i].x += (int)enemyba[i].hv; //X座標を変更
                enemyba[i].y += (int)enemyba[i].vv; //Y座標を変更
                if (enemyba[i].x < -enemybi.Width / 2 ||
                    enemyba[i].x - enemybi.Width / 2 > backi.Width ||
                    enemyba[i].y < -enemybi.Height / 2 ||
                    enemyba[i].y - enemybi.Height / 2 > backi.Height) { //画面端より外に出たら
                    enemyba[i].v = 0; //敵弾を消す
                }
            }
        }
        if (waitpb > 0) { //自弾発射待ち時間がセットされていたら
            waitpb--; //カウントダウンする
        }
        if (waitenemy > 0) { //敵機出現待ち時間がセットされていたら
            waitenemy--; //カウントダウンする
        }
        if (waitenemyb > 0) { //敵弾出現待ち時間がセットされていたら
            waitenemyb--; //カウントダウンする
        }
        Invalidate(); //画面再描画を依頼
    }
    //【以下追加】ゲームオーバーにする
    private void GameOver() {
        player.v = 0; //自機を消す
        gamemode = 9; //ゲームオーバーにする
        if (score > hiscore) { //ハイスコア更新？
            hiscore = score; //ハイスコア更新
            hiscoremsg = "HiSCORE Update !"; //メッセージ
        } else {
            hiscoremsg = String.Format("HiSCORE is {0:000,000}", hiscore); //スコア文字列を作る
        }
    }
    //コンストラクタ
    Program() {
        DoubleBuffered = true; //ダブルバッファリングを有効化
        KeyDown += new KeyEventHandler(OnKeyDown); //キー入力イベント登録
        timer.Tick += new EventHandler(Play); //タイマーイベント登録
        timer.Interval = 10; //タイマーインターバル(ミリ秒)
    }
    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); //フォームを現出
    }
}

テーマ33 画像の拡大・縮小表示

・DrawImage()メソッドのオーバーロードで、描画幅と高さを指定できる
・書式：DrawImage(Image image, int x, int y, int width, int height)
・これを用いて、画像の拡大・縮小表示が可能
・中心に向かって縮小したい場合は、x、yを拡大縮小率に合わせて調整すれば良い
・画像を中心座標で扱っている場合：
　x ← 中心X座標 - 画像幅 × 拡大縮小率% ÷ 100 ÷ 2
　y ← 中心Y座標 - 画像高 × 拡大縮小率% ÷ 100 ÷ 2
　width  ← 画像幅 × 拡大縮小率% ÷ 100
　height ← 画像高 × 拡大縮小率% ÷ 100

演習39 自機の落下

・敵機が当たった自機はすぐに消えるのではなく、ゲームオーバー画面においてその位置でだんだん縮小するようにしよう（落下に見える）
・縮小中は移動、自弾の発射はできない。また、衝突判定もしない。
・DrawItem(e, player)メソッドのオーバーロードとしてDrawItem(e, player, 縮小率％)を作って用いると良い
・DrawItem(e, player, 縮小率％)では、縮小率％が100の場合にDrawItem(e, player)を呼ぶようにすることで効率低下を防ごう

手順と仕様

① データの初期化処理：自機の表示（player.v）の初期値は100(%)にする
② 画像描画処理のオーバーロードの追加：DrawItem(PaintEventArgs e, Item it, int 倍率)
　・倍率が100なら、元のDrawItem(e, it)を呼ぶ
　・でなければ、倍率で左上座標、幅、高さを計算して、DrawImage(it, X, Y, 幅, 高さ)を呼ぶ
　　X ← it.x - it.i.Width  × 倍率 ÷ 100 ÷ 2
　　Y ← it.y - it.i.Height × 倍率 ÷ 100 ÷ 2
　　幅   ← it.i.Width  × 倍率 ÷ 100
　　高さ ← it.i.Height × 倍率 ÷ 100
③ 描画処理：自機の描画を②を用いるように変更
④ タイマーイベント処理：敵機と自機の衝突判定処理は自機の表示(倍率)が100の時のみにする
⑤ タイマーイベント処理：敵弾と自機の衝突判定処理は自機の表示(倍率)が100の時のみにする
⑥ タイマーイベント処理：ゲームオーバーで自機の表示(倍率)が0超100未満なら、自機の表示(倍率)をカウントダウンする処理を追加
⑦ ゲームオーバー処理：自機の表示(倍率)を0から99に変更

作成例

//演習39 自機の落下
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; //スコア
    int hiscore = 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"); //敵機画像を読込む
    Image enemybi = Image.FromFile("ebullet.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; //敵機の出現間隔
    string hiscoremsg = "";  //ハイスコアメッセージ
    Random rnd = new Random(); //乱数用のRandomクラスのインスタンスを生成
    const int enemybint = 75; //敵弾の出現間隔
    int waitenemyb = 200; //敵弾の出現待ち時間
    const int maxenemyb = 20; //敵弾の最大数
    Item[] enemyba = new Item[maxenemyb]; //敵弾の構造体オブジェクト配列
    const int enemyvv = 5; //敵機移動速度
    //各データをゲーム開始時の値にする
    void initData() {
        player.i = playeri;  //自機の画像
        player.x = 320; //自機の中心X座標
        player.y = 410; //自機の中心Y座標
        player.hv = 0;  //自機の左右方向の速度
        player.v = 100;  //【変更】自機を表示
        waitpb = 0; //自弾発射の待ち時間
        waitpb = 0; //自弾発射の待ち時間
        score = 0; //スコアクリア
        waitenemyb = 200; //敵弾の出現待ち時間
    }
    //中央座標を用いる画像描画処理
    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);
    }
    //【以下追加】中央座標を用いる画像描画処理(縮小率％付き)
    private void DrawItem(PaintEventArgs e, Item it, int rate) {
        if (rate == 100) { //等倍？
            DrawItem(e, it); //通常描画する
        } else {
            int xx = it.x - it.i.Width * rate / 100 / 2; //左上X座標を得る
            int yy = it.y - it.i.Height * rate / 100 / 2; //左上Y座標を得る
            e.Graphics.DrawImage(it.i, xx, yy, it.i.Width * rate / 100, it.i.Height * rate / 100);
        }
    }
    //楕円形の衝突判定(アイテム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); //距離２乗
        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); //距離２乗と内径の和の２乗を比較
    }
    //描画処理のオーバライド
    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, player.v); //【変更】自機を倍率描画
            }
            foreach (var pb in pba) { //全自弾について繰返す
                if (pb.v == 1) { //自弾がある？
                    DrawItem(e, pb); //自弾を描画
                }
            }
            foreach (var enemy in enemya) { //全敵機について繰返す
                if (enemy.v == 1) { //敵機がある？
                    DrawItem(e, enemy); //敵機を描画
                }
            }
            foreach (var enemyb in enemyba) { //全敵弾について繰返す
                if (enemyb.v == 1) { //敵弾がある？
                    DrawItem(e, enemyb); //敵弾を描画
                }
            }
        }
        if (gamemode == 9) { //ゲームオーバー画面？
            e.Graphics.DrawString("GAME OVER", fontm, brushs, 210, 150); //メッセージ表示
            e.Graphics.DrawString(hiscoremsg, fontm, brushs, 150, 200); //ハイスコアメッセージ表示
            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; //無しにする
            }
            for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
                enemyba[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 == 100 && isHit(enemya[i], player)) { //【変更】自機が100%存在し衝突？ 
                    GameOver(); //ゲームオーバーにする
                    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;  //この自弾を消す
                        score += 10; //スコアアップ
                        break; //次の敵機へ進む
                    }
                }
            }
        }
        for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
            if (enemyba[i].v != 0 && player.v == 100 && isHit(enemyba[i], player)) { //【変更】敵弾と自機が存在し衝突？ 
                GameOver(); //ゲームオーバーにする
                enemyba[i].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 = enemyi.Width / 2 + rnd.Next(backi.Width - enemyi.Width); //X座標はランダム
                    enemya[i].y = -enemyi.Height; //Y座標は上端の直上
                    enemya[i].vv = enemyvv; //下移動速度
                    waitenemy = enemyint; //敵機出現待ち時間をセット
                    break; //1機出現できればOK
                }
            }
        }
        if (waitenemyb <= 0) { //敵弾出現待ち時間がゼロ？
            int enemyi = -1; //発射敵機
            for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
                if (enemya[i].v == 1 && enemya[i].y > 0 && enemya[i].y < backi.Height / 2) { //敵機が画面上半分にいる？
                    enemyi = i; //発射敵機決定
                    break;
                }
            }
            if (enemyi != -1) { //発射敵機決定済？
                for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
                    if (enemyba[i].v == 0) { //敵弾が非表示？
                        enemyba[i].v = 1; //表示にする
                        enemyba[i].i = enemybi; //画像
                        enemyba[i].x = enemya[enemyi].x; //X座標は発射敵機と同じ
                        enemyba[i].y = enemya[enemyi].y - 15; //Y座標は発射敵機の15ドット上
                        double rad = Math.Atan2(player.y - enemyba[i].y, player.x - enemyba[i].x); //角度を得る
                        enemyba[i].hv = (int)(Math.Cos(rad) * enemyvv * 1.5); //X移動量
                        enemyba[i].vv = (int)(Math.Sin(rad) * enemyvv * 1.5); //Y移動量
                        waitenemyb = enemybint; //敵弾発射待ち時間をセット
                        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; //敵機を消す
                }
            }
        }
        for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
            if (enemyba[i].v != 0) { //敵弾が存在？
                enemyba[i].x += (int)enemyba[i].hv; //X座標を変更
                enemyba[i].y += (int)enemyba[i].vv; //Y座標を変更
                if (enemyba[i].x < -enemybi.Width / 2 ||
                    enemyba[i].x - enemybi.Width / 2 > backi.Width ||
                    enemyba[i].y < -enemybi.Height / 2 ||
                    enemyba[i].y - enemybi.Height / 2 > backi.Height) { //画面端より外に出たら
                    enemyba[i].v = 0; //敵弾を消す
                }
            }
        }
        if (waitpb > 0) { //自弾発射待ち時間がセットされていたら
            waitpb--; //カウントダウンする
        }
        if (waitenemy > 0) { //敵機出現待ち時間がセットされていたら
            waitenemy--; //カウントダウンする
        }
        if (waitenemyb > 0) { //敵弾出現待ち時間がセットされていたら
            waitenemyb--; //カウントダウンする
        }
        if (gamemode == 9 && player.v > 0 && player.v < 100) {  //【以下追加】ゲームオーバーで自機縮小中？
            player.v--; //自機描画倍率ダウン
        }
        Invalidate(); //画面再描画を依頼
    }
    //ゲームオーバーにする
    private void GameOver() {
        player.v = 99; //【変更】自機の落下を開始
        gamemode = 9; //ゲームオーバーにする
        if (score > hiscore) { //ハイスコア更新？
            hiscore = score; //ハイスコア更新
            hiscoremsg = "HiSCORE Update !"; //メッセージ
        } else {
            hiscoremsg = String.Format("HiSCORE is {0:000,000}", hiscore); //スコア文字列を作る
        }
    }
    //コンストラクタ
    Program() {
        DoubleBuffered = true; //ダブルバッファリングを有効化
        KeyDown += new KeyEventHandler(OnKeyDown); //キー入力イベント登録
        timer.Tick += new EventHandler(Play); //タイマーイベント登録
        timer.Interval = 10; //タイマーインターバル(ミリ秒)
    }
    public static void Main() {
        Program f = new Program(); //自分のオブジェクトを生成
        f.Size = new Size(660, 523); //フォームのサイズを設定
        f.Text = "Game"; //フォーム名を設定
        f.ControlBox = false; //コントロールボックスを非表示に
        f.FormBorderStyle = FormBorderStyle.Fixed3D; //サイズ変更を抑止
        Application.Run(f); //フォームを現出
    }
}

演習40 敵機の落下

・自弾が当たった敵機はすぐに消えるのではなく、縮小しながら移動するようにしよう（落下に見える）
・縮小中の敵機は敵弾の発射はできない。また、衝突判定もしない。

手順と仕様

① 描画処理：敵機の描画を表示倍率が0ではないときに、倍率付きで行うようにしよう
② タイマーイベント処理：敵機と自機の衝突判定処理は敵機の表示(倍率)も100の時のみにする。
　 また、ゲームオーバー処理後、その敵機の表示(倍率)を99にする処理を追加
③ タイマーイベント処理：敵機と自弾の衝突判定処理は敵機の表示(倍率)も100の時のみにする。
　 また、その敵機の表示(倍率)を0から99に変更
④ タイマーイベント処理：敵機の出現待ち時間がゼロになった時の敵機の表示を1ではなく100にする
⑤ タイマーイベント処理：敵弾の発射敵機を決める処理は100%表示の敵機のみからにする
⑥ タイマーイベント処理：敵機の移動処理の前に、その敵機の表示(倍率)が99以下(落下中)であればデクリメントする

作成例

//演習40 敵機の落下
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; //スコア
    int hiscore = 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"); //敵機画像を読込む
    Image enemybi = Image.FromFile("ebullet.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; //敵機の出現間隔
    string hiscoremsg = "";  //ハイスコアメッセージ
    Random rnd = new Random(); //乱数用のRandomクラスのインスタンスを生成
    const int enemybint = 75; //敵弾の出現間隔
    int waitenemyb = 200; //敵弾の出現待ち時間
    const int maxenemyb = 20; //敵弾の最大数
    Item[] enemyba = new Item[maxenemyb]; //敵弾の構造体オブジェクト配列
    const int enemyvv = 5; //敵機移動速度
    //各データをゲーム開始時の値にする
    void initData() {
        player.i = playeri;  //自機の画像
        player.x = 320; //自機の中心X座標
        player.y = 410; //自機の中心Y座標
        player.hv = 0;  //自機の左右方向の速度
        player.v = 100;  //自機を表示
        waitpb = 0; //自弾発射の待ち時間
        waitpb = 0; //自弾発射の待ち時間
        score = 0; //スコアクリア
        waitenemyb = 200; //敵弾の出現待ち時間
    }
    //中央座標を用いる画像描画処理
    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);
    }
    //中央座標を用いる画像描画処理(縮小率％付き)
    private void DrawItem(PaintEventArgs e, Item it, int rate) {
        if (rate == 100) { //等倍？
            DrawItem(e, it); //通常描画する
        } else {
            int xx = it.x - it.i.Width * rate / 100 / 2; //左上X座標を得る
            int yy = it.y - it.i.Height * rate / 100 / 2; //左上Y座標を得る
            e.Graphics.DrawImage(it.i, xx, yy, it.i.Width * rate / 100, it.i.Height * rate / 100);
        }
    }
    //楕円形の衝突判定(アイテム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); //距離２乗
        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); //距離２乗と内径の和の２乗を比較
    }
    //描画処理のオーバライド
    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, player.v); //自機を倍率描画
            }
            foreach (var pb in pba) { //全自弾について繰返す
                if (pb.v == 1) { //自弾がある？
                    DrawItem(e, pb); //自弾を描画
                }
            }
            foreach (var enemy in enemya) { //全敵機について繰返す
                if (enemy.v != 0) { //【変更】表示中？
                    DrawItem(e, enemy, enemy.v); //【変更】敵機を倍率描画
                }
            }
            foreach (var enemyb in enemyba) { //全敵弾について繰返す
                if (enemyb.v == 1) { //敵弾がある？
                    DrawItem(e, enemyb); //敵弾を描画
                }
            }
        }
        if (gamemode == 9) { //ゲームオーバー画面？
            e.Graphics.DrawString("GAME OVER", fontm, brushs, 210, 150); //メッセージ表示
            e.Graphics.DrawString(hiscoremsg, fontm, brushs, 150, 200); //ハイスコアメッセージ表示
            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; //無しにする
            }
            for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
                enemyba[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 == 100) { //【変更】敵機が100%存在？
                if (player.v == 100 && isHit(enemya[i], player)) { //自機が100%存在し衝突？ 
                    GameOver(); //ゲームオーバーにする
                    enemya[i].v = 99; //【追加】敵機落下開始
                    break; //抜ける
                }
                for (int j = 0; j < maxpb; j++) { //全自弾について繰返す
                    if (pba[j].v != 0 && isHit(enemya[i], pba[j])) { //自弾が存在し衝突？
                        enemya[i].v = 99; //【変更】敵機落下開始
                        pba[j].v = 0;  //この自弾を消す
                        score += 10; //スコアアップ
                        break; //次の敵機へ進む
                    }
                }
            }
        }
        for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
            if (enemyba[i].v != 0 && player.v == 100 && isHit(enemyba[i], player)) { //敵弾と自機が存在し衝突？ 
                GameOver(); //ゲームオーバーにする
                enemyba[i].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 = 100; //【変更】表示100%にする
                    enemya[i].i = enemyi; //画像
                    enemya[i].x = enemyi.Width / 2 + rnd.Next(backi.Width - enemyi.Width); //X座標はランダム
                    enemya[i].y = -enemyi.Height; //Y座標は上端の直上
                    enemya[i].vv = enemyvv; //下移動速度
                    waitenemy = enemyint; //敵機出現待ち時間をセット
                    break; //1機出現できればOK
                }
            }
        }
        if (waitenemyb <= 0) { //敵弾出現待ち時間がゼロ？
            int enemyi = -1; //発射敵機
            for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
                if (enemya[i].v == 100 && enemya[i].y > 0 && enemya[i].y < backi.Height / 2) { //【変更】敵機が画面上半分にいる？
                    enemyi = i; //発射敵機決定
                    break;
                }
            }
            if (enemyi != -1) { //発射敵機決定済？
                for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
                    if (enemyba[i].v == 0) { //敵弾が非表示？
                        enemyba[i].v = 1; //表示にする
                        enemyba[i].i = enemybi; //画像
                        enemyba[i].x = enemya[enemyi].x; //X座標は発射敵機と同じ
                        enemyba[i].y = enemya[enemyi].y - 15; //Y座標は発射敵機の15ドット上
                        double rad = Math.Atan2(player.y - enemyba[i].y, player.x - enemyba[i].x); //角度を得る
                        enemyba[i].hv = (int)(Math.Cos(rad) * enemyvv * 1.5); //X移動量
                        enemyba[i].vv = (int)(Math.Sin(rad) * enemyvv * 1.5); //Y移動量
                        waitenemyb = enemybint; //敵弾発射待ち時間をセット
                        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) { //敵機が存在？
                if (enemya[i].v <= 99) { //【以下追加】落下中？ 
                    enemya[i].v--; //落下
                }
                enemya[i].y += enemya[i].vv; //下へ移動
                if (enemya[i].y - enemya[i].i.Height / 2 > backi.Height) { //画面下端より下に出たら
                    enemya[i].v = 0; //敵機を消す
                }
            }
        }
        for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
            if (enemyba[i].v != 0) { //敵弾が存在？
                enemyba[i].x += (int)enemyba[i].hv; //X座標を変更
                enemyba[i].y += (int)enemyba[i].vv; //Y座標を変更
                if (enemyba[i].x < -enemybi.Width / 2 ||
                    enemyba[i].x - enemybi.Width / 2 > backi.Width ||
                    enemyba[i].y < -enemybi.Height / 2 ||
                    enemyba[i].y - enemybi.Height / 2 > backi.Height) { //画面端より外に出たら
                    enemyba[i].v = 0; //敵弾を消す
                }
            }
        }
        if (waitpb > 0) { //自弾発射待ち時間がセットされていたら
            waitpb--; //カウントダウンする
        }
        if (waitenemy > 0) { //敵機出現待ち時間がセットされていたら
            waitenemy--; //カウントダウンする
        }
        if (waitenemyb > 0) { //敵弾出現待ち時間がセットされていたら
            waitenemyb--; //カウントダウンする
        }
        if (gamemode == 9 && player.v > 0 && player.v < 100) {  //ゲームオーバーで自機縮小中？
            player.v--; //自機描画倍率ダウン
        }
        Invalidate(); //画面再描画を依頼
    }
    //ゲームオーバーにする
    private void GameOver() {
        player.v = 99; //自機の落下を開始
        gamemode = 9; //ゲームオーバーにする
        if (score > hiscore) { //ハイスコア更新？
            hiscore = score; //ハイスコア更新
            hiscoremsg = "HiSCORE Update !"; //メッセージ
        } else {
            hiscoremsg = String.Format("HiSCORE is {0:000,000}", hiscore); //スコア文字列を作る
        }
    }
    //コンストラクタ
    Program() {
        DoubleBuffered = true; //ダブルバッファリングを有効化
        KeyDown += new KeyEventHandler(OnKeyDown); //キー入力イベント登録
        timer.Tick += new EventHandler(Play); //タイマーイベント登録
        timer.Interval = 10; //タイマーインターバル(ミリ秒)
    }
    public static void Main() {
        Program f = new Program(); //自分のオブジェクトを生成
        f.Size = new Size(660, 523); //フォームのサイズを設定
        f.Text = "Game"; //フォーム名を設定
        f.ControlBox = false; //コントロールボックスを非表示に
        f.FormBorderStyle = FormBorderStyle.Fixed3D; //サイズ変更を抑止
        Application.Run(f); //フォームを現出
    }
}

演習41 自弾の敵弾衝突

・自弾と敵弾の当たり判定を行い、衝突していたら敵弾・自弾を消そう
・スコアに100点加算する

作成例

//演習41 自弾の敵弾衝突
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; //スコア
    int hiscore = 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"); //敵機画像を読込む
    Image enemybi = Image.FromFile("ebullet.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; //敵機の出現間隔
    string hiscoremsg = "";  //ハイスコアメッセージ
    Random rnd = new Random(); //乱数用のRandomクラスのインスタンスを生成
    const int enemybint = 75; //敵弾の出現間隔
    int waitenemyb = 200; //敵弾の出現待ち時間
    const int maxenemyb = 20; //敵弾の最大数
    Item[] enemyba = new Item[maxenemyb]; //敵弾の構造体オブジェクト配列
    const int enemyvv = 5; //敵機移動速度
    //各データをゲーム開始時の値にする
    void initData() {
        player.i = playeri;  //自機の画像
        player.x = 320; //自機の中心X座標
        player.y = 410; //自機の中心Y座標
        player.hv = 0;  //自機の左右方向の速度
        player.v = 100;  //自機を表示
        waitpb = 0; //自弾発射の待ち時間
        waitpb = 0; //自弾発射の待ち時間
        score = 0; //スコアクリア
        waitenemyb = 200; //敵弾の出現待ち時間
    }
    //中央座標を用いる画像描画処理
    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);
    }
    //中央座標を用いる画像描画処理(縮小率％付き)
    private void DrawItem(PaintEventArgs e, Item it, int rate) {
        if (rate == 100) { //等倍？
            DrawItem(e, it); //通常描画する
        } else {
            int xx = it.x - it.i.Width * rate / 100 / 2; //左上X座標を得る
            int yy = it.y - it.i.Height * rate / 100 / 2; //左上Y座標を得る
            e.Graphics.DrawImage(it.i, xx, yy, it.i.Width * rate / 100, it.i.Height * rate / 100);
        }
    }
    //楕円形の衝突判定(アイテム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); //距離２乗
        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); //距離２乗と内径の和の２乗を比較
    }
    //描画処理のオーバライド
    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, player.v); //自機を倍率描画
            }
            foreach (var pb in pba) { //全自弾について繰返す
                if (pb.v == 1) { //自弾がある？
                    DrawItem(e, pb); //自弾を描画
                }
            }
            foreach (var enemy in enemya) { //全敵機について繰返す
                if (enemy.v != 0) { //表示中？
                    DrawItem(e, enemy, enemy.v); //敵機を倍率描画
                }
            }
            foreach (var enemyb in enemyba) { //全敵弾について繰返す
                if (enemyb.v == 1) { //敵弾がある？
                    DrawItem(e, enemyb); //敵弾を描画
                }
            }
        }
        if (gamemode == 9) { //ゲームオーバー画面？
            e.Graphics.DrawString("GAME OVER", fontm, brushs, 210, 150); //メッセージ表示
            e.Graphics.DrawString(hiscoremsg, fontm, brushs, 150, 200); //ハイスコアメッセージ表示
            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; //無しにする
            }
            for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
                enemyba[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 == 100) { //敵機が100%存在？
                if (player.v == 100 && isHit(enemya[i], player)) { //自機が100%存在し衝突？ 
                    GameOver(); //ゲームオーバーにする
                    enemya[i].v = 99; //敵機落下開始
                    break; //抜ける
                }
                for (int j = 0; j < maxpb; j++) { //全自弾について繰返す
                    if (pba[j].v != 0 && isHit(enemya[i], pba[j])) { //自弾が存在し衝突？
                        enemya[i].v = 99; //敵機落下開始
                        pba[j].v = 0;  //この自弾を消す
                        score += 10; //スコアアップ
                        break; //次の敵機へ進む
                    }
                }
            }
        }
        for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
            if (enemyba[i].v != 0) { //【移動】敵弾が存在？
                if (player.v == 100 && isHit(enemyba[i], player)) { //【変更】自機が存在し敵弾と衝突？ 
                    GameOver(); //ゲームオーバーにする
                    enemyba[i].v = 0; //敵弾を消す
                    break; //抜ける
                }
                for (int j = 0; j < maxpb; j++) { //【以下追加】全自弾について繰返す
                    if (pba[j].v != 0 && isHit(enemyba[i], pba[j])) { //自弾が存在し敵弾と衝突？
                        enemyba[i].v = 0; //この敵弾を消す
                        pba[j].v = 0;  //この自弾を消す
                        score += 100; //スコアアップ
                        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 = 100; //表示100%にする
                    enemya[i].i = enemyi; //画像
                    enemya[i].x = enemyi.Width / 2 + rnd.Next(backi.Width - enemyi.Width); //X座標はランダム
                    enemya[i].y = -enemyi.Height; //Y座標は上端の直上
                    enemya[i].vv = enemyvv; //下移動速度
                    waitenemy = enemyint; //敵機出現待ち時間をセット
                    break; //1機出現できればOK
                }
            }
        }
        if (waitenemyb <= 0) { //敵弾出現待ち時間がゼロ？
            int enemyi = -1; //発射敵機
            for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
                if (enemya[i].v == 100 && enemya[i].y > 0 && enemya[i].y < backi.Height / 2) { //敵機が画面上半分にいる？
                    enemyi = i; //発射敵機決定
                    break;
                }
            }
            if (enemyi != -1) { //発射敵機決定済？
                for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
                    if (enemyba[i].v == 0) { //敵弾が非表示？
                        enemyba[i].v = 1; //表示にする
                        enemyba[i].i = enemybi; //画像
                        enemyba[i].x = enemya[enemyi].x; //X座標は発射敵機と同じ
                        enemyba[i].y = enemya[enemyi].y - 15; //Y座標は発射敵機の15ドット上
                        double rad = Math.Atan2(player.y - enemyba[i].y, player.x - enemyba[i].x); //角度を得る
                        enemyba[i].hv = (int)(Math.Cos(rad) * enemyvv * 1.5); //X移動量
                        enemyba[i].vv = (int)(Math.Sin(rad) * enemyvv * 1.5); //Y移動量
                        waitenemyb = enemybint; //敵弾発射待ち時間をセット
                        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) { //敵機が存在？
                if (enemya[i].v <= 99) { //落下中？ 
                    enemya[i].v--; //落下
                }
                enemya[i].y += enemya[i].vv; //下へ移動
                if (enemya[i].y - enemya[i].i.Height / 2 > backi.Height) { //画面下端より下に出たら
                    enemya[i].v = 0; //敵機を消す
                }
            }
        }
        for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
            if (enemyba[i].v != 0) { //敵弾が存在？
                enemyba[i].x += (int)enemyba[i].hv; //X座標を変更
                enemyba[i].y += (int)enemyba[i].vv; //Y座標を変更
                if (enemyba[i].x < -enemybi.Width / 2 ||
                    enemyba[i].x - enemybi.Width / 2 > backi.Width ||
                    enemyba[i].y < -enemybi.Height / 2 ||
                    enemyba[i].y - enemybi.Height / 2 > backi.Height) { //画面端より外に出たら
                    enemyba[i].v = 0; //敵弾を消す
                }
            }
        }
        if (waitpb > 0) { //自弾発射待ち時間がセットされていたら
            waitpb--; //カウントダウンする
        }
        if (waitenemy > 0) { //敵機出現待ち時間がセットされていたら
            waitenemy--; //カウントダウンする
        }
        if (waitenemyb > 0) { //敵弾出現待ち時間がセットされていたら
            waitenemyb--; //カウントダウンする
        }
        if (gamemode == 9 && player.v > 0 && player.v < 100) {  //ゲームオーバーで自機縮小中？
            player.v--; //自機描画倍率ダウン
        }
        Invalidate(); //画面再描画を依頼
    }
    //ゲームオーバーにする
    private void GameOver() {
        player.v = 99; //自機の落下を開始
        gamemode = 9; //ゲームオーバーにする
        if (score > hiscore) { //ハイスコア更新？
            hiscore = score; //ハイスコア更新
            hiscoremsg = "HiSCORE Update !"; //メッセージ
        } else {
            hiscoremsg = String.Format("HiSCORE is {0:000,000}", hiscore); //スコア文字列を作る
        }
    }
    //コンストラクタ
    Program() {
        DoubleBuffered = true; //ダブルバッファリングを有効化
        KeyDown += new KeyEventHandler(OnKeyDown); //キー入力イベント登録
        timer.Tick += new EventHandler(Play); //タイマーイベント登録
        timer.Interval = 10; //タイマーインターバル(ミリ秒)
    }
    public static void Main() {
        Program f = new Program(); //自分のオブジェクトを生成
        f.Size = new Size(660, 523); //フォームのサイズを設定
        f.Text = "Game"; //フォーム名を設定
        f.ControlBox = false; //コントロールボックスを非表示に
        f.FormBorderStyle = FormBorderStyle.Fixed3D; //サイズ変更を抑止
        Application.Run(f); //フォームを現出
    }
}

テーマ34 テクスチャによる図形の描画

・画像を用いてテクスチャブラシを生成することで、図形の塗りつぶしなどに利用できる
・生成書式例： Brush 参照変数 = new TextureBrush(Image);
・生成したTextureBrushは基本クラスであるBrushのオブジェクトとして、図形の塗りつぶしに利用できる
・テクスチャによる円の塗りつぶしの書式例： e.Graphics.FillEllipse(Brush, 左上X座標, 左上Y座標, 幅, 高さ);
・太さのある線をテクスチャで描くには、コンストラクタPen(Brush, 太さ)でPenオブジェクトを生成して用いると良い
例：
  Brush b = new TextureBrush(burni);
  Pen p = new Pen(b, 10);
  e.Graphics.DrawEllipse(p, 10, 10, 300, 300);

演習42 敵弾の自弾衝突による爆散

・自弾と敵弾が衝突していたら敵弾の中心から円を広げるように爆散するようにしよう
・爆散用画像ファイルを用いる
　<img src=https://rundog.org/si/burn.gif> burn.gif 60×60
・これをテクスチャにした太さ15の円を用い、直径200まで拡大したら消そう
・爆散中の敵弾は動かず、衝突判定もしない

手順と仕様

※コメントのみ：Item構造体のメンバvを「0:非表示、1～100：表示、101～200：爆散中」とする
①データメンバ：静的Image変数burniを追加し、爆散用の炎画像burn.gifを読み込む
②データメンバ：Pen変数burnpを追加し、burniのテクスチャブラシと太さ15を指定して生成
③描画処理：全敵弾について、enemy.vが爆散中であれば：
　・爆散円の直径＝(enemy.vー100)の２倍とする(つまり2から200まで)
　・爆散円をDrawEllipse(burnp, X座標ー(enemy.vー100), Y座標ー(enemy.vー100), 直径, 直径)で描画
④タイマーイベント処理：敵弾と自弾の衝突判定を敵弾.vが1の時のみ行うように変更
⑤タイマーイベント処理：敵弾と自弾が衝突したら、敵弾.vを101にして爆散を開始するように変更
⑥タイマーイベント処理：敵弾を移動する処理を敵弾.vが1の時のみ行うように変更。
　また、敵弾.vが101以上(爆散中)であえば、200までインクリメントする処理を追加 

作成例

//演習42 自弾の敵弾衝突による爆散
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～100：表示、101～200：爆散)
}
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; //スコア
    int hiscore = 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"); //敵機画像を読込む
    Image enemybi = Image.FromFile("ebullet.gif"); //敵弾画像を読込む
    static Image burni = Image.FromFile("burn.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); //黄色のブラシ
    Pen burnp = new Pen(new TextureBrush(burni), 15); //【追加】爆散用テクスチャブラシによる太さ15のペン
    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; //敵機の出現間隔
    string hiscoremsg = "";  //ハイスコアメッセージ
    Random rnd = new Random(); //乱数用のRandomクラスのインスタンスを生成
    const int enemybint = 75; //敵弾の出現間隔
    int waitenemyb = 200; //敵弾の出現待ち時間
    const int maxenemyb = 20; //敵弾の最大数
    Item[] enemyba = new Item[maxenemyb]; //敵弾の構造体オブジェクト配列
    const int enemyvv = 5; //敵機移動速度
    //各データをゲーム開始時の値にする
    void initData() {
        player.i = playeri;  //自機の画像
        player.x = 320; //自機の中心X座標
        player.y = 410; //自機の中心Y座標
        player.hv = 0;  //自機の左右方向の速度
        player.v = 100;  //自機を表示
        waitpb = 0; //自弾発射の待ち時間
        waitpb = 0; //自弾発射の待ち時間
        score = 0; //スコアクリア
        waitenemyb = 200; //敵弾の出現待ち時間
    }
    //中央座標を用いる画像描画処理
    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);
    }
    //中央座標を用いる画像描画処理(縮小率％付き)
    private void DrawItem(PaintEventArgs e, Item it, int rate) {
        if (rate == 100) { //等倍？
            DrawItem(e, it); //通常描画する
        } else {
            int xx = it.x - it.i.Width * rate / 100 / 2; //左上X座標を得る
            int yy = it.y - it.i.Height * rate / 100 / 2; //左上Y座標を得る
            e.Graphics.DrawImage(it.i, xx, yy, it.i.Width * rate / 100, it.i.Height * rate / 100);
        }
    }
    //楕円形の衝突判定(アイテム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); //距離２乗
        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); //距離２乗と内径の和の２乗を比較
    }
    //描画処理のオーバライド
    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, player.v); //自機を倍率描画
            }
            foreach (var pb in pba) { //全自弾について繰返す
                if (pb.v == 1) { //自弾がある？
                    DrawItem(e, pb); //自弾を描画
                }
            }
            foreach (var enemy in enemya) { //全敵機について繰返す
                if (enemy.v != 0) { //表示中？
                    DrawItem(e, enemy, enemy.v); //敵機を倍率描画
                }
            }
            foreach (var enemyb in enemyba) { //全敵弾について繰返す
                if (enemyb.v == 1) { //敵弾がある？
                    DrawItem(e, enemyb); //敵弾を描画
                } else if (enemyb.v >= 101) { //【以下追加】爆散中？
                    int rr = (enemyb.v - 100) * 2; //爆散円の直径(2→200)
                    e.Graphics.DrawEllipse(burnp, enemyb.x - (enemyb.v - 100), enemyb.y - (enemyb.v - 100), rr, rr); //爆散円を描く
                }
            }
        }
        if (gamemode == 9) { //ゲームオーバー画面？
            e.Graphics.DrawString("GAME OVER", fontm, brushs, 210, 150); //メッセージ表示
            e.Graphics.DrawString(hiscoremsg, fontm, brushs, 150, 200); //ハイスコアメッセージ表示
            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; //無しにする
            }
            for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
                enemyba[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 == 100) { //敵機が100%存在？
                if (player.v == 100 && isHit(enemya[i], player)) { //自機が100%存在し衝突？ 
                    GameOver(); //ゲームオーバーにする
                    enemya[i].v = 99; //敵機落下開始
                    break; //抜ける
                }
                for (int j = 0; j < maxpb; j++) { //全自弾について繰返す
                    if (pba[j].v != 0 && isHit(enemya[i], pba[j])) { //自弾が存在し衝突？
                        enemya[i].v = 99; //敵機落下開始
                        pba[j].v = 0;  //この自弾を消す
                        score += 10; //スコアアップ
                        break; //次の敵機へ進む
                    }
                }
            }
        }
        for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
            if (enemyba[i].v == 1) { //【変更】敵弾が存在？
                if (player.v == 100 && isHit(enemyba[i], player)) { //自機が存在し敵弾と衝突？ 
                    GameOver(); //ゲームオーバーにする
                    enemyba[i].v = 0; //敵弾を消す
                    break; //抜ける
                }
                for (int j = 0; j < maxpb; j++) { //全自弾について繰返す
                    if (pba[j].v != 0 && isHit(enemyba[i], pba[j])) { //自弾が存在し敵弾と衝突？
                        enemyba[i].v = 101; //【変更】この敵弾を爆散状態にする
                        pba[j].v = 0;  //この自弾を消す
                        score += 100; //スコアアップ
                        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 = 100; //表示100%にする
                    enemya[i].i = enemyi; //画像
                    enemya[i].x = enemyi.Width / 2 + rnd.Next(backi.Width - enemyi.Width); //X座標はランダム
                    enemya[i].y = -enemyi.Height; //Y座標は上端の直上
                    enemya[i].vv = enemyvv; //下移動速度
                    waitenemy = enemyint; //敵機出現待ち時間をセット
                    break; //1機出現できればOK
                }
            }
        }
        if (waitenemyb <= 0) { //敵弾出現待ち時間がゼロ？
            int enemyi = -1; //発射敵機
            for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
                if (enemya[i].v == 100 && enemya[i].y > 0 && enemya[i].y < backi.Height / 2) { //敵機が画面上半分にいる？
                    enemyi = i; //発射敵機決定
                    break;
                }
            }
            if (enemyi != -1) { //発射敵機決定済？
                for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
                    if (enemyba[i].v == 0) { //敵弾が非表示？
                        enemyba[i].v = 1; //表示にする
                        enemyba[i].i = enemybi; //画像
                        enemyba[i].x = enemya[enemyi].x; //X座標は発射敵機と同じ
                        enemyba[i].y = enemya[enemyi].y - 15; //Y座標は発射敵機の15ドット上
                        double rad = Math.Atan2(player.y - enemyba[i].y, player.x - enemyba[i].x); //角度を得る
                        enemyba[i].hv = (int)(Math.Cos(rad) * enemyvv * 1.5); //X移動量
                        enemyba[i].vv = (int)(Math.Sin(rad) * enemyvv * 1.5); //Y移動量
                        waitenemyb = enemybint; //敵弾発射待ち時間をセット
                        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) { //敵機が存在？
                if (enemya[i].v <= 99) { //落下中？ 
                    enemya[i].v--; //落下
                }
                enemya[i].y += enemya[i].vv; //下へ移動
                if (enemya[i].y - enemya[i].i.Height / 2 > backi.Height) { //画面下端より下に出たら
                    enemya[i].v = 0; //敵機を消す
                }
            }
        }
        for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
            if (enemyba[i].v == 1) { //【変更】敵弾が存在？
                enemyba[i].x += (int)enemyba[i].hv; //X座標を変更
                enemyba[i].y += (int)enemyba[i].vv; //Y座標を変更
                if (enemyba[i].x < -enemybi.Width / 2 ||
                    enemyba[i].x - enemybi.Width / 2 > backi.Width ||
                    enemyba[i].y < -enemybi.Height / 2 ||
                    enemyba[i].y - enemybi.Height / 2 > backi.Height) { //画面端より外に出たら
                    enemyba[i].v = 0; //敵弾を消す
                }
            } else if (enemyba[i].v >= 101) { //【以下追加】爆散中？
                enemyba[i].v = (enemyba[i].v <= 200) ? enemyba[i].v + 1 : 0; //200までカウントアップし超えたら0にする
            }
        }
        if (waitpb > 0) { //自弾発射待ち時間がセットされていたら
            waitpb--; //カウントダウンする
        }
        if (waitenemy > 0) { //敵機出現待ち時間がセットされていたら
            waitenemy--; //カウントダウンする
        }
        if (waitenemyb > 0) { //敵弾出現待ち時間がセットされていたら
            waitenemyb--; //カウントダウンする
        }
        if (gamemode == 9 && player.v > 0 && player.v < 100) {  //ゲームオーバーで自機縮小中？
            player.v--; //自機描画倍率ダウン
        }
        Invalidate(); //画面再描画を依頼
    }
    //ゲームオーバーにする
    private void GameOver() {
        player.v = 99; //自機の落下を開始
        gamemode = 9; //ゲームオーバーにする
        if (score > hiscore) { //ハイスコア更新？
            hiscore = score; //ハイスコア更新
            hiscoremsg = "HiSCORE Update !"; //メッセージ
        } else {
            hiscoremsg = String.Format("HiSCORE is {0:000,000}", hiscore); //スコア文字列を作る
        }
    }
    //コンストラクタ
    Program() {
        DoubleBuffered = true; //ダブルバッファリングを有効化
        KeyDown += new KeyEventHandler(OnKeyDown); //キー入力イベント登録
        timer.Tick += new EventHandler(Play); //タイマーイベント登録
        timer.Interval = 10; //タイマーインターバル(ミリ秒)
    }
    public static void Main() {
        Program f = new Program(); //自分のオブジェクトを生成
        f.Size = new Size(660, 523); //フォームのサイズを設定
        f.Text = "Game"; //フォーム名を設定
        f.ControlBox = false; //コントロールボックスを非表示に
        f.FormBorderStyle = FormBorderStyle.Fixed3D; //サイズ変更を抑止
        Application.Run(f); //フォームを現出
    }
}

演習43 敵機と爆散円の衝突判定

・敵機が爆散円に触れたらスコアに10点加算し。落下するようにしよう
・敵機が破壊されたことがわかりやすくなるように、落下中の画像を破壊画像に切り替えよう
　<img src=https://rundog.org/si/meteo3.gif> meteo3.gif
・スコアの上昇に合わせて、敵機の出現間隔が短くなるようにしよう(10まで)

手順と仕様

①データメンバ：Image変数meteoiを追加し、敵機落下用の画像meteo3.gifを読み込む
②初期化処理：敵機の出現間隔を50にする
③タイマーイベント処理：敵機と自機が衝突したら敵機画像をmeteoiにする処理を追加
④タイマーイベント処理：敵機と自弾が衝突したら敵機画像をmeteoiにする処理を追加
⑤タイマーイベント処理：敵機と爆散円の中央の距離が(爆散円の半径＋敵機幅÷2)未満なら衝突とする。
　そして、10点加算、敵機画像を差し替えて落下を開始する。
　※爆散円は存在中の敵弾の数だけチェックする必要がある
⑥タイマーイベント処理：スコア÷100を敵機の出現間隔(50)から差し引く(10以下にはしないこと)

作成例

//演習43 敵弾の爆散による誘爆
using System; //汎用的に利用
using System.Windows.Forms; //フォームアプリケーションに必須
using System.Drawing; //Size、Image用
//《0. アイテムを表す構造体》
struct Item {
    public Image i; //画像
    public int x;  //中心X座標
    public int y;  //中心Y座標
    public int hv; //左右方向の速度(左向きは負の数、右向きは正の数)
    public int vv; //上下方向の速度(上向きは負の数、下向きは正の数)
    public int v;  //表示状態(0：非表示、1～100：表示、101～200：爆散)
}
class Program : Form { //Formクラスの派生クラス
    //《1. DLLインポートと外部定義指定》
    [System.Runtime.InteropServices.DllImport("user32.dll")] //DLLインポート
    private static extern short GetKeyState(int nVirtKey); //外部定義指定
    //《2. データメンバ》
    int gamemode = 0; //モード(0:タイトル画面,1:プレイ画面,9:終了画面)
    int score = 0; //スコア
    int hiscore = 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"); //敵機画像を読込む
    Image enemybi = Image.FromFile("ebullet.gif"); //敵弾画像を読込む
    static Image burni = Image.FromFile("burn.gif"); //爆散画像を読込む
    static Image meteoi = Image.FromFile("meteo3.gif"); //【追加】炎上画像を読込む
    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); //黄色のブラシ
    Pen burnp = new Pen(new TextureBrush(burni), 15); //爆散用テクスチャブラシによる太さ15のペン
    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; //敵機の出現間隔
    string hiscoremsg = "";  //ハイスコアメッセージ
    Random rnd = new Random(); //乱数用のRandomクラスのインスタンスを生成
    const int enemybint = 75; //敵弾の出現間隔
    int waitenemyb = 200; //敵弾の出現待ち時間
    const int maxenemyb = 20; //敵弾の最大数
    Item[] enemyba = new Item[maxenemyb]; //敵弾の構造体オブジェクト配列
    const int enemyvv = 5; //敵機移動速度
    //《3. 初期化処理》各データをゲーム開始時の値にする
    void initData() {
        player.i = playeri;  //自機の画像
        player.x = 320; //自機の中心X座標
        player.y = 410; //自機の中心Y座標
        player.hv = 0;  //自機の左右方向の速度
        player.v = 100;  //自機を表示
        waitpb = 0; //自弾発射の待ち時間
        waitpb = 0; //自弾発射の待ち時間
        score = 0; //スコアクリア
        waitenemyb = 200; //敵弾の出現待ち時間
        enemyint = 50; //【追加】敵機の出現間隔
    }
    //《4. 中央座標を用いる画像描画処理》
   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);
    }
    //《5. 中央座標を用いる画像描画処理(縮小率％付き)》
    private void DrawItem(PaintEventArgs e, Item it, int rate) {
        if (rate == 100) { //等倍？
            DrawItem(e, it); //通常描画する
        } else {
            int xx = it.x - it.i.Width * rate / 100 / 2; //左上X座標を得る
            int yy = it.y - it.i.Height * rate / 100 / 2; //左上Y座標を得る
            e.Graphics.DrawImage(it.i, xx, yy, it.i.Width * rate / 100, it.i.Height * rate / 100);
        }
    }
    //《6. 楕円形の衝突判定》
    private bool isHit(Item a, Item b) { //アイテムaとアイテムbが衝突しているかどうかを返す
        int dest = (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y); //距離２乗
        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); //距離２乗と内径の和の２乗を比較
    }
    //《7. 描画処理》オーバライド
    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, player.v); //自機を倍率描画
            }
            foreach (var pb in pba) { //全自弾について繰返す
                if (pb.v == 1) { //自弾がある？
                    DrawItem(e, pb); //自弾を描画
                }
            }
            foreach (var enemy in enemya) { //全敵機について繰返す
                if (enemy.v != 0) { //表示中？
                    DrawItem(e, enemy, enemy.v); //敵機を倍率描画
                }
            }
            foreach (var enemyb in enemyba) { //全敵弾について繰返す
                if (enemyb.v == 1) { //敵弾がある？
                    DrawItem(e, enemyb); //敵弾を描画
                } else if (enemyb.v >= 101) { //【以下追加】爆散中？
                    int rr = (enemyb.v - 100) * 2; //爆散円の直径(2→200)
                    e.Graphics.DrawEllipse(burnp, enemyb.x - (enemyb.v - 100), enemyb.y - (enemyb.v - 100), rr, rr); //爆散円を描く
                }
            }
        }
        if (gamemode == 9) { //ゲームオーバー画面？
            e.Graphics.DrawString("GAME OVER", fontm, brushs, 210, 150); //メッセージ表示
            e.Graphics.DrawString(hiscoremsg, fontm, brushs, 150, 200); //ハイスコアメッセージ表示
            e.Graphics.DrawString("Hit Enter Key", fontm, brushs, 200, 300); //メッセージ表示
        }
    }
    //《8. キー入力時処理》
    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; //無しにする
            }
            for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
                enemyba[i].v = 0; //無しにする
            }
            gamemode = 1; //プレイ動画に遷移
        }
        Invalidate(); //画面再描画を依頼
    }
    //《9. タイマーイベント処理》
    void Play(object o, EventArgs e) {
        //《9.1. 背景画面のスクロール
        backy = (backy + 1) % backi.Height; //1枚目の背景描画開始Y座標を下げる
        //《9.2. 自機の向きを決める
        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; //右向き
            }
        }
        //《9.3. 敵機と自機・自弾の衝突
        for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
            if (enemya[i].v == 100) { //敵機が100%存在？
                if (player.v == 100 && isHit(enemya[i], player)) { //自機が100%存在し衝突？ 
                    GameOver(); //ゲームオーバーにする
                    enemya[i].v = 99; //敵機落下開始
                    enemya[i].i = meteoi; //【追加】破壊画像にする 
                    break; //抜ける
                }
                for (int j = 0; j < maxpb; j++) { //全自弾について繰返す
                    if (pba[j].v != 0 && isHit(enemya[i], pba[j])) { //自弾が存在し衝突？
                        enemya[i].v = 99; //敵機落下開始
                        enemya[i].i = meteoi; //【追加】破壊画像にする 
                        pba[j].v = 0;  //この自弾を消す
                        score += 10; //スコアアップ
                        break; //次の敵機へ進む
                    }
                }
            }
        }
        //《9. 4 敵弾と自機・自弾の衝突
        for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
            if (enemyba[i].v == 1) { //敵弾が存在？
                if (player.v == 100 && isHit(enemyba[i], player)) { //自機が存在し敵弾と衝突？ 
                    GameOver(); //ゲームオーバーにする
                    enemyba[i].v = 0; //敵弾を消す
                    break; //抜ける
                }
                for (int j = 0; j < maxpb; j++) { //全自弾について繰返す
                    if (pba[j].v != 0 && isHit(enemyba[i], pba[j])) { //自弾が存在し敵弾と衝突？
                        enemyba[i].v = 101; //この敵弾を爆散状態にする
                        pba[j].v = 0;  //この自弾を消す
                        score += 100; //スコアアップ
                        break; //次の敵弾へ進む
                    }
                }
            } else  if (enemyba[i].v >= 101) { //【以下追加】敵弾が爆散状態？
                for (int j = 0; j < maxenemy; j++) { //全敵機について繰返す
                    if (enemya[j].v == 100) { //敵機が100%存在？
                        double d1 = Math.Pow(enemyba[i].x - enemya[j].x, 2) + Math.Pow(enemyba[i].y - enemya[j].y, 2); //中心距離の2乗
                        double d2 = Math.Pow(enemyba[i].v - 100 + enemyi.Width / 2, 2); //爆散円と敵機半径の和の2乗
                        if (d1 < d2) {  //敵機が爆散円内？
                            enemya[j].v = 99; //敵機落下開始
                            enemya[j].i = meteoi; //破壊画像にする 
                            score += 10; //スコアアップ
                            break; //次の敵機へ進む
                        }
                    }
                }
            }
        }
        //《9.5 自弾発射
        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; //自弾発射待ち時間をゼロにして発射可能にする
            }
        }
        //《9.6 敵機出現
        if (waitenemy <= 0) { //敵機出現待ち時間がゼロ？
            for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
                if (enemya[i].v == 0) { //敵機が非表示？
                    enemya[i].v = 100; //表示100%にする
                    enemya[i].i = enemyi; //画像
                    enemya[i].x = enemyi.Width / 2 + rnd.Next(backi.Width - enemyi.Width); //X座標はランダム
                    enemya[i].y = -enemyi.Height; //Y座標は上端の直上
                    enemya[i].vv = enemyvv; //下移動速度
                    waitenemy = enemyint; //敵機出現待ち時間をセット
                    break; //1機出現できればOK
                }
            }
        }
        //《9.7 敵弾発射
        if (waitenemyb <= 0) { //敵弾出現待ち時間がゼロ？
            int enemyi = -1; //発射敵機
            for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
                if (enemya[i].v == 100 && enemya[i].y > 0 && enemya[i].y < backi.Height / 2) { //敵機が画面上半分にいる？
                    enemyi = i; //発射敵機決定
                    break;
                }
            }
            if (enemyi != -1) { //発射敵機決定済？
                for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
                    if (enemyba[i].v == 0) { //敵弾が非表示？
                        enemyba[i].v = 1; //表示にする
                        enemyba[i].i = enemybi; //画像
                        enemyba[i].x = enemya[enemyi].x; //X座標は発射敵機と同じ
                        enemyba[i].y = enemya[enemyi].y - 15; //Y座標は発射敵機の15ドット上
                        double rad = Math.Atan2(player.y - enemyba[i].y, player.x - enemyba[i].x); //角度を得る
                        enemyba[i].hv = (int)(Math.Cos(rad) * enemyvv * 1.5); //X移動量
                        enemyba[i].vv = (int)(Math.Sin(rad) * enemyvv * 1.5); //Y移動量
                        waitenemyb = enemybint; //敵弾発射待ち時間をセット
                        break; //1弾発射できればOK
                    }
                }
            }
        }
        //《9.8 自弾移動
        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; //自弾を消す
                }
            }
        }
        //《9.9 敵機移動
        for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
            if (enemya[i].v != 0) { //敵機が存在？
                if (enemya[i].v <= 99) { //落下中？ 
                    enemya[i].v--; //落下
                }
                enemya[i].y += enemya[i].vv; //下へ移動
                if (enemya[i].y - enemya[i].i.Height / 2 > backi.Height) { //画面下端より下に出たら
                    enemya[i].v = 0; //敵機を消す
                }
            }
        }
        //《9.10 敵弾移動
        for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
            if (enemyba[i].v == 1) { //敵弾が存在？
                enemyba[i].x += (int)enemyba[i].hv; //X座標を変更
                enemyba[i].y += (int)enemyba[i].vv; //Y座標を変更
                if (enemyba[i].x < -enemybi.Width / 2 ||
                    enemyba[i].x - enemybi.Width / 2 > backi.Width ||
                    enemyba[i].y < -enemybi.Height / 2 ||
                    enemyba[i].y - enemybi.Height / 2 > backi.Height) { //画面端より外に出たら
                    enemyba[i].v = 0; //敵弾を消す
                }
            } else if (enemyba[i].v >= 101) { //爆散中？
                enemyba[i].v = (enemyba[i].v <= 200) ? enemyba[i].v + 1 : 0; //200までカウントアップし超えたら0にする
            }
        }
        //《9.11 待ち時間カウントダウン・倍率ダウン・敵機の出現間隔ダウン・画面再描画
        if (waitpb > 0) { //自弾発射待ち時間がセットされていたら
            waitpb--; //カウントダウンする
        }
        if (waitenemy > 0) { //敵機出現待ち時間がセットされていたら
            waitenemy--; //カウントダウンする
        }
        if (waitenemyb > 0) { //敵弾出現待ち時間がセットされていたら
            waitenemyb--; //カウントダウンする
        }
        if (gamemode == 9 && player.v > 0 && player.v < 100) {  //ゲームオーバーで自機縮小中？
            player.v--; //自機描画倍率ダウン
        }
        if (enemyint > 10) { //【以下追加】敵機の出現間隔が10超なら
            enemyint = 50 - score / 100; //スコア÷100を敵機の出現間隔から差し引く
        }
        Invalidate(); //画面再描画を依頼
    }
    //《10. ゲームオーバーにする》
    private void GameOver() {
        player.v = 99; //自機の落下を開始
        gamemode = 9; //ゲームオーバーにする
        if (score > hiscore) { //ハイスコア更新？
            hiscore = score; //ハイスコア更新
            hiscoremsg = "HiSCORE Update !"; //メッセージ
        } else {
            hiscoremsg = String.Format("HiSCORE is {0:000,000}", hiscore); //スコア文字列を作る
        }
    }
    //《11. コンストラクタ》
    Program() {
        DoubleBuffered = true; //ダブルバッファリングを有効化
        KeyDown += new KeyEventHandler(OnKeyDown); //キー入力イベント登録
        timer.Tick += new EventHandler(Play); //タイマーイベント登録
        timer.Interval = 10; //タイマーインターバル(ミリ秒)
    }
    //《12. Main》
    public static void Main() {
        Program f = new Program(); //自分のオブジェクトを生成
        f.Size = new Size(660, 523); //フォームのサイズを設定
        f.Text = "Game"; //フォーム名を設定
        f.ControlBox = false; //コントロールボックスを非表示に
        f.FormBorderStyle = FormBorderStyle.Fixed3D; //サイズ変更を抑止
        Application.Run(f); //フォームを現出
    }
}


演習44 敵弾と爆散円の衝突判定と誘爆

・敵弾が爆散円に触れたら10点加算して爆散するようにしよう
・スコアの上昇に合わせて、敵弾の出現間隔が短くなるようにしよう(15まで)
・併せて、ゲームオーバー後は一切のスコア加算を行わないようにしよう

手順と仕様

①データメンバ：敵弾の出現間隔を定数から変数にする
②初期化処理：敵弾の出現間隔を75にする
③タイマーイベント処理（全体）：スコア加算はプレイ画面時のみにする
④タイマーイベント処理：敵弾と爆散円の中央の距離が(爆散円の半径＋敵弾幅÷2)未満なら衝突とする。
　そして、衝突した敵弾を爆散させ、プレイ画面時なら10点加算する。
⑤タイマーイベント処理：スコア÷100を敵機の出現間隔(75)から差し引く(15以下にはしないこと)

作成例

//演習44 敵弾と爆散円の衝突判定と誘爆
using System; //汎用的に利用
using System.Windows.Forms; //フォームアプリケーションに必須
using System.Drawing; //Size、Image用
//《0. アイテムを表す構造体》
struct Item {
    public Image i; //画像
    public int x;  //中心X座標
    public int y;  //中心Y座標
    public int hv; //左右方向の速度(左向きは負の数、右向きは正の数)
    public int vv; //上下方向の速度(上向きは負の数、下向きは正の数)
    public int v;  //表示状態(0：非表示、1～100：表示、101～200：爆散)
}
class Program : Form { //Formクラスの派生クラス
    //《1. DLLインポートと外部定義指定》
    [System.Runtime.InteropServices.DllImport("user32.dll")] //DLLインポート
    private static extern short GetKeyState(int nVirtKey); //外部定義指定
    //《2. データメンバ》
    int gamemode = 0; //モード(0:タイトル画面,1:プレイ画面,9:終了画面)
    int score = 0; //スコア
    int hiscore = 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"); //敵機画像を読込む
    Image enemybi = Image.FromFile("ebullet.gif"); //敵弾画像を読込む
    static Image burni = Image.FromFile("burn.gif"); //爆散画像を読込む
    static Image meteoi = Image.FromFile("meteo3.gif"); //炎上画像を読込む
    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); //黄色のブラシ
    Pen burnp = new Pen(new TextureBrush(burni), 15); //爆散用テクスチャブラシによる太さ15のペン
    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; //敵機の出現間隔
    string hiscoremsg = "";  //ハイスコアメッセージ
    Random rnd = new Random(); //乱数用のRandomクラスのインスタンスを生成
    int enemybint = 75; //【変更】敵弾の出現間隔
    int waitenemyb = 200; //敵弾の出現待ち時間
    const int maxenemyb = 20; //敵弾の最大数
    Item[] enemyba = new Item[maxenemyb]; //敵弾の構造体オブジェクト配列
    const int enemyvv = 5; //敵機移動速度
    //《3. 初期化処理》各データをゲーム開始時の値にする
    void initData() {
        player.i = playeri;  //自機の画像
        player.x = 320; //自機の中心X座標
        player.y = 410; //自機の中心Y座標
        player.hv = 0;  //自機の左右方向の速度
        player.v = 100;  //自機を表示
        waitpb = 0; //自弾発射の待ち時間
        waitpb = 0; //自弾発射の待ち時間
        score = 0; //スコアクリア
        waitenemyb = 200; //敵弾の出現待ち時間
        enemyint = 50; //敵機の出現間隔
        enemybint = 75; //【追加】敵弾の出現間隔
    }
    //《4. 中央座標を用いる画像描画処理》
   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);
    }
    //《5. 中央座標を用いる画像描画処理(縮小率％付き)》
    private void DrawItem(PaintEventArgs e, Item it, int rate) {
        if (rate == 100) { //等倍？
            DrawItem(e, it); //通常描画する
        } else {
            int xx = it.x - it.i.Width * rate / 100 / 2; //左上X座標を得る
            int yy = it.y - it.i.Height * rate / 100 / 2; //左上Y座標を得る
            e.Graphics.DrawImage(it.i, xx, yy, it.i.Width * rate / 100, it.i.Height * rate / 100);
        }
    }
    //《6. 楕円形の衝突判定》
    private bool isHit(Item a, Item b) { //アイテムaとアイテムbが衝突しているかどうかを返す
        int dest = (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y); //距離２乗
        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); //距離２乗と内径の和の２乗を比較
    }
    //《7. 描画処理》オーバライド
    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, player.v); //自機を倍率描画
            }
            foreach (var pb in pba) { //全自弾について繰返す
                if (pb.v == 1) { //自弾がある？
                    DrawItem(e, pb); //自弾を描画
                }
            }
            foreach (var enemy in enemya) { //全敵機について繰返す
                if (enemy.v != 0) { //表示中？
                    DrawItem(e, enemy, enemy.v); //敵機を倍率描画
                }
            }
            foreach (var enemyb in enemyba) { //全敵弾について繰返す
                if (enemyb.v == 1) { //敵弾がある？
                    DrawItem(e, enemyb); //敵弾を描画
                } else if (enemyb.v >= 101) { //爆散中？
                    int rr = (enemyb.v - 100) * 2; //爆散円の直径(2→200)
                    e.Graphics.DrawEllipse(burnp, enemyb.x - (enemyb.v - 100), enemyb.y - (enemyb.v - 100), rr, rr); //爆散円を描く
                }
            }
        }
        if (gamemode == 9) { //ゲームオーバー画面？
            e.Graphics.DrawString("GAME OVER", fontm, brushs, 210, 150); //メッセージ表示
            e.Graphics.DrawString(hiscoremsg, fontm, brushs, 150, 200); //ハイスコアメッセージ表示
            e.Graphics.DrawString("Hit Enter Key", fontm, brushs, 200, 300); //メッセージ表示
        }
    }
    //《8. キー入力時処理》
    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; //無しにする
            }
            for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
                enemyba[i].v = 0; //無しにする
            }
            gamemode = 1; //プレイ動画に遷移
        }
        Invalidate(); //画面再描画を依頼
    }
    //《9. タイマーイベント処理》
    void Play(object o, EventArgs e) {
        //《9.1. 背景画面のスクロール
        backy = (backy + 1) % backi.Height; //1枚目の背景描画開始Y座標を下げる
        //《9.2. 自機の向きを決める
        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; //右向き
            }
        }
        //《9.3. 敵機と自機・自弾の衝突
        for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
            if (enemya[i].v == 100) { //敵機が100%存在？
                if (player.v == 100 && isHit(enemya[i], player)) { //自機が100%存在し衝突？ 
                    GameOver(); //ゲームオーバーにする
                    enemya[i].v = 99; //敵機落下開始
                    enemya[i].i = meteoi; //破壊画像にする 
                    break; //抜ける
                }
                for (int j = 0; j < maxpb; j++) { //全自弾について繰返す
                    if (pba[j].v != 0 && isHit(enemya[i], pba[j])) { //自弾が存在し衝突？
                        enemya[i].v = 99; //敵機落下開始
                        enemya[i].i = meteoi; //破壊画像にする 
                        pba[j].v = 0;  //この自弾を消す
                        if (gamemode == 1) { //【追加】プレイ画面？
                            score += 10; //スコアアップ
                        }
                        break; //次の敵機へ進む
                    }
                }
            }
        }
        //《9. 4 敵弾と自機・自弾の衝突
        for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
            if (enemyba[i].v == 1) { //敵弾が存在？
                if (player.v == 100 && isHit(enemyba[i], player)) { //自機が存在し敵弾と衝突？ 
                    GameOver(); //ゲームオーバーにする
                    enemyba[i].v = 0; //敵弾を消す
                    break; //抜ける
                }
                for (int j = 0; j < maxpb; j++) { //全自弾について繰返す
                    if (pba[j].v != 0 && isHit(enemyba[i], pba[j])) { //自弾が存在し敵弾と衝突？
                        enemyba[i].v = 101; //この敵弾を爆散状態にする
                        pba[j].v = 0;  //この自弾を消す
                        if (gamemode == 1) { //【追加】プレイ画面？
                            score += 100; //スコアアップ
                        }
                        break; //次の敵弾へ進む
                    }
                }
            } else  if (enemyba[i].v >= 101) { //敵弾が爆散状態？
                for (int j = 0; j < maxenemy; j++) { //全敵機について繰返す
                    if (enemya[j].v == 100) { //敵機が100%存在？
                        double d1 = Math.Pow(enemyba[i].x - enemya[j].x, 2) + Math.Pow(enemyba[i].y - enemya[j].y, 2); //中心距離の2乗
                        double d2 = Math.Pow(enemyba[i].v - 100 + enemyi.Width / 2, 2); //爆散円と敵機半径の和の2乗
                        if (d1 < d2) {  //敵機が爆散円内？
                            enemya[j].v = 99; //敵機落下開始
                            enemya[j].i = meteoi; //破壊画像にする 
                            if (gamemode == 1) { //【追加】プレイ画面？
                                score += 10; //スコアアップ
                            }
                            break; //次の敵機へ進む
                        }
                    }
                }
                for (int j = 0; j < maxenemyb; j++) { //【以下追加】全敵弾について繰返す
                    if (enemyba[j].v == 1) { //敵弾が存在？
                        double d1 = Math.Pow(enemyba[i].x - enemyba[j].x, 2) + Math.Pow(enemyba[i].y - enemyba[j].y, 2); //中心距離の2乗
                        double d2 = Math.Pow(enemyba[i].v - 100 + enemybi.Width / 2, 2); //爆散円と敵弾半径の和の2乗
                        if (d1 < d2) {  //敵弾が爆散円内？
                            enemyba[j].v = 101; //敵弾爆散開始
                            if (gamemode == 1) { //プレイ画面？                            
                                score += 10; //スコアアップ
                            }
                            break; //次の敵弾へ進む
                        }
                    }
                }
            }
        }
        //《9.5 自弾発射
        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; //自弾発射待ち時間をゼロにして発射可能にする
            }
        }
        //《9.6 敵機出現
        if (waitenemy <= 0) { //敵機出現待ち時間がゼロ？
            for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
                if (enemya[i].v == 0) { //敵機が非表示？
                    enemya[i].v = 100; //表示100%にする
                    enemya[i].i = enemyi; //画像
                    enemya[i].x = enemyi.Width / 2 + rnd.Next(backi.Width - enemyi.Width); //X座標はランダム
                    enemya[i].y = -enemyi.Height; //Y座標は上端の直上
                    enemya[i].vv = enemyvv; //下移動速度
                    waitenemy = enemyint; //敵機出現待ち時間をセット
                    break; //1機出現できればOK
                }
            }
        }
        //《9.7 敵弾発射
        if (waitenemyb <= 0) { //敵弾出現待ち時間がゼロ？
            int enemyi = -1; //発射敵機
            for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
                if (enemya[i].v == 100 && enemya[i].y > 0 && enemya[i].y < backi.Height / 2) { //敵機が画面上半分にいる？
                    enemyi = i; //発射敵機決定
                    break;
                }
            }
            if (enemyi != -1) { //発射敵機決定済？
                for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
                    if (enemyba[i].v == 0) { //敵弾が非表示？
                        enemyba[i].v = 1; //表示にする
                        enemyba[i].i = enemybi; //画像
                        enemyba[i].x = enemya[enemyi].x; //X座標は発射敵機と同じ
                        enemyba[i].y = enemya[enemyi].y - 15; //Y座標は発射敵機の15ドット上
                        double rad = Math.Atan2(player.y - enemyba[i].y, player.x - enemyba[i].x); //角度を得る
                        enemyba[i].hv = (int)(Math.Cos(rad) * enemyvv * 1.5); //X移動量
                        enemyba[i].vv = (int)(Math.Sin(rad) * enemyvv * 1.5); //Y移動量
                        waitenemyb = enemybint; //敵弾発射待ち時間をセット
                        break; //1弾発射できればOK
                    }
                }
            }
        }
        //《9.8 自弾移動
        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; //自弾を消す
                }
            }
        }
        //《9.9 敵機移動
        for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
            if (enemya[i].v != 0) { //敵機が存在？
                if (enemya[i].v <= 99) { //落下中？ 
                    enemya[i].v--; //落下
                }
                enemya[i].y += enemya[i].vv; //下へ移動
                if (enemya[i].y - enemya[i].i.Height / 2 > backi.Height) { //画面下端より下に出たら
                    enemya[i].v = 0; //敵機を消す
                }
            }
        }
        //《9.10 敵弾移動・爆散
        for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
            if (enemyba[i].v == 1) { //敵弾が存在？
                enemyba[i].x += (int)enemyba[i].hv; //X座標を変更
                enemyba[i].y += (int)enemyba[i].vv; //Y座標を変更
                if (enemyba[i].x < -enemybi.Width / 2 ||
                    enemyba[i].x - enemybi.Width / 2 > backi.Width ||
                    enemyba[i].y < -enemybi.Height / 2 ||
                    enemyba[i].y - enemybi.Height / 2 > backi.Height) { //画面端より外に出たら
                    enemyba[i].v = 0; //敵弾を消す
                }
            } else if (enemyba[i].v >= 101) { //爆散中？
                enemyba[i].v = (enemyba[i].v <= 200) ? enemyba[i].v + 1 : 0; //200までカウントアップし超えたら0にする
            }
        }
        //《9.11 待ち時間カウント・倍率・敵機敵弾の出現間隔のダウン・画面再描画
        if (waitpb > 0) { //自弾発射待ち時間がセットされていたら
            waitpb--; //カウントダウンする
        }
        if (waitenemy > 0) { //敵機出現待ち時間がセットされていたら
            waitenemy--; //カウントダウンする
        }
        if (waitenemyb > 0) { //敵弾出現待ち時間がセットされていたら
            waitenemyb--; //カウントダウンする
        }
        if (gamemode == 9 && player.v > 0 && player.v < 100) {  //ゲームオーバーで自機縮小中？
            player.v--; //自機描画倍率ダウン
        }
        if (enemyint > 10) { //敵機の出現間隔が10超なら
           enemyint = 50 - score / 100; //スコア÷100を敵機の出現間隔から差し引く
        }
        if (enemybint > 15) { //【以下追加】敵弾の出現間隔が15超なら
            enemybint = 75 - score / 100; //スコア÷100を敵弾の出現間隔から差し引く
        }
        Invalidate(); //画面再描画を依頼
    }
    //《10. ゲームオーバーにする》
    private void GameOver() {
        player.v = 99; //自機の落下を開始
        gamemode = 9; //ゲームオーバーにする
        if (score > hiscore) { //ハイスコア更新？
            hiscore = score; //ハイスコア更新
            hiscoremsg = "HiSCORE Update !"; //メッセージ
        } else {
            hiscoremsg = String.Format("HiSCORE is {0:000,000}", hiscore); //スコア文字列を作る
        }
    }
    //《11. コンストラクタ》
    Program() {
        DoubleBuffered = true; //ダブルバッファリングを有効化
        KeyDown += new KeyEventHandler(OnKeyDown); //キー入力イベント登録
        timer.Tick += new EventHandler(Play); //タイマーイベント登録
        timer.Interval = 10; //タイマーインターバル(ミリ秒)
    }
    //《12. Main》
    public static void Main() {
        Program f = new Program(); //自分のオブジェクトを生成
        f.Size = new Size(660, 523); //フォームのサイズを設定
        f.Text = "Game"; //フォーム名を設定
        f.ControlBox = false; //コントロールボックスを非表示に
        f.FormBorderStyle = FormBorderStyle.Fixed3D; //サイズ変更を抑止
        Application.Run(f); //フォームを現出
    }
}

テーマ35 サウンド

・フォームアプリケーションで音を出力する方法は複数あるが、最もシンプルなのがSystem.Media.SoundPlayerクラスの利用
・利用には「using System.Media;」を記述しておくと良い
・SoundPlayerクラスのインスタンスを生成し、コンストラクタに音声ファイル名を渡すことで、再生準備が可能
・画像と異なり、このインスタンスのLoad()メソッドで音声データをメモリ上に展開しておく必要がある
　※大きな音声ファイルを用いる場合、Loadが完了する前に再生しないように、インスタンスのIsLoadCompletedプロパティ(bool型)を用いて完了待ちを行うことができる
・音を再生するには、インスタンスのPlay()メソッドを呼ぶ
・音声ファイルは画像ファイルなどと同じフォルダに置くと良い

演習45 効果音

・開始時とゲームオーバー時、爆散時と爆散円に敵弾が接触した再爆散時に効果音を鳴らそう（下記を利用可）
　maou_se_onepoint30.wav ：開始音
　maou_se_onepoint29.wav ：終了音
　maou_se_battle12.wav ：爆散音
　maou_se_battle18.wav ：再爆散音
　※効果音著作者：MaouDamashii(https://maou.audio/)

作成例

//演習45 効果音
using System; //汎用的に利用
using System.Windows.Forms; //フォームアプリケーションに必須
using System.Drawing; //Size、Image用
using System.Media; //【追加】SoundPlayer用
//《アイテムを表す構造体》
struct Item {
    public Image i; //画像
    public int x;  //中心X座標
    public int y;  //中心Y座標
    public int hv; //左右方向の速度(左向きは負の数、右向きは正の数)
    public int vv; //上下方向の速度(上向きは負の数、下向きは正の数)
    public int v;  //表示状態(0：非表示、1～100：表示、101～200：爆散)
}
//《ゲームを表すクラス》
class Program : Form { //Formクラスの派生クラス
    //《1. DLLインポートと外部定義指定》
    [System.Runtime.InteropServices.DllImport("user32.dll")] //DLLインポート
    private static extern short GetKeyState(int nVirtKey); //外部定義指定
    //《2. データメンバ》
    int gamemode = 0; //モード(0:タイトル画面,1:プレイ画面,9:終了画面)
    int score = 0; //スコア
    int hiscore = 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"); //敵機画像を読込む
    Image enemybi = Image.FromFile("ebullet.gif"); //敵弾画像を読込む
    static Image burni = Image.FromFile("burn.gif"); //爆散画像を読込む
    static Image meteoi = Image.FromFile("meteo3.gif"); //炎上画像を読込む
    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); //黄色のブラシ
    Pen burnp = new Pen(new TextureBrush(burni), 15); //爆散用テクスチャブラシによる太さ15のペン
    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; //敵機の出現間隔
    string hiscoremsg = "";  //ハイスコアメッセージ
    Random rnd = new Random(); //乱数用のRandomクラスのインスタンスを生成
    int enemybint = 75; //敵弾の出現間隔
    int waitenemyb = 200; //敵弾の出現待ち時間
    const int maxenemyb = 20; //敵弾の最大数
    Item[] enemyba = new Item[maxenemyb]; //敵弾の構造体オブジェクト配列
    const int enemyvv = 5; //敵機移動速度
    SoundPlayer begins = new SoundPlayer("maou_se_onepoint30.wav"); //【追加】開始音の準備
    SoundPlayer ends = new SoundPlayer("maou_se_onepoint29.wav"); //【追加】終了音の準備
    SoundPlayer mhits = new SoundPlayer("maou_se_battle12.wav"); //【追加】爆散音の準備
    SoundPlayer hits = new SoundPlayer("maou_se_battle18.wav"); //【追加】再爆散音の準備
    //《3. 初期化処理》各データをゲーム開始時の値にする
    void initData() {
        player.i = playeri;  //自機の画像
        player.x = 320; //自機の中心X座標
        player.y = 410; //自機の中心Y座標
        player.hv = 0;  //自機の左右方向の速度
        player.v = 100;  //自機を表示
        waitpb = 0; //自弾発射の待ち時間
        waitpb = 0; //自弾発射の待ち時間
        score = 0; //スコアクリア
        waitenemyb = 200; //敵弾の出現待ち時間
        enemyint = 50; //敵機の出現間隔
        enemybint = 75; //敵弾の出現間隔
    }
    //《4. 中央座標を用いる画像描画処理》
   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);
    }
    //《5. 中央座標を用いる画像描画処理(縮小率％付き)》
    private void DrawItem(PaintEventArgs e, Item it, int rate) {
        if (rate == 100) { //等倍？
            DrawItem(e, it); //通常描画する
        } else {
            int xx = it.x - it.i.Width * rate / 100 / 2; //左上X座標を得る
            int yy = it.y - it.i.Height * rate / 100 / 2; //左上Y座標を得る
            e.Graphics.DrawImage(it.i, xx, yy, it.i.Width * rate / 100, it.i.Height * rate / 100);
        }
    }
    //《6. 楕円形の衝突判定》
    private bool isHit(Item a, Item b) { //アイテムaとアイテムbが衝突しているかどうかを返す
        int dest = (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y); //距離２乗
        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); //距離２乗と内径の和の２乗を比較
    }
    //《7. 描画処理》オーバライド
    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, player.v); //自機を倍率描画
            }
            foreach (var pb in pba) { //全自弾について繰返す
                if (pb.v == 1) { //自弾がある？
                    DrawItem(e, pb); //自弾を描画
                }
            }
            foreach (var enemy in enemya) { //全敵機について繰返す
                if (enemy.v != 0) { //表示中？
                    DrawItem(e, enemy, enemy.v); //敵機を倍率描画
                }
            }
            foreach (var enemyb in enemyba) { //全敵弾について繰返す
                if (enemyb.v == 1) { //敵弾がある？
                    DrawItem(e, enemyb); //敵弾を描画
                } else if (enemyb.v >= 101) { //爆散中？
                    int rr = (enemyb.v - 100) * 2; //爆散円の直径(2→200)
                    e.Graphics.DrawEllipse(burnp, enemyb.x - (enemyb.v - 100), enemyb.y - (enemyb.v - 100), rr, rr); //爆散円を描く
                }
            }
        }
        if (gamemode == 9) { //ゲームオーバー画面？
            e.Graphics.DrawString("GAME OVER", fontm, brushs, 210, 150); //メッセージ表示
            e.Graphics.DrawString(hiscoremsg, fontm, brushs, 150, 200); //ハイスコアメッセージ表示
            e.Graphics.DrawString("Hit Enter Key", fontm, brushs, 200, 300); //メッセージ表示
        }
    }
    //《8. キー入力時処理》
    void OnKeyDown(object o, KeyEventArgs e) {
        if (e.KeyCode.ToString() == "Escape") { //Escキーが押されていたら
            Close(); //フォーム終了
        }
        //タイトル画面でEnterキーが押されていたら
        if (gamemode == 0 && e.KeyCode.ToString() == "Return") {
            begins.Play(); //【追加】開始音を出力
            initData(); //各データをゲーム開始時の値にする
            gamemode = 1; //プレイ動画に遷移
            timer.Start(); //タイマー開始                  
        }
        //ゲームオーバー画面でEnterキーが押されていたら
        else if (gamemode == 9 && e.KeyCode.ToString() == "Return") {
            begins.Play(); //【追加】開始音を出力
            initData(); //各データをゲーム開始時の値にする
            for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
                enemya[i].v = 0; //無しにする
            }
            for (int i = 0; i < maxpb; i++) { //全自弾について繰返す
                pba[i].v = 0; //無しにする
            }
            for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
                enemyba[i].v = 0; //無しにする
            }
            gamemode = 1; //プレイ動画に遷移
        }
        Invalidate(); //画面再描画を依頼
    }
    //《9. タイマーイベント処理》
    void Play(object o, EventArgs e) {
        //《9.1. 背景画面のスクロール
        backy = (backy + 1) % backi.Height; //1枚目の背景描画開始Y座標を下げる
        //《9.2. 自機の向きを決める
        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; //右向き
            }
        }
        //《9.3. 敵機と自機・自弾の衝突
        for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
            if (enemya[i].v == 100) { //敵機が100%存在？
                if (player.v == 100 && isHit(enemya[i], player)) { //自機が100%存在し衝突？ 
                    GameOver(); //ゲームオーバーにする
                    enemya[i].v = 99; //敵機落下開始
                    enemya[i].i = meteoi; //破壊画像にする 
                    break; //抜ける
                }
                for (int j = 0; j < maxpb; j++) { //全自弾について繰返す
                    if (pba[j].v != 0 && isHit(enemya[i], pba[j])) { //自弾が存在し衝突？
                        enemya[i].v = 99; //敵機落下開始
                        enemya[i].i = meteoi; //破壊画像にする 
                        pba[j].v = 0;  //この自弾を消す
                        if (gamemode == 1) { //プレイ画面？
//                         hits.Play(); //【追加】命中音を出力
                            score += 10; //スコアアップ
                        }
                        break; //次の敵機へ進む
                    }
                }
            }
        }
        //《9. 4 敵弾と自機・自弾の衝突
        for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
            if (enemyba[i].v == 1) { //敵弾が存在？
                if (player.v == 100 && isHit(enemyba[i], player)) { //自機が存在し敵弾と衝突？ 
                    GameOver(); //ゲームオーバーにする
                    enemyba[i].v = 0; //敵弾を消す
                    break; //抜ける
                }
                for (int j = 0; j < maxpb; j++) { //全自弾について繰返す
                    if (pba[j].v != 0 && isHit(enemyba[i], pba[j])) { //自弾が存在し敵弾と衝突？
                        enemyba[i].v = 101; //この敵弾を爆散状態にする
                        pba[j].v = 0;  //この自弾を消す
                        if (gamemode == 1) { //プレイ画面？
                            mhits.Play(); //【追加】命中音を出力
                            score += 100; //スコアアップ
                        }
                        break; //次の敵弾へ進む
                    }
                }
            } else  if (enemyba[i].v >= 101) { //敵弾が爆散状態？
                for (int j = 0; j < maxenemy; j++) { //全敵機について繰返す
                    if (enemya[j].v == 100) { //敵機が100%存在？
                        double d1 = Math.Pow(enemyba[i].x - enemya[j].x, 2) + Math.Pow(enemyba[i].y - enemya[j].y, 2); //中心距離の2乗
                        double d2 = Math.Pow(enemyba[i].v - 100 + enemyi.Width / 2, 2); //爆散円と敵機半径の和の2乗
                        if (d1 < d2) {  //敵機が爆散円内？
                            enemya[j].v = 99; //敵機落下開始
                            enemya[j].i = meteoi; //破壊画像にする 
                            if (gamemode == 1) { //プレイ画面？
                                score += 10; //スコアアップ
                            }
                            break; //次の敵機へ進む
                        }
                    }
                }
                for (int j = 0; j < maxenemyb; j++) { //全敵弾について繰返す
                    if (enemyba[j].v == 1) { //敵弾が存在？
                        double d1 = Math.Pow(enemyba[i].x - enemyba[j].x, 2) + Math.Pow(enemyba[i].y - enemyba[j].y, 2); //中心距離の2乗
                        double d2 = Math.Pow(enemyba[i].v - 100 + enemybi.Width / 2, 2); //爆散円と敵弾半径の和の2乗
                        if (d1 < d2) {  //敵弾が爆散円内？
                            enemyba[j].v = 101; //敵弾爆散開始
                            if (gamemode == 1) { //プレイ画面？
                                hits.Play(); //【追加】命中音を出力
                                score += 10; //スコアアップ
                            }
                            break; //次の敵弾へ進む
                        }
                    }
                }
            }
        }
        //《9.5 自弾発射
        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; //自弾発射待ち時間をゼロにして発射可能にする
            }
        }
        //《9.6 敵機出現
        if (waitenemy <= 0) { //敵機出現待ち時間がゼロ？
            for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
                if (enemya[i].v == 0) { //敵機が非表示？
                    enemya[i].v = 100; //表示100%にする
                    enemya[i].i = enemyi; //画像
                    enemya[i].x = enemyi.Width / 2 + rnd.Next(backi.Width - enemyi.Width); //X座標はランダム
                    enemya[i].y = -enemyi.Height; //Y座標は上端の直上
                    enemya[i].vv = enemyvv; //下移動速度
                    waitenemy = enemyint; //敵機出現待ち時間をセット
                    break; //1機出現できればOK
                }
            }
        }
        //《9.7 敵弾発射
        if (waitenemyb <= 0) { //敵弾出現待ち時間がゼロ？
            int enemyi = -1; //発射敵機
            for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
                if (enemya[i].v == 100 && enemya[i].y > 0 && enemya[i].y < backi.Height / 2) { //敵機が画面上半分にいる？
                    enemyi = i; //発射敵機決定
                    break;
                }
            }
            if (enemyi != -1) { //発射敵機決定済？
                for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
                    if (enemyba[i].v == 0) { //敵弾が非表示？
                        enemyba[i].v = 1; //表示にする
                        enemyba[i].i = enemybi; //画像
                        enemyba[i].x = enemya[enemyi].x; //X座標は発射敵機と同じ
                        enemyba[i].y = enemya[enemyi].y - 15; //Y座標は発射敵機の15ドット上
                        double rad = Math.Atan2(player.y - enemyba[i].y, player.x - enemyba[i].x); //角度を得る
                        enemyba[i].hv = (int)(Math.Cos(rad) * enemyvv * 1.5); //X移動量
                        enemyba[i].vv = (int)(Math.Sin(rad) * enemyvv * 1.5); //Y移動量
                        waitenemyb = enemybint; //敵弾発射待ち時間をセット
                        break; //1弾発射できればOK
                    }
                }
            }
        }
        //《9.8 自弾移動
        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; //自弾を消す
                }
            }
        }
        //《9.9 敵機移動
        for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
            if (enemya[i].v != 0) { //敵機が存在？
                if (enemya[i].v <= 99) { //落下中？ 
                    enemya[i].v--; //落下
                }
                enemya[i].y += enemya[i].vv; //下へ移動
                if (enemya[i].y - enemya[i].i.Height / 2 > backi.Height) { //画面下端より下に出たら
                    enemya[i].v = 0; //敵機を消す
                }
            }
        }
        //《9.10 敵弾移動・爆散
        for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
            if (enemyba[i].v == 1) { //敵弾が存在？
                enemyba[i].x += (int)enemyba[i].hv; //X座標を変更
                enemyba[i].y += (int)enemyba[i].vv; //Y座標を変更
                if (enemyba[i].x < -enemybi.Width / 2 ||
                    enemyba[i].x - enemybi.Width / 2 > backi.Width ||
                    enemyba[i].y < -enemybi.Height / 2 ||
                    enemyba[i].y - enemybi.Height / 2 > backi.Height) { //画面端より外に出たら
                    enemyba[i].v = 0; //敵弾を消す
                }
            } else if (enemyba[i].v >= 101) { //爆散中？
                enemyba[i].v = (enemyba[i].v <= 200) ? enemyba[i].v + 1 : 0; //200までカウントアップし超えたら0にする
            }
        }
        //《9.11 待ち時間カウント・倍率・敵機敵弾の出現間隔のダウン・画面再描画
        if (waitpb > 0) { //自弾発射待ち時間がセットされていたら
            waitpb--; //カウントダウンする
        }
        if (waitenemy > 0) { //敵機出現待ち時間がセットされていたら
            waitenemy--; //カウントダウンする
        }
        if (waitenemyb > 0) { //敵弾出現待ち時間がセットされていたら
            waitenemyb--; //カウントダウンする
        }
        if (gamemode == 9 && player.v > 0 && player.v < 100) {  //ゲームオーバーで自機縮小中？
            player.v--; //自機描画倍率ダウン
        }
        if (enemyint > 10) { //敵機の出現間隔が10超なら
           enemyint = 50 - score / 100; //スコア÷100を敵機の出現間隔から差し引く
        }
        if (enemybint > 15) { //敵弾の出現間隔が15超なら
            enemybint = 75 - score / 100; //スコア÷100を敵弾の出現間隔から差し引く
        }
        Invalidate(); //画面再描画を依頼
    }
    //《10. ゲームオーバーにする》
    private void GameOver() {
        player.v = 99; //自機の落下を開始
        gamemode = 9; //ゲームオーバーにする
        ends.Play(); //【追加】終了音を出力
        if (score > hiscore) { //ハイスコア更新？
            hiscore = score; //ハイスコア更新
            hiscoremsg = "HiSCORE Update !"; //メッセージ
        } else {
            hiscoremsg = String.Format("HiSCORE is {0:000,000}", hiscore); //スコア文字列を作る
        }
    }
    //《11. コンストラクタ》
    Program() {
        DoubleBuffered = true; //ダブルバッファリングを有効化
        KeyDown += new KeyEventHandler(OnKeyDown); //キー入力イベント登録
        timer.Tick += new EventHandler(Play); //タイマーイベント登録
        timer.Interval = 10; //タイマーインターバル(ミリ秒)
        begins.Load();   //【追加】開始音のロード
        ends.Load();  //【追加】終了音のロード
        mhits.Load();  //【追加】爆散音のロード
        hits.Load();  //【追加】再爆散音のロード
    }
    //《12. Main》
    public static void Main() {
        Program f = new Program(); //自分のオブジェクトを生成
        f.Size = new Size(660, 523); //フォームのサイズを設定
        f.Text = "Game"; //フォーム名を設定
        f.ControlBox = false; //コントロールボックスを非表示に
        f.FormBorderStyle = FormBorderStyle.Fixed3D; //サイズ変更を抑止
        Application.Run(f); //フォームを現出
    }
}

最終版

・フォームのサイズはMainでは指定せず、コンストラクタにおいてFormクラスのClientSizeプロパティを用いて
　「ClientSize = new Size(640, 480);」と描画領域のドット数を直接すれば良い
・これによりサイズの調整や「f.Size = new Size(660, 523); //フォームのサイズを設定」は不要になる。

作成例

//シューティングゲーム開発演習 最終版
using System; //汎用的に利用
using System.Windows.Forms; //フォームアプリケーションに必須
using System.Drawing; //Size、Image用
using System.Media; //SoundPlayer用
//《アイテムを表す構造体》
struct Item {
    public Image i; //画像
    public int x;  //中心X座標
    public int y;  //中心Y座標
    public int hv; //左右方向の速度(左向きは負の数、右向きは正の数)
    public int vv; //上下方向の速度(上向きは負の数、下向きは正の数)
    public int v;  //表示状態(0：非表示、1～100：表示、101～200：爆散)
}
//《ゲームを表すクラス》
class Program : Form { //Formクラスの派生クラス
    //《1. DLLインポートと外部定義指定》
    [System.Runtime.InteropServices.DllImport("user32.dll")] //DLLインポート
    private static extern short GetKeyState(int nVirtKey); //外部定義指定
    //《2. データメンバ》
    int gamemode = 0; //モード(0:タイトル画面,1:プレイ画面,9:終了画面)
    int score = 0; //スコア
    int hiscore = 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"); //敵機画像を読込む
    Image enemybi = Image.FromFile("ebullet.gif"); //敵弾画像を読込む
    static Image burni = Image.FromFile("burn.gif"); //爆散画像を読込む
    static Image meteoi = Image.FromFile("meteo3.gif"); //炎上画像を読込む
    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); //黄色のブラシ
    Pen burnp = new Pen(new TextureBrush(burni), 15); //爆散用テクスチャブラシによる太さ15のペン
    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; //敵機の出現間隔
    string hiscoremsg = "";  //ハイスコアメッセージ
    Random rnd = new Random(); //乱数用のRandomクラスのインスタンスを生成
    int enemybint = 75; //敵弾の出現間隔
    int waitenemyb = 200; //敵弾の出現待ち時間
    const int maxenemyb = 20; //敵弾の最大数
    Item[] enemyba = new Item[maxenemyb]; //敵弾の構造体オブジェクト配列
    const int enemyvv = 5; //敵機移動速度
    SoundPlayer begins = new SoundPlayer("maou_se_onepoint30.wav"); //開始音の準備
    SoundPlayer ends = new SoundPlayer("maou_se_onepoint29.wav"); //終了音の準備
    SoundPlayer mhits = new SoundPlayer("maou_se_battle12.wav"); //爆散音の準備
    SoundPlayer hits = new SoundPlayer("maou_se_battle18.wav"); //再爆散音の準備
    //《3. 初期化処理》各データをゲーム開始時の値にする
    void initData() {
        player.i = playeri;  //自機の画像
        player.x = 320; //自機の中心X座標
        player.y = 410; //自機の中心Y座標
        player.hv = 0;  //自機の左右方向の速度
        player.v = 100;  //自機を表示
        waitpb = 0; //自弾発射の待ち時間
        waitpb = 0; //自弾発射の待ち時間
        score = 0; //スコアクリア
        waitenemyb = 200; //敵弾の出現待ち時間
        enemyint = 50; //敵機の出現間隔
        enemybint = 75; //敵弾の出現間隔
    }
    //《4. 中央座標を用いる画像描画処理》
   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);
    }
    //《5. 中央座標を用いる画像描画処理(縮小率％付き)》
    private void DrawItem(PaintEventArgs e, Item it, int rate) {
        if (rate == 100) { //等倍？
            DrawItem(e, it); //通常描画する
        } else {
            int xx = it.x - it.i.Width * rate / 100 / 2; //左上X座標を得る
            int yy = it.y - it.i.Height * rate / 100 / 2; //左上Y座標を得る
            e.Graphics.DrawImage(it.i, xx, yy, it.i.Width * rate / 100, it.i.Height * rate / 100);
        }
    }
    //《6. 楕円形の衝突判定》
    private bool isHit(Item a, Item b) { //アイテムaとアイテムbが衝突しているかどうかを返す
        int dest = (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y); //距離２乗
        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); //距離２乗と内径の和の２乗を比較
    }
    //《7. 描画処理》オーバライド
    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, player.v); //自機を倍率描画
            }
            foreach (var pb in pba) { //全自弾について繰返す
                if (pb.v == 1) { //自弾がある？
                    DrawItem(e, pb); //自弾を描画
                }
            }
            foreach (var enemy in enemya) { //全敵機について繰返す
                if (enemy.v != 0) { //表示中？
                    DrawItem(e, enemy, enemy.v); //敵機を倍率描画
                }
            }
            foreach (var enemyb in enemyba) { //全敵弾について繰返す
                if (enemyb.v == 1) { //敵弾がある？
                    DrawItem(e, enemyb); //敵弾を描画
                } else if (enemyb.v >= 101) { //爆散中？
                    int rr = (enemyb.v - 100) * 2; //爆散円の直径(2→200)
                    e.Graphics.DrawEllipse(burnp, enemyb.x - (enemyb.v - 100), enemyb.y - (enemyb.v - 100), rr, rr); //爆散円を描く
                }
            }
        }
        if (gamemode == 9) { //ゲームオーバー画面？
            e.Graphics.DrawString("GAME OVER", fontm, brushs, 210, 150); //メッセージ表示
            e.Graphics.DrawString(hiscoremsg, fontm, brushs, 150, 200); //ハイスコアメッセージ表示
            e.Graphics.DrawString("Hit Enter Key", fontm, brushs, 200, 300); //メッセージ表示
        }
    }
    //《8. キー入力時処理》
    void OnKeyDown(object o, KeyEventArgs e) {
        if (e.KeyCode.ToString() == "Escape") { //Escキーが押されていたら
            Close(); //フォーム終了
        }
        //タイトル画面でEnterキーが押されていたら
        if (gamemode == 0 && e.KeyCode.ToString() == "Return") {
            begins.Play(); //開始音を出力
            initData(); //各データをゲーム開始時の値にする
            gamemode = 1; //プレイ動画に遷移
            timer.Start(); //タイマー開始                  
        }
        //ゲームオーバー画面でEnterキーが押されていたら
        else if (gamemode == 9 && e.KeyCode.ToString() == "Return") {
            begins.Play(); //開始音を出力
            initData(); //各データをゲーム開始時の値にする
            for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
                enemya[i].v = 0; //無しにする
            }
            for (int i = 0; i < maxpb; i++) { //全自弾について繰返す
                pba[i].v = 0; //無しにする
            }
            for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
                enemyba[i].v = 0; //無しにする
            }
            gamemode = 1; //プレイ動画に遷移
        }
        Invalidate(); //画面再描画を依頼
    }
    //《9. タイマーイベント処理》
    void Play(object o, EventArgs e) {
        //《9.1. 背景画面のスクロール
        backy = (backy + 1) % backi.Height; //1枚目の背景描画開始Y座標を下げる
        //《9.2. 自機の向きを決める
        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; //右向き
            }
        }
        //《9.3. 敵機と自機・自弾の衝突
        for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
            if (enemya[i].v == 100) { //敵機が100%存在？
                if (player.v == 100 && isHit(enemya[i], player)) { //自機が100%存在し衝突？ 
                    GameOver(); //ゲームオーバーにする
                    enemya[i].v = 99; //敵機落下開始
                    enemya[i].i = meteoi; //破壊画像にする 
                    break; //抜ける
                }
                for (int j = 0; j < maxpb; j++) { //全自弾について繰返す
                    if (pba[j].v != 0 && isHit(enemya[i], pba[j])) { //自弾が存在し衝突？
                        enemya[i].v = 99; //敵機落下開始
                        enemya[i].i = meteoi; //破壊画像にする 
                        pba[j].v = 0;  //この自弾を消す
                        if (gamemode == 1) { //プレイ画面？
                            score += 10; //スコアアップ
                        }
                        break; //次の敵機へ進む
                    }
                }
            }
        }
        //《9. 4 敵弾と自機・自弾の衝突
        for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
            if (enemyba[i].v == 1) { //敵弾が存在？
                if (player.v == 100 && isHit(enemyba[i], player)) { //自機が存在し敵弾と衝突？ 
                    GameOver(); //ゲームオーバーにする
                    enemyba[i].v = 0; //敵弾を消す
                    break; //抜ける
                }
                for (int j = 0; j < maxpb; j++) { //全自弾について繰返す
                    if (pba[j].v != 0 && isHit(enemyba[i], pba[j])) { //自弾が存在し敵弾と衝突？
                        enemyba[i].v = 101; //この敵弾を爆散状態にする
                        pba[j].v = 0;  //この自弾を消す
                        if (gamemode == 1) { //プレイ画面？
                            mhits.Play(); //命中音を出力
                            score += 100; //スコアアップ
                        }
                        break; //次の敵弾へ進む
                    }
                }
            } else  if (enemyba[i].v >= 101) { //敵弾が爆散状態？
                for (int j = 0; j < maxenemy; j++) { //全敵機について繰返す
                    if (enemya[j].v == 100) { //敵機が100%存在？
                        double d1 = Math.Pow(enemyba[i].x - enemya[j].x, 2) + Math.Pow(enemyba[i].y - enemya[j].y, 2); //中心距離の2乗
                        double d2 = Math.Pow(enemyba[i].v - 100 + enemyi.Width / 2, 2); //爆散円と敵機半径の和の2乗
                        if (d1 < d2) {  //敵機が爆散円内？
                            enemya[j].v = 99; //敵機落下開始
                            enemya[j].i = meteoi; //破壊画像にする 
                            if (gamemode == 1) { //プレイ画面？
                                score += 10; //スコアアップ
                            }
                            break; //次の敵機へ進む
                        }
                    }
                }
                for (int j = 0; j < maxenemyb; j++) { //全敵弾について繰返す
                    if (enemyba[j].v == 1) { //敵弾が存在？
                        double d1 = Math.Pow(enemyba[i].x - enemyba[j].x, 2) + Math.Pow(enemyba[i].y - enemyba[j].y, 2); //中心距離の2乗
                        double d2 = Math.Pow(enemyba[i].v - 100 + enemybi.Width / 2, 2); //爆散円と敵弾半径の和の2乗
                        if (d1 < d2) {  //敵弾が爆散円内？
                            enemyba[j].v = 101; //敵弾爆散開始
                            if (gamemode == 1) { //プレイ画面？
                                hits.Play(); //命中音を出力
                                score += 10; //スコアアップ
                            }
                            break; //次の敵弾へ進む
                        }
                    }
                }
            }
        }
        //《9.5 自弾発射
        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; //自弾発射待ち時間をゼロにして発射可能にする
            }
        }
        //《9.6 敵機出現
        if (waitenemy <= 0) { //敵機出現待ち時間がゼロ？
            for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
                if (enemya[i].v == 0) { //敵機が非表示？
                    enemya[i].v = 100; //表示100%にする
                    enemya[i].i = enemyi; //画像
                    enemya[i].x = enemyi.Width / 2 + rnd.Next(backi.Width - enemyi.Width); //X座標はランダム
                    enemya[i].y = -enemyi.Height; //Y座標は上端の直上
                    enemya[i].vv = enemyvv; //下移動速度
                    waitenemy = enemyint; //敵機出現待ち時間をセット
                    break; //1機出現できればOK
                }
            }
        }
        //《9.7 敵弾発射
        if (waitenemyb <= 0) { //敵弾出現待ち時間がゼロ？
            int enemyi = -1; //発射敵機
            for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
                if (enemya[i].v == 100 && enemya[i].y > 0 && enemya[i].y < backi.Height / 2) { //敵機が画面上半分にいる？
                    enemyi = i; //発射敵機決定
                    break;
                }
            }
            if (enemyi != -1) { //発射敵機決定済？
                for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
                    if (enemyba[i].v == 0) { //敵弾が非表示？
                        enemyba[i].v = 1; //表示にする
                        enemyba[i].i = enemybi; //画像
                        enemyba[i].x = enemya[enemyi].x; //X座標は発射敵機と同じ
                        enemyba[i].y = enemya[enemyi].y - 15; //Y座標は発射敵機の15ドット上
                        double rad = Math.Atan2(player.y - enemyba[i].y, player.x - enemyba[i].x); //角度を得る
                        enemyba[i].hv = (int)(Math.Cos(rad) * enemyvv * 1.5); //X移動量
                        enemyba[i].vv = (int)(Math.Sin(rad) * enemyvv * 1.5); //Y移動量
                        waitenemyb = enemybint; //敵弾発射待ち時間をセット
                        break; //1弾発射できればOK
                    }
                }
            }
        }
        //《9.8 自弾移動
        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; //自弾を消す
                }
            }
        }
        //《9.9 敵機移動
        for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
            if (enemya[i].v != 0) { //敵機が存在？
                if (enemya[i].v <= 99) { //落下中？ 
                    enemya[i].v--; //落下
                }
                enemya[i].y += enemya[i].vv; //下へ移動
                if (enemya[i].y - enemya[i].i.Height / 2 > backi.Height) { //画面下端より下に出たら
                    enemya[i].v = 0; //敵機を消す
                }
            }
        }
        //《9.10 敵弾移動・爆散
        for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
            if (enemyba[i].v == 1) { //敵弾が存在？
                enemyba[i].x += (int)enemyba[i].hv; //X座標を変更
                enemyba[i].y += (int)enemyba[i].vv; //Y座標を変更
                if (enemyba[i].x < -enemybi.Width / 2 ||
                    enemyba[i].x - enemybi.Width / 2 > backi.Width ||
                    enemyba[i].y < -enemybi.Height / 2 ||
                    enemyba[i].y - enemybi.Height / 2 > backi.Height) { //画面端より外に出たら
                    enemyba[i].v = 0; //敵弾を消す
                }
            } else if (enemyba[i].v >= 101) { //爆散中？
                enemyba[i].v = (enemyba[i].v <= 200) ? enemyba[i].v + 1 : 0; //200までカウントアップし超えたら0にする
            }
        }
        //《9.11 待ち時間カウント・倍率・敵機敵弾の出現間隔のダウン・画面再描画
        if (waitpb > 0) { //自弾発射待ち時間がセットされていたら
            waitpb--; //カウントダウンする
        }
        if (waitenemy > 0) { //敵機出現待ち時間がセットされていたら
            waitenemy--; //カウントダウンする
        }
        if (waitenemyb > 0) { //敵弾出現待ち時間がセットされていたら
            waitenemyb--; //カウントダウンする
        }
        if (gamemode == 9 && player.v > 0 && player.v < 100) {  //ゲームオーバーで自機縮小中？
            player.v--; //自機描画倍率ダウン
        }
        if (enemyint > 10) { //敵機の出現間隔が10超なら
           enemyint = 50 - score / 100; //スコア÷100を敵機の出現間隔から差し引く
        }
        if (enemybint > 15) { //敵弾の出現間隔が15超なら
            enemybint = 75 - score / 100; //スコア÷100を敵弾の出現間隔から差し引く
        }
        Invalidate(); //画面再描画を依頼
    }
    //《10. ゲームオーバーにする》
    private void GameOver() {
        player.v = 99; //自機の落下を開始
        gamemode = 9; //ゲームオーバーにする
        ends.Play(); //終了音を出力
        if (score > hiscore) { //ハイスコア更新？
            hiscore = score; //ハイスコア更新
            hiscoremsg = "HiSCORE Update !"; //メッセージ
        } else {
            hiscoremsg = String.Format("HiSCORE is {0:000,000}", hiscore); //スコア文字列を作る
        }
    }
    //《11. コンストラクタ》
    Program() {
        ClientSize = new Size(640, 480); //ゲーム用描画エリアの大きさを指定
        DoubleBuffered = true; //ダブルバッファリングを有効化
        KeyDown += new KeyEventHandler(OnKeyDown); //キー入力イベント登録
        timer.Tick += new EventHandler(Play); //タイマーイベント登録
        timer.Interval = 10; //タイマーインターバル(ミリ秒)
        begins.Load();   //開始音のロード
        ends.Load();  //終了音のロード
        mhits.Load();  //爆散音のロード
        hits.Load();  //再爆散音のロード
    }
    //《12. Main》
    public static void Main() {
        Program f = new Program(); //自分のオブジェクトを生成
        f.Text = "Game"; //フォーム名を設定
        f.ControlBox = false; //コントロールボックスを非表示に
        f.FormBorderStyle = FormBorderStyle.Fixed3D; //サイズ変更を抑止
        Application.Run(f); //フォームを現出
    }
}
