講義メモ ・オブジェクト指向演習① ・ゲーム開発演習:フォームの表示と設定 オブジェクト指向演習① ・コンソールアプリケーションによるRPGを題材にして、テキスト10章までのノウハウとテクニックを理解しましょう 予備知識:乱数の利用 ・ゲームやシミュレーションなどで多用される「毎回異なる値が得られる仕掛け」を乱数(Random)という ・乱数はシステムが用意している系列によって提供されるので、系列を決める基礎値であるシード(種)が等しいと同じ系列になり、弊害が起こるので注意 ・C#では、内部的に現在の時刻情報を数値化してシードにすることで、毎回、予測できない乱数になる ・乱数を用いるには、C#が標準で提供する System.Randomクラスのデフォルトコンストラクタでインスタンスを生成し、インスタンスメソッド Next(int n)を用いると、0以上n未満の整数が得られる。 例:  using System;  :  Random r = new Random(); //usingがあれば「System.」は省略可  Console.WriteLine(r.Next(3)); //0,1,2のどれかが表示される 予備知識:乱数の利用 サンプル codefile1.cs //予備知識:乱数の利用 サンプル using System; class codefile1 { public static void Main() { Random r = new Random(); Console.WriteLine(r.Next(3)); //0,1,2のどれかが表示される } } RPG演習1 仕様 ・魔道士とプレイヤーが戦うゲーム ・詳細はスケルトンのコメントを読み解いて理解してください ・ソースファイル名は「rpg.cs」とします 実行例 魔道士(HP=10)が現れた。 攻撃しますか?(1:はい 0:やめる):1 あなたの攻撃! 魔道士(HP=10)に5のダメージを与えた! 魔道士(HP=5)は消費MP4の魔法を唱えた! あなたは16のダメージを受けた! 攻撃しますか?(1:はい 0:やめる):1 あなたの攻撃! 魔道士(HP=5)に7のダメージを与えた! 魔道士を倒した! プログラム ver.0(穴埋めになっています) //オブジェクト指向演習 RPG演習1 using System; class Madoshi { int mp; //MP int hp; //HP Random rnd = new Random(); //乱数用のRandomクラスのインスタンスを生成 public ● () { //コンストラクタ mp = rnd.Next(10); //0~9の間のランダムな整数を得てMPの初期値とする hp = 10 + rnd.Next(10); //10~19の間のランダムな整数を得てHPの初期値とする } public int GetHP() { return hp; } //HPを返すメソッド public void DispInfo() { //魔導士の情報を表示する Console.Write("魔道士(HP=" + hp + ")"); //HPを表示(改行しない) } public int Fight() { //魔道士があなたを攻撃し、あなたのダメージ値を返す 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 minimadou { //ゲーム本体のクラス public static void Main() { //ゲームを進行するメソッド string ans; //入力用 // 戦闘開始 int myhp = 20; // あなたのHP Madoshi m = new Madoshi(); // 魔道士を生成 m.DispInfo(); // 魔道士の情報を表示する Console.WriteLine("が現れた。"); // 戦闘中(あなたが死ぬか,魔道士が死ぬまで繰り返す) ● (myhp > 0 && m.GetHP() > 0) { //あなたのHPがあり、魔導士のHPがある間 Console.Write("攻撃しますか?(1:はい 0:やめる):"); ans = Console.ReadLine(); // 攻撃するかどうかを得る if (ans == "1") { // はい? m.Damage(); // 魔道士を攻撃する if (m.GetHP() > 0) { // 魔道士は死んでない? myhp -= m.Fight(); // 魔道士の攻撃!あなたのHPをマイナス if (myhp <= 0) { // あなたのHPがもうない? Console.WriteLine("あなたは死にました。"); } } else { // 魔道士のHPがもうない? Console.WriteLine("魔道士を倒した!"); } } else { // やめる? ●// 繰り返し終了 } } } } 【ha243 解説編より】コンストラクタ ・C#のインスタンス(オブジェクト)はクラスや構造体を設計図として用いてメモリ上の生成されたもののこと ・この生成を行うのがnew演算子 ・new演算子を実行すると、その後に記述されている内容に応じてコンストラクタが呼びだされる ・例えば、Slime slalin = new Slime(); とするとコンストラクタ「Slime()」が呼び出される ・C#では、クラスや構造体を定義すると内部的に「クラス名()」「構造体名()」のコンストラクタが自動的に用意されるので(中身は空)記述しなくてもOK。 ・必要であれば、プログラマが記述することもできる ・記述する場合の目的は、準備作業であり、データメンバの値の設定やオブジェクトで行いたい処理の前処理などに用いる ・「Vector3 pos2 = new Vector3(2.0f, 4.0f, 0.0f);」のように、コンストラクタに引数を定義しておいて値を渡すことが可能。 ・コンストラクタは特殊なメソッドで、名前が無く(クラス名や構造体名が名前になる)戻り値もないのが特徴。 【ha243 解説編より】break ・while/for/do-whileの繰り返しの最中に、なんらかの理由で繰り返しを終了したい場合、「break;」文を実行すると良い ・例外的な事態が発生した場合に用いる事が多い。 ・または、繰り返しの継続条件が複数あり、複雑な場合、主たる継続条件をwhile/for/do-while文に書いておき、中断条件があれば、if文で判断してbreak文で抜けることも多い ・なお、break文の直後になんらかの文を記述すると、絶対に実行されないため、文法エラーになる ・break文は終了文ではなく、while/for/do-whileの末尾の「}」の後ろへのジャンプになり、後続の処理が実行される 作成例 //オブジェクト指向演習 RPG演習1 using System; class Madoshi { //魔導士を表すクラス(部品) int mp; //MP int hp; //HP Random rnd = new Random(); //乱数用のRandomクラスのインスタンスを生成 public Madoshi () { //コンストラクタ mp = rnd.Next(10); //0~9の間のランダムな整数を得てMPの初期値とする hp = 10 + rnd.Next(10); //10~19の間のランダムな整数を得てHPの初期値とする } public int GetHP() { return hp; } //HPを返すメソッド public void DispInfo() { //魔導士の情報を表示する Console.Write("魔道士(HP=" + hp + ")"); //HPを表示(改行しない) } public int Fight() { //魔道士があなたを攻撃し、あなたのダメージ値を返す 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() { //魔道士があなたからダメージをくらう int k = 5 + rnd.Next(5); //あなたの攻撃力は5~9の乱数 Console.WriteLine("あなたの攻撃!"); DispInfo(); //魔導士の情報を表示する(攻撃を受けた後の魔道士のHPを表示) Console.WriteLine("に" + k + "のダメージを与えた!"); hp -= k; //魔道士のHPをダウン } } class minimadou { //ゲーム本体のクラス public static void Main() { //ゲームを進行するメソッド string ans; //入力用 // 戦闘開始 int myhp = 20; // あなたのHP Madoshi m = new Madoshi(); // 魔道士を生成 m.DispInfo(); // 魔道士の情報を表示する Console.WriteLine("が現れた。"); // 戦闘中(あなたが死ぬか,魔道士が死ぬまで繰り返す) while (myhp > 0 && m.GetHP() > 0) { //あなたのHPがあり、魔導士のHPがある間 Console.Write("攻撃しますか?(1:はい 0:やめる):"); ans = Console.ReadLine(); // 攻撃するかどうかを得る if (ans == "1") { // はい? m.Damage(); // 魔道士を攻撃する if (m.GetHP() > 0) { // 魔道士は死んでない? myhp -= m.Fight(); // 魔道士の攻撃!あなたのHPをマイナス if (myhp <= 0) { // あなたのHPがもうない? Console.WriteLine("あなたは死にました。"); } } else { // 魔道士のHPがもうない? Console.WriteLine("魔道士を倒した!"); } } else { // やめる? break; // 繰り返し終了 } } } } RPG演習2 ・プレイヤーのHPを表示しよう ・「攻撃しますか?(1:はい 0:やめる):」を「残りHPは●です。攻撃しますか?(1:はい 0:やめる):」としよう 作成例 //オブジェクト指向演習 RPG演習2 using System; class Madoshi { //魔導士を表すクラス(部品) int mp; //MP int hp; //HP Random rnd = new Random(); //乱数用のRandomクラスのインスタンスを生成 public Madoshi () { //コンストラクタ mp = rnd.Next(10); //0~9の間のランダムな整数を得てMPの初期値とする hp = 10 + rnd.Next(10); //10~19の間のランダムな整数を得てHPの初期値とする } public int GetHP() { return hp; } //HPを返すメソッド public void DispInfo() { //魔導士の情報を表示する Console.Write("魔道士(HP=" + hp + ")"); //HPを表示(改行しない) } public int Fight() { //魔道士があなたを攻撃し、あなたのダメージ値を返す 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() { //魔道士があなたからダメージをくらう int k = 5 + rnd.Next(5); //あなたの攻撃力は5~9の乱数 Console.WriteLine("あなたの攻撃!"); DispInfo(); //魔導士の情報を表示する(攻撃を受けた後の魔道士のHPを表示) Console.WriteLine("に" + k + "のダメージを与えた!"); hp -= k; //魔道士のHPをダウン } } class minimadou { //ゲーム本体のクラス public static void Main() { //ゲームを進行するメソッド string ans; //入力用 // 戦闘開始 int myhp = 20; // あなたのHP Madoshi m = new Madoshi(); // 魔道士を生成 m.DispInfo(); // 魔道士の情報を表示する Console.WriteLine("が現れた。"); // 戦闘中(あなたが死ぬか,魔道士が死ぬまで繰り返す) while (myhp > 0 && m.GetHP() > 0) { //あなたのHPがあり、魔導士のHPがある間 Console.Write("残りHPは{0}です。攻撃しますか?(1:はい 0:やめる):", myhp); //【変更】 ans = Console.ReadLine(); // 攻撃するかどうかを得る if (ans == "1") { // はい? m.Damage(); // 魔道士を攻撃する if (m.GetHP() > 0) { // 魔道士は死んでない? myhp -= m.Fight(); // 魔道士の攻撃!あなたのHPをマイナス if (myhp <= 0) { // あなたのHPがもうない? Console.WriteLine("あなたは死にました。"); } } else { // 魔道士のHPがもうない? Console.WriteLine("魔道士を倒した!"); } } else { // やめる? break; // 繰り返し終了 } } } } 【ha243 解説編より】プロパティ ・プロパティは特殊なメソッドで、データメンバを安全に扱う仕組みを提供する ・データメンバをpublicにしておくと、外部から想定外の値を代入されたり、想定外の用途に使われてしまう可能性がある  例:身長に負の数。計算で決まるはずのプレイヤーの経験値を直接操作。 ・そこで、重要なデータメンバはprivate(または無指定でprivate扱い)にする ・データメンバの値を得ることをget、値を書き換えることをsetといい、合わせてアクセッサという ・プロパティはgetとsetのどちらかまたは両方を記述して、publicではないデータメンバを扱う仕組みを外部に提供する ・最も基本的な定義例:  private 型 データメンバ名;  public 型 プロパティ名 {   set { データメンバ名 = value; }   get { return データメンバ名; }  } ・このように定義すると、外部から「データメンバ名 = 値;」の代わりに「プロパティ名 = 値;」とすることで、  データメンバに代入できる。 ・この時、プロパティのsetが動作し、値はvalueに渡される。 ・また「Console.WriteLine(プロパティ名 )」などとすることでデータメンバの値を表示できる。 ・この時、プロパティのgetが動作し、データメンバの値がreturnされる。 ・この仕掛けを用いて、setを省略することで、変更不能のデータメンバにできる  private 型 データメンバ名;  public 型 プロパティ名 {   get { return データメンバ名; }  } ・また、この仕掛けを用いて、setに条件を記述できる  private 型 データメンバ名;  public 型 プロパティ名 {   set {    if (value >= 0) {     データメンバ名 = value;    }   }   get { return データメンバ名; }  } オブジェクト指向演習:次回予告 プロパティの導入とゲーム性の向上 ゲーム開発演習:フォームの表示と設定 解説:フォームプログラミング ・グラフィックス、サウンドと、マウスなどのポインティングディバイスを用いるプログラムをC#で開発するには、フォームアプリケーションのプロジェクトを用いると良い ・Unityなどの専用ツールを用いる場合に比べて自由度が高くなる(ただし記述量や求められる知識は増える) ・よって、将来いろいろな開発ツールを利用できるようになるための、基礎力をつけることに向いている ・プロジェクトの作成方法 ①「新しいプロジェクトの作成」 ②「C#」「Windowsフォームアプリケーション(.NET Framework)」「次へ」 ③ 適当なプロジェクト名、場所(USBメモリ推奨)などを指定して「作成」 ④ フォームデザインが表示されるが、プログラム側で設定・制御するので、閉じてOK ⑤ 代わりに「Program.cs」を開くと、フォームアプリケーションのスケルトンが表示され、利用可能になる ⑥ このソースは冗長なので、下記のようにシンプルにできる using System; using System.Windows.Forms; class Program { static void Main() { Application.Run(new Form()); } } ⑦「デバッグなしで開始」で実行でき、コンソールではなくフォームが表示される ⑧ 確認したらフォームの右上の「×」で必ず閉じる(開けたまま再実行するとエラーになる) 演習0 フォームアプリケーションの実行確認 //演習0 フォームアプリケーションの実行確認 using System; //C#標準クラス用 using System.Windows.Forms; //C#が提供するApplication、Formクラス用 class Program { static void Main() { //実行用メソッド(publicはなくてOK) Application.Run(new Form()); //C#が提供するFormクラスのインスタンスを生成し実行 } } テーマ1 フォームサイズの指定 ・フォームの大きさは、FormクラスのインスタンスプロパティWidthに幅のドット数、Heightに高さのドット数を与えることで設定できる ・インスタンスプロパティを用いるには、あらかじめ、Formクラスのインスタンスを生成しておくと良い ・この時、指定したインスタンス名(参照変数)を用いて、インスタンスプロパティを利用できる ・そして、指定したインスタンス名をApplication.Runに渡すようにしよう 例:  Form インスタンス名 = new Form();  インスタンス名.Width = 幅のドット数  インスタンス名.Height = 高さのドット数  Application.Run(インスタンス名); 演習1 フォームサイズの指定 ・フォームの幅を640、高さを480にしよう 作成例 //演習1 フォームサイズの指定 using System; //C#標準クラス用 using System.Windows.Forms; //C#が提供するApplication、Formクラス用 class Program { static void Main() { //実行用メソッド(publicはなくてOK) Form f = new Form(); //Formクラスのインスタンスを生成 f.Width = 640; //インスタンスプロパティで幅を設定 f.Height = 480; //インスタンスプロパティで高さを設定 Application.Run(f); //生成済のインスタンスを実行 } } テーマ2 フォームサイズの一括指定 ・フォームの幅(Width)と高さ(Height)は、まとめて大きさ(Size)として扱うことができる ・FormクラスのインスタンスプロパティSizeに、System.Drawing.Sizeクラスのオブジェクトを与えることで一括指定が可能 ・Sizeクラスの利用には「using System.Drawing;」を指定すると良い ・Sizeクラスのオブジェクトに幅と高さを与えるには、Sizeクラスのコストラクタ(int 幅, int 高さ)を用いると良い 例:  Form インスタンス名① = new Form();  Size インスタンス名② = new Size(幅のドット数, 高さのドット数);  インスタンス名①.Size = インスタンス名②;  Application.Run(インスタンス名①); 演習2 フォームサイズの一括指定 ・Sizeクラスのオブジェクトを用いて、フォームの幅を640、高さを480にしよう 作成例 //演習2 フォームサイズの一括指定 using System; //C#標準クラス用 using System.Windows.Forms; //C#が提供するApplication、Formクラス用 using System.Drawing; //Sizeクラス用 class Program { static void Main() { //実行用メソッド(publicはなくてOK) Form f = new Form(); //Formクラスのインスタンスを生成 Size s = new Size(640, 480); //幅と高さの大きさのSizeインスタンスを生成 f.Size = s; //インスタンスプロパティで大きさを設定 Application.Run(f); //生成済のインスタンスを実行 } } テーマ3 フォーム名の指定 ・フォーム名はFormクラスのインスタンスプロパティTextに文字列を代入することで設定できる ・フォーム名はWindowsからみたアプリケーション名になるので、予め設定しておくと良い 演習3 フォーム名の指定 ・フォーム名を「Game」にしよう 作成例 //演習3 フォーム名の指定 using System; //C#標準クラス用 using System.Windows.Forms; //C#が提供するApplication、Formクラス用 using System.Drawing; //Sizeクラス用 class Program { static void Main() { //実行用メソッド(publicはなくてOK) Form f = new Form(); //Formクラスのインスタンスを生成 f.Text = "Game"; //Form名を設定 Size s = new Size(640, 480); //幅と高さの大きさのSizeインスタンスを生成 f.Size = s; //インスタンスプロパティで大きさを設定 Application.Run(f); //生成済のインスタンスを実行 } } 補足:Formクラスについて ・APIリファレンスで仕様を確認しよう  https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.form ・Formクラスのコンストラクタはデフォルトしかなく、引数を指定できるコンストラクタはない ・プロパティが大量にあり、Height、Size、Text、Widthが含まれている テーマ4 フォームの初期位置の指定 ・FormクラスのインスタンスプロパティStartPositionに「手動設定」を設定すると、初期位置を指定できる ・手動設定には、System.Windows.Forms.FormStartPosition列挙体のManual列挙子を用いると良い ・すると、FormクラスのインスタンスプロパティLocationに、初期位置の座標を指定できる ・座標指定には、System.Drawing.Pointクラスのコストラクタ(int X座標 int Y座標)を用いると良い 例:  Form インスタンス名① = new Form();  インスタンス名①.StartPosition = FormStartPosition.Manual;  Pointインスタンス名② = new Point(X座標, Y座標);  インスタンス名①.Location = ②; 演習4 フォームの初期位置の指定 ・フォームの初期位置を(300, 0)にしよう 提出:演習4 フォームの初期位置の指定 ゲーム開発演習:次回予告 フォームサイズの固定化とコントールボックスの非表示、背景画像の表示 など