講義メモ

・文法編:ジェネリクス、ToStringメソッド など
・ゲーム開発編:特別編「3D迷路&シューティングゲーム開発演習」

文法編:ジェネリクス

・p.187の説明の通り、下記の構文の「<型名>」の構文をジェネリクスという
 Vegetable.Type typeA = GetComponent<Vegetable>().type;
・C言語などでは、同じ処理内容でも、用いるデータの型が異なる場合、その違いの数だけ名前の異なる関数を作成する必要がある
・これを改良する考え方がオーバーロードで、用いるデータの型が異なれば同じ名前の関数(メソッド)にできる
・さらに、改良して、用いるデータの型をパラメータとして与えられるようにしたのが、ジェネリクス(ジェネリックメソッド)
・GetComponentはUnityが提供するジェネリックメソッドであり、プログラマが自前で定義することも可能
・用いるデータの型を受け渡すパラメータを型パラメータといい、大文字のTなどを用いることが多い

文法編:ジェネリックメソッドの例:同じ型の2引数を受け取って両方を出力するメソッド

・int型用は public void Log2(int a, int b) { Debug.Log(a);  Debug.Log(b); }
・double型用は public void Log2(double a, double b) { Debug.Log(a);  Debug.Log(b); }
・これをジェネリックメソッドにすると、型パラメータを引数型にも用いて下記のようにできる
 public void Log2<T>(T a, T b) { Debug.Log(a);  Debug.Log(b); }
・そして、呼び出しにおいて、型情報を渡すと、それに応じたメソッドが自動生成&利用される
 Log2<int>(10, 20);
 Log2<double>(3.14, 2.22);

演習 ex0914a.cs

・上記の例を試すプログラムを作成しよう

作成例

//演習 ex0914a.cs
using UnityEngine;
public class ex0914a : MonoBehaviour{
    public void Log2<T>(T a, T b) { //ジェネリックメソッド(引数型がT)
        Debug.Log(a);  Debug.Log(b); 
    }
    void Start()    {
        Log2<int>(10, 20); //型としてintを指定してジェネリックメソッドを呼ぶ
        Log2<double>(3.14, 2.22); //型としてdoubleを指定してジェネリックメソッドを呼ぶ    
    }
}

文法編:ジェネリッククラス

・クラス定義そのものをジェネリックにして、クラス内で型パラメータを使いまわすことも可能
・これをジェネリッククラスという
・例:
    class Nums<T> { 
        public void Log2(T a, T b) { //ジェネリックメソッド(引数型がT)
            Debug.Log(a);  Debug.Log(b); 
        }
    }
・ジェネリッククラスはオブジェクトの生成時に型パラメータを渡すことができる
・例:
    Nums<int> num = new Nums<int>();

演習 ex0914b.cs

・上記の例を試すプログラムを作成しよう

作成例

//演習 ex0914b.cs
using UnityEngine;
public class ex0914b : MonoBehaviour{
    class Nums<T> { //ジェネリッククラス
        public void Log2(T a, T b) { //メソッド(引数型がTで得られる)
            Debug.Log(a);  Debug.Log(b); 
        }
    }
    void Start()    {
        Nums<int> num = new Nums<int>(); //ジェネリッククラスに型を与えてオブジェクト生成
        num.Log2(10, 20); //メソッドの引数型が決まる
        Nums<char> cnum = new Nums<char>();
        cnum.Log2('A', 'x');
    }
}

文法編:C#が提供するジェネリックコレクションクラス

・コレクションクラスとはデータ構造を提供する汎用クラス
・例えば、配列の機能を拡張するクラスである ArrayListクラスがあり、配列の弱点である
「要素数が固定」「格納するデータの型が固定」「途中挿入や途中削除ができない(手間がかかる)」
 などを解消している
 参照: https://learn.microsoft.com/ja-jp/dotnet/api/system.collections.arraylist
・しかし、要素の型情報がない
(格納と同時にObject型に変換されてしまう=型情報が失われるので、プログラム側で管理する必要がある)ため、
 現在では、代わりに ジェネリックコレクションクラスList<T>を用いることが推奨されている
・利用方法
 冒頭に「using System.Collections.Generic;」を挿入(Unityでは自動挿入されることもある)
 生成: List<型> オブジェクト名 = new List<型>();
 格納: オブジェクト名.Add(要素値)メソッドで
 要素数: オブジェクト名.Count プロパティで
 要素値: オブジェクト名[添字] で配列と同様に
 参照: https://learn.microsoft.com/ja-jp/dotnet/api/system.collections.generic.list-1

演習 ex0914c.cs

・Monsterクラスを定義し、中にpublicで文字列型のデータメンバnameを置く
・ジェネリックコレクションクラスList<Monster>のオブジェクトmonsを生成
・適当なMonsterオブジェクトをいくつか生成しつつ、monsに格納する
・要素数を表示
・全要素のnameを表示

作成例

using System.Collections.Generic;
using UnityEngine;
public class ex0914c : MonoBehaviour{
    class Monster {
        public string name;
        public Monster(string name) { //コンストラクタ
            this.name = name;
        } 
    }
    void Start()    {
        List<Monster> mons = new List<Monster>(); //ジェネリックコレクションを生成
        mons.Add(new Monster("ヴェルドラ")); //Monsterを生成して格納
        mons.Add(new Monster("リムル"));
        mons.Add(new Monster("シュナ"));
        Debug.Log("要素数:" + mons.Count); //プロパティで格納済要素数を得る
        foreach (var m in mons) { //全要素について繰返す
            Debug.Log("名前:" + m.name); //各要素のデータメンバを得て出力
        }
    }
}

文法編:ToStringメソッド

・C#のクラス構造の原則として「全クラスは自動的にObjectクラスの派生クラス扱い」
・よって、Objectクラスのメンバが自動的に継承されている
・その一つがToStringメソッドで、内容はクラスを表す文字列になる
 例: "ex0914c+Monster"
・C#では、自前のクラスの定義時に、ToStringメソッドをオーバーライドして、オブジェクトを表す文字列をreturnすることを推奨している
・なお、オブジェクトを文字列に連結したり、対応するメソッドに渡すと、自動的にToStringメソッドが呼び出される

演習 ex0914c.cs

・上記を試してみよう

作成例

using System.Collections.Generic;
using UnityEngine;
public class ex0914c : MonoBehaviour{
    class Monster {
        public string name;
        public Monster(string name) { //コンストラクタ
            this.name = name;
        }
        public override string ToString() { //自動継承したメソッドのオーバーライド
            return "My name is " + this.name; //自オブジェクトを表す文字列を返す
        }
    }
    void Start()    {
        List<Monster> mons = new List<Monster>(); //ジェネリックコレクションを生成
        mons.Add(new Monster("ヴェルドラ")); //Monsterを生成して格納
        mons.Add(new Monster("リムル"));
        mons.Add(new Monster("シュナ"));
        Debug.Log("要素数:" + mons.Count); //プロパティで格納済要素数を得る
        foreach (var m in mons) { //全要素について繰返す
            Debug.Log("名前:" + m); //各要素のToString()メソッドを自動的に呼ぶ
        }
    }
}

今週の話題

ゲームソフト販売本数ランキング:今週1位は再び「マリオカート ワールド(Switch2)」 GO!
ガンホー、2Qは59%営業減益で正念場-アクティビストとの対立激化、社長の進退問う臨時総会へ【ゲーム企業の決算を読む】GO!
東京ゲームショウ 2025/TGS2025 最新ニュース GO!

同梱品目当ての“粗悪ゲー”がPSストアに氾濫? PS5でのアバター販売停止が生んだ新たな問題点 GO!
『トゥームレイダー』のリマスターで生成AIボイスが勝手に使われている!声優がメーカー提訴、この問題が起きた理由とは GO!

講義メモ 後半

p.192 壁のプレハブを作成する:補足

①Hierarchyの「+」をクリック
②[2D Object][Sprites][Square]
③名前を「Wall」に変更
④[Sprite]にWall.jpgを設定
⑤[Add Component][Pysics 2D][BoxCollider 2D]
⑥[Size]の[X]を「0.7」に変更
⑦「Wall」を[Project]にドラッグ&ドロップ
⑧Hierarchyの「Wall」をクリックしDeleteキーで消す

p.193 スクリプトを使って壁を配置する:補足

①Hierarchyの「+」をクリックし[Create Empty]をクリック
②名前を「GameManager」に変更
③[Add Component][New Script]で「Manager」と入力し[Create and add][:][Edit Script]

p.193 スクリプトを使って壁を配置する:補足:Manager.csについて

・ゲームを構成するオブジェクトに所属しないプログラム(スクリプト)であり、別途、GameManagerと名付けたゲームオブジェクトを
 用意して配置している
・このプログラム(スクリプト)をゲーム開始時に実行し、インスタンスを生成して利用するためにパブリック変数
 wallpfb(壁のプレハブ)を定義している

p.194-195 Manager.cs

using UnityEngine;
public class Manager : MonoBehaviour {
    public GameObject wallpfb; //壁のプレハブになるゲームオブジェクトのパブリック変数
    void Start() {
        for (int x = 0; x < 5; x++) { //X(横)方向に5回繰返す
            for (int y = 0; y < 4; y++) { //Y(縦)方向に4回繰返す
                Vector3 pos = new Vector3(); //座標指定用の構造体オブジェクトを用意
                pos.x = x * 1.2f - 2.4f; //X座標を繰り返しカウンタから得る
                pos.y = 2.5f - y; //Y座標を繰り返しカウンタから得る
                Instantiate(wallpfb, pos, Quaternion.identity); //ゲームオブジェクトと座標を指定してインスタンス生成
            }
        }
    }
}

p.194-195 Manager.cs:補足

・座標指定用のVector3構造体オブジェクトを用意している:p.168参照
・Instantiateメソッドはp.163参照
・Quaternion.identityプロパティはp.163-164参照。無回転の意味。

p.198 スクリプトを使ってWallを回転させる:補足

②[Open]をクリック
③[Add Component][New Script]で「Wall」と入力し[Create and add][:][Edit Script]

p.199 Wall.cs:補足

・メソッドの引数として渡すオブジェクトには名前がなくても構わない
・よって、引数の指定場所においてオブジェクトを生成して渡している
・つまり:下記のように2行を1行にし、使い捨てになる変数を省略している
 transform.Rotate(new Vector3(0, 0, -5));
 ↑
 Vector3 work = new Vector3(0, 0, -5);
 transform.Rotate(work);
・なお、使い捨てになる変数はできる限り(可読性を下げない限り)定義しないことが望ましい
・RotateはTransformクラスのメソッドで、Vector3構造体型のオブジェクトを渡すことで、ゲームオブジェクトを
 回転させることができる
・なお、6種類のオーバーロードがあり、回転方法を都合に合わせて指定できる

p.200 回転方向を切り替える Wall.cs:補足

・clockwiseはbool型の変数で、true/falseのみを保持でき、時計回り/反時計回りを意味している
・bool型の変数は、if/whileなどの条件にそのまま指定できる

p.201 Wall.cs

using UnityEngine;
public class Wall : MonoBehaviour {
    public bool clockwise = true; //時計回りを意味するパブリック変数
    void Update() {
        if (clockwise) { //時計回り?
            transform.Rotate(new Vector3(0, 0, -5)); //ゲームオブジェクトを座標指定により回転
        } else { //反時計回り?
            transform.Rotate(new Vector3(0, 0, 5)); //ゲームオブジェクトを座標指定により回転
        }
    }
}

p.202 Manager.cs

using UnityEngine;
public class Manager : MonoBehaviour {
    public GameObject wallpfb; //壁のプレハブになるゲームオブジェクトのパブリック変数
    void Start() {
        for (int x = 0; x < 5; x++) { //X(横)方向に5回繰返す
            for (int y = 0; y < 4; y++) { //Y(縦)方向に4回繰返す
                Vector3 pos = new Vector3(); //座標指定用の構造体オブジェクトを用意
                pos.x = x * 1.2f - 2.4f; //X座標を繰り返しカウンタから得る
                pos.y = 2.5f - y; //Y座標を繰り返しカウンタから得る
                GameObject obj = Instantiate(wallpfb, pos, Quaternion.identity); //ゲームオブジェクトと座標を指定してインスタンス生成
                Wall wall = obj.GetComponent<Wall>(); //壁プレハブからゲームオブジェクトを得る
                if (y % 2 == 0) { //Y(縦)方向で偶数番目?
                    wall.clockwise = true; //時計回りにする
                } else {
                    wall.clockwise = false; //反時計回りにする
                }
            }
        }
    }
}

p.205 ゲーム画面の底を作成する 前半

①Hierarchyの「+」をクリックし[2D Object][Sprites][Square]をクリック
②名前を「Bottom」に変更
③[Position]のXを「0」Yを「-6」に変更し、[SpriteRenderer]の[Sprite]を「Background」にする.
※下から7個目にある

p.205 ゲーム画面の底を作成する 後半

①[Add Component][Physics 2D][BoxCollider 2D]を追加
②[Is Trigger]をクリックしてチェックをオンにする

p.206 ゲーム画面の底を作成する 前半

①[Transform]の[Scale]のXを「100」Yを「4」に変更
※ここでScene上でCtrlキーを押しながらマウスホイールを手前に回すことで表示を縮小すると下の方に「底」が見えてくる

p.206「ゲーム画面の底を作成する 後半

①[Add Component][New Script]で「Bottom」と入力し[Create and add][:][Edit Script]

p.206 Bottom.cs

using UnityEngine;
public class Bottom : MonoBehaviour {
    private void OnTriggerEnter2D(Collider2D other) { //衝突したら
        Destroy(other.gameObject); //何であろうと破棄(消去)する
    }
}

提出:p.206 Bottom.cs

講義メモ

・ゲーム開発編:p.183「野菜とBoxの衝突判定のスクリプトを書く」から
・文法編:ジェネリクス など

ゲーム開発編:p.183「野菜とBoxの衝突判定のスクリプトを書く」から

p.184 Box.cs

using UnityEngine;
using UnityEngine.UI; //UnityUIのText用
public class Box : MonoBehaviour {
    public Text scoretext; //UnityUIが提供するTextクラスを型とするパブリック変数
    int score = 0; //スコアを扱うインスタンス変数を0で初期化
    private void OnTriggerEnter2D(Collider2D other) { //野菜とボックスの衝突時に呼び出されるメソッド
        Vegetable.Type typeA = GetComponent<Vegetable>().type; //ボックスの種類を得る
        Vegetable.Type typeB = other.GetComponent<Vegetable>().type; //野菜の種類を得る
        if (typeA == typeB) { //得た野菜型が等しい=野菜とボックスが一致?
            score++; //スコアをインクリメント
            scoretext.text = score.ToString(); //整数値を文字列化してTextオブジェクトに与える
            Destroy(other.gameObject); //野菜オブジェクトを破棄することで消す
        }
    }
}

p.186 野菜とBoxの衝突判定のスクリプトを書く:OnTriggerEnter2Dメソッドについて

・BoxCollider2Dを設定してあるオブジェクトにおいて、このメソッドを実装することで、Unity側でオブジェクトの衝突を
 検知して呼び出してもらえる
・この考え方はイベントリスナーとして汎用的に扱われており、言語やゲームエンジンによって実装方法や内部仕様が異なる。
・Unityの場合「OnTrigger●」と「OnCollision●」があり、トリガーモードかどうかで使い分ける。
・トリガーモード(p.182)は衝突時に跳ね返らないモードで、これをオンにすることで、衝突後に通過や破棄(画面から消す)
 したい場合に用いる。
・また、「OnTrigger●」には「OnTriggerEnter」「OnTriggerExit」「OnTriggerStay」があり、それぞれ、衝突時、
 衝突終了時、衝突中に対応して動作する

p.187 GetComponentメソッドでコンポーネントを取得する

・MonoBehaviourから継承したGetComponentメソッドを実行することで、自分自身や他のMonoBehaviour派生クラスオブジェクトの
 コンポーネントを取得できる
・GetComponentメソッドは型情報を引数(型パラメータ)として得ることができるジェネリクスメソッド(詳細は後述)なので、
 今回はVegetableを型パラメータに指定している。
・GetComponentメソッドで、Vegetableクラスオブジェクトが得られるので、その中で定義しているType型の変数typeを
 「.type」で得ることができる
・Type型は列挙型で野菜の種類を示すので、Tomato, Gpepper, Broccoli, Potatoのどれかになり、Vegetable.Type型の
 変数typeAに代入される
・これが、ボックスの種類(どの野菜か)を得ることになる
・OnTriggerEnter2DメソッドのCollider2D型の引数otherには、衝突相手である野菜が得られる
・よって、other.GetComponentメソッドで、同様の手順で野菜の種類が得られ、Vegetable.Type型の変数typeBに
 代入される

p.189 GetComponentメソッドでコンポーネントを取得する:補足:ドット演算子

・「other.GetComponent().type」のように、ドットで「の」「に所属する」などを連続して表現できる
・複雑で可読性が下がると思われる場合は中間結果を変数に置くことで複数行に分割するとよい
例:
 Vegetable.Type typeB = other.GetComponent().type;
 ↓
 Vegetable work = other.GetComponent(); //①まず野菜オブジェクトを得る
 Vegetable.Type typeB = work.type; //②それから中にあるtypeを得る

p.189 GetComponentメソッドでコンポーネントを取得する:補足:null

・GetComponentメソッドがオブジェクトを得られなかった場合、異常終了せずに、nullが返される
・nullは無を意味するキーワードで、全ての参照型に対応できる特殊な値。
・よって「参照型の変数 == null」というような比較が型に限定されずに可能
・なお、int型などの値型や、Vector3などの構造体型は、nullの代入や比較は不可

p.189 数を増やしてBoxに入った野菜を削除する:補足:スコア処理

・scoretextは、UnityUIが提供するTextクラスを型とするパブリック変数
・Textクラスには描画用の文字列を保持するstring型のインスタンス変数textが定義されている
・ここに文字列を代入することで、自動的に画面上に反映する(Unityが制御してくれる)
・スコアを持つ変数scoreはint型なので、string型にするためにToString()メソッドを用いている
・これは「"" + score」としてもOK

p.189 数を増やしてBoxに入った野菜を削除する:補足:Destory

・MonoBehaviourクラスから継承されているDestory()メソッドに、ゲームオブジェクトを引数として渡すことで、ゲームオブジェクトを
 破棄できる
・OnTriggerEnter2DメソッドのCollider2D型の引数otherに対してgameObjectプロパティを指定することで、これに含まれる
 ゲームオブジェクトを得ることができる
・よって、other.gameObjectをDestory()メソッドに渡せば、一致するボックスに衝突してきた野菜がオブジェクトの破棄により
 画面から消える

p.191 野菜の種類と関連するTextを指定する:補足

・② 1つめのText(Regacy)を[Scoretext]にドラッグ&ドロップ

今週の話題

ゲームソフト販売本数ランキング:今週1位は「METAL GEAR SOLID Δ: SNAKE EATER(PS5)」 GO!
『ブルアカ』『メイプルストーリー』減衰…ネクソンの日本事業は上期25%の減収で折り返し【ゲーム企業の決算を読む】GO!
プロが教える動画制作、中高生向け無料オンライン講座9-10月 GO!
【キャリアクエスト】FGOだけじゃない、ラセングルならではの挑戦を求めて。“若い会社”で活躍する、とあるゼネラリストの働き方事情 GO!
インディーゲームの祭典「INDIE Live Expo」開催日は11月29日に決定、100作品以上の紹介やアワードを予定 GO!

Roblox、ユーザーに対しての「年齢確認」取り組み強化へ―未成年と大人のコミュニケーションを大きく制限する機能も追加予定 GO!
R-18コンテンツを開いているブラウザタブを検知・スクショするマルウェア Webカムで撮影も 性的脅迫に使用か GO!
App Storeの審査が突如通らず──DMM傘下のスマホゲームでトラブル 「リリースから実装済みの全イラストが修正対象に」GO!
「みんなのGOLF」新作、発売日に異例声明 ユーザーからの“不具合報告”続出で 「現象の原因究明に努める」GO!

前回のコメント

・インデクサ、コンストラクタ、デストラクタについて理解できました。

 何よりです。

・ゲーム開発編で完成が見えてきて良かったです
 インスタンス、インデクサ、などの意味を間違えて覚えないように実際に利用して理解を深めたいです

 ぜひ、いろいろと試してみてください。

・欠席した8/24分の課題提出です
 クラスと構造体の違いを前よりも理解できました。

 後追い提出も大歓迎です。