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

:演習24、自機の表示と左右移動、自機のアニメーション など

テーマ28 キーボードの状態を得る(再掲載)

・キー入力イベントを用いる手法は「キーを押している間、〇〇する」には向かない
・代わりにタイマーイベントを用いてキーボードの状態を得る処理を呼び出してもらうと良い
・これを実現するには、WindowsAPIを提供するDLL(動的リンクライブラリ)の一つである「user32.dll」を直接インポートする
・インポートの書式: [System.Runtime.InteropServices.DllImport("user32.dll")] ※セミコロン不要
・すると、これに含まれるGetKeyStateメソッドを外部定義指定により利用可能になる
・外部定義指定の書式: private static extern short GetKeyState(int nVirtKey);
・これでGetKeyStateメソッドに引数としてKey列挙子をint型にキャストして与えると、そのキーが押されていれば負の数が返される
・インポートと外部定義指定はクラス定義の先頭で行うこと

演習24 上矢印キーが押されていたらスコアアップ(再掲載)

・上矢印キーのKey列挙子はKeys.Up
・これを用いて、Playメソッド内で、上矢印キーが押されているかチェックし、押されていたらスコアをインクリメントしよう

作成例

//演習24 上矢印キーが押されていたらスコアアップ
using System; //フォームアプリケーションに必須
using System.Windows.Forms; //フォームアプリケーションに必須
using System.Drawing; //Image用
class Program : Form { //Formクラスを継承
    [System.Runtime.InteropServices.DllImport("user32.dll")] //【追加】外部DLLのインポート
    private static extern short GetKeyState(int nVirtKey); //【追加】メソッドの外部定義指定
    Image backb = Image.FromFile("backb.bmp"); //背景画像の読込
    Image enemy = Image.FromFile("enemy.gif"); //アイテム画像の読込
    Image burn = Image.FromFile("burn.gif"); //効果用画像の読込
    Pen mypen = new Pen(Color.Red, 2); //ペンを生成(初期値は赤、2)
    Brush bred = new SolidBrush(Color.FromArgb(63, 255, 0, 0)); //ブラシを生成(赤の透明色)
    Brush byel = new SolidBrush(Color.Yellow); //ブラシを生成(黄色)
    Font f20 = new Font("メイリオ", 20, FontStyle.Bold); //フォントを生成(メイリオ20P太字)
    Font f80 = new Font("メイリオ", 80, FontStyle.Bold); //フォントを生成(メイリオ80P太字)
    int gamemode = 0; //0:スタート画面、1:プレイ画面
    int score = 0; //スコア
    int backy = 0; //背景画像のつなぎ目のY座標
    Timer timer = new Timer(); //タイマーを生成
    void DrawCircle(PaintEventArgs e, Pen p, int x, int y, int r) { //ペン、中心座標、半径を指定して円を描画
        e.Graphics.DrawEllipse(p, x - r, y - r, 2 * r, 2 * r); //円の描画を呼ぶ
    }
    protected override void OnPaint(PaintEventArgs e) { //Formのメソッドをオーバーライド
        base.OnPaint(e); //まず、Formクラスにおけるメソッドの内容(基本再描画処理)を実行
        e.Graphics.DrawImage(backb, 0, backy); //背景画像を(0,つなぎ目)から描画
        e.Graphics.DrawImage(backb, 0, backy - backb.Height); //背景画像をその上に描画
        if (gamemode == 0) { //スタート画面
            e.Graphics.DrawString("GAME1", f80, byel, 100, 100); //タイトルの描画
            e.Graphics.DrawString("Hit Enter to Start", f20, byel, 200, 300); //メッセージの描画
        } else if (gamemode == 1) { //プレイ画面
            e.Graphics.FillRectangle(bred, 78, 411, 485, 64); //判定エリアを透明赤で塗りつぶす
            e.Graphics.DrawRectangle(mypen, 78, 411, 485, 64); //判定エリアの描画
            string s = String.Format("SCORE:{0:000,000}", score); //スコア文字列を編集
            e.Graphics.DrawString(s, f20, byel, 400, 10); //スコア文字列の描画
        }
    }
    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) { //タイマーから呼ばれるメソッド
        //score++; //【削除】スコアアップ
        backy = (backy < backb.Height) ? backy + 1 : 0; //背景画像のつなぎ目を下げる
        if (GetKeyState((int)Keys.Up) < 0) { //【以下追加】↑キーが押されている?
            score++; //スコアアップ
        }
        Invalidate(); //画面再描画を依頼する
    }
    Program() { //コンストラクタ
        DoubleBuffered = true; //ダブルバッファリングの導入
        KeyDown += new KeyEventHandler(OnKeyDown); //キーボードが押された時に呼ばれるメソッドを登録
        timer.Tick += new EventHandler(Play); //タイマーから呼ばれるメソッドを登録
        timer.Interval = 10; //タイマーの間隔
    }
    static void Main() { //publicの指定は任意
        Program p = new Program(); //継承したフォームのインスタンスを生成
        p.Width = 660; //インスタンスのWidthプロパティに幅を代入
        p.Height = 520; //インスタンスのHeightプロパティに高さを代入
        p.Text = "Game"; //インスタンスのTextプロパティにフォーム名を代入
        p.ControlBox = false; //ControlBoxを非表示にする
        p.FormBorderStyle = FormBorderStyle.Fixed3D; //フォームサイズ変更を禁止
        Application.Run(p); //インスタンスを画面に出す
    }
}

演習25 自機を表示し左右に動かす

・自機の通常画像(player.gif)をダウンロードして配置
 
・自機通常画像を初期位置(300,390)から描画
・左矢印キー(Keys.Left)が押されていたら左へ10ドット、右(Keys.Right)なら右へ10ドット移動する
・赤い枠とスコアアップは削除

作成例

//演習25 自機を表示し左右に動かす
using System; //フォームアプリケーションに必須
using System.Windows.Forms; //フォームアプリケーションに必須
using System.Drawing; //Image用
class Program : Form { //Formクラスを継承
    [System.Runtime.InteropServices.DllImport("user32.dll")] //外部DLLのインポート
    private static extern short GetKeyState(int nVirtKey); //メソッドの外部定義指定
    Image backb = Image.FromFile("backb.bmp"); //背景画像の読込
    Image enemy = Image.FromFile("enemy.gif"); //アイテム画像の読込
    Image burn = Image.FromFile("burn.gif"); //効果用画像の読込
    Image playeri = Image.FromFile("player.gif"); //【追加】自機画像の読込
    //Pen mypen = new Pen(Color.Red, 2); //【削除】ペンを生成(初期値は赤、2)
    //Brush bred = new SolidBrush(Color.FromArgb(63, 255, 0, 0)); //【削除】ブラシを生成(赤の透明色)
    Brush byel = new SolidBrush(Color.Yellow); //ブラシを生成(黄色)
    Font f20 = new Font("メイリオ", 20, FontStyle.Bold); //フォントを生成(メイリオ20P太字)
    Font f80 = new Font("メイリオ", 80, FontStyle.Bold); //フォントを生成(メイリオ80P太字)
    int gamemode = 0; //0:スタート画面、1:プレイ画面
    int score = 0; //スコア
    int backy = 0; //背景画像のつなぎ目のY座標
    int playerx = 300; //【追加】自機のX座標
    Timer timer = new Timer(); //タイマーを生成
    void DrawCircle(PaintEventArgs e, Pen p, int x, int y, int r) { //ペン、中心座標、半径を指定して円を描画
        e.Graphics.DrawEllipse(p, x - r, y - r, 2 * r, 2 * r); //円の描画を呼ぶ
    }
    protected override void OnPaint(PaintEventArgs e) { //Formのメソッドをオーバーライド
        base.OnPaint(e); //まず、Formクラスにおけるメソッドの内容(基本再描画処理)を実行
        e.Graphics.DrawImage(backb, 0, backy); //背景画像を(0,つなぎ目)から描画
        e.Graphics.DrawImage(backb, 0, backy - backb.Height); //背景画像をその上に描画
        if (gamemode == 0) { //スタート画面
            e.Graphics.DrawString("GAME1", f80, byel, 100, 100); //タイトルの描画
            e.Graphics.DrawString("Hit Enter to Start", f20, byel, 200, 300); //メッセージの描画
        } else if (gamemode == 1) { //プレイ画面
            //e.Graphics.FillRectangle(bred, 78, 411, 485, 64); //【削除】判定エリアを透明赤で塗りつぶす
            //e.Graphics.DrawRectangle(mypen, 78, 411, 485, 64); //【削除】判定エリアの描画
            string s = String.Format("SCORE:{0:000,000}", score); //スコア文字列を編集
            e.Graphics.DrawString(s, f20, byel, 400, 10); //スコア文字列の描画
            e.Graphics.DrawImage(playeri, playerx, 390); //【追加】自機画像を描画
        }
    }
    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 < backb.Height) ? backy + 1 : 0; //背景画像のつなぎ目を下げる
        //if (GetKeyState((int)Keys.Up) < 0) { //【以下削除】↑キーが押されている?
        //    score++; //スコアアップ
        //}
        if (gamemode == 1) { //【以下追加】プレイ画面
            if (GetKeyState((int)Keys.Left) < 0) { //←キーが押されている?
                playerx -= 10; //自機を左へ
            }
            if (GetKeyState((int)Keys.Right) < 0) { //→キーが押されている?
                playerx += 10; //自機を右へ
            }
        }
        Invalidate(); //画面再描画を依頼する
    }
    Program() { //コンストラクタ
        DoubleBuffered = true; //ダブルバッファリングの導入
        KeyDown += new KeyEventHandler(OnKeyDown); //キーボードが押された時に呼ばれるメソッドを登録
        timer.Tick += new EventHandler(Play); //タイマーから呼ばれるメソッドを登録
        timer.Interval = 10; //タイマーの間隔
    }
    static void Main() { //publicの指定は任意
        Program p = new Program(); //継承したフォームのインスタンスを生成
        p.Width = 660; //インスタンスのWidthプロパティに幅を代入
        p.Height = 520; //インスタンスのHeightプロパティに高さを代入
        p.Text = "Game"; //インスタンスのTextプロパティにフォーム名を代入
        p.ControlBox = false; //ControlBoxを非表示にする
        p.FormBorderStyle = FormBorderStyle.Fixed3D; //フォームサイズ変更を禁止
        Application.Run(p); //インスタンスを画面に出す
    }
}

演習25の続き 自機移動範囲の制限と画像変更

・←が押されているとき、自機左寄画像 playerl.gif を表示しよう
 
・→が押されているとき、自機右寄画像 playerr.gif を表示しよう
 
・そして、自機が左端・右端から出ないように制限しよう
・なお、背景、自機の幅は.Widthを用いること
・つまり、左は0チェックで良いが、右はX座標が「背景.Width-自機.Width」を超えるとはみ出す可能性がある(マージンによって調整する)

ヒント
・←と→が押されているとき、違う画像を表示するには、自機がどちらに向かっているのかを示す変数playervを用いると良い
・playerv(0:停止,-1:左,1:右)を、playメソッドの先頭で0にしておき、左や右が押されていたら、対応する値にする
・そして、onPaintメソッドでplayervの値に応じて描画する画像を選べば良い

作成例

//演習25 自機を表示し左右に動かす
using System; //フォームアプリケーションに必須
using System.Windows.Forms; //フォームアプリケーションに必須
using System.Drawing; //Image用
class Program : Form { //Formクラスを継承
    [System.Runtime.InteropServices.DllImport("user32.dll")] //外部DLLのインポート
    private static extern short GetKeyState(int nVirtKey); //メソッドの外部定義指定
    Image backb = Image.FromFile("backb.bmp"); //背景画像の読込
    Image enemy = Image.FromFile("enemy.gif"); //アイテム画像の読込
    Image burn = Image.FromFile("burn.gif"); //効果用画像の読込
    Image playeri = Image.FromFile("player.gif"); //【追加】自機画像の読込
    Image playerl = Image.FromFile("playerl.gif"); //【追加】自機左寄画像の読込
    Image playerr = Image.FromFile("playerr.gif"); //【追加】自機右寄画像の読込
    Brush byel = new SolidBrush(Color.Yellow); //ブラシを生成(黄色)
    Font f20 = new Font("メイリオ", 20, FontStyle.Bold); //フォントを生成(メイリオ20P太字)
    Font f80 = new Font("メイリオ", 80, FontStyle.Bold); //フォントを生成(メイリオ80P太字)
    int gamemode = 0; //0:スタート画面、1:プレイ画面
    int score = 0; //スコア
    int backy = 0; //背景画像のつなぎ目のY座標
    int playerx = 300; //【追加】自機のX座標
    int playerv = 0; //【追加】自機の左右移動(0:停止,-1:左,1:右)
    Timer timer = new Timer(); //タイマーを生成
    protected override void OnPaint(PaintEventArgs e) { //Formのメソッドをオーバーライド
        base.OnPaint(e); //まず、Formクラスにおけるメソッドの内容(基本再描画処理)を実行
        e.Graphics.DrawImage(backb, 0, backy); //背景画像を(0,つなぎ目)から描画
        e.Graphics.DrawImage(backb, 0, backy - backb.Height); //背景画像をその上に描画
        if (gamemode == 0) { //スタート画面
            e.Graphics.DrawString("GAME1", f80, byel, 100, 100); //タイトルの描画
            e.Graphics.DrawString("Hit Enter to Start", f20, byel, 200, 300); //メッセージの描画
        } else if (gamemode == 1) { //プレイ画面
            string s = String.Format("SCORE:{0:000,000}", score); //スコア文字列を編集
            e.Graphics.DrawString(s, f20, byel, 400, 10); //スコア文字列の描画
            switch (playerv) { //【以下追加】動きに合わせた自機画像を描画
                case  0: e.Graphics.DrawImage(playeri, playerx, 390); break;
                case  1: e.Graphics.DrawImage(playerr, playerx, 390); break; 
                case -1: e.Graphics.DrawImage(playerl, playerx, 390); break; 
            }
        }
    }
    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 < backb.Height) ? backy + 1 : 0; //背景画像のつなぎ目を下げる
        if (gamemode == 1) { //【以下追加】プレイ画面
            playerv = 0; //仮に自機は停止中とする
            if (playerx > 0 && GetKeyState((int)Keys.Left) < 0) { //左端ではなく←キーが押されている?
                playerx -= 10; //自機を左へ
                playerv = -1; //左に移動中
            }
            if (playerx < backb.Width - playeri.Width && GetKeyState((int)Keys.Right) < 0) { //右端ではなく→キーが押されている?
                playerx += 10; //自機を右へ
                playerv = 1; //右に移動中
            }
        }
        Invalidate(); //画面再描画を依頼する
    }
    Program() { //コンストラクタ
        DoubleBuffered = true; //ダブルバッファリングの導入
        KeyDown += new KeyEventHandler(OnKeyDown); //キーボードが押された時に呼ばれるメソッドを登録
        timer.Tick += new EventHandler(Play); //タイマーから呼ばれるメソッドを登録
        timer.Interval = 10; //タイマーの間隔
    }
    static void Main() { //publicの指定は任意
        Program p = new Program(); //継承したフォームのインスタンスを生成
        p.Width = 660; //インスタンスのWidthプロパティに幅を代入
        p.Height = 520; //インスタンスのHeightプロパティに高さを代入
        p.Text = "Game"; //インスタンスのTextプロパティにフォーム名を代入
        p.ControlBox = false; //ControlBoxを非表示にする
        p.FormBorderStyle = FormBorderStyle.Fixed3D; //フォームサイズ変更を禁止
        Application.Run(p); //インスタンスを画面に出す
    }
}

提出:演習25 自機を表示し左右に動かす(追加分は含んでもOK)

コメントを残す

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