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

:自弾の移動、アニメーション、複数化 など

演習28 自弾の上移動(単独バージョン)

・自弾が出現状態であれば、上へ移動しよう
・完全に見えなくなったら、出現状態を無に戻して再発射可能にしよう
・上下方向の移動速度vvをItem構造体に追加しよう(値は適当に)

手順と仕様

① アイテムを表す構造体に上下方向の速度(上向きは負の数、下向きは正の数)を持つint型変数vvを追加
② タイマーイベント処理:スペースキーが押されている&自弾が非表示ならば、自弾の上移動速度に-5を代入
③ タイマーイベント処理:自弾が存在したら、自弾を上へ移動し、画面上端より上に出たら自弾を消す

作成例

//演習28 自弾の上移動(単独バージョン)
using System; //フォームアプリケーションに必須
using System.Windows.Forms; //フォームアプリケーションに必須
using System.Drawing; //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:プレイ画面
    int score = 0; //スコア
    Image backi = Image.FromFile("backb.bmp"); //背景画像の読込
    Image playeri = Image.FromFile("player.gif"); //自機画像の読込
    Image playerl = Image.FromFile("playerl.gif"); //自機左寄画像の読込
    Image playerr = Image.FromFile("playerr.gif"); //自機右寄画像の読込
    Image bulleti = Image.FromFile("bullet.gif"); //自弾画像の読込
    Font font1 = new Font("メイリオ", 20, FontStyle.Bold); //フォントを生成(メイリオ20P太字)
    Font fontt = new Font("メイリオ", 80, FontStyle.Bold); //フォントを生成(メイリオ80P太字)
    Brush brushs = new SolidBrush(Color.Yellow); //ブラシを生成(黄色)
    Timer timer = new Timer(); //タイマーを生成
    int backy = 0; //背景画像のつなぎ目のY座標
    Item player; //プレイヤーの構造体オブジェクト
    Item pb; //自弾の構造体オブジェクト
    //中心座標を用いて画像を描画
    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);
    }
    //描画処理
    protected override void OnPaint(PaintEventArgs e) { //Formのメソッドをオーバーライド
        base.OnPaint(e); //まず、Formクラスにおけるメソッドの内容(基本再描画処理)を実行
        e.Graphics.DrawImage(backi, 0, backy); //背景画像を(0,つなぎ目)から描画
        e.Graphics.DrawImage(backi, 0, backy - backi.Height); //背景画像をその上に描画
        if (gamemode == 0) { //スタート画面
            e.Graphics.DrawString("GAME1", fontt, brushs, 100, 100); //タイトルの描画
            e.Graphics.DrawString("Hit Enter to Start", font1, brushs, 200, 300); //メッセージの描画
        } else if (gamemode == 1) { //プレイ画面
            string s = String.Format("SCORE:{0:000,000}", score); //スコア文字列を編集
            e.Graphics.DrawString(s, font1, brushs, 400, 10); //スコア文字列の描画
            switch (player.hv) { //自機の向きによって分岐
                case  0: player.i = playeri; break; //通常
                case  1: player.i = playerr; break; //右寄 
                case -1: player.i = playerl; break; //左寄
            }
            DrawItem(e, player); //自機を表示
            if (pb.v == 1) { //自弾がある? 
                DrawItem(e, pb); //自弾を表示
            }
        }
    }
    //キー入力時処理
    void OnKeyDown(object o, KeyEventArgs e) { //キーボードが押された時に呼ばれるメソッド
        if(e.KeyCode.ToString() == "Escape") { //Escキーが押されたら
            Close(); //フォームアプリケーション終了
        }
        if(gamemode == 0 && e.KeyCode.ToString() == "Return") { //スタート画面でEnterキーが押されたら
            gamemode = 1; //プレイ画面に切り替える
            timer.Start(); //タイマー開始
            Invalidate(); //画面再描画を依頼する
        }
    }
    //タイマーイベント処理
    void Play(object o, EventArgs e) { //タイマーから呼ばれるメソッド
        backy = (backy < backi.Height) ? backy + 1 : 0; //背景画像のつなぎ目を下げる
        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; //右に移動中
            }
        }
        if (GetKeyState((int)Keys.Space) < 0) { //スペースキーが押されている?
            if (pb.v == 0) { //自弾が非表示?
                pb.v = 1; //表示にする
                pb.i = bulleti; //画像を設定
                pb.x = player.x; //X座標は自機の中心に合わせる
                pb.y = player.y - player.i.Height / 2 - pb.i.Height / 2; //Y座標は自機の直上に合わせる
                pb.vv = -5; //【追加】上移動速度を設定
            }
        }
        if (pb.v == 1) { //【以下追加】自弾が存在したら
            pb.y += pb.vv; //Y座標に上移動速度を反映することで上昇
            if (pb.y + pb.i.Height / 2 < 0) { //画面上端より上に出たら
                pb.v = 0; //自弾を消す
            }
        }
        Invalidate(); //画面再描画を依頼する
    }
    //コンストラクタ
    Program() { 
        DoubleBuffered = true; //ダブルバッファリングの導入
        KeyDown += new KeyEventHandler(OnKeyDown); //キーボードが押された時に呼ばれるメソッドを登録
        timer.Tick += new EventHandler(Play); //タイマーから呼ばれるメソッドを登録
        timer.Interval = 10; //タイマーの間隔
        player.i = playeri; //自機の画像
        player.x = 320; //自機のX座標
        player.y = 410; //自機のY座標
        player.hv = 0; //自機の左右方向の速度
    }
    static void Main() { //publicの指定は任意
        Program f = new Program(); //継承したフォームのインスタンスを生成
        f.Size = new Size(660, 520); //インスタンスのSizeプロパティに高さと幅を代入
        f.Text = "Game"; //インスタンスのTextプロパティにフォーム名を代入
        f.ControlBox = false; //ControlBoxを非表示にする
        f.FormBorderStyle = FormBorderStyle.Fixed3D; //フォームサイズ変更を禁止
        Application.Run(f); //インスタンスを画面に出す
    }
}

作成例

//演習29 自弾の簡易アニメーション
using System; //フォームアプリケーションに必須
using System.Windows.Forms; //フォームアプリケーションに必須
using System.Drawing; //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:プレイ画面
    int score = 0; //スコア
    Image backi = Image.FromFile("backb.bmp"); //背景画像の読込
    Image playeri = Image.FromFile("player.gif"); //自機画像の読込
    Image playerl = Image.FromFile("playerl.gif"); //自機左寄画像の読込
    Image playerr = Image.FromFile("playerr.gif"); //自機右寄画像の読込
    Image bulleti = Image.FromFile("bullet.gif"); //自弾画像の読込
    Image bullet2i = Image.FromFile("bullet2.gif"); //【追加】自弾画像2の読込
    Font font1 = new Font("メイリオ", 20, FontStyle.Bold); //フォントを生成(メイリオ20P太字)
    Font fontt = new Font("メイリオ", 80, FontStyle.Bold); //フォントを生成(メイリオ80P太字)
    Brush brushs = new SolidBrush(Color.Yellow); //ブラシを生成(黄色)
    Timer timer = new Timer(); //タイマーを生成
    int backy = 0; //背景画像のつなぎ目のY座標
    Item player; //プレイヤーの構造体オブジェクト
    Item pb; //自弾の構造体オブジェクト
    //中心座標を用いて画像を描画
    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);
    }
    //描画処理
    protected override void OnPaint(PaintEventArgs e) { //Formのメソッドをオーバーライド
        base.OnPaint(e); //まず、Formクラスにおけるメソッドの内容(基本再描画処理)を実行
        e.Graphics.DrawImage(backi, 0, backy); //背景画像を(0,つなぎ目)から描画
        e.Graphics.DrawImage(backi, 0, backy - backi.Height); //背景画像をその上に描画
        if (gamemode == 0) { //スタート画面
            e.Graphics.DrawString("GAME1", fontt, brushs, 100, 100); //タイトルの描画
            e.Graphics.DrawString("Hit Enter to Start", font1, brushs, 200, 300); //メッセージの描画
        } else if (gamemode == 1) { //プレイ画面
            string s = String.Format("SCORE:{0:000,000}", score); //スコア文字列を編集
            e.Graphics.DrawString(s, font1, brushs, 400, 10); //スコア文字列の描画
            switch (player.hv) { //自機の向きによって分岐
                case  0: player.i = playeri; break; //通常
                case  1: player.i = playerr; break; //右寄 
                case -1: player.i = playerl; break; //左寄
            }
            DrawItem(e, player); //自機を表示
            if (pb.v == 1) { //自弾がある? 
                pb.i = (pb.i == bulleti) ? bullet2i : bulleti; //【追加】画像を交互変更
                DrawItem(e, pb); //自弾を表示
            }
        }
    }
    //キー入力時処理
    void OnKeyDown(object o, KeyEventArgs e) { //キーボードが押された時に呼ばれるメソッド
        if(e.KeyCode.ToString() == "Escape") { //Escキーが押されたら
            Close(); //フォームアプリケーション終了
        }
        if(gamemode == 0 && e.KeyCode.ToString() == "Return") { //スタート画面でEnterキーが押されたら
            gamemode = 1; //プレイ画面に切り替える
            timer.Start(); //タイマー開始
            Invalidate(); //画面再描画を依頼する
        }
    }
    //タイマーイベント処理
    void Play(object o, EventArgs e) { //タイマーから呼ばれるメソッド
        backy = (backy < backi.Height) ? backy + 1 : 0; //背景画像のつなぎ目を下げる
        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; //右に移動中
            }
        }
        if (GetKeyState((int)Keys.Space) < 0) { //スペースキーが押されている?
            if (pb.v == 0) { //自弾が非表示?
                pb.v = 1; //表示にする
                pb.i = bulleti; //画像を設定
                pb.x = player.x; //X座標は自機の中心に合わせる
                pb.y = player.y - player.i.Height / 2 - pb.i.Height / 2; //Y座標は自機の直上に合わせる
                pb.vv = -5; //上移動速度を設定
            }
        }
        if (pb.v == 1) { //自弾が存在したら
            pb.y += pb.vv; //Y座標に上移動速度を反映することで上昇
            if (pb.y + pb.i.Height / 2 < 0) { //画面上端より上に出たら
                pb.v = 0; //自弾を消す
            }
        }
        Invalidate(); //画面再描画を依頼する
    }
    //コンストラクタ
    Program() { 
        DoubleBuffered = true; //ダブルバッファリングの導入
        KeyDown += new KeyEventHandler(OnKeyDown); //キーボードが押された時に呼ばれるメソッドを登録
        timer.Tick += new EventHandler(Play); //タイマーから呼ばれるメソッドを登録
        timer.Interval = 10; //タイマーの間隔
        player.i = playeri; //自機の画像
        player.x = 320; //自機のX座標
        player.y = 410; //自機のY座標
        player.hv = 0; //自機の左右方向の速度
    }
    static void Main() { //publicの指定は任意
        Program f = new Program(); //継承したフォームのインスタンスを生成
        f.Size = new Size(660, 520); //インスタンスのSizeプロパティに高さと幅を代入
        f.Text = "Game"; //インスタンスのTextプロパティにフォーム名を代入
        f.ControlBox = false; //ControlBoxを非表示にする
        f.FormBorderStyle = FormBorderStyle.Fixed3D; //フォームサイズ変更を禁止
        Application.Run(f); //インスタンスを画面に出す
    }
}

演習30 自弾の複数化

・自弾を10個にしよう
・スペースキーを押すと順に発射され、画面上に10個ある時は、押されても無視する
・位置情報などは弾ごとに持ち、画面上部から消えたら、再度発射可能とする

手順と仕様

① 自弾の最大数を定数maxpbで保持し初期値を10とする
  例: static int maxpb = 10;
② 自弾の構造体オブジェクトを、要素数maxpbの配列pbaにする
  例: Item[] pba = new Item[maxpb];
③ 描画処理:自弾を描画を描画する処理を、
 「自弾があれば描画する」を全自弾について繰返すように変更
  また、描画画像の入れ替えはタイマーイベント処理に移す
④ タイマーイベント処理:スペースキーが押されているときの処理を、
 「非表示の自弾が見つかったら発射する」を全自弾について繰返すようにする
  なお、1つ見つかったら抜けること
⑤ タイマーイベント処理:自弾があれば上へ移動し、上端より上になったら非表示にすることを
  全自弾について繰返すようにする
   また、描画画像の入れ替えはここで行う

作成例

//演習30 自弾の複数化
using System; //フォームアプリケーションに必須
using System.Windows.Forms; //フォームアプリケーションに必須
using System.Drawing; //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:プレイ画面
    int score = 0; //スコア
    Image backi = Image.FromFile("backb.bmp"); //背景画像の読込
    Image playeri = Image.FromFile("player.gif"); //自機画像の読込
    Image playerl = Image.FromFile("playerl.gif"); //自機左寄画像の読込
    Image playerr = Image.FromFile("playerr.gif"); //自機右寄画像の読込
    Image bulleti = Image.FromFile("bullet.gif"); //自弾画像の読込
    Image bullet2i = Image.FromFile("bullet2.gif"); //自弾画像2の読込
    Font font1 = new Font("メイリオ", 20, FontStyle.Bold); //フォントを生成(メイリオ20P太字)
    Font fontt = new Font("メイリオ", 80, FontStyle.Bold); //フォントを生成(メイリオ80P太字)
    Brush brushs = new SolidBrush(Color.Yellow); //ブラシを生成(黄色)
    Timer timer = new Timer(); //タイマーを生成
    int backy = 0; //背景画像のつなぎ目のY座標
    Item player; //プレイヤーの構造体オブジェクト
    static int maxpb = 10; //【追加】自弾の最大数
    Item[] pba = new Item[maxpb]; //【変更】自弾の構造体配列
    //中心座標を用いて画像を描画
    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);
    }
    //描画処理
    protected override void OnPaint(PaintEventArgs e) { //Formのメソッドをオーバーライド
        base.OnPaint(e); //まず、Formクラスにおけるメソッドの内容(基本再描画処理)を実行
        e.Graphics.DrawImage(backi, 0, backy); //背景画像を(0,つなぎ目)から描画
        e.Graphics.DrawImage(backi, 0, backy - backi.Height); //背景画像をその上に描画
        if (gamemode == 0) { //スタート画面
            e.Graphics.DrawString("GAME1", fontt, brushs, 100, 100); //タイトルの描画
            e.Graphics.DrawString("Hit Enter to Start", font1, brushs, 200, 300); //メッセージの描画
        } else if (gamemode == 1) { //プレイ画面
            string s = String.Format("SCORE:{0:000,000}", score); //スコア文字列を編集
            e.Graphics.DrawString(s, font1, brushs, 400, 10); //スコア文字列の描画
            switch (player.hv) { //自機の向きによって分岐
                case  0: player.i = playeri; break; //通常
                case  1: player.i = playerr; break; //右寄 
                case -1: player.i = playerl; break; //左寄
            }
            DrawItem(e, player); //自機を表示
            foreach(var pb in pba) { //【追加】全自弾について繰返す
                if (pb.v == 1) { //自弾がある? 
                    //pb.i = (pb.i == bulleti) ? bullet2i : bulleti; //【移動】画像を交互変更
                    DrawItem(e, pb); //自弾を表示
                }
            }
        }
    }
    //キー入力時処理
    void OnKeyDown(object o, KeyEventArgs e) { //キーボードが押された時に呼ばれるメソッド
        if(e.KeyCode.ToString() == "Escape") { //Escキーが押されたら
            Close(); //フォームアプリケーション終了
        }
        if(gamemode == 0 && e.KeyCode.ToString() == "Return") { //スタート画面でEnterキーが押されたら
            gamemode = 1; //プレイ画面に切り替える
            timer.Start(); //タイマー開始
            Invalidate(); //画面再描画を依頼する
        }
    }
    //タイマーイベント処理
    void Play(object o, EventArgs e) { //タイマーから呼ばれるメソッド
        backy = (backy < backi.Height) ? backy + 1 : 0; //背景画像のつなぎ目を下げる
        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; //右に移動中
            }
        }
        if (GetKeyState((int)Keys.Space) < 0) { //スペースキーが押されている?
            for(int i = 0; i < maxpb; i++) { //【追加】全自弾について繰返す
                if (pba[i].v == 0) { //【変更】自弾[i]が非表示?
                    pba[i].v = 1; //【変更】表示にする
                    pba[i].i = bulleti; //【変更】画像を設定
                    pba[i].x = player.x; //【変更】X座標は自機の中心に合わせる
                    pba[i].y = player.y - player.i.Height / 2 - bulleti.Height / 2; //【変更】Y座標は自機の直上に合わせる
                    pba[i].vv = -5; //【変更】上移動速度を設定
                    break; //【追加】1つ見つかったら抜ける
                }
            }
        }
        for(int i = 0; i < maxpb; i++) { //【追加】全自弾について繰返す
            if (pba[i].v == 1) { //【変更】自弾[i]が存在したら
                pba[i].i = (pba[i].i == bulleti) ? bullet2i : bulleti; //【移動して変更】画像を交互変更
                pba[i].y += pba[i].vv; //【変更】Y座標に上移動速度を反映することで上昇
                if (pba[i].y + bulleti.Height / 2 < 0) { //【変更】画面上端より上に出たら
                    pba[i].v = 0; //【変更】自弾[i]を消す
                }
            }
        }
        Invalidate(); //画面再描画を依頼する
    }
    //コンストラクタ
    Program() { 
        DoubleBuffered = true; //ダブルバッファリングの導入
        KeyDown += new KeyEventHandler(OnKeyDown); //キーボードが押された時に呼ばれるメソッドを登録
        timer.Tick += new EventHandler(Play); //タイマーから呼ばれるメソッドを登録
        timer.Interval = 10; //タイマーの間隔
        player.i = playeri; //自機の画像
        player.x = 320; //自機のX座標
        player.y = 410; //自機のY座標
        player.hv = 0; //自機の左右方向の速度
    }
    static void Main() { //publicの指定は任意
        Program f = new Program(); //継承したフォームのインスタンスを生成
        f.Size = new Size(660, 520); //インスタンスのSizeプロパティに高さと幅を代入
        f.Text = "Game"; //インスタンスのTextプロパティにフォーム名を代入
        f.ControlBox = false; //ControlBoxを非表示にする
        f.FormBorderStyle = FormBorderStyle.Fixed3D; //フォームサイズ変更を禁止
        Application.Run(f); //インスタンスを画面に出す
    }
}

テーマ29 冷却期間

・主にアクション系ゲームにおいて、プレイヤーの挙動に合わせて複数の物体を動かす場合「ボタンが押されている間、
 連続して処理」とすると、1挙動で必要以上の数の物体に影響してしまうことが多い。
・これは、プログラムの動作速度が1挙動より大幅に早いことが原因。
・特に、連射を可能にしている場合、このことへの配慮が必要
・これを防ぐには、1物体の動作後から一定時間の動作不能時間=冷却時間を持てば良い
・動作直後にタイミングを計る変数をセットしてカウントダウンを行い、ゼロになったら、次の動作を可能とすると良い
・ボタンによる連射も可能にしたい場合は、ボタンの状況を監視して手が離れたら、タイミングをはかる変数をリセット
 する(0に戻す)と良い

演習31 自弾発射の改良

・スペースキーを押しっぱなしの場合は、次の弾の発射まで適当な冷却時間を置くことにしよう
・スペースキーが押されてなかったら、冷却時間なしで次の弾の発射を可能にしよう

手順と仕様

① 冷却時間を定数coldで保持し初期値を10とする
② 自弾発射待ちを変数waitpbで表し初期値を0とする
③ タイマーイベント処理:スペースキーが押されたとき、waitpbが0なら自弾を発射する
  そして、waitpbにcoldを代入して冷却時間としよう
④ タイマーイベント処理:waitpbが0以上なら0までカウントダウンしよう
⑤ タイマーイベント処理:スペースキーが押されていなければ、waitpbを0にする

作成例

//演習31 自弾発射の改良
using System; //フォームアプリケーションに必須
using System.Windows.Forms; //フォームアプリケーションに必須
using System.Drawing; //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:プレイ画面
    int score = 0; //スコア
    Image backi = Image.FromFile("backb.bmp"); //背景画像の読込
    Image playeri = Image.FromFile("player.gif"); //自機画像の読込
    Image playerl = Image.FromFile("playerl.gif"); //自機左寄画像の読込
    Image playerr = Image.FromFile("playerr.gif"); //自機右寄画像の読込
    Image bulleti = Image.FromFile("bullet.gif"); //自弾画像の読込
    Image bullet2i = Image.FromFile("bullet2.gif"); //自弾画像2の読込
    Font font1 = new Font("メイリオ", 20, FontStyle.Bold); //フォントを生成(メイリオ20P太字)
    Font fontt = new Font("メイリオ", 80, FontStyle.Bold); //フォントを生成(メイリオ80P太字)
    Brush brushs = new SolidBrush(Color.Yellow); //ブラシを生成(黄色)
    Timer timer = new Timer(); //タイマーを生成
    int backy = 0; //背景画像のつなぎ目のY座標
    Item player; //プレイヤーの構造体オブジェクト
    static int maxpb = 10; //自弾の最大数
    Item[] pba = new Item[maxpb]; //自弾の構造体配列
    const int cold = 10; //【追加】自弾の冷却時間
    int waitpb = 0; //【追加】自弾発射待ち時間
    //中心座標を用いて画像を描画
    private void DrawItem(PaintEventArgs e, Item it) {
        int xx = it.x - it.i.Width / 2; //幅の半分を差し引いて左上X座標を得る
        int yy = it.y - it.i.Height / 2; //高さの半分を差し引いて左上Y座標を得る
        e.Graphics.DrawImage(it.i, xx, yy);
    }
    //描画処理
    protected override void OnPaint(PaintEventArgs e) { //Formのメソッドをオーバーライド
        base.OnPaint(e); //まず、Formクラスにおけるメソッドの内容(基本再描画処理)を実行
        e.Graphics.DrawImage(backi, 0, backy); //背景画像を(0,つなぎ目)から描画
        e.Graphics.DrawImage(backi, 0, backy - backi.Height); //背景画像をその上に描画
        if (gamemode == 0) { //スタート画面
            e.Graphics.DrawString("GAME1", fontt, brushs, 100, 100); //タイトルの描画
            e.Graphics.DrawString("Hit Enter to Start", font1, brushs, 200, 300); //メッセージの描画
        } else if (gamemode == 1) { //プレイ画面
            string s = String.Format("SCORE:{0:000,000}", score); //スコア文字列を編集
            e.Graphics.DrawString(s, font1, brushs, 400, 10); //スコア文字列の描画
            switch (player.hv) { //自機の向きによって分岐
                case  0: player.i = playeri; break; //通常
                case  1: player.i = playerr; break; //右寄 
                case -1: player.i = playerl; break; //左寄
            }
            DrawItem(e, player); //自機を表示
            foreach(var pb in pba) { //全自弾について繰返す
                if (pb.v == 1) { //自弾がある? 
                    DrawItem(e, pb); //自弾を表示
                }
            }
        }
    }
    //キー入力時処理
    void OnKeyDown(object o, KeyEventArgs e) { //キーボードが押された時に呼ばれるメソッド
        if(e.KeyCode.ToString() == "Escape") { //Escキーが押されたら
            Close(); //フォームアプリケーション終了
        }
        if(gamemode == 0 && e.KeyCode.ToString() == "Return") { //スタート画面でEnterキーが押されたら
            gamemode = 1; //プレイ画面に切り替える
            timer.Start(); //タイマー開始
            Invalidate(); //画面再描画を依頼する
        }
    }
    //タイマーイベント処理
    void Play(object o, EventArgs e) { //タイマーから呼ばれるメソッド
        backy = (backy < backi.Height) ? backy + 1 : 0; //背景画像のつなぎ目を下げる
        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; //右に移動中
            }
        }
        if (GetKeyState((int)Keys.Space) < 0) { //スペースキーが押されている?
            if (waitpb <= 0) { //【追加】自弾発射待ち時間ゼロ(発射可能)?
                for(int i = 0; i < maxpb; i++) { //全自弾について繰返す
                    if (pba[i].v == 0) { //自弾[i]が非表示?
                        pba[i].v = 1; //表示にする
                        pba[i].i = bulleti; //画像を設定
                        pba[i].x = player.x; //X座標は自機の中心に合わせる
                        pba[i].y = player.y - player.i.Height / 2 - bulleti.Height / 2; //Y座標は自機の直上に合わせる
                        pba[i].vv = -5; //上移動速度を設定
                        waitpb = cold; //【追加】自弾発射待ち時間をセット
                        break; //1つ見つかったら抜ける
                    }
                }
            }
        } else { //【以下追加】スペースキーが押されていない?
            waitpb = 0; //自弾発射待ち時間をゼロ(発射可能)にする
        }
        for(int i = 0; i < maxpb; i++) { //全自弾について繰返す
            if (pba[i].v == 1) { //自弾[i]が存在したら
                pba[i].i = (pba[i].i == bulleti) ? bullet2i : bulleti; //画像を交互変更
                pba[i].y += pba[i].vv; //Y座標に上移動速度を反映することで上昇
                if (pba[i].y + bulleti.Height / 2 < 0) { //画面上端より上に出たら
                    pba[i].v = 0; //自弾[i]を消す
                }
            }
        }
        if (waitpb > 0) { //【以下追加】自弾発射待ち時間がセットされている(待ち状態)?
            waitpb--; //カウントダウンする
        }
        Invalidate(); //画面再描画を依頼する
    }
    //コンストラクタ
    Program() { 
        DoubleBuffered = true; //ダブルバッファリングの導入
        KeyDown += new KeyEventHandler(OnKeyDown); //キーボードが押された時に呼ばれるメソッドを登録
        timer.Tick += new EventHandler(Play); //タイマーから呼ばれるメソッドを登録
        timer.Interval = 10; //タイマーの間隔
        player.i = playeri; //自機の画像
        player.x = 320; //自機のX座標
        player.y = 410; //自機のY座標
        player.hv = 0; //自機の左右方向の速度
    }
    static void Main() { //publicの指定は任意
        Program f = new Program(); //継承したフォームのインスタンスを生成
        f.Size = new Size(660, 520); //インスタンスのSizeプロパティに高さと幅を代入
        f.Text = "Game"; //インスタンスのTextプロパティにフォーム名を代入
        f.ControlBox = false; //ControlBoxを非表示にする
        f.FormBorderStyle = FormBorderStyle.Fixed3D; //フォームサイズ変更を禁止
        Application.Run(f); //インスタンスを画面に出す
    }
}

提出: 演習30または31(未完成でもOK)

コメントを残す

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