講義メモ オブジェクト指向演習:仕上げ(継承によるラスボスの生成と登場) ゲーム開発演習:画像のプロパティ、画像の重ね合わせ、キーが押された情報を得る など 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 ゲーム開発演習: 画像のプロパティ、画像の重ね合わせ、キーが押された情報を得る など 提出フォロー:演習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 背景画像の下部中央に自機画像を描画 ・背景画像をゲーム用の下記画像に差し替えよう  https://rundog.org/si/backb2.bmp backb2.bmp ・透過色を設定した自機画像をダウンロード  https://rundog.org/si/player.gif ・自機画像を背景画像の下部中央に描画しよう  X座標:自機画像を背景画像の中央になるように指定  Y座標:自機画像の下部に自機の高さ分の空きができるように指定  https://ha242.rundog.org/wp-content/uploads/2025/05/e3e418b17a743717f3ef02bbdd02462f.png 作成例 //演習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")であれば終了しよう  ※メッセージボックスの表示は削除する テキスト篇次回予告:第11章「構造体」から ゲーム開発演習次回予告:図形の描画 など