前回のコメント

・前から習っていた列挙型でしたが、意外と使われている場面が多いんだなと感じました。

 ですね。いろいろな応用例がありますので、是非、使いこなしてください。

・プロパティの細部まで知れるのは面白いと思いました!

 実践編ではプロと同じリソースを使いこなす練習をしますので、
 是非、ニュアンスを掴んでください。

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

ゲーム開発演習:キーボードが押された情報を得る、終了処理 など

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

・C#ではキーボードが押された情報を得る手段が2つあり、どちらも12章後半で学習するイベントを用いる
① キーボードが押されたイベントを用いる
② タイマーイベントを用いてキーボードの状態をスキャンする
・今回は①を用いて作ってみよう
・イベントを受け取るには、主にコンストラクタにおいてイベントの登録を行う
・System.Windows.Forms.Controlクラスが提供するKeyDownイベントに、
 System.Windows.Forms.KeyEventHandlerデリゲートのインスタンスを「+=」することで、イベントが登録される
・この時、デリゲートの引数としてイベントにつなげたいメソッド名を指定する。
・書式: KeyDown += new KeyEventHandler(メソッド名);
・KeyDownイベントにつなげたいメソッドは、戻り値型はvoid、引数はobject型と、KeyEventArgs型とする
 (メソッド名は自由)
・これで、どのキーが押されても、このメソッドが呼ばれるようになる

テーマ12 フォームアプリケーションのプログラム側からの終了方法

・FormクラスのClose()メソッドを呼び出せば良い

演習9 キー入力で終了

・何か、キーが押されたらプログラムが終了するようにしよう

作成例

//演習9 キー入力で終了
using System; //フォームアプリケーションに必須
using System.Windows.Forms; //フォームアプリケーションに必須
using System.Drawing; //Image用
class Program : Form { //Formクラスを継承
    Image backb = Image.FromFile("backb.bmp"); //背景画像の読込
    Image enemy = Image.FromFile("enemy.gif"); //アイテム画像の読込
    Image burn = Image.FromFile("burn.gif"); //効果用画像の読込
    protected override void OnPaint(PaintEventArgs e) { //Formのメソッドをオーバーライド
        base.OnPaint(e); //まず、Formクラスにおけるメソッドの内容(基本再描画処理)を実行
        e.Graphics.DrawImage(backb, 0, 0); //背景画像を(0,0)から描画
        int mx = backb.Width / 2, my = backb.Height / 2; //中央座標を得る
        e.Graphics.DrawImage(enemy, mx - enemy.Width / 2, my - enemy.Height / 2); //アイテム画像を中央に描画
        e.Graphics.DrawImage(burn, mx - burn.Width / 2, my - burn.Height / 2); //アイテム画像を中央に描画
    }
    void OnKeyDown(object o, KeyEventArgs e) { //【以下追加】キーボードが押された時に呼ばれるメソッド
        Close(); //フォームアプリケーション終了
    }
    Program() { //【以下追加】コンストラクタ
        KeyDown += new KeyEventHandler(OnKeyDown); //キーボードが押された時に呼ばれるメソッドを登録
    }
    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); //インスタンスを画面に出す
    }
}

テーマ13 どのキーが押されたのか知る

・KeyDownを用いてキー入力イベント登録して呼び出すようにしたメソッドは、KeyEventArgs型の引数を持つ必要がある
・このKeyEventArgsオブジェクトのKeyCodeプロパティにより、Keys列挙型オブジェクトが得られる。
 https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.keys
・Keys列挙型オブジェクトからキーの情報を文字列で得て比較に用いると良い
 例: e.KeyCode.ToString()

演習10 押されたキーの文字列を表示

・OnKeyDownメソッドの「Close();」をコメントアウトする
・代わりに、押されたキーの文字列をMessageBoxで表示しよう
・終了にはタスクバーで右クリックし「ウィンドウを閉じる」で

作成例

//演習10 押されたキーの文字列を表示
using System; //フォームアプリケーションに必須
using System.Windows.Forms; //フォームアプリケーションに必須
using System.Drawing; //Image用
class Program : Form { //Formクラスを継承
    Image backb = Image.FromFile("backb.bmp"); //背景画像の読込
    Image enemy = Image.FromFile("enemy.gif"); //アイテム画像の読込
    Image burn = Image.FromFile("burn.gif"); //効果用画像の読込
    protected override void OnPaint(PaintEventArgs e) { //Formのメソッドをオーバーライド
        base.OnPaint(e); //まず、Formクラスにおけるメソッドの内容(基本再描画処理)を実行
        e.Graphics.DrawImage(backb, 0, 0); //背景画像を(0,0)から描画
        int mx = backb.Width / 2, my = backb.Height / 2; //中央座標を得る
        e.Graphics.DrawImage(enemy, mx - enemy.Width / 2, my - enemy.Height / 2); //アイテム画像を中央に描画
        e.Graphics.DrawImage(burn, mx - burn.Width / 2, my - burn.Height / 2); //アイテム画像を中央に描画
    }
    void OnKeyDown(object o, KeyEventArgs e) { //キーボードが押された時に呼ばれるメソッド
        MessageBox.Show(e.KeyCode.ToString()); //【追加】
        //Close(); //フォームアプリケーション終了
    }
    Program() { //コンストラクタ
        KeyDown += new KeyEventHandler(OnKeyDown); //キーボードが押された時に呼ばれるメソッドを登録
    }
    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); //インスタンスを画面に出す
    }
}

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

・OnKeyDownメソッドMessageBoxを削除する
・押されたキーの文字列が"Escape"ならばClose()すると良い

作成例

//演習11 Escキーが押されたら終了
using System; //フォームアプリケーションに必須
using System.Windows.Forms; //フォームアプリケーションに必須
using System.Drawing; //Image用
class Program : Form { //Formクラスを継承
    Image backb = Image.FromFile("backb.bmp"); //背景画像の読込
    Image enemy = Image.FromFile("enemy.gif"); //アイテム画像の読込
    Image burn = Image.FromFile("burn.gif"); //効果用画像の読込
    protected override void OnPaint(PaintEventArgs e) { //Formのメソッドをオーバーライド
        base.OnPaint(e); //まず、Formクラスにおけるメソッドの内容(基本再描画処理)を実行
        e.Graphics.DrawImage(backb, 0, 0); //背景画像を(0,0)から描画
        int mx = backb.Width / 2, my = backb.Height / 2; //中央座標を得る
        e.Graphics.DrawImage(enemy, mx - enemy.Width / 2, my - enemy.Height / 2); //アイテム画像を中央に描画
        e.Graphics.DrawImage(burn, mx - burn.Width / 2, my - burn.Height / 2); //アイテム画像を中央に描画
    }
    void OnKeyDown(object o, KeyEventArgs e) { //キーボードが押された時に呼ばれるメソッド
        if(e.KeyCode.ToString() == "Escape") {  //【変更】
            Close(); //フォームアプリケーション終了
        }
    }
    Program() { //コンストラクタ
        KeyDown += new KeyEventHandler(OnKeyDown); //キーボードが押された時に呼ばれるメソッドを登録
    }
    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); //インスタンスを画面に出す
    }
}

テーマ14 線の描画

・画面上に線を描画することは、OSによる描きなおしの影響を受ける。また、描画に必要な情報を得る必要があるので、
 OnPaintメソッド(オーバーライド)の中で行う必要がある。
・画像の描画の後であれば、画像の上に線を描画できる。
・線を描画するには、描画に用いるペンを得る必要がある。
・ペンはSystem.Drawing.Penオブジェクトで得られる。
・System.Drawing.Penクラスのコンストラクタに色と太さを指定することで好みの線にできる
 https://learn.microsoft.com/ja-jp/dotnet/api/system.drawing.pen
・書式例: Pen 変数 = new Pen(色, 太さ);
・色はSystem.Drawing.Color構造体の静的プロパティ(p.211)で指定できる
 例:Color.Red、Color.Black 等
 https://learn.microsoft.com/ja-jp/dotnet/api/system.drawing.color
 ※構造体はテキスト11章で説明する「軽量クラス」※C/C++の構造体とは異なる
・太さはfloat型の実数で指定する(整数でもOK)
・線を描画するには、ペン、開始座標、終了座標をDrawImageと同様にDrawLineメソッドに渡せばよい
・書式: e.Graphics.DrawLine(Penオブジェクト, 開始X座標, 開始Y座標, 終了X座標, 終了Y座標)

演習12 対角線を描く

・画面上に左上から右下へ赤色太さ10の線を描く
・画面上に右上から左下へ黄色太さ20の線を描く

提出:演習12

講義メモ

テキスト篇:p.282「構造体でインターフェイスを実装する」
ゲーム開発演習:キーボードが押された情報を得る、終了処理 など

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

・クラスとまったく同様に構造体でインターフェイスを実装できる
・複数の実装も可能
・ただし、利用側ではnew演算子で明示的にオブジェクトを生成すること

p.282 struct05.cs

//p.282 struct05.cs
using System;
interface IMyInterface { //インターフェイスの定義
    void Show(); //抽象メソッド
    int xprop { get; set; }  //抽象プロパティ
}
struct MyStruct : IMyInterface { //インターフェイスを実装した構造体
    int x;
    public void Show() { //抽象メソッドの実装
        Console.WriteLine("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.Show();
    }
}

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

・クラスとの違いは値型であることで、引数の構造体から仮引数の構造体へ全メンバがコピーされる
・よって、クラスと同様に参照渡しにするにはref(p.188)を指定すれば良い

p.284 struct06.cs

//p.284 struct06.cs
using System;
struct MyData { //構造体の定義
    public int x, y;
}
class MyDataClass { //クラスの定義
    public int x, y;
}
class ChangeData {
    public void change(ref MyData md) { //構造体型引数を参照渡し
        int temp; //作業用
        temp = md.x; //値を交換
        md.x = md.y;
        md.y = temp;
    }
    public void change(MyData md) { //構造体型引数を値渡し(オーバロード可能)
        int temp; //作業用
        temp = md.x; //値を交換
        md.x = md.y;
        md.y = temp; //(※元の引数には反映しないのでこの処理は無意味)
    }
    public void change(MyDataClass m) { //クラス型引数なので参照渡し(オーバロード)
        int temp; //作業用
        temp = m.x; //値を交換
        m.x = m.y;
        m.y = temp;
    }
}
class struct06 {
    public static void Main() {
        MyData md = new MyData(); //構造体オブジェクトを生成
        md.x = 10;
        md.y = 20;
        Console.WriteLine("最初の状態");
        Console.WriteLine("md.x = {0}, md.y = {1}", md.x, md.y);
        ChangeData cd = new ChangeData();
        cd.change(md); //構造体型引数を値渡し
        Console.WriteLine("cd.change(md)後");
        Console.WriteLine("md.x = {0}, md.y = {1}", md.x, md.y); //値渡しなので変わらない
        cd.change(ref md); //構造体型引数を参照渡し
        Console.WriteLine("cd.change(ref md)後");
        Console.WriteLine("md.x = {0}, md.y = {1}", md.x, md.y); //参照渡しなので変わる
        MyData m; //構造体オブジェクトを生成(new無し)
        m.x = 10;
        m.y = 20;
        Console.WriteLine("最初の状態");
        Console.WriteLine("m.x = {0}, m.y = {1}", m.x, m.y);
        cd.change(ref m); //構造体型引数を参照渡し
        Console.WriteLine("cd.change(ref m)後");
        Console.WriteLine("m.x = {0}, m.y = {1}", m.x, m.y); //参照渡しなので変わる
        MyDataClass mdc = new MyDataClass(); //クラスオブジェクトを生成
        mdc.x = 10;
        mdc.y = 20;
        Console.WriteLine("最初の状態");
        Console.WriteLine("mdc.x = {0}, mdc.y = {1}", mdc.x, mdc.y);
        cd.change(mdc); //クラスオブジェクトなので参照渡し
        Console.WriteLine("cd.change(mdc)後");
        Console.WriteLine("mdc.x = {0}, mdc.y = {1}", mdc.x, mdc.y);  //参照渡しなので変わる
    }
}

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

・日付時刻の処理や現在時刻の取得などに便利なC#が提供する構造体がDateTime
・年月日時分秒とミリ秒をint型で提供するプロパティ Year、Month、Day、Hour、Minute、Second、MilliSecondがある
 ※ C/C++とは異なり、年や月を調整する必要はない
・他に便利なプロパティが含まれている
 ・DateTime Now:実行時点の現在時刻を持つDateTimeオブジェクトを返す(静的プロパティ)
 ・DayOfWeek DayOfWeek:曜日をDayOfWeek列挙型で返す。このまま表示すると英語曜日名になる
 ・DateTime Date:日付部分のみを返す(時分秒はゼロになる)
・ToString()メソッドのオーバーライドにより、オブジェクトを表示すると「YYYY/MM/DD HH:MM:SS」形式の文字列が得られる
・これを「YYYY/MM/DD」だけにするのがToShortDateString()メソッド。

p.290 datetime01.cs

//p.290 datetime01.cs
using System;
class datetime01 {
    public static void Main(){
        DateTime dt = DateTime.Now;
        Console.WriteLine("今日は{0}年{1}月{2}日({3})です",
            dt.Year, dt.Month, dt.Day, dt.DayOfWeek //年、月、日、曜日(英語)
            );
        Console.WriteLine("現在{0}時{1}分{2}秒{3}ミリセコンドです",
            dt.Hour, dt.Minute, dt.Second, dt.Millisecond); //時、分、秒、ミリ秒
        Console.WriteLine("dt.Date = {0}", dt.Date); //年月日のみを持つのでToString()で時分秒はゼロになる
        Console.WriteLine("短い日付形式 = {0}", dt.ToShortDateString()); //年月日のみを表示
    }
}

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

・曜日を日本語にしよう
・文字列配列 string[] dow = {"日","月","火","水","木","金","土"}; を用いると良い

作成例

//アレンジ演習:p.290 datetime01.cs
using System;
class datetime01 {
    public static void Main(){
        string[] 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); //年月日のみを持つのでToString()で時分秒はゼロになる
        Console.WriteLine("短い日付形式 = {0}", dt.ToShortDateString()); //年月日のみを表示
    }
}

今週の話題

販売本数ランキング 今回トップも「スーパーマリオブラザーズ ワンダー(Switch)」GO!
2024年リリースの「Unity 6」公開…パフォーマンス大幅向上やAIサポートの強化―AIツール「Unity Muse」、開発管理ツール「Unity Cloud」も早期アクセス開始 GO!
IP戦略が奏功の任天堂、過去最高益を達成―次なる一手『ゼルダの伝説』映画化にも本気【ゲーム企業の決算を読む】GO!
『RPG Maker Unite』で3Dダンジョンが作成できる公式DLC「アドオン 3Dダンジョン」配信!簡単に2Dと3Dの切り替えも可能、日本語チュートリアル映像も公開 GO!
米大学で『ゼルダの伝説 ティアキン』を使った機械設計のコースが開設…ゲーム内で乗り物をデザインし単位を貰う GO!
ホロライブの二次創作ゲームブランド「holo Indie」立ち上げ、有償配布をサポートへ!近日中に第1弾『ホロパレード』も配信 GO!
アトラス、2024年4月に初任給を30万円へ。社員の平均年収15%引き上げも実施―グローバル展開の好調を反映 GO!

一見、あのPS作品の”続編”がSteamに?しかし実態は…熱意溢れすぎたユーザーによる勝手なナンバリング二次創作―目的は「SIEの気を引くこと」か GO!
またもや『原神』リーク者に鉄槌!Xで配信前キャラを漏らしたユーザーに開示請求実施へ GO!
どんなゲームが”インディーゲーム”?「The Game Awards」のノミネート一覧発表きっかけに「どこからがインディーか」議論が勃発 GO!

前回のコメント

・今日習った構造体は、classと似すぎてて使い方についてなかなか想像できないなと思いました。
 とりあえず禁止事項があって処理が軽い、コピーするというのはわかりました。
 これからの授業で理解を深められたらいいなと思いました。(^^♪

 はい、概ねその通りの理解でOKです。
 なお、もう一つの目的はC言語で書かれたプログラムのC#への移植時に、
 構造体定義を(ほぼ)そのまま引き継ぐことです。

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

ゲーム開発演習:Imageオブジェクトのプロパティ、画像の重ね合わせなど

テーマ8 フォームサイズの変更の抑止

・FormクラスのFormBorderStyleプロパティに、System.Windows.Forms.FormBorderStyle列挙型の列挙子をだいにゅすることで、
 フォームの縁のスタイルを変更できる
・フォームサイズの変更の抑止するには、FormBorderStyle.Fixed3D列挙子を用いると良い

テーマ9 Imageオブジェクトのプロパティ

・描画した画像の大きさは、ImageクラスのWidth、Heightプロパティで得られるので、整数リテラルでもたずに、都度得て用いるほうが
 変更に強いプログラムになる

演習7 フォームサイズの変更の抑止とImageオブジェクトのプロパティ

・フォームサイズの変更の抑止をしよう
・ImageオブジェクトのWidth、Heightプロパティの値をメッセージボックスに表示して確認しよう

作成例

//演習7 フォームサイズの変更の抑止とImageオブジェクトのプロパティ
using System; //フォームアプリケーションに必須
using System.Windows.Forms; //フォームアプリケーションに必須
using System.Drawing; //Image用
class Program : Form { //Formクラスを継承
    Image myimage = Image.FromFile("back.bmp"); //画像の読込
    protected override void OnPaint(PaintEventArgs e) { //Formのメソッドをオーバーライド
        base.OnPaint(e); //まず、Formクラスにおけるメソッドの内容(基本再描画処理)を実行
        e.Graphics.DrawImage(myimage, 0, 0); //画像を表示
        MessageBox.Show("W = " + myimage.Width + " H = " + myimage.Height); //【追加】
    }
    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); //インスタンスを画面に出す
    }
}

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

・フォームへの描画では、後に描画したものが上になる
・よって、背景画像を描画してから、アイテム画像を必要な順序で描画すると良い
・なお、GIF形式などで透過色を設定した画像の場合、描画順序に注意

演習8 画像の重ね合わせ

・背景画像をゲーム用に差し替えよう
  https://ha221.rundog.org/wp-content/uploads/backb.bmp
・透過色を設定したアイテム画像を使おう
 https://rundog.org/si/enemy.gif
・透過色を設定した効果用画像を使おう
 https://rundog.org/si/burn.gif
・アイテム画像を画面の中央におき、確認してから、効果用画像をその上に配置しよう
・中心に描画する方法:
 ① 背景画像の中心座標を得る: X=背景画像の幅の半分、Y=背景画像の高さの半分
 ② アイテム画像の左上座標を得る: Xからアイテム画像の幅の半分を引く、Yからアイテム画像の高さの半分を引く

作成例

//演習8 画像の重ね合わせ
using System; //フォームアプリケーションに必須
using System.Windows.Forms; //フォームアプリケーションに必須
using System.Drawing; //Image用
class Program : Form { //Formクラスを継承
    Image backb = Image.FromFile("backb.bmp"); //【変更】背景画像の読込
    Image enemy = Image.FromFile("enemy.gif"); //【追加】アイテム画像の読込
    Image burn = Image.FromFile("burn.gif"); //【追加】効果用画像の読込
    protected override void OnPaint(PaintEventArgs e) { //Formのメソッドをオーバーライド
        base.OnPaint(e); //まず、Formクラスにおけるメソッドの内容(基本再描画処理)を実行
        e.Graphics.DrawImage(backb, 0, 0); //【変更】背景画像を(0,0)から描画
        int mx = backb.Width / 2, my = backb.Height / 2; //【追加】中央座標を得る
        e.Graphics.DrawImage(enemy, mx - enemy.Width / 2, my - enemy.Height / 2); //【追加】アイテム画像を中央に描画
        e.Graphics.DrawImage(burn, mx - burn.Width / 2, my - burn.Height / 2); //【追加】アイテム画像を中央に描画
    }
    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); //インスタンスを画面に出す
    }
}

提出:演習8 画像の重ね合わせ

講義メモ

テキスト篇:第11章「構造体」
ゲーム開発演習:imageオブジェクトのプロパティ、画像の重ね合わせなど

参考:魔導士クラスの改良例(静的メンバの活用による引数付きコンストラクタの廃止・一本化)

//オブジェクト指向演習 RPG演習6・改
using System;
class Madoshi { //魔導士を表すクラス
    //継承するデータメンバ
    protected int mp; //MP(カプセル化(p.166)のためにpublicにしない)
    protected int hp; //HP(〃)
    protected static Random rnd = new Random(); //乱数用のRandomクラスのインスタンスを生成
    //継承しないデータメンバ
    int id; //魔導士番号
    static int num = 0; //魔導士の数
    //デフォルトコンストラクタ
    public Madoshi() { 
        mp = rnd.Next(10); //0~9の間のランダムな整数を得てMPの初期値とする
        hp = 10 + rnd.Next(10); //10~19の間のランダムな整数を得てHPの初期値とする
        num++; //【追加】魔導士の数をカウントアップ
        id = num; //【変更】魔導士の数で魔導士番号を決める
    }
    //プロパティ
    public int HP { //HPを返すプロパティ
        get { return hp; } 
    }
    //メソッド
    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~50を返す(攻撃終了)
        }
    }
    public void Damage() { //魔導士があなたからダメージをくらう
        int k = 5 + rnd.Next(5); //あなたの攻撃力は5~9の乱数
        Console.WriteLine("あなたの攻撃!");
        DispInfo(); //魔導士の情報を表示する(攻撃を受けた後の魔導士のHPを表示)
        Console.WriteLine("に" + k + "のダメージを与えた!");
        hp -= k; //魔導士のHPをダウン
    }
}
class Daimado : Madoshi { //大魔導クラス(魔導士クラスの派生クラス)
    public Daimado() { //コンストラクタ
        mp = 40 + rnd.Next(10); //40~49の間のランダムな整数を得てMPの初期値とする
        hp = 40 + rnd.Next(10); //40~49の間のランダムな整数を得てHPの初期値とする
    }
    public override void DispInfo() { //大魔導の情報を表示する
        Console.Write("大魔導(HP={0} MP={1})", hp, mp); //情報表示(改行しない)
    }
}
class minimadou { //ゲーム本体のクラス
    public static void Main() { //ゲームを進行するメソッド
        Random rnd = new Random(); //乱数用のRandomクラスのインスタンスを生成
        string ans = null; //入力用
        // 戦闘開始
        int myhp = 40 + rnd.Next(21); //あなたのHPを40+0から40+20にする
        Madoshi[] m = { new Madoshi(),new Madoshi(),new Madoshi() }; //【変更】全魔導士を生成
        foreach (var w in m) { //全魔導士ついて繰返す
            w.DispInfo(); //魔導士の情報を表示する
            Console.WriteLine("が現れた。");
        }
        // 戦闘中(あなたが死ぬか,魔導士が死ぬまで繰り返す)
        while (myhp > 0 && (m[0].HP > 0 || m[1].HP > 0 || m[2].HP > 0)) { //あなたとどれかの魔導士のHPがある間
            Console.Write("残りHPは{0}です。どれを攻撃しますか?(1/2/3:魔導士1,2,3 0:やめる):", myhp); 
            ans = Console.ReadLine(); //回答を得る
            int ansi = int.Parse(ans) - 1; //整数変換して-1し魔導士の添字0~2を得る
            if (ansi == 0 || ansi == 1 || ansi == 2) { //魔導士を攻撃?
                if (m[ansi].HP > 0) { //活きている魔導士?
                    m[ansi].Damage(); //魔導士を攻撃する
                    if(m[ansi].HP <= 0) { //魔導士[]を倒した?
                        Console.WriteLine("魔導士{0}を倒した!", ans);
                    }
                } else { 
                    Console.WriteLine("魔導士{0}はもういない!", ans);
                }
                for (int i = 0; i < 3 && myhp > 0; i++) { //全魔導士について繰返す
                    myhp -= m[i].Fight(); //魔導士の攻撃!あなたのHPをマイナス
                }
            } else { // やめる?
                if (rnd.Next(2) == 0) { //確率50%で
                    Console.WriteLine("逃げることができた!");
                    break; // 繰り返し終了(p.125)
                } else {
                    Console.WriteLine("逃げられない!");
                    for (int i = 0; i < 3 && myhp > 0; i++) { //全魔導士について繰返す
                        myhp -= m[i].Fight(); //魔導士の攻撃!あなたのHPをマイナス
                    }
                }
            }
            if (myhp <= 0) { //あなたのHPがもうない?
                Console.WriteLine("あなたは死にました。");
                return; //ゲーム終了
            }
        }
        if (ans != "0") { //逃げたのでなければ
            Console.WriteLine("3人の魔導士を倒した!");
        }
        //ボス戦開始
        Daimado d = new Daimado(); //大魔導を生成
        d.DispInfo(); //大魔導の情報を表示する
        Console.WriteLine("が現れた。");
        int newhp = 40 + rnd.Next(10); //新しいHPを40から49で決める
        if (myhp < newhp) { //回復する?
            myhp = newhp; //HPを回復
            Console.WriteLine("HPを回復!");
        }
        // 戦闘中(あなたが死ぬか,大魔導が死ぬまで繰り返す)
        while (myhp > 0 && d.HP > 0) { //あなたと大魔導のHPがある間
            Console.Write("残りHPは{0}です。攻撃しますか?(1:攻撃する 0:やめる):", myhp); 
            ans = Console.ReadLine(); //回答を得る
            if (ans == "1") { //大魔導を攻撃?
                d.Damage(); //大魔導を攻撃する
                if(d.HP <= 0) { //大魔導を倒した?
                    Console.WriteLine("大魔導を倒した!");
                    break; //ゲーム終了
                }
                myhp -= d.Fight(); //大魔導の攻撃!あなたのHPをマイナス
            } else { // やめる?
                if (rnd.Next(2) == 0) { //確率50%で
                    Console.WriteLine("逃げることができた!");
                    break; //ゲーム終了
                } else {
                    Console.WriteLine("逃げられない!");
                    myhp -= d.Fight(); //大魔導の攻撃!あなたのHPをマイナス
                }
            }
            if (myhp <= 0) { //あなたのHPがもうない?
                Console.WriteLine("あなたは死にました。");
                break; //ゲーム終了
            }
        }
    }
}

p.275 構造体とは

・C言語の構造体は複数の変数や配列を1まとめにしたデータ構造
・対して、C#の構造体は軽量クラス=クラスの仕様を一部除いて軽くしたもので、同時にC言語からの移植にも対応する
・よって「なにが除かれたか」に着目すると良い
・定義書式: struct 構造体名 {…}
・構造体のメンバはデータメンバ(クラスと同様)、メソッド、プロパティ、インデクサ等
・インターフェイスを実装できる
・実装の書式: struct 構造体名 : インターフェイス名,… {…}
・構造体の制限事項:
 ・他のクラスを継承できない
 ・データメンバの初期化ができない(利用する側で行うこと)
 ・引数のないコンストラクタは自動生成のみ(自前で記述は禁止)
 ・オブジェクトの扱いが参照型ではなく値型なので、代入するとコピーされる

p.276 struct01.cs

//p.276 struct01.cs
using System;
struct MyStruct { //構造体の定義
    public int x; //宣言のみ可能で初期化はできない
    public void show()
    {
        Console.WriteLine("x = {0}", x);
    }
}
class struct01
{
    public static void Main()
    {
        MyStruct ms; //構造体変数の宣言と構造体オブジェクトの生成(new不要)
        ms.x = 10; //構造体変数.メンバ名で利用可
        ms.show(); //構造体変数.メソッド名(…)で利用可
    }
}

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

・クラスの場合と全く同様
・「構造体名.メンバ名」で利用できる
・また、静的メンバであれば初期化可能
・よって、配列の静的メンバであれば要素の生成も可能

p.277 struct02.cs

//p.277 struct02.cs
using System;
struct MyStruct
{
    public static int x = 10; //静的メンバであれば初期化可能
    static int[] myarray = new int[10]; //静的メンバなので初期化(要素の生成)可能
    public static void show() { //静的メソッド
        Console.WriteLine("x = {0}", x);
    }
}
class struct02
{
    public static void Main()
    {
        MyStruct.show(); //構造体オブジェクトを生成せずに構造体名.メンバ名で利用可
        MyStruct.x = 20; //同上
        MyStruct.show(); //同上
    }
}

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

・利用していないmyarrayの要素をMainメソッドで利用してみよう
・その為にインデクサを追記しよう
・構造体に静的でない配列データメンバyourarrayを追加し、要素の生成はMainメソッドで行うようにしよう

作成例

//アレンジ演習:p.277 struct02.cs
using System;
struct MyStruct
{
    public static int x = 10; //静的メンバであれば初期化可能
    static int[] myarray = new int[10]; //静的メンバなので初期化(要素の生成)可能
    public static void show() { //静的メソッド
        Console.WriteLine("x = {0}", x);
    }
    public int this[int n] { //【追加】インデクサ
        get { return myarray[n]; } set { myarray[n] = value; }
    }
    public int[] yourarray; //【追加】静的メンバではないので配列は宣言のみ
}
class struct02
{
    public static void Main()
    {
        MyStruct.show(); //構造体オブジェクトを生成せずに構造体名.メンバ名で利用可
        MyStruct.x = 20; //同上
        MyStruct.show(); //同上
        MyStruct a = new MyStruct(); //【追加】インデクサ用の構造体オブジェクトを生成
        a[0] = 100; //【追加】インデクサ経由で代入
        Console.WriteLine("a[0] = {0}", a[0]); //【追加】インデクサ経由で利用
        a.yourarray = new int[3]; //【追加】要素の生成は利用側で行う
        a.yourarray[0] = 200; //【追加】代入
        Console.WriteLine("a.yourarray[0] = {0}", a.yourarray[0]); //【追加】利用
    }
}

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

・引数のないコンストラクタは自動生成のみ(自前で記述は禁止)
・なお、オブジェクトの扱いが参照型ではなく値型なので、代入するとコピーされる

p.278 struct03.cs

//p.278 struct03.cs
using System;
struct MyStruct {
    public int x;
    //public MyStruct() { //引数のないコンストラクタは定義不可(自動生成のみ)
    //    x = 0;
    //}
    public MyStruct(int a) { //引数のあるコンストラクタは定義可能
        x = a;
    }
}
class struct03 {
    public static void Main() {
        MyStruct ms = new MyStruct(); //自動生成の引数のないコンストラクタを呼ぶ
        Console.WriteLine("ms.x = {0}", ms.x); //ms.xは0で初期化されている
        MyStruct ms2 = new MyStruct(100); //自前の引数のあるコンストラクタを呼ぶ
        Console.WriteLine("ms2.x = {0}", ms2.x); //ms2.xは100で初期化されている
        MyStruct ms3; //MyStruct型変数ms3を宣言(new不要)
        ms3 = ms2; // ms2の値をms3に代入(値型なのでオブジェクトのコピーになる)
        Console.WriteLine("ms3.x = {0}", ms3.x); // 当然ms3.xの値は100
        ms3.x = 50; //ms3.xの値を50に変更
        // ms3.xの値を50に変更してもms2.xは影響を受けない(コピーなので)
        Console.WriteLine("ms2.x = {0}", ms2.x);
    }
}

p.280 struct04.cs

//p.280 struct04.cs
using System;
struct MyStruct { //構造体の定義
    public int x;
}
class MyClass { //クラスの定義
    public int x;
}
class struct04 {
    public static void Main() {
        MyStruct ms1 = new MyStruct(); //構造体オブジェクトの生成
        MyStruct ms2; //構造体変数の宣言
        ms1.x = 20;
        ms2 = ms1; //構造体オブジェクトをコピー(よってms1とms2は別のオブジェクトを指す)
        Console.WriteLine("ms2.x = {0}", ms2.x); //コピーしたので20
        ms2.x = 10; //コピー結果を書き換えても
        Console.WriteLine("ms1.x = {0}", ms1.x); //コピーの元なので変わらず20
        MyClass mc1 = new MyClass(); //クラスオブジェクトの生成
        MyClass mc2; //参照変数の宣言
        mc1.x = 20;
        mc2 = mc1; //参照のコピーなのでmc1とmc2は同じオブジェクトを指す
        Console.WriteLine("mc2.x = {0}", mc2.x); //同じオブジェクトなので20
        mc2.x = 10; //参照経由で書き換えたので
        Console.WriteLine("mc1.x = {0}", mc1.x); //元のオブジェクトの値が書き変わる
    }
}

今週の話題

販売本数ランキング 今回トップも「スーパーマリオブラザーズ ワンダー(Switch)」GO!
カプコンはデジタル好調で増収増益、ただし『エグゾプライマル』で完全新作の難しさ露呈か【ゲーム企業の決算を読む】GO!
PS5新モデルが本日発売! 小型化されディスクドライブも着脱式に GO!
新発表のOLED版Steam Deckはあくまで「顧客意見を元にしたアップグレード」―次世代「Steam Deck 2」はまだ数年以上先、Valve改めて姿勢鮮明に GO!
「ゲーム業界と他業界のエンジニアは何が違う?」クリーク・アンド・リバーのエンジニア座談会11月16日開催 GO!
8年目を迎えるニンテンドースイッチ、これまでのライフサイクルに囚われることなく「新作タイトルの展開を続ける」GO!
『ティアキン』2,000万本秒読み、『マリカ8DX』や『スマブラSP』などミリオンタイトルが16本も…任天堂が24年3月期 第2四半期の決算資料を公開 GO!

【決算】コロプラの23年9月期決算は43%減益―『白猫プロジェクト』など主要IP盛り上げも売上は微減、新作の広告宣伝費も影響 GO!
今後ルール変更されても「遡及適用」もう行いません―Unity、利用規約更新で開発者の信頼回復図る GO!
人気タイトル独占や無料配布施策行うも「Epic Gamesストア」未だ利益出せず…立ち上げから約5年―しかし目標は依然として「成長」GO!