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

:図形の描画 など

提出フォロー:演習13 Escキーが押されたら終了

・何かキーが押されたら、どのキーかを得て、Escキー(文字列"Escape")であれば終了しよう
 ※メッセージボックスの表示は削除する

作成例

//演習13 Escキーが押されたら終了
using System; //C#標準クラス用
using System.Windows.Forms; //C#が提供するApplication、Formクラス用
using System.Drawing; //Size、Graphics、Imageクラス用
class Program : Form { //Formクラスの派生クラス
    Image backi, playeri; //画像ファイル用変数
    protected override void OnPaint(PaintEventArgs e) { //描画処理のオーバライド
        base.OnPaint(e); //元のメソッドの内容を呼び出す
        e.Graphics.DrawImage(backi, 0, 0); //画像をフォーム左上に配置
        e.Graphics.DrawImage(playeri, backi.Width / 2 - playeri.Width / 2,
            backi.Height - playeri.Height * 2); //自機を中央下部に描画
    }
    public Program() { //コンストラクタ
        try { //例外処理対象
            backi = Image.FromFile("backb2.bmp"); //背景画像ファイルを読み込む
            playeri = Image.FromFile("player.gif"); //自機画像ファイルを読み込む
        } catch (Exception e) { //例外処理内容
            MessageBox.Show(e.ToString()); //内容をメッセージボックスに表示
        }
        KeyDown += new KeyEventHandler(OnKeyDown); //キー押し下げ時のメソッドを登録
    }
    void OnKeyDown(object o, KeyEventArgs e){ //キー押し下げ時に呼ばれるメソッド
        if (e.KeyCode.ToString() == "Escape") { //【変更】押されたキーのコードを文字列化したらEscape?
            Close(); //フォームアプリケーションを終了 //【復活】
        }
    }
    static void Main() { //実行用メソッド(publicはなくてOK)
        Program f = new Program(); //自クラスのインスタンスを生成
        f.Text = "Game"; //Form名を設定
        f.StartPosition = FormStartPosition.Manual; //「手動設定」を設定
        Point p = new Point(300,0); //X座標とY座標のPointインスタンスを生成
        f.Location = p; //インスタンスプロパティで初期位置を設定
        f.FormBorderStyle = FormBorderStyle.FixedSingle; //フォームサイズの固定化
        f.ControlBox = false; //コントールボックスの非表示
        f.ClientSize = new Size(640, 480); //クライアントサイズの幅と高さを指定
        Application.Run(f); //生成済のインスタンスを実行
    }
}

テーマ15 線の描画

・画面の線の描画にはPaintEventArgsオブジェクトが必要で、OSによる画面再描画の影響を受けるので、
 OnPaintオーバーライドメソッドの中で行う
・線の描画には、描画に用いるSystem.Drawing.Penオブジェクトを用いる
・System.Drawing.Penクラスのコンストラクタ(Color, float)に色と太さを与えることで、好みのPenオブジェクトが得られる
 ※なお、Penクラスのコンストラクタは多数あり、各種の効果を得ることが可能
・Penクラスのコンストラクタに与える色は、System.Drawing.Color構造体オブジェクトに含まれる静的プロパティを用いると便利
・色ごとに、Color.Red、Color.Yellowなどの静的プロパティが多数用意されている
 https://learn.microsoft.com/ja-jp/dotnet/api/system.drawing.color
・Penクラスのコンストラクタに与える太さは単精度実数で指定可能
 例: Pen mypen = new Pen(Color.Blue, 10); //青で太さ10のペンを生成
・線の描画は PaintEventArgsオブジェクトeとPenオブジェクトを用いて、下記の書式で行う
 e.Graphics.DrawLine(Penオブジェクト, 開始X座標, 開始Y座標, 終了X座標, 終了X座標);

演習14 対角線を描こう

・画面上に赤い太さ10の対角線2本を描こう

作成例

//演習14 対角線を描こう
using System; //C#標準クラス用
using System.Windows.Forms; //C#が提供するApplication、Formクラス用
using System.Drawing; //Size、Graphics、Imageクラス用
class Program : Form { //Formクラスの派生クラス
    Image backi, playeri; //画像ファイル用変数
    protected override void OnPaint(PaintEventArgs e) { //描画処理のオーバライド
        base.OnPaint(e); //元のメソッドの内容を呼び出す
        e.Graphics.DrawImage(backi, 0, 0); //画像をフォーム左上に配置
        e.Graphics.DrawImage(playeri, backi.Width / 2 - playeri.Width / 2,
            backi.Height - playeri.Height * 2); //自機を中央下部に描画
        Pen pRed10 = new Pen(Color.Red , 10); //【以下追加】赤色の太さ10のペンを生成
        e.Graphics.DrawLine(pRed10, 0, 0, backi.Width - 1, backi.Height - 1); //対角線①を描く
        e.Graphics.DrawLine(pRed10, 0, backi.Height - 1, backi.Width - 1, 0); //対角線②を描く
    }
    public Program() { //コンストラクタ
        try { //例外処理対象
            backi = Image.FromFile("backb2.bmp"); //背景画像ファイルを読み込む
            playeri = Image.FromFile("player.gif"); //自機画像ファイルを読み込む
        } catch (Exception e) { //例外処理内容
            MessageBox.Show(e.ToString()); //内容をメッセージボックスに表示
        }
        KeyDown += new KeyEventHandler(OnKeyDown); //キー押し下げ時のメソッドを登録
    }
    void OnKeyDown(object o, KeyEventArgs e){ //キー押し下げ時に呼ばれるメソッド
        if (e.KeyCode.ToString() == "Escape") { //押されたキーのコードを文字列化したらEscape?
            Close(); //フォームアプリケーションを終了
        }
    }
    static void Main() { //実行用メソッド(publicはなくてOK)
        Program f = new Program(); //自クラスのインスタンスを生成
        f.Text = "Game"; //Form名を設定
        f.StartPosition = FormStartPosition.Manual; //「手動設定」を設定
        Point p = new Point(300,0); //X座標とY座標のPointインスタンスを生成
        f.Location = p; //インスタンスプロパティで初期位置を設定
        f.FormBorderStyle = FormBorderStyle.FixedSingle; //フォームサイズの固定化
        f.ControlBox = false; //コントールボックスの非表示
        f.ClientSize = new Size(640, 480); //クライアントサイズの幅と高さを指定
        Application.Run(f); //生成済のインスタンスを実行
    }
}

テーマ16 矩形の描画

・矩形(長方形、正方形)はDrawLineでも描けるが、DrawRectangle()を用いると便利
・PaintEventArgsオブジェクトeとPenオブジェクトを用いて、下記の書式で行う
 e.Graphics.DrawRectangle(Penオブジェクト, 左上X座標, 左上Y座標, 幅, 高さ);

演習15 升目を描こう

・画面上に9行9列の升目を描こう
・座標(10, 20)から各マスを30×30で、枠線の色はCyan、枠線の太さは2とする
・演習14の対角線は削除する

作成例

//演習15 升目を描こう
using System; //C#標準クラス用
using System.Windows.Forms; //Application、Formクラス用
using System.Drawing; //Size、Graphics、Image、Penクラス、Color構造体用
class Program : Form { //Formクラスの派生クラス
    Image backi, playeri; //画像ファイル用変数
    protected override void OnPaint(PaintEventArgs e) { //描画処理のオーバライド
        base.OnPaint(e); //元のメソッドの内容を呼び出す
        e.Graphics.DrawImage(backi, 0, 0); //画像をフォーム左上に配置
        e.Graphics.DrawImage(playeri, backi.Width / 2 - playeri.Width / 2,
            backi.Height - playeri.Height * 2); //自機を中央下部に描画
        Pen pCyan2 = new Pen(Color.Cyan, 2); //【以下追加】水色の太さ2のペンを生成
        for(int i = 0; i < 9; i++) { //9列
            for(int j = 0; j < 9; j++) { //9行
                e.Graphics.DrawRectangle(pCyan2, 10 + i * 30, 20 + j * 30, 30, 30); //枠を描く
            }
        }
    }
    public Program() { //コンストラクタ
        try { //例外処理対象
            backi = Image.FromFile("backb2.bmp"); //背景画像ファイルを読み込む
            playeri = Image.FromFile("player.gif"); //自機画像ファイルを読み込む
        } catch (Exception e) { //例外処理内容
            MessageBox.Show(e.ToString()); //内容をメッセージボックスに表示
        }
        KeyDown += new KeyEventHandler(OnKeyDown); //キー押し下げ時のメソッドを登録
    }
    void OnKeyDown(object o, KeyEventArgs e){ //キー押し下げ時に呼ばれるメソッド
        if (e.KeyCode.ToString() == "Escape") { //押されたキーのコードを文字列化したらEscape?
            Close(); //フォームアプリケーションを終了
        }
    }
    static void Main() { //実行用メソッド(publicはなくてOK)
        Program f = new Program(); //自クラスのインスタンスを生成
        f.Text = "Game"; //Form名を設定
        f.StartPosition = FormStartPosition.Manual; //「手動設定」を設定
        Point p = new Point(300,0); //X座標とY座標のPointインスタンスを生成
        f.Location = p; //インスタンスプロパティで初期位置を設定
        f.FormBorderStyle = FormBorderStyle.FixedSingle; //フォームサイズの固定化
        f.ControlBox = false; //コントールボックスの非表示
        f.ClientSize = new Size(640, 480); //クライアントサイズの幅と高さを指定
        Application.Run(f); //生成済のインスタンスを実行
    }
}

テーマ17 円・楕円の描画

・円・楕円はDrawRectangle()の応用で、DrawEllipse()を用いる
・中心座標や半径を指定する代わりに、円・楕円に外接する矩形の左上座標, 幅, 高さを用いる
・よって、幅と高さが等しければ円に、でなければ楕円になる
・PaintEventArgsオブジェクトeとPenオブジェクトを用いて、下記の書式で行う
 e.Graphics.DrawEllipse(Penオブジェクト, 外接矩形の左上X座標, 外接矩形の左上Y座標, 外接矩形の幅, 外接矩形の高さ);

演習16 升目の中に円を描こう

・演習15で描いた9行9列の升目の中に内接する円を描こう

作成例

//演習16 升目の中に円を描こう
using System; //C#標準クラス用
using System.Windows.Forms; //Application、Formクラス用
using System.Drawing; //Size、Graphics、Image、Penクラス、Color構造体用
class Program : Form { //Formクラスの派生クラス
    Image backi, playeri; //画像ファイル用変数
    protected override void OnPaint(PaintEventArgs e) { //描画処理のオーバライド
        base.OnPaint(e); //元のメソッドの内容を呼び出す
        e.Graphics.DrawImage(backi, 0, 0); //画像をフォーム左上に配置
        e.Graphics.DrawImage(playeri, backi.Width / 2 - playeri.Width / 2,
            backi.Height - playeri.Height * 2); //自機を中央下部に描画
        Pen pCyan2 = new Pen(Color.Cyan, 2); //水色の太さ2のペンを生成
        for(int i = 0; i < 9; i++) { //9列
            for(int j = 0; j < 9; j++) { //9行
                e.Graphics.DrawRectangle(pCyan2, 10 + i * 30, 20 + j * 30, 30, 30); //枠を描く
                e.Graphics.DrawEllipse(pCyan2, 10 + i * 30, 20 + j * 30, 30, 30); //【追加】内接円を描く
            }
        }
    }
    public Program() { //コンストラクタ
        try { //例外処理対象
            backi = Image.FromFile("backb2.bmp"); //背景画像ファイルを読み込む
            playeri = Image.FromFile("player.gif"); //自機画像ファイルを読み込む
        } catch (Exception e) { //例外処理内容
            MessageBox.Show(e.ToString()); //内容をメッセージボックスに表示
        }
        KeyDown += new KeyEventHandler(OnKeyDown); //キー押し下げ時のメソッドを登録
    }
    void OnKeyDown(object o, KeyEventArgs e){ //キー押し下げ時に呼ばれるメソッド
        if (e.KeyCode.ToString() == "Escape") { //押されたキーのコードを文字列化したらEscape?
            Close(); //フォームアプリケーションを終了
        }
    }
    static void Main() { //実行用メソッド(publicはなくてOK)
        Program f = new Program(); //自クラスのインスタンスを生成
        f.Text = "Game"; //Form名を設定
        f.StartPosition = FormStartPosition.Manual; //「手動設定」を設定
        Point p = new Point(300,0); //X座標とY座標のPointインスタンスを生成
        f.Location = p; //インスタンスプロパティで初期位置を設定
        f.FormBorderStyle = FormBorderStyle.FixedSingle; //フォームサイズの固定化
        f.ControlBox = false; //コントールボックスの非表示
        f.ClientSize = new Size(640, 480); //クライアントサイズの幅と高さを指定
        Application.Run(f); //生成済のインスタンスを実行
    }
}

演習17 同心円を描こう

・フォームの中央から外側に向かって半径を10ずつ増やしながら9個の同心円を描こう

提出:演習17 同心円を描こう

講義メモ

テキスト篇:第11章「構造体」から
ゲーム開発演習:図形の描画 など

第11章 構造体

p.275 構造体とは

・C#の基であるC言語にあった「クラスの基になったデータ構造」で、複数の変数や配列をグループ化して名前を付け、
 ユーザー定義の型であるとして用いる手法
・対して、C#はこれをっ拡大定義して、メソッドやプロパティなども持つことができるようにしている
・結果的に、C#の構造体は「軽量クラス」の位置づけで、クラスの機能を削って軽くしたもの。
 ※そのため、Unityなどで活用されており、Vector3構造体など、Unity独自の構造体の出番が多い
・クラスの違いは:
 ①値型(p.188)なので、メソッドに渡した場合、全データメンバのコピーが発生する
 ②継承(p.223)できない(インターフェイスの実装であれば可能)
 ③引数のないコンストラクタ(p.167)をプログラマが定義することはできない(自動的に用意されるもののみ)
 ④インスタンス変数(p.154)の初期化は不可
 ⑤デストラクタ(p.171)は定義不可
・定義書式: struct 構造体名 {…}
 例: struct Map { int x; int y; } //X座標とY座標を持つマップの構造体
・インターフェイス(p.255)を実装する場合の書式: struct 構造体名 : インターフェイス名 {…}
・クラスと同様に構造体オブジェクトを宣言して利用する(宣言のみでオブジェクトが生成される)
・宣言書式: 構造体名 変数名; //構造体を型とする変数の宣言
・メンバの利用書式はクラスのインスタンスメンバと同様で「変数名.メンバ名」で良い
 例: Map hoimin; hoimin.x = 50; hoimin.y = 60; //マップ「hoimin」を生成しX座標Y座標を設定
・構造体のメンバとしては、データメンバに加えて、メソッド、プロパティ、インデクサ、コンストラクタ(引数有り)などが
 定義できる

アレンジ演習:p.276 struct01.cs

・p.277にあるとおり、インスタンス変数xを初期化するとどういうエラーになるか確認しよう
⇒ 初期化において変数に「10.0以上の言語バージョンをお使いください」と表示される
 ※.NETフレームワークでは6.xに該当するが、VS2022に標準インストールされるのは4.7.2
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/language-versioning
 ※よって「Visual Studio用 .NET SDK」のページで上位バージョンの.NETフレームワークをインストールすれば解決するが、
  他の影響に配慮が必要
 https://dotnet.microsoft.com/ja-jp/download/visual-studio-sdks?cid=getdotnetsdk

作成例

//アレンジ演習:p.276 struct01.cs
using System;
struct MyStruct //構造体MyStructの定義
{
    public int x; //公開のインスタンス変数
    //public int x = 10; //公開のインスタンス変数 ※初期化するとエラーになる
    public void show() //公開のインスタンスメソッド
    {
        Console.WriteLine("x = {0}", x);
    }
}
class struct01
{
    public static void Main()
    {
        MyStruct ms; //構造体MyStruct型の変数msの定義(構造体オブジェクト生成)
        ms.x = 10; //変数ms経由でインスタンス変数xに代入
        ms.show(); //変数ms経由でインスタンスメソッドshow()を実行
    }
}

p.277 静的メンバを持つ構造体

・クラスと同様に、構造体にも静的メンバ(p.202)を定義できる
・よって、構造体に所属するデータメンバは静的データメンバにし、これを用いるメソッド、プロパティは静的メンバにすると良い
・「構造体名.静的メンバ名」でアクセスできる

アレンジ演習:p.277 struct02.cs その1

・静的データメンバxを扱う静的プロパティPXを追加定義
・xをprivateに変更し、Main()では静的プロパティPX経由でアクセスするように変更

作成例

//アレンジ演習:p.277 struct02.cs
using System;
struct MyStruct //構造体の定義
{
    static int x = 10; //【変更】非公開の静的データメンバ
    static int[] myarray = new int[10]; //静的データメンバである配列
    public static void show() //公開の静的メソッド
    {
        Console.WriteLine("x = {0}", x); //静的データメンバを利用
    }
    public static int PX //【追加】公開の静的プロパティ
    {
        set { x = value; }
        get { return x; }
    }
}
class struct02
{
    public static void Main()
    {
        MyStruct.show(); //公開の静的メソッドなので「構造体名.メソッド名」で呼べる
        MyStruct.PX = 20; //【変更】公開の静的プロパティなので「構造体名.プロパティ名」で利用可
        MyStruct.show(); //20になっている
    }
}

アレンジ演習:p.277 struct02.cs その2

・MyStruct構造体には静的データメンバである配列myarrayが定義されているが使われてない
・これを外部から利用できるようにインデクサ(p.212)を追記し、Main()から利用してみよう

作成例

//アレンジ演習:p.277 struct02.cs その2
using System;
struct MyStruct //構造体の定義
{
    static int x = 10; //【変更】非公開の静的データメンバ
    static int[] myarray = new int[10]; //静的データメンバである配列
    public static void show() //公開の静的メソッド
    {
        Console.WriteLine("x = {0}", x); //静的データメンバを利用
    }
    public static int PX //【追加】公開の静的プロパティ
    {
        get { return x; }
        set { x = value; }
    }
    public int this[int i] //【追加】公開のインデクサ(静的にはできない)
    {
        get { return myarray[i]; } //静的データメンバである配列は利用可
        set { myarray[i] = value; }
    }
}
class struct02
{
    public static void Main()
    {
        MyStruct.show(); //公開の静的メソッドなので「構造体名.メソッド名」で呼べる
        MyStruct.PX = 20; //【変更】公開の静的プロパティなので「構造体名.プロパティ名」で利用可
        MyStruct.show(); //20になっている
        MyStruct m; m[0] = 30; //【追加】変数を定義し、変数経由でインデクサ(set)を呼ぶ
        Console.WriteLine("m[0] = {0}", m[0]); //【追加】変数経由でインデクサ(get)を呼ぶ
    }
}

アレンジ演習:p.277 struct02.cs その3

・インデクサを静的にはできないので、Main()で変数を定義し、変数経由でインデクサを呼んでいる
・しかし、インデクサで用いている静的データメンバである配列は構造体に所属するので、オブジェクトには含まれていない
・このことを、変数を追加定義し、その変数経由でインデクサを呼ぶことで確認しよう

作成例

//アレンジ演習:p.277 struct02.cs その3
using System;
struct MyStruct //構造体の定義
{
    static int x = 10; //【変更】非公開の静的データメンバ
    static int[] myarray = new int[10]; //静的データメンバである配列
    public static void show() //公開の静的メソッド
    {
        Console.WriteLine("x = {0}", x); //静的データメンバを利用
    }
    public static int PX //【追加】公開の静的プロパティ
    {
        get { return x; }
        set { x = value; }
    }
    public int this[int i] //【追加】公開のインデクサ(静的にはできない)
    {
        get { return myarray[i]; } //静的データメンバである配列は利用可
        set { myarray[i] = value; }
    }
}
class struct02
{
    public static void Main()
    {
        MyStruct.show(); //公開の静的メソッドなので「構造体名.メソッド名」で呼べる
        MyStruct.PX = 20; //【変更】公開の静的プロパティなので「構造体名.プロパティ名」で利用可
        MyStruct.show(); //20になっている
        MyStruct m; m[0] = 30; //【追加】変数を定義し、変数経由でインデクサ(set)を呼ぶ
        Console.WriteLine("m[0] = {0}", m[0]); //【追加】変数経由でインデクサ(get)を呼ぶ
        MyStruct n; //【追加】変数を追加定義する(m[0]とn[0]は同じ実体になる)
        Console.WriteLine("n[0] = {0}", n[0]); //【追加】この変数経由でインデクサを呼ぶと同じ値になる
    }
}

p.278 コンストラクタを持つ構造体

・引数のあるコンストラクタを構造体に定義できる
・このコンストラクタは、構造体オブジェクトの生成時に、new演算子を用いて、
 構造体型の変数 = new 構造体名(実引数,…);
 などとすることで呼び出せる。
・なお、引数のないコンストラクタは定義できないが、自動的に全データメンバを初期化するデフォルトコンストラクタが用意されるので
 「new 構造体名()」で明示的に呼び出すことも可能

p.278(構造体型の変数の間の代入)

・構造体型の2変数において代入が可能で、全データメンバの値がコピーされる
・よって、代入後の変数経由で値を書き換えた場合、コピー後の値なので、コピー元には反映しない
※同様のことをクラスで行うと参照により、代入元にも反映する

アレンジ演習:p.278 struct03.cs

・引数のないコンストラクタを追記するとどういうエラーになるか確認しよう
⇒初期化において変数に「10.0以上の言語バージョンをお使いください」と表示される
 ※よって「Visual Studio用 .NET SDK」のページで上位バージョンの.NETフレームワークをインストールすれば解決するが、
  他の影響に配慮が必要

作成例

//アレンジ演習:p.278 struct03.cs
using System;
struct MyStruct
{
    public int x; //公開のデータメンバ
    public MyStruct(int a) //引数のあるコンストラクタ
    {
        x = a;
    }
    //public MyStruct() { }//引数のないコンストラクタは記述不可
}
class struct03
{
    public static void Main()
    {
        MyStruct ms = new MyStruct(); //用意された引数のないコンストラクタが呼ばれる
        Console.WriteLine("ms.x = {0}", ms.x); //自動的に0で初期化されている
        MyStruct ms2 = new MyStruct(100); //引数のあるコンストラクタが呼ばれる
        Console.WriteLine("ms2.x = {0}", ms2.x); //引数経由で100が代入されている
        MyStruct ms3; //用意された引数のないコンストラクタが呼ばれる
        ms3 = ms2; //ms2の値をms3に代入すると構造体オブジェクトのコピーになる
        Console.WriteLine("ms3.x = {0}", ms3.x); //値(100)がコピーされている
        ms3.x = 50; //コピー先の構造体オブジェクトのms3.xの値を50に変更しても
        Console.WriteLine("ms2.x = {0}", ms2.x); //コピー元の構造体オブジェクトの値には影響しない
    }
}

今週の話題

ゲームソフト販売本数ランキング:今週1位は「ファンタジーライフi グルグルの⻯と時をぬすむ少女(Switch)」 GO!
「MMORPG運営はユーザーの気持ちがわかるのか」運営Dとユーザーを呼んで答え合わせしてみた。 GO!
AI活用ゲーム開発ハッカソン、賞金100万円を獲得した作品は?QwenのAlibaba Cloud主催『Tokyo Game Jam』イベントリポート GO!
PlayStationロイヤリティプログラム「PlayStation Stars」2026年11月2日サービス終了。新規メンバー受付はすでに終了 GO!
6月7日より開催の「Summer Game Fest 2025」は60以上のパートナーが参加!スイッチ2も名を連ねる GO!

トランプ対策!?テイクツー、年次報告の多様性に関する表記を「思考の多様性」に置き換え―LGBTQ支援で授賞の雇用賞も表記せず GO!
米ニュースメディア連合、Google 『AIモード』は記事の「窃盗そのものだ」と非難 GO!

【参考】Switch2でアップデートしたフレームレートやHDR、DLSS、レイトレーシングなどの用語をなるべくわかりやすく解説してみます  GO!

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

画像のプロパティ、画像の重ね合わせ、キーが押された情報を得る など

提出フォロー:演習9 背景画像の描画(エラー対処付き)

・画像ファイルの読み込みをコンストラクタで行うようにしよう
・そして、画像ファイル読込のトラブルへの対応を盛り込み、エラー発生時はメッセージボックスを表示しよう
・この時、catchを「catch (exception e)」とすることで、エラー発生オブジェクトを得ると良い
・このオブジェクトの文字列表現を「e.ToString()」で得てメッセージボックスに表示しよう

作成例

//演習9 背景画像の描画(エラー対処付き)
using System; //C#標準クラス用
using System.Windows.Forms; //C#が提供するApplication、Formクラス用
using System.Drawing; //Size、Graphics、Imageクラス用
class Program : Form { //Formクラスの派生クラス
    Image backi; //【変更】画像ファイル用変数
    protected override void OnPaint(PaintEventArgs e) { //描画処理のオーバライド
        base.OnPaint(e); //元のメソッドの内容を呼び出す
        e.Graphics.DrawImage(backi, 0, 0); //【変更】画像をフォーム左上に配置
    }
    public Program() { //【以下追加】コンストラクタ
        try { //例外処理対象
            backi = Image.FromFile("back.bmp"); //画像ファイルを読み込む
        } catch (Exception e) { //例外処理内容
            MessageBox.Show(e.ToString()); //内容をメッセージボックスに表示
        }
    }
    static void Main() { //実行用メソッド(publicはなくてOK)
        Program f = new Program(); //自クラスのインスタンスを生成
        f.Text = "Game"; //Form名を設定
        f.StartPosition = FormStartPosition.Manual; //「手動設定」を設定
        Point p = new Point(300,0); //X座標とY座標のPointインスタンスを生成
        f.Location = p; //インスタンスプロパティで初期位置を設定
        f.FormBorderStyle = FormBorderStyle.FixedSingle; //フォームサイズの固定化
        f.ControlBox = false; //コントールボックスの非表示
        f.ClientSize = new Size(640, 480); //クライアントサイズの幅と高さを指定
        Application.Run(f); //生成済のインスタンスを実行
    }
}

テーマ10 画像のプロパティ

・Imageクラスが提供するプロパティを用いることで、画像の情報を動的に得るようにすれば、保守性が高い(変更しやすい)プログラムにできる
 → https://learn.microsoft.com/ja-jp/dotnet/api/system.drawing.image 
・プロパティの例
 int Height:高さ(ピクセル単位)
 int Width:幅(ピクセル単位)
 System.Drawing.Size Size:幅と高さ (ピクセル単位) をSize構造体オブジェクトで得る

テーマ11 画像の重ね合わせ

・FormアプリケーションではOnPainメソッドにおいて後に描画したものが上に重ねられる
・よって、背景画像などは先に描画すれば良い
・なお、GIF、PNG形式などでは画像ファイルに透過色を設定できる
・透過色で描画した部分は先に描画したものが抜けて表示されるので、画像が矩形にならない効果が得られる

演習10 背景画像の下部中央に自機画像を描画

・背景画像をゲーム用の下記画像に差し替えよう
  backb2.bmp
・透過色を設定した自機画像をダウンロード
  player.gif
・自機画像を背景画像の下部中央に描画しよう
 X座標:自機画像を背景画像の中央になるように指定
 Y座標:自機画像の下部に自機の高さ分の空きができるように指定
 

作成例

//演習10 背景画像の下部中央に自機画像を描画
using System; //C#標準クラス用
using System.Windows.Forms; //C#が提供するApplication、Formクラス用
using System.Drawing; //Size、Graphics、Imageクラス用
class Program : Form { //Formクラスの派生クラス
    Image backi, playeri; //【変更】画像ファイル用変数
    protected override void OnPaint(PaintEventArgs e) { //描画処理のオーバライド
        base.OnPaint(e); //元のメソッドの内容を呼び出す
        e.Graphics.DrawImage(backi, 0, 0); //画像をフォーム左上に配置
        e.Graphics.DrawImage(playeri, backi.Width / 2 - playeri.Width / 2,
            backi.Height - playeri.Height * 2); //【追加】自機を中央下部に描画
    }
    public Program() { //コンストラクタ
        try { //例外処理対象
            backi = Image.FromFile("backb2.bmp"); //【変更】背景画像ファイルを読み込む
            playeri = Image.FromFile("player.gif"); //【追加】自機画像ファイルを読み込む
        } catch (Exception e) { //例外処理内容
            MessageBox.Show(e.ToString()); //内容をメッセージボックスに表示
        }
    }
    static void Main() { //実行用メソッド(publicはなくてOK)
        Program f = new Program(); //自クラスのインスタンスを生成
        f.Text = "Game"; //Form名を設定
        f.StartPosition = FormStartPosition.Manual; //「手動設定」を設定
        Point p = new Point(300,0); //X座標とY座標のPointインスタンスを生成
        f.Location = p; //インスタンスプロパティで初期位置を設定
        f.FormBorderStyle = FormBorderStyle.FixedSingle; //フォームサイズの固定化
        f.ControlBox = false; //コントールボックスの非表示
        f.ClientSize = new Size(640, 480); //クライアントサイズの幅と高さを指定
        Application.Run(f); //生成済のインスタンスを実行
    }
}

テーマ12 キーボードが押された情報を得る

・フォームアプリケーションでキーボードが押された情報を得る方法は複数ある
・まず、最も単純に「押されたというイベントを得る」方法を用いよう
 ※なお、イベントの詳細についてはテキスト12章で解説
・押されたというイベントを得る手順
 ①コンストラクタにおいて、イベントを登録し受け取れるようにする
  キーが押されたというイベントはSystem.Windows.Forms.ControlクラスのKeyDown。
  これに、System.Windows.Forms.KeyEventHandlerデリゲートを「+=」すると良い。
  ここで引数としてイベント発生時に呼び出してほしいメソッド名を記述する。
  書式例: KeyDown += new KeyEventHandler(メソッド名);
 ②イベント発生時に呼び出してほしいメソッドを下記の書式で記述する
  void メソッド名(object o, KeyEventArgs e){…}
・上記により、キーが押されると指定したメソッドが呼ばれるようになる

テーマ13 フォームアプリケーションを終了

・System.Windows.Forms.FormクラスのClose()メソッドを実行するとフォームアプリケーションを適切に終了できる

演習11 キーが押されたら終了

・何かキーが押されたら(どのキーであっても)終了するようにしよう

作成例

作成例

//演習11 キーが押されたら終了
using System; //C#標準クラス用
using System.Windows.Forms; //C#が提供するApplication、Formクラス用
using System.Drawing; //Size、Graphics、Imageクラス用
class Program : Form { //Formクラスの派生クラス
    Image backi, playeri; //画像ファイル用変数
    protected override void OnPaint(PaintEventArgs e) { //描画処理のオーバライド
        base.OnPaint(e); //元のメソッドの内容を呼び出す
        e.Graphics.DrawImage(backi, 0, 0); //画像をフォーム左上に配置
        e.Graphics.DrawImage(playeri, backi.Width / 2 - playeri.Width / 2,
            backi.Height - playeri.Height * 2); //自機を中央下部に描画
    }
    public Program() { //コンストラクタ
        try { //例外処理対象
            backi = Image.FromFile("backb2.bmp"); //背景画像ファイルを読み込む
            playeri = Image.FromFile("player.gif"); //自機画像ファイルを読み込む
        } catch (Exception e) { //例外処理内容
            MessageBox.Show(e.ToString()); //内容をメッセージボックスに表示
        }
        KeyDown += new KeyEventHandler(OnKeyDown); //【追加】キー押し下げ時のメソッドを登録
    }
    void OnKeyDown(object o, KeyEventArgs e){ //【以下追加】キー押し下げ時に呼ばれるメソッド
        Close(); //フォームアプリケーションを終了
    }
    static void Main() { //実行用メソッド(publicはなくてOK)
        Program f = new Program(); //自クラスのインスタンスを生成
        f.Text = "Game"; //Form名を設定
        f.StartPosition = FormStartPosition.Manual; //「手動設定」を設定
        Point p = new Point(300,0); //X座標とY座標のPointインスタンスを生成
        f.Location = p; //インスタンスプロパティで初期位置を設定
        f.FormBorderStyle = FormBorderStyle.FixedSingle; //フォームサイズの固定化
        f.ControlBox = false; //コントールボックスの非表示
        f.ClientSize = new Size(640, 480); //クライアントサイズの幅と高さを指定
        Application.Run(f); //生成済のインスタンスを実行
    }
}

テーマ14 どのキーが押されたのかを得る

・KeyEventHandlerに設定したメソッドの第2引数「KeyEventArgs e」には、押されたキーの情報が含まれている
・この情報は、KeyEventArgsクラスのKeyCodeプロパティで得られる
・このプロパティはKeys列挙型であり、ToString()メソッドで文字列化して用いると良い
 https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.keys

演習12 キーが押されたら表示

・何かキーが押されたら(どのキーであっても)終了する処理をコメントにしておこう
・終了せずに、メッセージボックスでその文字列を表示するようにしよう

作成例

//演習12 キーが押されたら表示
using System; //C#標準クラス用
using System.Windows.Forms; //C#が提供するApplication、Formクラス用
using System.Drawing; //Size、Graphics、Imageクラス用
class Program : Form { //Formクラスの派生クラス
    Image backi, playeri; //画像ファイル用変数
    protected override void OnPaint(PaintEventArgs e) { //描画処理のオーバライド
        base.OnPaint(e); //元のメソッドの内容を呼び出す
        e.Graphics.DrawImage(backi, 0, 0); //画像をフォーム左上に配置
        e.Graphics.DrawImage(playeri, backi.Width / 2 - playeri.Width / 2,
            backi.Height - playeri.Height * 2); //自機を中央下部に描画
    }
    public Program() { //コンストラクタ
        try { //例外処理対象
            backi = Image.FromFile("backb2.bmp"); //背景画像ファイルを読み込む
            playeri = Image.FromFile("player.gif"); //自機画像ファイルを読み込む
        } catch (Exception e) { //例外処理内容
            MessageBox.Show(e.ToString()); //内容をメッセージボックスに表示
        }
        KeyDown += new KeyEventHandler(OnKeyDown); //キー押し下げ時のメソッドを登録
    }
    void OnKeyDown(object o, KeyEventArgs e){ //キー押し下げ時に呼ばれるメソッド
        MessageBox.Show(e.KeyCode.ToString()); //【追加】押されたキーのコードを文字列化して表示
        //Close(); //フォームアプリケーションを終了 //【一時削除】
    }
    static void Main() { //実行用メソッド(publicはなくてOK)
        Program f = new Program(); //自クラスのインスタンスを生成
        f.Text = "Game"; //Form名を設定
        f.StartPosition = FormStartPosition.Manual; //「手動設定」を設定
        Point p = new Point(300,0); //X座標とY座標のPointインスタンスを生成
        f.Location = p; //インスタンスプロパティで初期位置を設定
        f.FormBorderStyle = FormBorderStyle.FixedSingle; //フォームサイズの固定化
        f.ControlBox = false; //コントールボックスの非表示
        f.ClientSize = new Size(640, 480); //クライアントサイズの幅と高さを指定
        Application.Run(f); //生成済のインスタンスを実行
    }
}

提出:演習13 Escキーが押されたら終了

・何かキーが押されたら、どのキーかを得て、Escキー(文字列"Escape")であれば終了しよう
 ※メッセージボックスの表示は削除する

講義メモ

オブジェクト指向演習:仕上げ(継承によるラスボスの生成と登場)
ゲーム開発演習:画像のプロパティ、画像の重ね合わせ、キーが押された情報を得る など

RPG演習9 3人の魔導士 ver.5 継承によるラスボス(大魔道)の生成と登場

・ラスボス(大魔道)はDaimadoクラスで実装する
・Daimadoクラスは機能が重複するMadoshiクラスを継承することで効率的に扱う
・下記はそのまま継承できる
 ① public int HP //HPを返すプロパティ
 ② public int Fight() //魔道士があなたを攻撃し、あなたのダメージ値を返す
 ③ public void Damage() //魔道士があなたからダメージをくらう
・public void DispInfo()はオーバライドして、大魔道用にアレンジしよう
・public static bool isSurvプロパティは、new public bool isSurvプロパティとして隠ぺいし、大魔道用にアレンジしよう
・データメンバはそのままで良い(ただし、int id; //魔導士番号 は用いない)
・コンストラクタは、MPとHPの初期値を40~49にする。mnum、id、survに関する処理は不要
・minimadouクラスのMainメソッドでは、メインループの後に大魔道戦を追記する
・内容は「3人の魔導士」とほぼ同じだが、配列を用いずに、下記をアレンジしよう
 ① 戦闘開始では、プレイヤーのHPを回復(倍増)し、大魔道を生成して情報を表示する
 ②「あなたのHPがあり、生きている魔導士がいる間」は「あなたと大魔道のHPがある間」に
 ③「どれを攻撃しますか?(1/2/3:魔導士を攻撃 0:やめる):」を「攻撃しますか?(1:攻撃 0:やめる)」に
 ④「攻撃対象の魔導士の添字を得る」ことは不要
 ⑤「魔導士の数だけ繰返す」ことは不要
 ⑥「全ての魔道士を倒した!」は「大魔道を倒した」にする

作成例(完成版)

//オブジェクト指向演習 RPG演習9 3人の魔導士 ver.5 継承によるラスボス(大魔道)の生成と登場
using System;
class Madoshi { //魔導士を表すクラス(部品)
    protected int mp; //【変更】MP(継承可能)
    protected int hp; //【変更】HP(継承可能)
    int id; //魔導士番号
    static int mnum = 0; //魔導士が何人生成されたか
    static int surv = 0; //生きている魔導士は何人いるか
    protected static Random rnd = new Random(); //【変更】乱数用のRandomクラスのインスタンスを生成(継承可能)
    public Madoshi () { //コンストラクタ
        mp = rnd.Next(10); //0~9の間のランダムな整数を得てMPの初期値とする
        hp = 10 + rnd.Next(10); //10~19の間のランダムな整数を得てHPの初期値とする
        mnum++; //魔導士が何人生成されたかをカウントアップ
        this.id = mnum; //何人目の魔導士かを得て魔導士番号を設定
        surv = mnum; //生きている魔導士の数に格納
    }
    public int HP { //HPを返すプロパティ
        get { return hp; } //getのみなので外部からのhpの書換を禁止
    }
    public static bool isSurv { //生きている魔導士がいるかを返すプロパティ
        get { return surv > 0; } //生きている魔導士がいればtrue、いなければfalseを返す
    }
    public virtual void DispInfo() { //【変更】魔導士の情報を表示する仮想メソッド
        Console.Write("魔道士{0}(HP={1} MP={2})", id, hp, mp); //表示(改行しない)
    }
    public int Fight() { //魔道士があなたを攻撃し、あなたのダメージ値を返す
        if (hp <= 0) { //すでに倒されていたら
            return 0; //0を返す
        }
        if (rnd.Next(2) == 1) { //確率1/2で魔法攻撃かどうかを選ぶ
            int j = rnd.Next(5) + 1; //消費MPは1~5の乱数
            DispInfo(); // 魔導士の情報を表示する
            Console.WriteLine("は消費MP" + j + "の魔法を唱えた!");
            if (mp < j) { // MP不足?
                Console.WriteLine("MPが足りない!");
                return 0; // あなたのダメージ=0を返す(攻撃終了)
            } else { // MP充足?
                Console.WriteLine("あなたは" + j * 4 + "のダメージを受けた!");
                mp -= j; //MP消費
                return j * 4; //あなたのダメージ=消費MPの4倍を返す(攻撃終了)
            }
        } else { //通常攻撃?
            int k = rnd.Next(5) + 1; //攻撃力は1~5の乱数
            DispInfo(); //魔導士の情報を表示する
            Console.WriteLine("の攻撃!");
            Console.WriteLine("あなたは" + k + "のダメージを受けた!");
            return k; //あなたのダメージ=1~5を返す(攻撃終了)
        }
    }
    public void Damage() { //魔道士があなたからダメージをくらう
        if (hp <= 0) { //すでに倒されている?
            Console.WriteLine("魔道士{0}はもういない", id);
            return; //抜ける
        }
        int k = 5 + rnd.Next(5); //あなたの攻撃力は5~9の乱数
        Console.WriteLine("あなたの攻撃!");
        DispInfo(); //魔導士の情報を表示する(攻撃を受けた後の魔道士のHPを表示)
        Console.WriteLine("に" + k + "のダメージを与えた!");
        hp -= k; //魔道士のHPをダウン
        if (hp <= 0) { //倒された?
            Console.WriteLine("魔道士{0}を倒した!", id);
            surv--; //生きている魔導士の数を減らす
        }
    }
}
class Daimado : Madoshi { //【以下追加】大魔道を表すクラス(魔導士クラスの派生クラス)
    public Daimado () { //コンストラクタ
        mp = 40 + rnd.Next(10); //40~49の間のランダムな整数を得てMPの初期値とする
        hp = 40 + rnd.Next(10); //40~49の間のランダムな整数を得てHPの初期値とする
    }
    new public bool isSurv { //大魔道が生きているかを返すプロパティ
        get { return hp > 0; } //生きていればtrue、いなければfalseを返す
    }
    public override void DispInfo() { //魔導士の情報を表示するオーバライドメソッド
        Console.Write("大魔道(HP={0} MP={1})", hp, mp); //表示(改行しない)
    }
}
class minimadou { //ゲーム本体のクラス
    const int N = 3; //魔導士の数
    static string selection() { //魔導士の数に応じて「1」「1/2」「1/2/3」「1/2/3/4」…の文字列を返す
        string ans = "1"; //返す文字列
        for (int i = 1; i < N; i++) { //魔導士の数だけ繰返す
            ans += "/" + (i + 1); 
        }
        return ans;
    }
    public static void Main() { //ゲームを進行するメソッド
        Random rnd = new Random(); //乱数用のRandomクラスのインスタンスを生成
        string ans; //入力用
        // 戦闘開始
        int myhp = 20 * N; // あなたのHP(20×魔導士の数)
        Madoshi[] m = new Madoshi[N]; //魔導士の配列を生成
        for (int i = 0; i < N; i++) { //魔導士の数だけ繰返す
            m[i] = new Madoshi(); //魔導士[i]を生成
            m[i].DispInfo(); //魔道士[i]の情報を表示する
            Console.WriteLine("が現れた。");
        }
        // 戦闘中(あなたが死ぬか,魔道士が死ぬまで繰り返す)
        while (myhp > 0 && Madoshi.isSurv) { //あなたのHPがあり、生きている魔導士がいる間
            Console.Write("残りHPは{0}です。どれを攻撃しますか?({1}:魔導士を攻撃 0:やめる):", myhp, selection());
            ans = Console.ReadLine(); // 攻撃するかどうかを得る
            if (ans != "0") { //攻撃する?
                int num = int.Parse(ans) - 1; //攻撃対象の魔導士の添字を得る
                m[num].Damage(); //魔道士[num]を攻撃する
                if (Madoshi.isSurv) { //生きている魔導士がいる?
                    for (int i = 0; i < N; i++) { //魔導士の数だけ繰返す
                        myhp -= m[i].Fight(); //魔道士[i]の攻撃!あなたのHPをマイナス
                    }
                } else { //魔道士のHPがもうない?
                    Console.WriteLine("全ての魔道士を倒した!");
                }
            } else { // やめる?
                if (rnd.Next(2) == 0) { //確率50%で
                    Console.WriteLine("逃げられない!");
                    for (int i = 0; i < N; i++) { //魔導士の数だけ繰返す
                        myhp -= m[i].Fight(); //魔道士[i]の攻撃!あなたのHPをマイナス
                    }
                } else {
                    Console.WriteLine("逃げることができた");
                    break; // 繰り返し終了
                }
            }
            if (myhp <= 0) { //あなたのHPがもうない?
                Console.WriteLine("あなたは死にました。");
            }
        }
        //【以下追加】大魔道戦開始
        myhp *= 2; // あなたのHPを回復(倍増)
        Daimado dm = new Daimado(); //大魔道を生成
        dm.DispInfo(); //大魔道の情報を表示する
        Console.WriteLine("が現れた。");
        // 戦闘中(あなたが死ぬか,魔道士が死ぬまで繰り返す)
        while (myhp > 0 && dm.isSurv) { //あなたのHPがあり、生きている魔導士がいる間
            Console.Write("残りHPは{0}です。攻撃しますか?(1:攻撃 0:やめる):", myhp);
            ans = Console.ReadLine(); // 攻撃するかどうかを得る
            if (ans != "0") { //攻撃する?
                dm.Damage(); //大魔道を攻撃する
                if (dm.isSurv) { //大魔道が生きている?
                    myhp -= dm.Fight(); //大魔道の攻撃!あなたのHPをマイナス
                } else { //大魔道のHPがもうない?
                    Console.WriteLine("大魔道を倒した!");
                }
            } else { // やめる?
                if (rnd.Next(2) == 0) { //確率50%で
                    Console.WriteLine("逃げられない!");
                    myhp -= dm.Fight(); //大魔道の攻撃!あなたのHPをマイナス
                } else {
                    Console.WriteLine("逃げることができた");
                    break; // 繰り返し終了
                }
            }
            if (myhp <= 0) { //あなたのHPがもうない?
                Console.WriteLine("あなたは死にました。");
            }
        }
    }
}

補足:テキストの関連ページ

・クラスの継承:p.223
・protected:p.226
・newによる名前の隠ぺい:p.228
・メソッドのオーバライド、virtualによる仮想メソッド、overrideによるオーバーライドメソッド:p.231

今週の話題

ゲームソフト販売本数ランキング:今週1位は「カプコン ファイティング コレクション2(Switch)」 GO!
データイーストIPは死なず! 権利を保持するジー・モードの担当者にライセンス事業戦略を訊く。GO!
コナミ新会社「コナミアーケードゲームス」誕生。代表取締役は西村“DJ YOSHITAKA”宜隆氏―果たしてどんな立ち位置に GO!

新作脱出シューター『Marathon』にてデザイン盗用!?Bungieは元スタッフの関与を認め調査 GO!
iOS版『フォートナイト』近日復活と意気込むも…「Appleが審査をブロックしている」Epic Gamesが報告 GO!

講義メモ ゲーム開発演習

:メッセージボックス、システム側からの必要に応じた再描画の確認、背景画像の描画

テーマ7 メッセージボックス

・フォームアプリケーションにおいてテスト用に短い文字列を表示させたい場合などに便利なのがメッセージボックス
・System.Windows.Forms.MessageBoxクラスの静的メソッドShow(string)に引数として表示させたい文字列を渡すだけで良い
・「OK」ボタンの表示により閉じることができる

演習7 システム側からの必要に応じた再描画の確認

・描画処理のオーバライド(override void OnPaint())において、メッセージボックスを表示しよう
・これにより、システム側からの必要に応じた再描画が行われ、自動的にOnPaint()が呼び出されることを確認しよう
・フォームの一部を画面外に移動させてから、戻す時にシステム側からの必要に応じた再描画が起きて、OnPaint()が呼び出されることがわかる

作成例

//演習7 システム側からの必要に応じた再描画の確認
using System; //C#標準クラス用
using System.Windows.Forms; //C#が提供するApplication、Formクラス用
using System.Drawing; //Sizeクラス用
class Program : Form { //Formクラスの派生クラス
    protected override void OnPaint(PaintEventArgs e) { //描画処理のオーバライド
        base.OnPaint(e); //元のメソッドの内容を呼び出す
        MessageBox.Show("OnPaintが呼ばれた"); //【追加】
    }
    static void Main() { //実行用メソッド(publicはなくてOK)
        Program f = new Program(); //自クラスのインスタンスを生成
        f.Text = "Game"; //Form名を設定
        f.StartPosition = FormStartPosition.Manual; //「手動設定」を設定
        Point p = new Point(300,0); //X座標とY座標のPointインスタンスを生成
        f.Location = p; //インスタンスプロパティで初期位置を設定
        f.FormBorderStyle = FormBorderStyle.FixedSingle; //フォームサイズの固定化
        f.ControlBox = false; //コントールボックスの非表示
        f.ClientSize = new Size(640, 480); //クライアントサイズの幅と高さを指定
        Application.Run(f); //生成済のインスタンスを実行
    }
}

テーマ8 フォーム上への画像の描画

・画像ファイルをプロジェクトに配置することで、フォーム上に描画できる
・画像ファイルの形式としてはjpeg、bmp、gifなどに対応している
・描画の手順
① System.Drawing.Imageクラスの静的メソッドFromFile(string)に、画像ファイル名を渡すと、Imageクラスのオブジェクトが生成される
② 描画処理の中で、System.Drawing.Graphicsクラスの静的メソッドDrawImage(Image, int, int)に、①とX座標、Y座標を渡すと、指定した位置に画像が描画される
・なお、X座標、Y座標はフォームの左上隅を(0,0)とする座標になる
・②で用いるGraphicsクラスのオブジェクトはOnPaintメソッドの引数「PaintEventArgs e」で得られるPaintEventArgsオブジェクトのGraphicsプロパティの戻り値で得る
・よって「e.Graphics.DrawImage(Image, int, int)」と呼び出せば良い

演習8 背景画像の描画

・下記のテスト用画像をプロジェクトの\bin\debugフォルダにダウンロード
   back.bmp
・この画像をフォームの座標(0,0)に配置しよう

作成例

//演習8 背景画像の描画
using System; //C#標準クラス用
using System.Windows.Forms; //C#が提供するApplication、Formクラス用
using System.Drawing; //Size、Graphics、Imageクラス用
class Program : Form { //Formクラスの派生クラス
    Image backi = Image.FromFile("back.bmp"); //【追加】画像ファイルを読み込む
    protected override void OnPaint(PaintEventArgs e) { //描画処理のオーバライド
        base.OnPaint(e); //元のメソッドの内容を呼び出す
        e.Graphics.DrawImage(backi, 0, 0); //【変更】画像をフォーム左上に配置
    }
    static void Main() { //実行用メソッド(publicはなくてOK)
        Program f = new Program(); //自クラスのインスタンスを生成
        f.Text = "Game"; //Form名を設定
        f.StartPosition = FormStartPosition.Manual; //「手動設定」を設定
        Point p = new Point(300,0); //X座標とY座標のPointインスタンスを生成
        f.Location = p; //インスタンスプロパティで初期位置を設定
        f.FormBorderStyle = FormBorderStyle.FixedSingle; //フォームサイズの固定化
        f.ControlBox = false; //コントールボックスの非表示
        f.ClientSize = new Size(640, 480); //クライアントサイズの幅と高さを指定
        Application.Run(f); //生成済のインスタンスを実行
    }
}

テーマ9 画像ファイル読込のトラブルへの対応

・画像ファイルの読込においては、エラーの発生時に何も表示されないため、処置が難しい
・そこで、p.323のtry-catchを仕込んでおくと良い
・書式例:
 try {
  変数 = Image.FromFile(ファイル名);
 } catch {
  エラー発生時処理
 }

演習9 背景画像の描画(エラー対処付き)

・画像ファイルの読み込みをコンストラクタで行うようにしよう
・そして、画像ファイル読込のトラブルへの対応を盛り込み、エラー発生時はメッセージボックスを表示しよう
・この時、catchを「catch (exception e)」とすることで、エラー発生オブジェクトを得ると良い
・このオブジェクトの文字列表現を「e.ToString()」で得てメッセージボックスに表示しよう

提出:演習9