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

文字列の描画(つづき)、画面遷移、など

提出フォロー:演習19 チェス盤を描こう・改

・木目の画像を用意してImageとして用いることで、リアルなチェス盤を描こう
・mokume0.png https://ha242.rundog.org/wp-content/uploads/2025/06/mokume0.png
 mokume1.png https://ha242.rundog.org/wp-content/uploads/2025/06/mokume1.png
・合わせて升目のサイズを50×50にしよう

作成例

//演習19 チェス盤を描こう・改
using System; //C#標準クラス用
using System.Windows.Forms; //Application、Formクラス用
using System.Drawing; //Size、Graphics、Image、Penクラス、Color構造体用
class Program : Form { //Formクラスの派生クラス
    Image backi, playeri, chess0, chess1; //【変更】画像ファイル用変数
    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のペンを生成
        Brush bMokume0 = new TextureBrush(chess0); //【変更】木目テクスチャブラシ0を生成
        Brush bMokume1 = new TextureBrush(chess1); //【変更】木目テクスチャブラシ1を生成
        for(int i = 0; i < 8; i++) { //8列
            for(int j = 0; j < 8; j++) { //8行
                if ((i + j) % 2 == 0) { //1つおきに
                    e.Graphics.FillRectangle(bMokume0, 10 + i * 50, 20 + j * 50, 50, 50); //木目0
                } else {
                    e.Graphics.FillRectangle(bMokume1, 10 + i * 50, 20 + j * 50, 50, 50); //木目1
                }
            }
        }
    }
    public Program() { //コンストラクタ
        try { //例外処理対象
            backi = Image.FromFile("backb2.bmp"); //背景画像ファイルを読み込む
            playeri = Image.FromFile("player.gif"); //自機画像ファイルを読み込む
            chess0 = Image.FromFile("mokume0.png"); //【追加】チェス画像ファイル0を読み込む
            chess1 = Image.FromFile("mokume1.png"); //【追加】チェス画像ファイル1を読み込む
        } 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); //生成済のインスタンスを実行
    }
}

テーマ19 文字列の描画【再掲載】

・コンソールアプリケーションでは既定のフォントによりデバッグコンソールに文字列を表示している
・しかし、フォームアプリケーションでは画像などと同様にフォーム上に文字列を描画する
・よって、色や大きさに加えてフォントも指定できる
・文字列の描画にはDrawString()メソッドを用い、色の指定にはブラシを用いる
・フォントの指定にはSystem.Drawing.Fontクラスのオブジェクトを用いる
・Fontクラスには複数のコンストラクタがあるが、シンプルなのはFont("フォント名", ポイント数)
 例: Font f = new Font("メイリオ", 15);
・DrawStringメソッドにブラシと左上座標、幅、高さを渡すことで矩形の塗りつぶしが可能
・書式例: e.Graphics.DrawString(文字列, フォント, ブラシ, 左上X座標, 左上Y座標);

演習20 チェス盤の下に文字列を描こう

・8×8の升目の下に「Game Start」という文字列を、フォントスタイル「メイリオ」、ポイント数「15」、太字、色「Cyan」で描こう

作成例

//演習20 チェス盤の下に文字列を描こう
using System; //C#標準クラス用
using System.Windows.Forms; //Application、Formクラス用
using System.Drawing; //Size、Graphics、Image、Penクラス、Color構造体用
class Program : Form { //Formクラスの派生クラス
    Image backi, playeri, chess0, chess1; //画像ファイル用変数
    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); //自機を中央下部に描画
        Brush bMokume0 = new TextureBrush(chess0); //木目テクスチャブラシ0を生成
        Brush bMokume1 = new TextureBrush(chess1); //木目テクスチャブラシ1を生成
        for(int i = 0; i < 8; i++) { //8列
            for(int j = 0; j < 8; j++) { //8行
                if ((i + j) % 2 == 0) { //1つおきに
                    e.Graphics.FillRectangle(bMokume0, 10 + i * 50, 20 + j * 50, 50, 50); //木目0
                } else {
                    e.Graphics.FillRectangle(bMokume1, 10 + i * 50, 20 + j * 50, 50, 50); //木目1
                }
            }
        }
        Font fm15 = new Font("メイリオ", 15, FontStyle.Bold); //【追加】フォントを生成
        Brush bcyan = new SolidBrush(Color.Cyan); //【追加】シアン色のソリッドブラシを生成
        e.Graphics.DrawString("Game Start", fm15, bcyan, 150, 420); //【追加】文字列を描画
    }
    public Program() { //コンストラクタ
        try { //例外処理対象
            backi = Image.FromFile("backb2.bmp"); //背景画像ファイルを読み込む
            playeri = Image.FromFile("player.gif"); //自機画像ファイルを読み込む
            chess0 = Image.FromFile("mokume0.png"); //チェス画像ファイル0を読み込む
            chess1 = Image.FromFile("mokume1.png"); //チェス画像ファイル1を読み込む
        } 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); //生成済のインスタンスを実行
    }
}

テーマ20 画面遷移

・タイトル画面とプレイ画面とゲームオーバー画面のように、画面が移り変わることを画面遷移という
・画面ごとに違うプログラムを用意して呼び出すこともあるが、プログラム内で「今はどの画面なのか」を変数で保持すればシンプルに記述できる
・この変数をゲームモードやシーン等と呼び、プログラム内で共有することで、画面遷移を見やすく記述できる
・ゲームモードやシーンに用いる値は列挙型にすると意味がわかりやすくなる
・構造例:
class クラス名 : Form { //Formクラスを継承
  enum { TITLE, PLAY, OVER }; //タイトル画面、プレイ画面、ゲームオーバー画面
  int ゲームモード = TITLE;
  その他の初期化処理を記述
  protected override void OnPaint(PaintEventArgs e) { //描画処理のオーバライド
    全画面に共通の描画処理
    ゲームモードがTITLEならばタイトル画面を描画
    ゲームモードがPLAYならばプレイ画面を描画
    ゲームモードがOVERならばゲームオーバー画面を描画
  }
  public static void Main() { //実行用メソッド
    :
  }
  クラス名() { //コンストラクタ
    :
  }
  キー押し下げ時に実行されるメソッド {
    共通処理(Escキーが押し下げられたら終了)
    ゲームモードがTITLEの時にEnterやSpaceキーが押し下げられたらゲームモードをPLAYに
    ゲームモードがOVERの時にEnterキーが押し下げられたらゲームモードをTITLEやPLAYに
  }
}

テーマ21 画面再描画

・プログラム側で画面を書き換えても、そのままでは実画面には反映しない
・そのため、画面再描画をシステムに依頼する必要がある(OnPaintの直接実行は不可)
・画面再描画をシステムに依頼するにはContolクラスの静的メソッドInvalidateを引数なしで呼べばよい(「Contol.」は省略可)
・書式:Invalidate(); //画面再描画を依頼

演習21 タイトル画面の表示とプレイ画面への遷移:次回

・テーマ20「画面遷移」の「構造例」を実装してタイトル画面の表示とプレイ画面への遷移を確認しよう
・タイトル画面は中央に「GAME」、中央下に「Hit Enter Key」と適当なフォントで表示
・プレイ画面は中央に「START」と適当なフォントで表示
 ※チェス盤、機体などの描画はいったん削除

提出:演習20 チェス盤の下に文字列を描こう

講義メモ

・テキスト編「第12章 デリゲートとイベント」から
・ゲーム開発演習:文字列の描画(つづき)、画面遷移、など

第12章 デリゲートとイベント

p.295 デリゲートとは

・メソッドへの参照を保持しておいて、これを用いてメソッドを呼び出せる仕掛け
・つまり、デリゲートを通じてのメソッドの呼び出しが可能
・C/C++における「関数へのポインタ」と同じ考え方を洗練したもの
・イベント処理などで必須のテクニック
・宣言書式: delegate メソッドの戻り値型 デリゲート名(メソッドの引数リスト);
・例:bool answer(int x){…}メソッド用のデリゲートmdならば、delegate bool md(int w) とする
・利用にはデリゲートオブジェクトの生成が必要で、この時に呼び出したいメソッドの指定もできる
・生成書式: デリゲート名 参照変数 = new デリゲート名(メソッド名);
・例:bool answer(int x){…}メソッド用のデリゲートmdならば、md mymd = new md(answer);
・デリゲート経由で指定済のメソッドを呼び出すには、参照変数を別名のように利用できる
・例: bool b = mymd(5); // answer(5)が呼ばれ戻り値が返される

p.297 delegate01.cs について

・Main()メソッドの戻り値をint型にしているが、この場合、必要性はないので、いつもの通り voidにして良い
・よって「return 0;」も不要

p.297 delegate01.cs(修正後)

//p.297 delegate01.cs
using System;
delegate void MyDelegate();
class delegate01 {
    public static void show() { //Main()メソッドから呼ばれる静的メソッド
        Console.WriteLine("呼ばれました");
    }
    public static void Main() {
        //直接showメソッドを呼び出す
        show();
        //デリゲートの作成
        MyDelegate md = new MyDelegate(show);
        //デリゲートを通してshowメソッドを実行
        md();
    }
}

アレンジ演習:p.297 delegate01.cs

・上記の例のメソッドを追記し、これを呼び出すデリゲートをMainに追加して動作を確認しよう
 static bool answer(int x){ return (x >= 0); } //xが0以上ならtrueを、でなければfalseを返す

作成例

//アレンジ演習:p.297 delegate01.cs
using System;
delegate void MyDelegate();
delegate bool MyDelegate2(int i); //【追加】answer用のデリゲートの宣言
class delegate01 {
    public static void show() { //Main()メソッドから呼ばれる静的メソッド
        Console.WriteLine("呼ばれました");
    }
    static bool answer(int x){ //【以下追加】※publicでなくて良い
        return (x >= 0); //xが0以上ならtrueを、でなければfalseを返す
    }
    public static void Main() {
        show(); //直接showメソッドを呼び出す
        MyDelegate md = new MyDelegate(show);//デリゲートの作成
        md();//デリゲートを通してshowメソッドを実行
        MyDelegate2 md2 = new MyDelegate2(answer); //【追加】answer用のデリゲートの作成
        Console.WriteLine(md2(10));//【追加】デリゲートを通して引数10を渡して実行結果を表示
    }
}

p.298 デリゲートとは:別のクラスに属するメソッドをデリゲートを通して用いる

・デリゲートの宣言は特に変わらない
・別のクラスに属するインスタンスメソッドであれば、インスタンスを生成して、参照変数.メソッド名で、デリゲートを作成すれば良い

p.297 delegate02.cs

//p.298 delegate02.cs
using System;
delegate void MyDelegate(); //デリゲートの宣言
class MyClass
{
    public void show()
    {
        Console.WriteLine("呼ばれました");
    }
}
class delegate02
{
    public static void Main()
    {
        MyClass mc = new MyClass(); //呼び出すメソッドのあるクラスのインスタンスを生成
        mc.show();
        MyDelegate m = new MyDelegate(mc.show); //参照変数.メソッド名を与える
        m(); //デリゲートを通してメソッドを実行
    }
}

アレンジ演習:p.297 delegate02.cs

・MyClassクラスに下記のメソッドを追記し、これを呼び出すデリゲートをMainに追加して動作を確認しよう
 bool answer(int x){ return (x >= 0); } //xが0以上ならtrueを、でなければfalseを返す

作成例

//アレンジ演習:p.298 delegate02.cs
using System;
delegate void MyDelegate(); //デリゲートの宣言
delegate bool MyDelegate2(int i); //【追加】answer用のデリゲートの宣言
class MyClass {
    public void show() {
        Console.WriteLine("呼ばれました");
    }
    public bool answer(int x) { //【以下追加】※publicが必要
        return (x >= 0); //xが0以上ならtrueを、でなければfalseを返す
    }
}
class delegate02 {
    public static void Main() {
        MyClass mc = new MyClass(); //呼び出すメソッドのあるクラスのインスタンスを生成
        mc.show();
        MyDelegate m = new MyDelegate(mc.show); //参照変数.メソッド名を与える
        m(); //デリゲートを通してメソッドを実行
        MyDelegate2 m2 = new MyDelegate2(mc.answer); //【追加】answer用のデリゲートの作成
        Console.WriteLine(m2(10));//【追加】デリゲートを通して引数10を渡して実行結果を表示
    }
}

p.298 デリゲートとは:別のクラスに属する静的メソッドをデリゲートを通して用いる

・デリゲートの宣言は特に変わらない
・別のクラスに属する静的メソッドであれば、クラス名.メソッド名で、デリゲートを作成すれば良い

アレンジ演習:p.299 delegate03.cs

・MyClassクラスに下記のメソッドを追記し、これを呼び出すデリゲートをMainに追加して動作を確認しよう
 public static bool answer(int x){ return (x >= 0); } //xが0以上ならtrueを、でなければfalseを返す

作成例

//アレンジ演習:p.299 delegate03.cs
using System;
delegate string MyDelegate(string a, string b); //デリゲートの宣言(戻り値有り、2引数)
delegate bool MyDelegate2(int i); //【追加】answer用のデリゲートの宣言(戻り値有り、1引数)
class MyClass {
    public static string show(string s1, string s2) { //静的メソッド
        return s1 + "は" + s2 + "です";
    }
    public static bool answer(int x){ //【以下追加】静的メソッド
        return (x >= 0); //xが0以上ならtrueを、でなければfalseを返す
    }
}
class delegate03 {
    public static void Main() {
        Console.WriteLine(MyClass.show("猫", "ほ乳類"));
        MyDelegate md = new MyDelegate(MyClass.show); //静的メソッドなのでクラス名.メソッド名
        Console.WriteLine(md("C#", "おもしろい"));
        MyDelegate2 m2 = new MyDelegate2(MyClass.answer); //【追加】answer用のデリゲートの作成
        Console.WriteLine(m2(10));//【追加】デリゲートを通して引数10を渡して実行結果を表示
    }
}

p.298 デリゲートとは:デリゲート変数の再利用

・戻り値型と引数リストが同じメソッドを呼び出すデリゲートであれば、デリゲート変数を再利用できる
・これにより、同じ名前のデリゲートを通して異なるメソッドを呼び出すことができる

p.300 delegate04.cs

//p.300 delegate04.cs
using System;
delegate DateTime MyDelegate(DateTime dt, int n); //デリゲートの宣言(戻り値有り、2引数)
class MyClass1 { //デリゲートに用いるメソッドのあるクラス①
    public DateTime Calc(DateTime d, int n) { //デリゲートに用いるインスタンスメソッド①
        return d.AddDays(n); //日付時刻オブジェクトのn日後の日付時刻オブジェクトを返す
    }
}
class Myclass2 { //デリゲートに用いるメソッドのあるクラス②
    public DateTime Calc(DateTime d, int n) { //デリゲートに用いるインスタンスメソッド②
        return d.AddHours(n); //日付時刻オブジェクトのn時間後の日付時刻オブジェクトを返す
    }
}
class delegate04 {
    public static void Main() {
        MyClass1 mc1 = new MyClass1(); //デリゲートに用いるメソッドのあるクラス①
        Myclass2 mc2 = new Myclass2(); //デリゲートに用いるメソッドのあるクラス②
        MyDelegate md = new MyDelegate(mc1.Calc); //デリゲートに用いるインスタンスメソッド①を指定
        DateTime dt = DateTime.Now; //現在日付時刻のオブジェクトを得る
        DateTime mydate; //作業用の日付時刻オブジェクトを得る
        mydate = md(dt, 100); //デリゲート経由でMyClass1のCalcメソッドを呼び、現在日付時刻オブジェクトと100を渡す
        Console.WriteLine("今日から100日後は、{0}です", mydate.ToShortDateString());
        md = new MyDelegate(mc2.Calc); //デリゲートに用いるインスタンスメソッド②で置き換える
        mydate = md(dt, 100); //デリゲート経由でMyClass2のCalcメソッドを呼び、現在日付時刻オブジェクトと100を渡す
        Console.WriteLine("今から100時間後は、{0}です", mydate);
    }
}

アレンジ演習:p.299 delegate03.cs

・MyClass2クラスに下記のメソッドを追記し、名前が異なるメソッドも同じデリゲートで呼び出せることを確認しよう
 public DateTime Calc2(DateTime d, int n) { //デリゲートに用いるインスタンスメソッド③
  return d.AddDays(n).AddHours(n); //n日後のn時間後の日付時刻オブジェクトを返す
 }

作成例

//アレンジ演習:p.300 delegate04.cs
using System;
delegate DateTime MyDelegate(DateTime dt, int n); //デリゲートの宣言(戻り値有り、2引数)
class MyClass1 { //デリゲートに用いるメソッドのあるクラス①
    public DateTime Calc(DateTime d, int n) { //デリゲートに用いるインスタンスメソッド①
        return d.AddDays(n); //日付時刻オブジェクトのn日後の日付時刻オブジェクトを返す
    }
}
class Myclass2 { //デリゲートに用いるメソッドのあるクラス②
    public DateTime Calc(DateTime d, int n) { //デリゲートに用いるインスタンスメソッド②
        return d.AddHours(n); //日付時刻オブジェクトのn時間後の日付時刻オブジェクトを返す
    }
    public DateTime Calc2(DateTime d, int n) { //【以下追加】デリゲートに用いるインスタンスメソッド③
        return d.AddDays(n).AddHours(n); //n日後のn時間後の日付時刻オブジェクトを返す
    }
}
class delegate04 {
    public static void Main() {
        MyClass1 mc1 = new MyClass1(); //デリゲートに用いるメソッドのあるクラス①
        Myclass2 mc2 = new Myclass2(); //デリゲートに用いるメソッドのあるクラス②
        MyDelegate md = new MyDelegate(mc1.Calc); //デリゲートに用いるインスタンスメソッド①を指定
        DateTime dt = DateTime.Now; //現在日付時刻のオブジェクトを得る
        DateTime mydate; //作業用の日付時刻オブジェクトを得る
        mydate = md(dt, 100); //デリゲート経由でMyClass1のCalcメソッドを呼び、現在日付時刻オブジェクトと100を渡す
        Console.WriteLine("今日から100日後は、{0}です", mydate.ToShortDateString());
        md = new MyDelegate(mc2.Calc); //デリゲートに用いるインスタンスメソッド②で置き換える
        mydate = md(dt, 100); //デリゲート経由でMyClass2のCalcメソッドを呼び、現在日付時刻オブジェクトと100を渡す
        Console.WriteLine("今から100時間後は、{0}です", mydate);
        md = new MyDelegate(mc2.Calc2); //【以下追加】デリゲートに用いるインスタンスメソッド③で置き換える
        mydate = md(dt, 100); //デリゲート経由でMyClass2のCalc2メソッドを呼び、現在日付時刻オブジェクトと100を渡す
        Console.WriteLine("今から100日後の100時間後は、{0}です", mydate);
    }
}

p.302 delegate05.cs

//p.302 delegate05.cs
using System;
delegate void MyDG(); //デリゲートの宣言(戻り値無し、引数無し)
class MyClass {
    public void show1() { //デリゲートに用いるインスタンスメソッド①
        Console.WriteLine("show1が呼ばれました");
    }
    public void show2() { //デリゲートに用いるインスタンスメソッド②
        Console.WriteLine("show2が呼ばれました");
    }
    public void show3() { //デリゲートに用いるインスタンスメソッド③
        Console.WriteLine("show3が呼ばれました");
    }
}
class delegate05 {
    public static void Main() {
        MyClass mc = new MyClass(); //デリゲートに用いるメソッドのあるクラス
        MyDG md = new MyDG(mc.show1); //デリゲートに用いるインスタンスメソッド①を指定
        Console.WriteLine("1回目のmd()を実行します");
        md(); //デリゲーションで①を実行
        md += new MyDG(mc.show2); //デリゲートに用いるインスタンスメソッド②を追加
        Console.WriteLine("2回目のmd()を実行します");
        md(); //マルチキャストデリゲーションになり①②を実行
        md += new MyDG(mc.show3); //デリゲートに用いるインスタンスメソッド③を追加
        Console.WriteLine("3回目のmd()を実行します");
        md(); //マルチキャストデリゲーションになり①②③を実行
        md -= new MyDG(mc.show1); //マルチキャストデリゲーションからインスタンスメソッド①を除外
        Console.WriteLine("4回目のmd()を実行します");
        md(); //マルチキャストデリゲーションになり②③を実行
    }
}

アレンジ演習:p.302 delegate05.cs

・一度除外したインスタンスメソッド①を、再度、マルチキャストデリゲーションに追加できるかどうかを確認しよう
 ⇒可能
・また、実行順序がどうなるかも確認しよう
 ⇒追加順なので②③①になる
・それから、追加済のインスタンスメソッド②を、さらに、マルチキャストデリゲーションに追加できるかどうかを確認しよう
 ⇒可能
・また、実行順序がどうなるかも確認しよう
 ⇒追加順なので②③①②になる
・加えて、インスタンスメソッド③を除外し、さらに除外できるかどうかを確認しよう
 ⇒無視される(文法エラーや実行時エラーにはならない)

作成例

//アレンジ演習:p.302 delegate05.cs
using System;
delegate void MyDG(); //デリゲートの宣言(戻り値無し、引数無し)
class MyClass {
    public void show1() { //デリゲートに用いるインスタンスメソッド①
        Console.WriteLine("show1が呼ばれました");
    }
    public void show2() { //デリゲートに用いるインスタンスメソッド②
        Console.WriteLine("show2が呼ばれました");
    }
    public void show3() { //デリゲートに用いるインスタンスメソッド③
        Console.WriteLine("show3が呼ばれました");
    }
}
class delegate05 {
    public static void Main() {
        MyClass mc = new MyClass(); //デリゲートに用いるメソッドのあるクラス
        MyDG md = new MyDG(mc.show1); //デリゲートに用いるインスタンスメソッド①を指定
        Console.WriteLine("1回目のmd()を実行します");
        md(); //デリゲーションで①を実行
        md += new MyDG(mc.show2); //デリゲートに用いるインスタンスメソッド②を追加
        Console.WriteLine("2回目のmd()を実行します");
        md(); //マルチキャストデリゲーションになり①②を実行
        md += new MyDG(mc.show3); //デリゲートに用いるインスタンスメソッド②を追加
        Console.WriteLine("3回目のmd()を実行します");
        md(); //マルチキャストデリゲーションになり①②③を実行
        md -= new MyDG(mc.show1); //マルチキャストデリゲーションからインスタンスメソッド①を除外
        Console.WriteLine("4回目のmd()を実行します");
        md(); //マルチキャストデリゲーションになり②③を実行
        md += new MyDG(mc.show1); //【以下追加】デリゲートに用いるインスタンスメソッド①を再追加
        Console.WriteLine("5回目のmd()を実行します");
        md(); //マルチキャストデリゲーションになり②③①を実行
        md += new MyDG(mc.show2); //【以下追加】デリゲートに用いるインスタンスメソッド②を再追加
        Console.WriteLine("6回目のmd()を実行します");
        md(); //マルチキャストデリゲーションになり②③①②を実行
        md -= new MyDG(mc.show3); //【以下追加】デリゲートに用いるインスタンスメソッド③を除外
        md -= new MyDG(mc.show3); //デリゲートに用いるインスタンスメソッド③をさらに除外
        Console.WriteLine("7回目のmd()を実行します");
        md(); //マルチキャストデリゲーションになり②①②を実行
    }
}

p.304 匿名メソッド

・デリゲートからの呼び出し専用のメソッドであれば、記述の簡略化が可能
・これにより、メソッド名が不要になるので、匿名メソッドという
・生成書式: デリゲート名 参照変数 = delegate(引数型 引数名, …){メソッドの内容};
・匿名メソッドの戻り値型はデリゲートの宣言により決まるので、生成においては指定不要

p.304 anomethed01.cs

//p.304 anomethed01.cs
using System;
delegate int MyDelegate(string s); //デリゲートの宣言(戻り値有り、1引数)
class MyClass {
    public int Show(string s) { //デリゲートに用いるインスタンスメソッド
        Console.WriteLine("{0}と入力されました。", s);
        return 0;
    }
}
class AnoMethed01 {
    public static void Main() {
        MyClass mc = new MyClass();
        Console.Write("文字列入力 --- ");
        string x = Console.ReadLine();
        MyDelegate mdg = new MyDelegate(mc.Show); //従来型
        mdg(x);
        MyDelegate mdg2 = delegate (string i) { //匿名メソッド
            Console.WriteLine("{0}と入力されました。", i);
            return 0;
        };
        mdg2(x);
    }
}

p.306 ラムダ式

・元は関数の簡易書式で、多くの仕掛やプログラム言語に採用されている。
・ただし、活用場面や言語によって文法が異なるので注意。例えば、C#とC++、Javaでは違いがある。
・C#では、主に、匿名メソッドの簡易記述に活用することが多い。
・数学のラムダ式では引数は1つだけだが、C#では、複数の引数を指定できる
・書式: (引数リスト) => 式; //引数リストでは型の記述は不要で、自動的に推定される
・例:
 通常のメソッドで書くと int method(int x, int y) { return x + y; };
 匿名メソッドで書くと   delegate(int x, int y) { return x + y; };
 ラムダ式にすると       (x, y) => { return x + y; } //内容が1文であれば{}は省略可能
・引数が不要の場合、カッコのみを記述する
・例: () => Show(); //定義済のメソッドShow()を呼ぶ
・例: () => Console.Write("OK"); //外部で定義済のメソッドを呼ぶ
・よって、デリゲートへのメソッドの登録において、匿名メソッドの代わりに用いると良い
・例:
 通常のメソッドで書くと デリゲート名 変数名 = new デリゲート名(メソッド名);
 匿名メソッドで書くと   デリゲート名 変数名 = delegate() { メソッド名(); };
 ラムダ式にすると       デリゲート名 変数名 = () => メソッド名();

p.307 lambda01.cs

//p.307 lambda01.cs
using System;
delegate void MyDelegate(); //デリゲートの宣言(戻り値無し、引数無し)
class lambda01 {
    public static void show() {
        Console.WriteLine("呼ばれました");
    }
    public static void Main() {
        show(); // 直接showメソッドを呼び出す
        MyDelegate md = () => show(); // デリゲートのラムダ式による作成
        md(); //デリゲートを通してshowメソッドを実行
    }
}

アレンジ演習:p.307 lambda01.cs

・デリゲートを通してshowメソッドを実行する前に、下記の外部で定義済のメソッドを呼ぶマルチキャストデリゲーションを追加して、
 動作を確認しよう
 md += () => Console.Write("OK");

作成例

//アレンジ演習:p.307 lambda01.cs
using System;
delegate void MyDelegate(); //デリゲートの宣言(戻り値無し、引数無し)
class lambda01 {
    public static void show() {
        Console.WriteLine("呼ばれました");
    }
    public static void Main() {
        show(); // 直接showメソッドを呼び出す
        MyDelegate md = () => show(); // デリゲートのラムダ式による作成
        md += () => Console.WriteLine("OK"); //【追加】ラムダ式でマルチキャストデリゲーションに追加
        md(); //マルチキャストデリゲーションを実行
    }
}

p.308 ラムダ式:引数型の省略

・デリゲートへのメソッドの登録をラムダ式で行うと、デリゲートの宣言で指定した引数型が用いられる
・よって、引数型の指定は不要

p.308 lambda02.cs

//p.308 lambda02.cs
using System;
delegate int MyDelegate(int x, int y); //デリゲートの宣言(戻り値有り、2引数)
class lambda02 {
    public static void Main() {
        MyDelegate md = (x, y) => { return x + y; }; //引数x、yの型は指定不要
        Console.WriteLine("2 + 3 = {0}", md(2, 3)); //デリゲート経由で呼び出す
    }
}

p.308 ラムダ式:マルチキャストデリゲーション

・マルチキャストデリゲーションにおいてもラムダ式の活用は有効

アレンジ演習:p.309 lambda03.cs

・デリゲーションの変数md1、md2を用いずに、mdのみでマルチキャストデリゲーションにしよう

作成例

//アレンジ演習:p.309 lambda03.cs
using System;
delegate void MyDelegate(int x); //デリゲートの宣言(戻り値無し、1引数)
class lambda03 {
    public static void Main() {
        //ラムダ式によるデリゲーションの定義
        MyDelegate md = (int x) => { Console.WriteLine("{0}の2乗は{1}", x, x * x); };
        //ラムダ式によるデリゲーションへの追加
        md += (int x) => { Console.WriteLine("{0}の2倍は{1}", x, x * 2); };
        //マルチキャストデリゲーションの実行
        md(10);
    }
}

今週の話題

ゲームソフト販売本数ランキング:今週1位も「マリオカート ワールド(Switch2)」 GO!
DLSS/FSRは全部同じじゃない!ゲーマーなら知っておきたい「超解像」「AIアンチエイリアシング」「フレーム生成」の違いを徹底解説 GO!
Limited Run Games、同社のレトロゲーム復刻ゲームエンジン「Carbon Engine」がスイッチ2に対応したことを発表。12タイトルの互換性が保証される GO!
著作権訴訟渦中のミッドジャーニーがAI動画生成スタート。5秒480pという低スペックながら使い勝手がいい理由 GO!

『Limbo』『Inside』の共同開発者の間で訴訟が勃発…古巣との9年に及ぶ確執に裁判での情報公開に期待と元スタジオCEO GO!
Steam版『モンハンワイルズ』“圧倒的に不評”を記録―一時期は同接138万人越えの大作 GO!

第7回のテキスト編の講義メモ

本日予定していた講義内容(テキスト編)の講義メモです。 memo20250614.txt
次回のテキスト編は、このフォローからになりますので、一読をお願いします。

講義メモ
テキスト編:p.291「DateTime構造体:clock01.cs」から

p.291 DateTime構造体:clock01.cs

・構造体の中でもC/C++から引き継がれた日付時刻の機能にメソッドやプロパティを加えたDateTime構造体は活用しやすい。
・コンソールに関する各種のプロパティやメソッドを併用することで、動きのあるコンソールアプリケーションを作成できる。

p.292(Consoleクラスの静的プロパティと静的メソッド)

・Consoleクラスには、すでに学習した静的メソッドWrite、WriteLine、ReadLineに加えて便利な静的メソッド、静的プロパティが多数提供されており、コンソールを疑似的な実行ウインドウにすることができる
・bool CursorVisible静的プロパティ:falseを設定するとカーソルが見えなくなる。チラつき防止に便利。
・string Title静的プロパティ:コンソール名を設定できる。
・void SetWindowsSize(int,int)静的メソッド:ウィンドウの幅と高さを文字数で指定できる
・ConsoleColor BackgroundColor静的プロパティ:ConsoleColor列挙型で背景色を指定できる。例:ConsoleColor.Yellow
・ConsoleColor ForegroundColor静的プロパティ:ConsoleColor列挙型で文字色を指定できる。
・void Clear()静的メソッド:再描画して設定を反映する(※実行すると文字色・背景色の変更が有効になる)
・void SetCursolPosition(int,int)静的メソッド:カーソルの位置を変える。移動先は文字数。
・bool KeyAvailable静的プロパティ:キーが押されているとtrueになる

アレンジ演習:p.291 clock01.cs

・ストップウォッチにしよう
・実行すると「00:00:00」を表示してカウントを開始する
・これを実現するには、時刻を表す1000万分の1秒刻みのカウンタを返すDateTime構造体のlong Ticksプロパティを用いる
・起動時のTicks値を保持しておいて、Nowで得た時点のTicks値との差で経過時間が得られる
・この差を1000万倍すると秒になるので、これを3600で割った結果が時間、60で割った余りが秒、60で割ってから60で割った余りが分になる

作成例

//アレンジ演習:p.201 clock01.cs
using System;
class clock01 {
    public static void Main() {
        int oldsecond = 0; //秒が変わったどうかの判断用に現在の秒を待避しておく変数
        Console.CursorVisible = false; //カーソルを消す
        Console.Title = "時計"; //コンソールタイトルを設定
        Console.SetWindowSize(12, 3); //コンソールウィンドウの大きさを12字×3行に変更
        Console.BackgroundColor = ConsoleColor.Yellow; //背景色を黄色に
        Console.ForegroundColor = ConsoleColor.Black; //文字色を黒色に
        Console.Clear(); //色の設定を反映する
        DateTime st = DateTime.Now; //【追加】開始時の現在時刻を得る
        DateTime mt; //日付時刻構造体を生成
        while (true) { //無限ループ
            mt = DateTime.Now; //現在時刻を得る
            int lap = (int)((mt.Ticks - st.Ticks) / 10000000L); //【追加】経過秒数を得る
            int hor = lap / 3600; //【追加】経過秒数から経過時間を得る
            int min = lap / 60 % 60; //【追加】経過秒数から経過分を得る
            int sec = lap % 60; //【追加】経過秒数から経過秒を得る
            if (sec == oldsecond) { //【変更】秒が変わっていなければ
                continue; //今回のループのこれ以降をスキップ
            } else { //秒が変わっていれば
                oldsecond = sec; //【変更】次回の判断用に現在の秒を待避しておく
            }
            Console.SetCursorPosition(2, 1); //カーソル位置を2字目に戻す
            Console.Write("{0:00}:{1:00}:{2:00}", hor, min, sec); //【変更】時分秒を各2桁で表示
            if (Console.KeyAvailable) { //何かキーが押されていたら
                break; //ループを抜ける(終了)
            }
        }
    }
}

アレンジ演習:p.291 clock01.cs・改

・ミリ秒も表示するストップウォッチにしよう
・実行すると「00:00:00.000」を表示してカウントを開始する
・経過時間秒数を1万倍するとミリ秒数になるので、これを3600000で割った結果が時間、60000で割ってから60で割った余りが分、1000で割ってから60で割った余りが秒、1000で割った余りがミリ秒
・表示ウィンドウの大きさを16×3に広げること

作成例

//アレンジ演習:p.201 clock01.cs・改
using System;
class clock01 {
    public static void Main() {
        int oldmsecond = 0; //【変更】ミリ秒が変わったどうかの判断用に現在のミリ秒を待避しておく変数
        Console.CursorVisible = false; //カーソルを消す
        Console.Title = "時計"; //コンソールタイトルを設定
        Console.SetWindowSize(16, 3); //【変更】コンソールウィンドウの大きさを16字×3行に変更
        Console.BackgroundColor = ConsoleColor.Yellow; //背景色を黄色に
        Console.ForegroundColor = ConsoleColor.Black; //文字色を黒色に
        Console.Clear(); //色の設定を反映する
        DateTime st = DateTime.Now; //開始時の現在時刻を得る
        DateTime mt; //日付時刻構造体を生成
        while (true) { //無限ループ
            mt = DateTime.Now; //現在時刻を得る
            int lap = (int)((mt.Ticks - st.Ticks) / 10000L); //【変更】経過ミリ秒数を得る
            int hor = lap / 3600000; //【変更】経過ミリ秒数から経過時間を得る
            int min = lap / 60000 % 60; //【変更】経過ミリ秒数から経過分を得る
            int sec = lap / 1000 % 60; //【変更】経過ミリ秒数から経過秒を得る
            int mil = lap % 1000; //【追加】経過ミリ秒数から経過ミリ秒を得る
            if (mil == oldmsecond) { //【変更】ミリ秒が変わっていなければ
                continue; //今回のループのこれ以降をスキップ
            } else { //ミリ秒が変わっていれば
                oldmsecond = mil; //【変更】次回の判断用に現在のミリ秒を待避しておく
            }
            Console.SetCursorPosition(2, 1); //カーソル位置を2字目に戻す
            Console.Write("{0:00}:{1:00}:{2:00}.{3:000}", hor, min, sec, mil); //【変更】時分秒ミリ秒を表示
            if (Console.KeyAvailable) { //何かキーが押されていたら
                break; //ループを抜ける(終了)
            }
        }
    }
}

p.294 練習問題 1 ヒント

・プロパティによる制限なので、クラス内でのエラー表示は不要(するならmain側で)
・偶数は整数なので、構造体のデータメンバの型をint型(またはlong型)にすると良い
・偶数しか保持できないようにするには、データメンバへの直接アクセスを禁止するためprivateにする
・このデータメンバを扱うプロパティを定義し、setにおいてvalueの値が偶数かチェックしてから代入する
・getは通常形式で良い

作成例

//p.294 練習問題 1 ex1101.cs
using System;
struct EvenOnly { //構造体の定義
    private int x; //非公開にすることで偶数以外の直接代入を防止する
    public int X { //偶数のみをセットできるプロパティ
        get { return x; } 
        set { x = (value % 2 == 0) ? value : x; } //偶数の場合のみセット
    }
}
class ex1101 {
    public static void Main() {
        EvenOnly eo = new EvenOnly(); //構造体オブジェクトを生成
        eo.X = 6; //プロパティを用いて偶数を設定すると
        Console.WriteLine("偶数設定後:{0}", eo.X);
        eo.X = 5; //プロパティを用いて奇数を設定しようとしたが
        Console.WriteLine("奇数設定後:{0}", eo.X);
    }
}

第12章 デリゲートとイベント

p.295 デリゲートとは

・メソッドへの参照を保持しておいて、これを用いてメソッドを呼び出せる仕掛け
・つまり、デリゲートを通じてのメソッドの呼び出しが可能
・C/C++における「関数へのポインタ」と同じ考え方を洗練したもの
・イベント処理などで必須のテクニック
・宣言書式: delegate メソッドの戻り値型 デリゲート名(メソッドの引数リスト);
・例:bool answer(int x){…}メソッド用のデリゲートmdならば、delegate bool md(int w) とする
・利用にはデリゲートオブジェクトの生成が必要で、この時に呼び出したいメソッドの指定もできる
・生成書式: デリゲート名 参照変数 = new デリゲート名(メソッド名);
・例:bool answer(int x){…}メソッド用のデリゲートmdならば、md mymd = new md(answer);
・デリゲート経由で指定済のメソッドを呼び出すには、参照変数を別名のように利用できる
・例: bool b = mymd(5); // answer(5)が呼ばれ戻り値が返される

p.297 delegate01.cs について

・Main()メソッドの戻り値をint型にしているが、この場合、必要性はないので、いつもの通り voidにして良い
・よって「return 0;」も不要

p.297 delegate01.cs

//p.297 delegate01.cs
using System;
delegate void MyDelegate();
class delegate01 {
    public static void show() { //Main()メソッドから呼ばれる静的メソッド
        Console.WriteLine("呼ばれました");
    }
    public static void Main() {
        //直接showメソッドを呼び出す
        show();
        //デリゲートの作成
        MyDelegate md = new MyDelegate(show);
        //デリゲートを通してshowメソッドを実行
        md();
    }
}

テキスト編次回予告:p.298「(別のクラスにあるメソッドをデリゲートで扱う)」から

今週の話題

ゲームソフト販売本数ランキング:今週1位は「マリオカート ワールド(Switch2)」 GO!
“SUMMER GAME FEST 2025”発表まとめ。『バイオ9』『コードヴェイン2』発表、龍が如くチーム新作の最新情報も! GO!
6月5日配信「State of Play」ピーク時視聴者数が最高記録達成!225万人超え GO!
「Nintendo Live 2025」開催決定。スイッチ2ゲーム体験や『マリオカート ワールド』で世界各地のプレイヤーが競うイベント実施 GO!
「スイッチ2」での「スイッチ」ソフト動作確認状況が一目瞭然!マイニンテンドーストアにて互換性表記開始 GO!

『ボーダーランズ2』レビュー荒らしのきっかけとなった利用規約変更について声明公開―スパイウェアは使用しておらず、シングルプレイ&非商用Modは黙認の方針 GO!
「Ubisoft Forward」2025年は開催されない見通し?海外ジャーナリスト報じる。5月には一部主要タイトル延期発表も GO!

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

:図形の塗りつぶし、文字列の描画 など

提出フォロー:演習17 同心円を描こう

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

作成例

//演習17 同心円を描こう
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のペンを生成
        int mx = backi.Width / 2, my = backi.Height / 2; //【以下追加】中心座標
        for(int i = 1; i <= 9; i++) { //9個
            e.Graphics.DrawEllipse(pCyan2, mx - i * 10, my - i * 10, i * 20, i * 20); //円を描く
        }
    }
    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); //生成済のインスタンスを実行
    }
}

テーマ18 矩形の塗りつぶし

・矩形の塗りつぶしには、DrawRectangleの代わりに、FillRectangleを用いる
・また、Penの代わりにBrushを用いる
・Brushは、System.Drawing.Brushクラスを指すが、これは抽象クラスであり、実際の描画ではBrushクラスの派生クラスを用いる
 https://learn.microsoft.com/ja-jp/dotnet/api/system.drawing.brush
・複数ある派生クラスの中で、最もシンプルなのがSystem.Drawing.SolidBrushクラスで、このクラスのコンストラクタに
 Color列挙体の列挙子を渡すことで、単色の塗りつぶし用のブラシが得られる
・書式例: Brush sb = new SolidBrush(Color.色名);
・FillRectangleメソッドにブラシと左上座標、幅、高さを渡すことで矩形の塗りつぶしが可能
・書式例: e.Graphics.FillRectangle(ブラシ, 左上X座標, 左上Y座標, 幅, 高さ);

演習18 チェス盤を描こう

・画面上に8行8列の升目を描こう
・座標(10, 20)から各マスを30×30で、枠線の色はCyan、枠線の太さは2とする
・1行目の1マス目はCyanで塗りつぶし、1つおきに塗りつぶす
・演習17の同心円は削除する

作成例

//演習18 チェス盤を描こう
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のペンを生成
        Brush bCyan = new SolidBrush(Color.Cyan); //【以下追加】水色の塗りつぶしブラシを生成
        for(int i = 0; i < 8; i++) { //8列
            for(int j = 0; j < 8; j++) { //8行
                if ((i + j) % 2 == 0) { //1つおきに
                    e.Graphics.FillRectangle(bCyan, 10 + i * 30, 20 + j * 30, 30, 30); //塗りつぶす
                } else {
                    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); //生成済のインスタンスを実行
    }
}

演習19 チェス盤を描こう・改

・木目の画像を用意してImageとして用いることで、リアルなチェス盤を描こう
・mokume0.png  mokume1.png 
・合わせて升目のサイズを50×50にしよう

作成例

//演習19 チェス盤を描こう・改
using System; //C#標準クラス用
using System.Windows.Forms; //Application、Formクラス用
using System.Drawing; //Size、Graphics、Image、Penクラス、Color構造体用
class Program : Form { //Formクラスの派生クラス
    Image backi, playeri, chess0, chess1; //【変更】画像ファイル用変数
    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のペンを生成
        Brush bMokume0 = new TextureBrush(chess0); //【変更】木目テクスチャブラシ0を生成
        Brush bMokume1 = new TextureBrush(chess1); //【変更】木目テクスチャブラシ1を生成
        for(int i = 0; i < 8; i++) { //8列
            for(int j = 0; j < 8; j++) { //8行
                if ((i + j) % 2 == 0) { //1つおきに
                    e.Graphics.FillRectangle(bMokume0, 10 + i * 50, 20 + j * 50, 50, 50); //【変更】木目0
                } else {
                    e.Graphics.FillRectangle(bMokume1, 10 + i * 50, 20 + j * 50, 50, 50); //【変更】木目1
                }
            }
        }
    }
    public Program() { //コンストラクタ
        try { //例外処理対象
            backi = Image.FromFile("backb2.bmp"); //背景画像ファイルを読み込む
            playeri = Image.FromFile("player.gif"); //自機画像ファイルを読み込む
            chess0 = Image.FromFile("mokume0.png"); //【追加】チェス画像ファイル0を読み込む
            chess1 = Image.FromFile("mokume1.png"); //【追加】チェス画像ファイル1を読み込む
        } 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); //生成済のインスタンスを実行
    }
}

テーマ19 文字列の描画

・コンソールアプリケーションでは既定のフォントによりデバッグコンソールに文字列を表示しているが、フォームアプリケーションでは
 画像などと同様にフォーム上に文字列を描画する
・よって、色や大きさに加えてフォントも指定できる
・文字列の描画にはDrawString()メソッドを用い、色の指定にはブラシを用いる
・フォントの指定にはSystem.Drawing.Fontクラスのオブジェクトを用いる
・Fontクラスには複数のコンストラクタがあるが、シンプルなのはFont("フォント名", ポイント数)
 例: Font f = new Font("メイリオ", 15);
・DrawStringメソッドにブラシと左上座標、幅、高さを渡すことで矩形の塗りつぶしが可能
・書式例: e.Graphics.FillRectangle(文字列, フォント, ブラシ, 左上X座標, 左上Y座標);

提出:演習19 チェス盤を描こう・改

講義メモ

テキスト篇:p.282「構造体でインターフェイスを実装する」
ゲーム開発演習:図形の塗りつぶし、文字列の描画 など

p.282 構造体でインターフェイスを実装する

・C#の構造体は「軽量クラス」の位置づけで、クラスの機能を削って軽くしたもの。
 ※そのため、Unityなどで活用されており、Vector3構造体など、Unity独自の構造体の出番が多い
・継承はできないが、インターフェイスの実装であれば可能
・クラスにおけるインターフェイスの実装とほぼ同じだが、インターフェイスを実装した構造体の利用には
 new演算子による明示的なオブジェクトの生成が必要

アレンジ演習:p.282 struct05.cs

・「= new MyStruct();」を省略するとどういうエラーになる確認しよう
・適当なインターフェイスの定義を追加し、構造体が複数のインターフェイスを実装可能か確認しよう

作成例(p.263「複数のインターフェイスを実装する」参照:抽象メソッド名が異なる場合)

//アレンジ演習:p.282 struct05.cs
using System;
interface IMyInterface { //インターフェイス①の定義
    void Show(); //抽象メソッド
    int xprop { //抽象プロパティ
        get;
        set;
    }
}
interface Flyable { //【追加】インターフェイス②の定義
    void Fly(); //抽象メソッド(インターフェイス①とは名前が異なる)
}
struct MyStruct : IMyInterface, Flyable { //【変更】2インターフェイスを実装する構造体
    int x;
    public void Show() { //インターフェイス①の抽象メソッドのオーバーライド
        Console.WriteLine("x = {0}", x);
    }
    public void Fly() { //【以下追加】インターフェイス②の抽象メソッドのオーバーライド
        Console.WriteLine("Flyable.x = {0}", x);
    }
    public int xprop { //抽象プロパティのオーバーライド
        get { return x; }
        set { x = value; }
    }
}
class struct05 {
    public static void Main() {
        MyStruct msNew = new MyStruct(); //newによる明示的なオブジェクト生成が必要
        msNew.Show(); //インターフェイス①の抽象メソッドのオーバーライドを呼ぶ
        msNew.xprop = 20; //抽象プロパティのオーバーライド(set)を呼ぶ
        msNew.Fly(); //【変更】インターフェイス②の抽象メソッドのオーバーライドを呼ぶ
    }
}

作成例(p.263「複数のインターフェイスを実装する」参照:抽象メソッド名が同じ場合)

//アレンジ演習:p.282 struct05.cs
using System;
interface IMyInterface { //インターフェイス①の定義
    void Show(); //抽象メソッド
    int xprop { //抽象プロパティ
        get;
        set;
    }
}
interface Flyable { //【追加】インターフェイス②の定義
    void Show(); //抽象メソッド
}
struct MyStruct : IMyInterface, Flyable { //【変更】2インターフェイスを実装する構造体
    int x;
    void IMyInterface.Show() { //【変更】インターフェイス①の抽象メソッドのオーバーライド
        Console.WriteLine("x = {0}", x);
    }
    void Flyable.Show() { //【以下追加】インターフェイス②の抽象メソッドのオーバーライド
        Console.WriteLine("Flyable.x = {0}", x);
    }
    public int xprop { //抽象プロパティのオーバーライド
        get { return x; }
        set { x = value; }
    }
}
class struct05 {
    public static void Main() {
        MyStruct msNew = new MyStruct(); //newによる明示的なオブジェクト生成が必要
        IMyInterface i1; //【追加】インターフェイス①の参照変数
        i1 = msNew; //【追加】構造体オブジェクトへの参照を代入
        i1.Show(); //【変更】インターフェイス①の参照経由で抽象メソッドのオーバーライドを呼ぶ
        msNew.xprop = 20; //抽象プロパティのオーバーライド(set)を呼ぶ
        Flyable i2; //【追加】インターフェイス②の参照変数
        i2 = msNew; //【追加】構造体オブジェクトへの参照を代入
        i2.Show(); //【追加】インターフェイス②の参照経由で抽象メソッドのオーバーライドを呼ぶ
    }
}

p.284 構造体をメソッドに渡す

・クラスオブジェクトをメソッドに渡すと参照渡しになり、引数で受け取ったオブジェクトのデータメンバの値をメソッド側で操作すると
 呼び出し元のオブジェクトに反映する
・構造体オブジェクトをメソッドに渡すと値渡しになりコピーされて渡されるので、引数で受け取ったオブジェクトのデータメンバの値を
 メソッド側で操作しても呼び出し元のオブジェクトには反映しない
・よって、構造体オブジェクトをメソッドに渡し、クラスオブジェクトと同様に扱いたい場合は、ref(p.188)を指定して
 参照渡しにすると良い

アレンジ演習:p.284 struct06.cs

・MyData構造体と、MyDataClassクラスの双方に、データメンバx、yの値を表示するShowメソッドを追加してコードをシンプルにしよう

作成例

//アレンジ演習:p.284 struct06.cs
using System;
struct MyData { //構造体の定義
    public int x, y;
    public void show(string s) { //【以下追加】
        Console.WriteLine("{0}\nx = {0}, y = {1}", s, x, y);
    }
}
class MyDataClass { //(比較用の)クラスの定義
    public int x, y;
    public void show(string s) { //【以下追加】
        Console.WriteLine("{0}\nx = {0}, y = {1}", s, x, y);
    }
}
class ChangeData { //構造体とクラスのオブジェクトを操作するメソッドを持つクラス
    public void change(ref MyData md) { //構造体オブジェクトを参照渡しする
        int temp; //交換用の変数を用いてxとyの値を交換(呼び出し元に反映する)
        temp = md.x;
        md.x = md.y;
        md.y = temp;
    }
    public void change(MyData md) { //構造体オブジェクトを値渡しする
        int temp; //交換用の変数を用いてxとyの値を交換(呼び出し元には反映しない)
        temp = md.x;
        md.x = md.y;
        md.y = temp;
    }
    public void change(MyDataClass m) { //クラスオブジェクトを参照渡しする
        int temp; //交換用の変数を用いてxとyの値を交換(呼び出し元に反映する)
        temp = m.x;
        m.x = m.y;
        m.y = temp;
    }
}
class struct06 {
    public static void Main() {
        MyData md = new MyData(); //newで明示的に構造体オブジェクトを生成
        md.x = 10;
        md.y = 20;
        md.show("最初の状態"); //【変更】
        ChangeData cd = new ChangeData();
        cd.change(md); //構造体オブジェクトを値渡しする
        md.show("cd.change(md)後"); //【変更】変化しない
        cd.change(ref md); //構造体オブジェクトを参照渡しする
        md.show("cd.change(ref md)後");//【変更】変化する
        MyData m; //newを用いずに構造体オブジェクトを生成
        m.x = 10;
        m.y = 20;
        m.show("最初の状態"); //【変更】
        cd.change(ref m); //構造体オブジェクトを参照渡しする
        m.show("cd.change(ref m)後"); //【変更】変化する
        MyDataClass mdc = new MyDataClass(); //クラスオブジェクトを生成
        mdc.x = 10;
        mdc.y = 20;
        mdc.show("最初の状態"); //【変更】
        cd.change(mdc); //クラスオブジェクトを参照渡しする
        mdc.show("cd.change(mdc)後"); //【変更】変化する
    }
}

p.288 DateTime構造体を使ってみる

・C#が提供する構造体は多数あり、最も利用されているのは.NET基本型の構造体(p.45参照)
・例えば、int型は内部的にはSystem.Int32構造体として定義されている
 https://learn.microsoft.com/ja-jp/dotnet/fundamentals/runtime-libraries/system-int32
・p.45で説明したMaxValue、MinValueは、この構造体の持つ定数データメンバになっている。
・p.45で説明したParse(string)は、この構造体の持つ静的メソッド。
・p.47で説明したConvert.ToInt32(string)は、Convertクラスのメソッドだが、この構造体が
 IConvertible インターフェイスを実装しているため、Parseと同様に利用できる
・また、日付時刻の利用に便利なSystem.DateTime構造体も提供されている

p.288 DateTime構造体を使ってみる:主なメンバ

・Nowプロパティ:getのみの静的プロパティでDateTime型。現在日付時刻を持つDateTime構造体オブジェクトを返す
・Dateプロパティ:getのみのプロパティでDateTime型。時刻を00:00:00にしたDateTime構造体オブジェクトを返す
 ※ このままConsole.Writeなどで表示すると「yyyy/mm/dd 0:00:00」形式となる
・Yearプロパティ:getのみのプロパティでint型。西暦年を返す。
・Monthプロパティ:getのみのプロパティでint型。月を1から12で返す。(他言語とは異なり1月から)
・Dayプロパティ:getのみのプロパティでint型。日を返す。
・DayOfWeekプロパティ:getのみのプロパティでDayOfWeek列挙型。曜日を示す列挙子を返す。
 ※ このままConsole.Writeなどで表示すると英語の曜日名("Sunday"などのフルスペル)となる
・DayOfYearプロパティ:getのみのプロパティでint型。その年の何日目かを返す。
・Hourプロパティ:getのみのプロパティでint型。24時間での時を0から23で返す。
・Minuteプロパティ:getのみのプロパティでint型。分を0から59で返す。
・Secondプロパティ:getのみのプロパティでint型。秒を0から59で返す。
・MilliSecondプロパティ:getのみのプロパティでint型。ミリ秒を0から999で返す。
・DateTime(int y, int m, int d)コンストラクタ:西暦y年m月d日の構造体オブジェクトを生成
・DateTime(int y, int m, int d, int h, int n, int s)コンストラクタ:西暦y年m月d日h時n分s秒の構造体オブジェクトを生成
・ToShortDateStringメソッド:インスタンスメソッドでstring型。「yyyy/mm/dd」形式の文字列を返す

アレンジ演習:p.290 DateTime01.cs

・コンソールから入力した年、月、日、時、分、秒でDateTime構造体オブジェクトを生成し、各プロパティやメソッドの動作を確認しよう
・また、不正な年、月、日、時、分、秒を入力するとどうなるか確認しよう
 ⇒ArgumentOutOfRangeExceptionとなり実行時エラーで異常終了する(年は1以上ならOK)
・曜日を日本語曜日1文字で表示するようにしよう
 ⇒DayOfWeek列挙型をintにキャストすると0~6の整数になるので、これを添字にして用いると良い

作成例

//アレンジ演習:p.290 datetime01.cs
using System;
class datetime01
{
    public static void Main()
    {
        char[] dow = {'日','月','火','水','木','金','土'}; //【追加】
        DateTime dt = DateTime.Now;
        Console.WriteLine("今日は{0}年{1}月{2}日({3})です",
            dt.Year, dt.Month, dt.Day, dow[(int)dt.DayOfWeek]); //【変更】年月日曜日をプロパティで得る
        Console.WriteLine("現在{0}時{1}分{2}秒{3}ミリセコンドです",
            dt.Hour, dt.Minute, dt.Second, dt.Millisecond); //時分秒ミリ秒をプロパティで得る
        Console.WriteLine("dt.Date = {0}", dt.Date); //「yyyy/mm/dd 0:00:00」を表示
        Console.WriteLine("短い日付形式 = {0}", dt.ToShortDateString()); //「yyyy/mm/dd」を表示
     //【以下追加】
        int y, m, d, h, n, s; //年月日時分秒
        Console.Write("年:"); y = int.Parse(Console.ReadLine());
        Console.Write("月:"); m = int.Parse(Console.ReadLine());
        Console.Write("日:"); d = int.Parse(Console.ReadLine());
        Console.Write("時:"); h = int.Parse(Console.ReadLine());
        Console.Write("分:"); n = int.Parse(Console.ReadLine());
        Console.Write("秒:"); s = int.Parse(Console.ReadLine());
        dt = new DateTime(y, m, d, h, n, s); //入力した年月日時分秒で構造体オブジェクトを生成
        Console.WriteLine("{0}年{1}月{2}日({3})です",
            dt.Year, dt.Month, dt.Day, dow[(int)dt.DayOfWeek]); //年月日曜日をプロパティで得る
        Console.WriteLine("{0}時{1}分{2}秒{3}ミリセコンドです",
            dt.Hour, dt.Minute, dt.Second, dt.Millisecond); //時分秒ミリ秒をプロパティで得る
        Console.WriteLine("dt.Date = {0}", dt.Date); //「yyyy/mm/dd 0:00:00」を表示
        Console.WriteLine("短い日付形式 = {0}", dt.ToShortDateString()); //「yyyy/mm/dd」を表示
    }
}

今週の話題

ゲームソフト販売本数ランキング:今週1位は「ELDEN RING NIGHTREIGN(PS5)」 GO!
6月7日より開催の「Summer Game Fest 2025」は60以上のパートナーが参加!スイッチ2も名を連ねる GO!
「BitSummit the 13th」オフィシャルPRサポーターズ&追加スポンサー発表!そのほか松竹の新作『夢幻桜楼閣』出展も明らかに GO!
「150点のゲームを毎回作る」「人がプレイしてなんぼ」―“カプコン流”のアートや仕事の秘訣とは?「大カプコン展」会場で実施!クリエイター対談イベントをレポート GO!

EAのマーベル「ブラックパンサー」ゲームが開発中止…開発スタジオも閉鎖、日の目を見ることなく消滅へ GO!
「ワンピース」酷似の『パイレーツアニメクエスト』マイニンテンドーストアから削除 GO!

【最新】“SUMMER GAME FEST 2025”発表まとめ。『バイオ9』『コードヴェイン2』発表、龍が如くチーム新作の最新情報も! GO!