講義メモ ゲーム開発編 続き

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

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

手順と仕様

① 2.データメンバ:敵弾の出現間隔を定数から変数に変更
② 3.初期化処理:敵弾の出現間隔を75に戻す処理を追加
③ 9.3.敵機と自機・自弾の衝突処理:スコアアップはプレイ画面中のみにする
④ 9.4.敵弾と自機・自弾、爆散中敵弾と敵機の衝突処理:同上
⑤ 9.4.敵弾と自機・自弾、爆散中敵弾と敵機の衝突処理:
  敵弾と爆散円の中央距離が(爆散円の半径+敵弾幅÷2)未満なら、敵弾が爆散円に入ったとして、
  その敵弾を爆散にし、プレイ画面中ならスコア10点加算
⑥ 9.11.カウントダウンと画面再描画:敵弾発射間隔が15超なら、スコア÷100を差し引く

作成例

//演習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"); //敵弾画像を読込む
    Image meteoi = Image.FromFile("meteo3.gif"); //破壊画像を読込む
    static Image burni = Image.FromFile("burn.gif"); //炎画像を読込む
    Pen burnp = new Pen(new TextureBrush(burni), 15); //炎のテクスチャペン
    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クラスのインスタンスを生成
    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;  //自機を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.楕円形の衝突判定(アイテムaとアイテムbが衝突しているかどうかを返す)
    private bool isHit(Item a, Item b) {
        int dest = (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y); //距離2乗
        double ra = (a.i.Width < a.i.Height) ? a.i.Width / 2.0 : a.i.Height / 2.0; //内径a
        double rb = (b.i.Width < b.i.Height) ? b.i.Width / 2.0 : b.i.Height / 2.0; //内径b
        return dest < (ra + rb) * (ra + rb); //距離2乗と内径の和の2乗を比較
    }
    //《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; //直径を計算
                    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%存在?
                        //敵機と爆散円の中央の距離の2乗
                        double d1 = Math.Pow(enemya[j].x - enemyba[i].x, 2)
                            + Math.Pow(enemya[j].y - enemyba[i].y, 2);
                        //敵機半径と爆散円の半径の和の2乗
                        double d2 = Math.Pow(enemyi.Width / 2 + enemyba[i].v - 100, 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) { //敵弾が存在?
                        //敵弾と爆散円の中央の距離の2乗
                        double d1 = Math.Pow(enemyba[j].x - enemyba[i].x, 2)
                            + Math.Pow(enemyba[j].y - enemyba[i].y, 2);
                        //敵弾半径と爆散円の半径の和の2乗
                        double d2 = Math.Pow(enemybi.Width / 2 + enemyba[i].v - 100, 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);
                    enemya[i].y = -enemyi.Height; //Y座標は上端の直上
                    enemya[i].vv = enemyvv; //下移動速度
                    waitenemy = enemyint; //敵機出現待ち時間をセット
                    break; //1機出現できればOK
                }
            }
        }
        //《9.7.敵弾発射
        if (waitenemyb <= 0) { //敵弾出現待ち時間がゼロ?
            int enemyi = -1; //発射敵機を-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座標は発射敵機中央の上
                        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 += enemyba[i].hv; //X移動量をX座標に加算
                enemyba[i].y += enemyba[i].vv; //Y移動量を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までカウントアップ
            }
        }
        //《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) { //敵機出現間隔が下限より大きければ
            enemyint = 50 - score / 100; //スコア÷100だけ出現間隔を短縮
        }
        if (enemybint > 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, 520); //フォームのサイズを設定
        f.Text = "Game"; //フォーム名を設定
        f.ControlBox = false; //コントロールボックスを非表示に
        f.FormBorderStyle = FormBorderStyle.Fixed3D; //サイズ変更を抑止
        Application.Run(f); //フォームを現出
    }
}

テーマ35 サウンド

・フォームアプリケーションで音を出力する方法は複数ある
 ① Console.Beep静的メソッドに周波数と時間を与えてブザー音を鳴らす
   例: Console.Beep(800, 500); //800MHzの音を500ミリ秒
 ② System.Media.SoundPlayerクラスを用いてWAVファイルを再生する(詳細は後述)
 ③ NAudioライブラリを利用する(複雑な処理が可能だが、追加インストールが必要)
・今回は②で効果音を追加する
・利用には「using System.Media;」を記述すると便利
・SoundPlayerクラスのインスタンスを生成し、コンストラクタにWAVファイル名を渡すことで、その参照変数による
 再生が可能になる
・書式: SoundPlayer 参照変数 = new SoundPlayer(WAVファイル名);
  例: SoundPlayer sound1 = new SoundPlayer("mysound1.wav");
・このままでも再生できることが多いが、再生準備をさせるために、SoundPlayerインスタンスのLoadメソッドで
 メモリロードを明示しておくと良い
・書式: 参照変数.Load();
・サイズの大きなWAVファイルを用いる場合は、メモリロードが完了する前に再生できないことがあるので、
 bool IsLoadCompletedプロパティでロード待ちを行うことができる
・音を再生するには、SoundPlayerインスタンスのPlayメソッドを用いる
・WAVファイルは画像ファイルなどと同じフォルダ(開発中はdebugフォルダ)に置くと良い

演習45 効果音

・ゲーム開始のEnterキー入力時、ゲームオーバー時、爆散時、爆散円の誘爆時に効果音を鳴らそう
・下記を右クリックし「名前をつけてリンク先保存」でDebugフォルダにダウンロードする。
 開始音
 終了音
 爆散音
 誘爆音
 ※効果音著作者:MaouDamashii(https://maou.audio/)

手順と仕様

① 冒頭:「using System.Media;」を記述
② 2.データメンバ:4つの効果音のインスタンスを生成
③ 11.コンストラクタ:4つの効果音をロード
④ 8.キー入力時処理:タイトル画面でEnterキーが押されたら開始音を再生。
⑤ 8.キー入力時処理:ゲームオーバー画面でEnterキーが押されたら開始音を再生。
⑥ 9.4.敵弾と自機・自弾、爆散中敵弾と敵機の衝突処理:敵弾と自弾が衝突したら爆散音を再生。
⑦ 9.4.敵弾と自機・自弾、爆散中敵弾と敵機の衝突処理:敵弾と爆散円が衝突したら誘爆音を再生。
⑧ 10.ゲームオーバー処理:終了音を再生。

作成例

//演習45 効果音
using System; //汎用的に利用
using System.Windows.Forms; //フォームアプリケーションに必須
using System.Drawing; //Size、Image用
using System.Media; //【追加】SoundPlayer用
//《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"); //敵弾画像を読込む
    Image meteoi = Image.FromFile("meteo3.gif"); //破壊画像を読込む
    static Image burni = Image.FromFile("burn.gif"); //炎画像を読込む
    Pen burnp = new Pen(new TextureBrush(burni), 15); //炎のテクスチャペン
    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クラスのインスタンスを生成
    int enemybint = 75; //敵弾の出現間隔
    int waitenemyb = 200; //敵弾の出現待ち時間
    const int maxenemyb = 20; //敵弾の最大数
    Item[] enemyba = new Item[maxenemyb]; //敵弾の構造体配列
    const int enemyvv = 5; //敵機移動速度
    SoundPlayer starts = new SoundPlayer("maou_se_onepoint30.wav"); //【追加】開始音
    SoundPlayer ends = new SoundPlayer("maou_se_onepoint29.wav"); //【追加】終了音
    SoundPlayer hits = new SoundPlayer("maou_se_battle12.wav"); //【追加】爆散音
    SoundPlayer rhits = 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;  //自機を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.楕円形の衝突判定(アイテムaとアイテムbが衝突しているかどうかを返す)
    private bool isHit(Item a, Item b) {
        int dest = (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y); //距離2乗
        double ra = (a.i.Width < a.i.Height) ? a.i.Width / 2.0 : a.i.Height / 2.0; //内径a
        double rb = (b.i.Width < b.i.Height) ? b.i.Width / 2.0 : b.i.Height / 2.0; //内径b
        return dest < (ra + rb) * (ra + rb); //距離2乗と内径の和の2乗を比較
    }
    //《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; //直径を計算
                    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") {
            starts.Play(); //【追加】開始音を再生
            initData(); //各データをゲーム開始時の値にする
            gamemode = 1; //プレイ動画に遷移
            timer.Start(); //タイマー開始                  
        }
        //ゲームオーバー画面でEnterキーが押されていたら
        else if (gamemode == 9 && e.KeyCode.ToString() == "Return") {
            starts.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])) { //敵弾と自弾が衝突?
                        hits.Play(); //【追加】爆散音を再生
                        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%存在?
                        //敵機と爆散円の中央の距離の2乗
                        double d1 = Math.Pow(enemya[j].x - enemyba[i].x, 2)
                            + Math.Pow(enemya[j].y - enemyba[i].y, 2);
                        //敵機半径と爆散円の半径の和の2乗
                        double d2 = Math.Pow(enemyi.Width / 2 + enemyba[i].v - 100, 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) { //敵弾が存在?
                        //敵弾と爆散円の中央の距離の2乗
                        double d1 = Math.Pow(enemyba[j].x - enemyba[i].x, 2)
                            + Math.Pow(enemyba[j].y - enemyba[i].y, 2);
                        //敵弾半径と爆散円の半径の和の2乗
                        double d2 = Math.Pow(enemybi.Width / 2 + enemyba[i].v - 100, 2);
                        if (d1 < d2) { //敵弾が爆散円の中?
                            rhits.Play(); //【追加】誘爆音を再生
                            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);
                    enemya[i].y = -enemyi.Height; //Y座標は上端の直上
                    enemya[i].vv = enemyvv; //下移動速度
                    waitenemy = enemyint; //敵機出現待ち時間をセット
                    break; //1機出現できればOK
                }
            }
        }
        //《9.7.敵弾発射
        if (waitenemyb <= 0) { //敵弾出現待ち時間がゼロ?
            int enemyi = -1; //発射敵機を-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座標は発射敵機中央の上
                        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 += enemyba[i].hv; //X移動量をX座標に加算
                enemyba[i].y += enemyba[i].vv; //Y移動量を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までカウントアップ
            }
        }
        //《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) { //敵機出現間隔が下限より大きければ
            enemyint = 50 - score / 100; //スコア÷100だけ出現間隔を短縮
        }
        if (enemybint > 15) { //敵弾出現間隔が下限より大きければ
            enemybint = 75 - score / 100; //スコア÷100だけ出現間隔を短縮
        }
        Invalidate(); //画面再描画を依頼
    }
    //《10.ゲームオーバー処理
    private void GameOver() {
        ends.Play(); //【追加】終了音を再生
        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; //タイマーインターバル(ミリ秒)
        starts.Load(); //【追加】開始音をロード
        ends.Load(); //【追加】終了音をロード
        hits.Load(); //【追加】爆散音をロード
        rhits.Load(); //【追加】誘爆音をロード
    }
    //《12.Main
    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); //フォームを現出
    }
}

ゲーム開発編は以上です。

講義メモ ゲーム開発編

:敵機と爆散円の衝突判定、敵弾と爆散円の衝突判定と誘爆、サウンド処理

演習43 敵機と爆散円の衝突判定(再掲載)


・敵機が爆散円に触れたら落下するようにしよう
・敵機が破壊されたことがわかりやすくなるように、落下中の画像に切り替えよう
  meteo3.gif
・スコアの上昇に合わせて、敵機の出現間隔が短くなるようにしよう(10まで)

手順と仕様(再掲載+α)

①データメンバ:Image変数meteoiを追加し、敵機落下用の画像meteo3.gifを読み込む
②初期化処理:敵機の出現間隔を初期値(50)に戻す
③タイマーイベント処理:敵機と自機が衝突したら敵機画像をmeteoiにする処理を追加
④タイマーイベント処理:敵機と自弾が衝突したら敵機画像をmeteoiにする処理を追加
⑤タイマーイベント処理:敵機と爆散円の中央の距離が(爆散円の半径+敵機幅)未満なら衝突とする。
 そして、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"); //敵弾画像を読込む
    Image meteoi = Image.FromFile("meteo3.gif"); //【追加】破壊画像を読込む
    static Image burni = Image.FromFile("burn.gif"); //炎画像を読込む
    Pen burnp = new Pen(new TextureBrush(burni), 15); //炎のテクスチャペン
    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; //敵機移動速度
    //《3.初期化処理:各データをゲーム開始時の値にする
    void initData() {
        player.i = playeri;  //自機の画像
        player.x = 320; //自機の中心X座標
        player.y = 410; //自機の中心Y座標
        player.hv = 0;  //自機の左右方向の速度
        player.v = 100;  //自機を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.楕円形の衝突判定(アイテムaとアイテムbが衝突しているかどうかを返す)
    private bool isHit(Item a, Item b) {
        int dest = (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y); //距離2乗
        double ra = (a.i.Width < a.i.Height) ? a.i.Width / 2.0 : a.i.Height / 2.0; //内径a
        double rb = (b.i.Width < b.i.Height) ? b.i.Width / 2.0 : b.i.Height / 2.0; //内径b
        return dest < (ra + rb) * (ra + rb); //距離2乗と内径の和の2乗を比較
    }
    //《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; //直径を計算
                    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%存在?
                        //敵機と爆散円の中央の距離の2乗
                        double d1 = Math.Pow(enemya[j].x - enemyba[i].x, 2)
                            + Math.Pow(enemya[j].y - enemyba[i].y, 2);
                        //敵機半径と爆散円の半径の和の2乗
                        double d2 = Math.Pow(enemyi.Width / 2 + enemyba[i].v - 100, 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);
                    enemya[i].y = -enemyi.Height; //Y座標は上端の直上
                    enemya[i].vv = enemyvv; //下移動速度
                    waitenemy = enemyint; //敵機出現待ち時間をセット
                    break; //1機出現できればOK
                }
            }
        }
        //《9.7.敵弾発射
        if (waitenemyb <= 0) { //敵弾出現待ち時間がゼロ?
            int enemyi = -1; //発射敵機を-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座標は発射敵機中央の上
                        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 += enemyba[i].hv; //X移動量をX座標に加算
                enemyba[i].y += enemyba[i].vv; //Y移動量を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までカウントアップ
            }
        }
        //《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) { //【以下追加】敵機出現間隔が下限より大きければ
            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, 520); //フォームのサイズを設定
        f.Text = "Game"; //フォーム名を設定
        f.ControlBox = false; //コントロールボックスを非表示に
        f.FormBorderStyle = FormBorderStyle.Fixed3D; //サイズ変更を抑止
        Application.Run(f); //フォームを現出
    }
}

講義メモ

テキスト編:p.416「XMLデータとLINQ」から
ゲーム開発編:敵機と爆散円の衝突判定、敵弾と爆散円の衝突判定と誘爆、サウンド処理

p.416(XMLとは)

・XMLはエクステンシブル(拡張可能な)マークアップランゲージの略で、Webページの記述に用いるHTMLに近いマークアップ言語
・マークアップ言語は文字列情報に<>で挟んだタグと呼ばれる制御情報を与える仕組み
・HTMLの場合は、Webブラウザで定められたタグを用いて、文字色や画像挿入などを指定できる
 例: <HTML><HEAD>…<BODY>ここは<b>太字</b></BODY></HTML>
・XMLの場合は、汎用であり、作成者が自由に定めたタグを用いて、構造情報を持つ文書を作成できる
 例: <?xml … ?><book><title>題名</title><bookbody>本文</bookbody></book>
・よって、ゲームなどの大量の文字列情報を用いるアプリにおいて、これをXML形式で表現する例が増えている
・XML形式の文書の先頭行には<?xml version="1.0" encoding="文字コード" ?>を置く
・文字コードは主にuft-8を用いることが多い

p.416 XMLデータとLINQ

・LINQのクエリ対象としてXMLデータが利用可能
・Visual Studioにおいて、下記の準備作業が必要
 ①ソリューションエクスプローラで「参照」を右クリックし「参照の追加」
 ②「System.Xml」と「System.Xml.Linq」のチェックをオンにして「OK」
・外部のXMLファイルの読込には、System.Xml.Linq.XElementクラスの静的メソッド XElement Load("ファイル名")を用いる
 例(using済): XElement x = Load("myxmlsfile.xml");
・クエリ式ではin句において、XElementクラスのインスタンスメソッドElements()でコレクションを得て用いる
・where/orderby句では、XElementクラスのインスタンスメソッドElement("タグ名")でタグへの参照(コンテナ)を得て、
 Valueプロパティで文字列を受け取る
・コンテナはXContainerクラス型なので、そこにValueプロパティがある
・クエリ結果の取り出しのforeachにおいても、Element("タグ名").Valueを用いる

p.417 xml01.cs

//p.417 xml01.cs
using System;
using System.Linq;
using System.Xml.Linq;
class xml01 {
    public static void Main() {
        XElement students = XElement.Load("student.xml"); //XMLを開く
        var q =
            from person in students.Elements() //要素名を与える
            where int.Parse(person.Element("point").Value) >= 60 //pointの条件
            orderby person.Element("point").Value descending //降順でソート
            select person; //要素名を指定
        foreach (var p in q) { //XElementを1つずつ取り出して
            Console.WriteLine("{0}, {1}, {2}",
                p.Element("name").Value,
                p.Element("point").Value,
                p.Element("id").Value); //値を得て表示
        }
    }
}

p.419(XMLデータとラムダ式によるLINQ)

・クエリ式をメソッド構文にして、ラムダ式を用いると記述がシンプルになる
・書式例: XElementオブジェクト.Elements().Select(x => x);

p.419 xml02.cs

//p.419 xml02.cs
using System;
using System.Linq; //LINQ用
using System.Xml.Linq; //XElement用
class xml02 {
    public static void Main() {
        XElement Students = XElement.Load("student.xml");
        var q =
            Students.Elements()
              .Where(x => int.Parse(x.Element("point").Value) >= 60) //抽出
              .OrderByDescending(x => x.Element("point").Value) //整列
              .Select(x => x); //メソッド構文をラムダ式で示したもの
        foreach (var p in q) {
            Console.WriteLine("{0}, {1}, {2}",
                p.Element("name").Value,
                p.Element("point").Value,
                p.Element("id").Value);
        }
    }
}

※ テキストは以上

今週の話題

今回トップは「ユニコーンオーバーロード(Switch)」GO!
【決算】ANYCOLORの3Q、2割の増収増益―コマース・ファンクラブ利用者は約120万人へ GO!
松竹とBrave groupがゲームメタバース事業で協業―『フォートナイト』内にオリジナルワールド制作、リアル連動イベントも GO!
「Snowflake」活用でパフォーマンス向上とコストダウンを同時に達成―『FF7 EC』開発のアプリボットがデータ分析基盤の構築事例を紹介【CAGC2024】GO!

オンラインゲームにおけるハラスメントの放置はビジネス的損失をもたらす―米団体が無料レポート公開 GO!

講義メモ:ゲーム開発編

:敵機の落下、自弾の敵弾衝突処理、テクスチャによる描画、自弾敵弾の衝突による爆散、など

演習40 敵機の落下(再掲載)

・自弾が当たった敵機もすぐに消えるのではなく、落下(縮小)しながら移動するようにしよう
・落下(縮小)中の敵機は敵弾の発射はできない。また、衝突判定もしない。
・敵機の表示(enemya[i].v)も0/1ではなく0~100(%)の表示倍率にしよう

手順(再掲載)

① 描画処理:敵機の描画を表示倍率が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;  //自機を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); //距離2乗
        double ra = (a.i.Width < a.i.Height) ? a.i.Width / 2.0 : a.i.Height / 2.0; //内径a
        double rb = (b.i.Width < b.i.Height) ? b.i.Width / 2.0 : b.i.Height / 2.0; //内径b
        return dest < (ra + rb) * (ra + rb); //距離2乗と内径の和の2乗を比較
    }
    //描画処理のオーバライド
    protected override void OnPaint(PaintEventArgs e) {
        base.OnPaint(e); //基本クラスの描画処理を呼ぶ
        e.Graphics.DrawImage(backi, 0, backy); //背景画像を描画
        e.Graphics.DrawImage(backi, 0, backy - backi.Height); //背景画像を描画
        if (gamemode == 0) { //スタート画面?
            e.Graphics.DrawString("GAME1", fontt, brushs, 100, 150); //タイトル表示
            e.Graphics.DrawString("Hit Enter Key", fontm, brushs, 200, 300); //メッセージ表示
        } else { //プレイ画面かゲームオーバー画面?
            string s = String.Format("SCORE:{0:000,000}", score); //スコア文字列を作る
            e.Graphics.DrawString(s, font1, brushs, 400, 10); //スコア表示
            if (player.v != 0) { //自機が表示中?
                switch (player.hv) { //自機の向きによって分岐
                    case 0: player.i = playeri; break; //通常画像にする
                    case -1: player.i = playerl; break; //左寄画像にする
                    case 1: player.i = playerr; break; //右寄画像にする
                }
                DrawItem(e, player, 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);
                    enemya[i].y = -enemyi.Height; //Y座標は上端の直上
                    enemya[i].vv = enemyvv; //下移動速度
                    waitenemy = enemyint; //敵機出現待ち時間をセット
                    break; //1機出現できればOK
                }
            }
        }
        if (waitenemyb <= 0) { //敵弾出現待ち時間がゼロ?
            int enemyi = -1; //発射敵機を-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座標は発射敵機中央の上
                        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 += enemyba[i].hv; //X移動量をX座標に加算
                enemyba[i].y += enemyba[i].vv; //Y移動量を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, 520); //フォームのサイズを設定
        f.Text = "Game"; //フォーム名を設定
        f.ControlBox = false; //コントロールボックスを非表示に
        f.FormBorderStyle = FormBorderStyle.Fixed3D; //サイズ変更を抑止
        Application.Run(f); //フォームを現出
    }
}

演習41 自弾の敵弾衝突処理

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

手順と仕様

①タイマーイベント処理:敵弾と自機の衝突判定に加えて、自弾と敵弾の衝突判定を行う。
 衝突していたら両方を消し、スコアに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;  //自機を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); //距離2乗
        double ra = (a.i.Width < a.i.Height) ? a.i.Width / 2.0 : a.i.Height / 2.0; //内径a
        double rb = (b.i.Width < b.i.Height) ? b.i.Width / 2.0 : b.i.Height / 2.0; //内径b
        return dest < (ra + rb) * (ra + rb); //距離2乗と内径の和の2乗を比較
    }
    //描画処理のオーバライド
    protected override void OnPaint(PaintEventArgs e) {
        base.OnPaint(e); //基本クラスの描画処理を呼ぶ
        e.Graphics.DrawImage(backi, 0, backy); //背景画像を描画
        e.Graphics.DrawImage(backi, 0, backy - backi.Height); //背景画像を描画
        if (gamemode == 0) { //スタート画面?
            e.Graphics.DrawString("GAME1", fontt, brushs, 100, 150); //タイトル表示
            e.Graphics.DrawString("Hit Enter Key", fontm, brushs, 200, 300); //メッセージ表示
        } else { //プレイ画面かゲームオーバー画面?
            string s = String.Format("SCORE:{0:000,000}", score); //スコア文字列を作る
            e.Graphics.DrawString(s, font1, brushs, 400, 10); //スコア表示
            if (player.v != 0) { //自機が表示中?
                switch (player.hv) { //自機の向きによって分岐
                    case 0: player.i = playeri; break; //通常画像にする
                    case -1: player.i = playerl; break; //左寄画像にする
                    case 1: player.i = playerr; break; //右寄画像にする
                }
                DrawItem(e, player, 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);
                    enemya[i].y = -enemyi.Height; //Y座標は上端の直上
                    enemya[i].vv = enemyvv; //下移動速度
                    waitenemy = enemyint; //敵機出現待ち時間をセット
                    break; //1機出現できればOK
                }
            }
        }
        if (waitenemyb <= 0) { //敵弾出現待ち時間がゼロ?
            int enemyi = -1; //発射敵機を-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座標は発射敵機中央の上
                        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 += enemyba[i].hv; //X移動量をX座標に加算
                enemyba[i].y += enemyba[i].vv; //Y移動量を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, 520); //フォームのサイズを設定
        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 敵弾の自弾衝突による爆散

・自弾と敵弾が衝突していたら敵弾の中心から円を広げるように爆散するようにしよう
・爆散用画像ファイルを用いる
  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倍とする(つまり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 burnp = new Pen(new TextureBrush(burni), 15); //【追加】炎のテクスチャペン
    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;  //自機を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); //距離2乗
        double ra = (a.i.Width < a.i.Height) ? a.i.Width / 2.0 : a.i.Height / 2.0; //内径a
        double rb = (b.i.Width < b.i.Height) ? b.i.Width / 2.0 : b.i.Height / 2.0; //内径b
        return dest < (ra + rb) * (ra + rb); //距離2乗と内径の和の2乗を比較
    }
    //描画処理のオーバライド
    protected override void OnPaint(PaintEventArgs e) {
        base.OnPaint(e); //基本クラスの描画処理を呼ぶ
        e.Graphics.DrawImage(backi, 0, backy); //背景画像を描画
        e.Graphics.DrawImage(backi, 0, backy - backi.Height); //背景画像を描画
        if (gamemode == 0) { //スタート画面?
            e.Graphics.DrawString("GAME1", fontt, brushs, 100, 150); //タイトル表示
            e.Graphics.DrawString("Hit Enter Key", fontm, brushs, 200, 300); //メッセージ表示
        } else { //プレイ画面かゲームオーバー画面?
            string s = String.Format("SCORE:{0:000,000}", score); //スコア文字列を作る
            e.Graphics.DrawString(s, font1, brushs, 400, 10); //スコア表示
            if (player.v != 0) { //自機が表示中?
                switch (player.hv) { //自機の向きによって分岐
                    case 0: player.i = playeri; break; //通常画像にする
                    case -1: player.i = playerl; break; //左寄画像にする
                    case 1: player.i = playerr; break; //右寄画像にする
                }
                DrawItem(e, player, 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; //直径を計算
                    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);
                    enemya[i].y = -enemyi.Height; //Y座標は上端の直上
                    enemya[i].vv = enemyvv; //下移動速度
                    waitenemy = enemyint; //敵機出現待ち時間をセット
                    break; //1機出現できればOK
                }
            }
        }
        if (waitenemyb <= 0) { //敵弾出現待ち時間がゼロ?
            int enemyi = -1; //発射敵機を-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座標は発射敵機中央の上
                        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 += enemyba[i].hv; //X移動量をX座標に加算
                enemyba[i].y += enemyba[i].vv; //Y移動量を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までカウントアップ
            }
        }
        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, 520); //フォームのサイズを設定
        f.Text = "Game"; //フォーム名を設定
        f.ControlBox = false; //コントロールボックスを非表示に
        f.FormBorderStyle = FormBorderStyle.Fixed3D; //サイズ変更を抑止
        Application.Run(f); //フォームを現出
    }
}

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

・敵機が爆散円に触れたら落下するようにしよう
・敵機が破壊されたことがわかりやすくなるように、落下中の画像に切り替えよう
  meteo3.gif
・スコアの上昇に合わせて、敵機の出現間隔が短くなるようにしよう(10まで)

手順と仕様

①データメンバ:Image変数meteoiを追加し、敵機落下用の画像meteo3.gifを読み込む
②タイマーイベント処理:敵機と自機が衝突したら敵機画像をmeteoiにする処理を追加
③タイマーイベント処理:敵機と自弾が衝突したら敵機画像をmeteoiにする処理を追加
④タイマーイベント処理:敵機と爆散円の中央の距離が(爆散円の半径+敵機幅)未満なら衝突とする。
 そして、10点加算、敵機画像を差し替えて落下を開始する。
 ※爆散円は存在中の敵弾の数だけチェックする必要がある

提出:演習41または42(未完成でも可)

講義メモ

テキスト編:p.405「LINQとは」から
ゲーム開発編:自弾の敵弾衝突処理、テクスチャによる描画、自弾敵弾の衝突による爆散、など

p.405 LINQとは

・SQL(データベース操作言語)に近い構文によって、データ構造からの検索を行う仕掛け
・SQLの書式例:
 SELECT 項目名,… FROM 構造名 WHERE 項目名 条件演算子 値など
例:
 SELECT モンスター名,HP FROM モンスター表 WHERE HP > 100
・SQLによる検索のポイント:値を返す関数やメソッドと異なり、データ構造が返される
・LINQの対象例: 配列、リストなどのコレクション、XML文書 など

p.406 LINQのクエリ

・クエリとはSQLにおける問い合わせのことで、データ構造が返される
・C#が提供するジェネリックインターフェイスを用いると、SQLに近い構文が使える

p.408 linq01.cs

//p.408 linq01.cs
using System;
using System.Linq; //LINQ構文用
using System.Collections.Generic; //IEnumerable<T>インターフェイス用 
class linq01 {
    public static void Main() {
        int[] numbers = new int[] { -3, 4, 1, 2, -6, 3, 100, -25}; //クエリ対象の配列
        // クエリの作成
        IEnumerable<int> q = from n in numbers //配列numbersから
                where n > 0 //要素値が正であるものを
                select n; //得た結果の構造を返す
        // クエリの実行
        foreach (int x in q) { //IEnumerable<T>インターフェイス型オブジェクトから全要素(int型)について繰返す
            Console.WriteLine(x);
        }
    }
}

p.406 LINQのクエリ(続き)

・LINQ構文を用いるには「using System.Linq;」を記述する(必須)
・LINQ構文で得られるデータ構造は、IEnumerable<T>インターフェイス型になる
・このジェネリクスに、クエリ対象の型を指定する
・配列を対象とする場合は、配列の要素型を指定すれば良い
・得られたIEnumerable<T>インターフェイス型のオブジェクトから、foreachなどで要素を取り出す処理を繰返すと良い
・クエリ式の基本書式: 
 from 項目名 in 構造名 where 項目名 条件演算子 値 select 構造名
・配列の場合のクエリ式の基本書式: 
 from 作業変数 in 配列名 where 作業変数 条件演算子 値 select 配列名
・なお、IQueryable<T>を用いることも可能
※ VS2008ではp.407記載の手順が必要、VS2008より前のバージョンでは利用不可

p.409 データの並べ替えと個数

・LINQのメリットの一つはSQLのソート機能や関数機能などを利用できること
・構文:
 from 項目名 in 構造名 orderby 対象項目名 select 構造名
・規定値は昇順(小⇒大)だが、対象項目名に descending を付記すると降順(大⇒小)になる
・得られたIEnumerable<T>インターフェイス型のオブジェクトで、Count()メソッドを呼ぶと件数が得られる
 ※ SQLでもCOUNT(*)構文で件数が得られる

p.409 linq02.cs

//p.409 linq02.cs
using System;
using System.Collections.Generic; //IEnumerable<T>インターフェイス用 
using System.Linq; //LINQ構文用
class linq02 {
  public static void Main() {
    string[] myStr = {"flower", "cat", "dog", "bird", "rabbit"}; //クエリ対象の配列
    // クエリの作成
    IEnumerable<string> q =
      from s in myStr  //配列myStrから
      where s.Length >=  4 //要素値の文字数が4以上であるものを
      orderby s descending //要素値の降順に整列せよ
      select s;
    // クエリの実行
    foreach (string x in q)
      Console.WriteLine(x);
    // 検索条件に適合した要素の個数
    Console.WriteLine("適合した数={0}", q.Count());
  }
}

p.410 var型の利用

・クエリが返すオブジェクトの型はIEnumerable<T>インターフェイス型と決まっている
・しかも、型パラメータの型もクエリによって自動決定できる
・よって、IEnumerable<T>型をvarキーワードで置き換えて良い
・これにより「using System.Collections.Generic;」も自動化されるので記述不要
※ 加えて、foreachで用いる作業変数の型もvarキーワード指定で良い

アレンジ演習:p.409 linq02.cs

・IEnumerable<T>型をvarキーワードで置き換えてみよう

作成例

//アレンジ演習:p.409 linq02.cs
using System;
// using System.Collections.Generic; //【削除】不要
using System.Linq; //LINQ構文用
class linq02 {
    public static void Main() {
        string[] myStr = { "flower", "cat", "dog", "bird", "rabbit" }; //クエリ対象の配列
        var q = from s in myStr where s.Length >= 4 orderby s descending select s;
        // クエリの実行
        foreach (var x in q)
            Console.WriteLine(x);
        // 検索条件に適合した要素の個数
        Console.WriteLine("適合した数={0}", q.Count());
    }
}

p.410 Listコレクションからのデータ抽出

・配列と同様にList<T>型のコレクションオブジェクトをLINQで扱うことが可能
・この場合、List<T>型に与えた型パラメータが自動的にクエリ結果の要素型になる
・List<T>型の利用には「using System.Collections.Generic;」が必要

p.411 linq03.cs

//p.411 linq03.cs
using System;
using System.Collections.Generic; //List<T>用
using System.Linq; //LINQ構文用
class data { //Listに格納するオブジェクトの型になるクラス
  public string name;
  public string address;
  public int age;
}
class linq03 {
  public static void Main() {
    List<data> mydata = new List<data> { //クラスdataを要素型とするリスト
        new data { name = "田中和夫", address = "東京都", age = 24}, //オブジェクト初期化子で生成
        new data { name = "鈴木義男", address = "東京都", age = 17},
        new data { name = "吉田育子", address = "青森県", age = 24},
        new data { name = "田中和夫", address = "東京都", age = 34},
        new data { name = "小林一郎", address = "北海道", age = 29}
    };
    // 旧式のデータ追加
    data datax = new data();
    datax.name = "粂井康孝";
    datax.address = "北海道";
    datax.age = 22;
    mydata.Add(datax); //Listオブジェクトに格納
    // 新方式のデータ追加
    mydata.Add(new data { name = "猫山太郎", address = "青森県", age = 15 }); //オブジェクト初期化子で生成
    var q = from x in mydata
            where x.age < 30
            orderby x.age ascending //※ascendingは省略可能
            select x; //クエリを実行
    Console.WriteLine("氏名\t\t住所\t年齢");
    foreach (data z in q) {
      Console.WriteLine("{0}\t{1}\t{2}", z.name, z.address, z.age);
    }
  }
}

p.412 メソッド構文の利用

・LINQのクエリ構文はC#の文法と馴染まないので、可読性のために、メソッドをドットでつなぐ形式のメソッド構文も利用できる
・書式例:
 構造名.Where(条件メソッド名).Select(選択メソッド名);
・条件メソッドの書式例:
 static bool 条件メソッド名(型 作業変数) { return 作業変数を用いた条件式; }
・選択メソッドの書式例:
 static 項目型 選択メソッド名(型 作業変数) { return 作業変数; }
・ソートなども可能
・書式例:
 構造名.Orderby(整列メソッド名).Select(選択メソッド名);
 構造名.OrderbyDesending(整列メソッド名).Select(選択メソッド名); //降順用
・整列メソッドの書式例:
 static 項目型 整列メソッド名(型 作業変数) { return 作業変数; }

p.413 linq04.cs

//p.413 linq04.cs
using System;
using System.Linq; //LINQ構文用
class linq04 {
  static bool MyWhere(int x) { //条件メソッド
    return (x > 0); //作業変数を用いて条件(正の数のみ)を表す
  }
  static int MyOrderBy(int x) { //整列メソッド
    return x; //引数で得た作業変数を返すのみ
  }
  static int MySelect(int x) { //選択メソッド
    return x; //引数で得た作業変数を返すのみ
  }
  public static void Main() {
    int[] numbers = new int[] {200, -3, 4, 1, 2, -6, 3, 100, -25}; //クエリ対象の配列
    //メソッド構文
    var q = numbers.Where(MyWhere) //条件メソッドを指定
      .OrderByDescending(MyOrderBy) //整列メソッドを指定し降順でソート
      .Select(MySelect); //選択メソッドを指定
    //クエリの実行
    foreach (int x in q) {
      Console.WriteLine(x);
    }
  }
}

p.415(メソッド構文における匿名メソッドの利用)

・メソッド構文で別記している各メソッドは、匿名メソッド(p.304)にすることで簡略化できる
・書式例:
 構造名.Where(delegate(型 引数){return 条件式;}).Select(delegate(型 引数){return 引数;});
・デリゲートにより、各メソッドを別記する必要がなくなる

p.415 linq05.cs

//p.415 linq05.cs
using System;
using System.Linq; //LINQ構文用
class linq05 {
  public static void Main() {
    int[] numbers = new int[] {200, -3, 4, 1, 2, -6, 3, 100, -25}; //クエリ対象の配列
    //メソッド構文
    var q = numbers.Where(delegate(int x) { return x > 0; }) //条件をデリゲートで指定
      .OrderByDescending(delegate (int x) { return x; }) //整列メソッドをデリゲートで指定し降順でソート
      .Select(delegate (int x) { return x; }); //選択をデリゲートで指定
    //クエリの実行
    foreach (int x in q)
      Console.WriteLine(x);
  }
}

p.415(メソッド構文におけるラムダ式の利用)

・匿名メソッドはラムダ式(p.306)にすることで、よりシンプルにできる
・書式例:
 構造名.Where(引数 => 条件式).Select(引数 => 引数);
・型の自動判定ができるので、引数型は省略可能
・「引数 => 引数」は一律に「x => x」「w => w」などとしてOK

p.415 linq06.cs

//p.415 linq06.cs
using System;
using System.Linq; //LINQ構文用
class linq05 {
  public static void Main() {
    int[] numbers = new int[] {200, -3, 4, 1, 2, -6, 3, 100, -25}; //クエリ対象の配列
    //メソッド構文
    var q = numbers.Where(x => x > 0) //条件をラムダ式で指定
      .OrderByDescending(x => x) //整列をラムダ式で指定し降順でソート
      .Select(x => x); //選択をラムダ式で指定
    //クエリの実行
    foreach (int x in q)
      Console.WriteLine(x);
  }
}

今週の話題

今回トップは「ファイナルファンタジーVII リバース(PS5)」GO!
学生がもっと“eスポーツ”に触れる機会を―ヒューマンアカデミーが学校にゲーミングデバイスを寄贈するプランを展開する狙い GO!
広井王子氏が考えるeスポーツとゲームの未来、そして映画との深い繋がりとは―eスポーツ映画「PLAY! ~勝つとか負けるとかは、どーでもよくて~」公開直前インタビュー! GO!
Unity、有償プランの値上げを日本でのみ実施―円安に伴う為替レート変更 個人向けの無料版であるUnity Personalに影響はありません GO!

セガサミーのゲーム事業は新作軟調、過度な広告費で営業利益が急減【ゲーム企業の決算を読む】GO!
従業員の発言切っ掛けに議論が過熱。ゲームへの多様性導入巡り開発コンサル企業に海外で是非の声 GO!
【決算】gumi、3Qの売上高3割減、営業損失30億円―『アスタタ』不調やブロックチェーンゲーム宣伝費が重く GO!