Unityで覚えるC♯
  Learn C# in Unity
Learn C# in Unity
 初学者にありがちな混乱
 ・言語の仕様
 ・標準ライブラリの仕様
 ・実行環境の仕様
    この3つをごっちゃにして「何がわからないのかわからない」
    状態になりがち。C#の文法の問題なのか、.Net Framework
    の問題なのか、Unityの問題なのか切り分けて考えよう。

    今回は主にC#の文法の話と、よく使う(知らないとそもそも発
    想に至らない).Net Frameworkの機能の話をします。




                                    2/57
Learn C# in Unity
 オブジェクト指向 再々(xN)入門
 ・その前に、とても眠くなるそもそも論
    そもそもプログラムとは「データの加工」です。つまり「デー
    タ」と「加工」という2つの要素から成り立っています。プロ
    グラムの用語に直すと「変数」と「関数」です。

    オブジェクト指向以前は、データと加工処理が完全に別れてい
    ました。データ、つまり変数を、それを処理する関数に渡して
    加工してもらい、受け取った結果をまた別の関数に渡して、と
    いう流れを延々繰り返します。

    オブジェクト指向はその考え方を1段階抽象化して、関連する
    変数と関数を1まとまりにしたオブジェクトという概念を導入
    し、プログラムは各オブジェクトが協調動作して動くものだ、
    と考えるようになりました。
                               3/57
Learn C# in Unity
 オブジェクト指向 再々(xN)入門
 ・オブジェクト指向以前・以後
    オブジェクト指向という考え方をした場合としなかった場合、
    プログラムの構造がどのようにかわるか、という例。

   ■BEFORE                                        ■AFTER
   int GetFileSize(string fname);                 class File
   bytes ReadFromFile(string fname, int size);    {
   void WriteToFile(string fname, bytes data);         bytes data;
   void CloseFile(string fname);                       void Open(string fname);
                                                       void Close();
   main()                                         }
   {
        string fname = "hoge.txt";                main()
        int size = GetFileSize(fname);            {
        bytes data = ReadFromFile(fname, size);        File f = new File();
        for (int i = 0; i < size; i++)                 f.Open("hoge.txt");
             data[i] += 1;                             for (int i = 0; i < f.data.size; i++)
        WriteToFile(fname, data);                           f.data[i] += 1;
        CloseFile(fname);                              f.Close();
   }                                              }




                                                                                               4/57
Learn C# in Unity
 オブジェクト指向 再々(xN)入門
 ・一般的な意味での用語の定義(≠C#の用語)
    ■オブジェクト
     データや処理が1まとまりになった最小のプログラム片
    ■クラス/構造体
     オブジェクトの設計図
    ■インスタンス
     メモリ上に配置されたクラス/構造体の実体
    ■メンバー変数/メンバーフィールド/メンバープロパティ
     オブジェクトが持つ情報
    ■メンバー関数/メンバーメソッド
     オブジェクトの動作を定義した関数
    ■インターフェース
     オブジェクトがどんなメンバーを持っているか、どう振る舞
     うかの定義。実は一番大事な概念。
                              5/57
Learn C# in Unity
 オブジェクト指向 再々(xN)入門
 ・プログラムの設計=インターフェースの設計
     オブジェクト指向に限らず、プログラムの設計とはインターフェー
    スの設計に他なりません。オブジェクト指向ならそもそも何をオブ
    ジェクトに見立てて、それがどんなメンバーを持っているのか、つま
    りどんなインターフェースにするのかを決めることが最大の関心事で
    あり、一番難しい問題です。
     ライブラリの場合だと、ユーザーのプログラムと、どんなデータを
    どんな関数を介してやりとりするのか決めること=インターフェース
    の設計です。
     ゲームだってユーザーに対してどんな情報を見せるのか、あるいは
    見せないのか、つまりユーザーインターフェースの設計がキモであ
    り、難しい部分です。
     インターフェースが適切になっているか、常に意識しながらプログ
    ラミングしましょう。
                                 6/57
Learn C# in Unity
 オブジェクト指向 再々(xN)入門
 ・Unityの代表的なオブジェクト
    ■Vector3
      X,Y,Zの値を持つベクトル。位置情報などを表す。
    ■Input
      ユーザーからの入力を一元管理している。
    ■GameObject
      ゲーム空間(シーン)の中に存在するものは全てこれ。
    ■Component
      GameObjectにアタッチすることで、GameObjectがどう
      振る舞うか定義できる。
    ■Transform
      Componentの一種で、ゲーム空間中での位置や向きなどを
      管理する。全てのGameObjectに付いている。

                                     7/57
Learn C# in Unity
 オブジェクト指向 再々(xN)入門
 ・継承
    あるオブジェクトの機能を引き継ぎ、より具体的な機能を追加
    した新しいオブジェクトを作る仕組み。

       Component    Transform


                     Camera          BoxCollider


                     Collider       SphereCollider


                    MonoBehaviour      PlayerBehaviour


                                       EnemyBehaviour


                                                         8/57
Learn C# in Unity
 オブジェクト指向 再々(xN)入門
 ・多態(ポリモーフィズム)
    あるオブジェクトが継承先で何をやっているか意識することな
    く、継承元のインターフェースでアクセスできる仕組み。

                                  BoxCollider
                                  bool Raycast(ray)
              Collider
                                  {
              bool Raycast(ray)
                                     return 線と立方体の判定結果;
                                  }


        Ray         Bool          SphereCollider
                                  bool Raycast(ray)
                                  {
                                     return 線と球の判定結果;
     GameObject                   }


                                                        9/57
Learn C# in Unity
 Unityの設計から学ぶオブジェクト指向
 ・継承が全てではない!
    Unityのゲームシステムは、オブジェクト指向を一歩進めたコン
    ポーネント指向という考え方で作られています。
    ■古いオブジェクト指向
                     GameCharacter             PlayerCharacter
     GameObject
                     MeshData mesh;            Controller controller;


                                               EnemyCharacter
                                               AI ai;

    ■コンポーネント指向
                    MeshData                            MeshData
     GameObject                       GameObject
                    Controller                          EnemyAI

                                                                        10/57
Learn C# in Unity
 C#におけるクラス
 ・クラスの定義の仕方
    using UnityEngine; // 使うライブラリの宣言

    // MonoBehaviourを継承したNewBehaviourScriptクラスの宣言
    public class NewBehaviourScript : MonoBehaviour
    {
         // メンバーフィールドの宣言
         public Vector3 velocity;

        // メンバーメソッドの定義
        void Update()
        {
             transform.Translate(velocity * Time.deltaTime);
        }
    }


    メンバーの宣言に、publicを付けるとクラスの外からも見える
    ようになる。privateを付けるか何も付けないと、クラスの外か
    らは見えないようになる。内部で何をやっているかはできるだ
    け見せない方がよい。

                                                               11/57
Learn C# in Unity
 C#におけるクラス
 ・クラスがメンバーに持てるもの
    ■フィールド
     変数。通常あまり生のメンバー変数を公開するものではない
     が、Unityの場合パブリックフィールドにしないとインスペ
     クタに表示されない。
    ■メソッド/コンストラクタ/デストラクタ
     関数。コンストラクタとデストラクタは少し特殊な関数。
    ■プロパティ
     アクセサの簡易表記法
    ■インデクサー
     オブジェクトを配列のように扱えるようにするための機能
    ■イベント
     処理の一部を外部に委譲したり、何かのタイミングを通知す
     るための機能。
                               12/57
Learn C# in Unity
 C#におけるクラス
 ・コンストラクタ
    オブジェクトの初期化処理を行うためのメソッド。ただしUnity
    はMonoBehaviourを継承したクラスがコンストラクタを実装
    することを推奨していません。Awake/Startを使いましょう。
    public class Foo : Bar
    {
         int i;
         // コンストラクタの定義。戻り値のない、クラス名とまったく同じ名前のメソッドにする。引数は取れる。
         public Foo(int i)
         {
              this.i = i;
         }
         // 同じクラスの別のコンストラクタを呼ぶ書き方。
         public Foo() : this(0)
         {
         }
         // 継承元のコンストラクタを明示的に呼び出す書き方。省略すると継承元の引数なしコンストラクタが呼ばれる。
         public Foo(float f) : base(f)
         {
         }
    }

    Foo f = new Foo(1.0f); // 3つめのコンストラクタが呼ばれる

                                                           13/57
Learn C# in Unity
 C#におけるクラス
 ・デストラクタ
    オブジェクトの破棄処理を行うためのメソッド。C#はGCが動
    いていて明示的な破棄はしなくてよいので、通常意識する必要
    はありません。
    public class Foo
    {
         // デストラクタの定義。戻り値も引数もアクセス指定もない、クラス名の最初に~を付けた名前のメソッドにする。
         ~Foo()
         {
         }
    }




    C#で破棄のタイミングを管理したいクラスはIDisposableを継
    承して、Dispose()メソッドを実装し、using文と合わせて使
    います。ただDispose()の実装は慣れてないと難しい…。


                                                           14/57
Learn C# in Unity
 C#におけるクラス
 ・プロパティ:アクセサの簡易表記法
    public class NewBehaviourScript : MonoBehaviour
    {
         // メンバープロパティの定義
         public Vector3 velocity
         {
              get { return rigidbody.velocity; }
              set { rigidbody.velocity = value; }
         }
         // 上記は、以下の様なアクセサの定義を簡単にできるようにしたもの
         Vector3 GetVelocity()
         {
              return rigidbody.velocity;
         }
         void SetVelocity(Vector3 value)
         {
              rigidbody.velocity = value;
         }
         // プロパティへは普通のメンバーフィールドと同じようにアクセスできる
         void Start()
         {
              velocity = Vector3.zero;
         }
    }




                                                      15/57
Learn C# in Unity
 C#におけるクラス
 ・プロパティをもっと便利に使う
    プロパティは実装を省略してもOK
    public class NewBehaviourScript : MonoBehaviour
    {
         public Vector3 velocity { get; set; }

        // 上記は、以下のような暗黙のメンバーフィールドを持ったプロパティが作られていると考えてOK
        Vector3 m_velocity;
        public Vector3 velocity
             get { return m_velocity; }
             set { m_velocity = value; }
        }
    }


    プロパティはget/setごとにアクセス指定を付けることも可能
    public class NewBehaviourScript : MonoBehaviour
    {
         public Vector3 velocity
         {
              get; private set;
         }
    }




                                                          16/57
Learn C# in Unity
 C#におけるクラス
 ・プロパティをもっともっと便利に使う
    読み取りのみ許可するプロパティの場合は、setを省略できる
    public class NewBehaviourScript : MonoBehaviour
    {
         public Vector3 velocity
         {
              get { return rigidbody.velocity; }
         }
    }




                                                      17/57
Learn C# in Unity
 C#におけるクラス
 ・インデクサー
    public class NewBehaviourScript : MonoBehaviour
    {
         int[] data;
         // インデクサーの定義
         public int this[int index]
         {
              get { return data[index]; }
              set { data[index] = value; }
         }
         // インデクサーをメンバーに持つオブジェクトへは、配列のようにアクセスできる
         void Start()
         {
              NewBehaviourScript nbs = new NewBehaviourScript();
              print(nbs[0]);
         }
    }




                                                                   18/57
Learn C# in Unity
 C#におけるクラス
 ・イベント
    重要な機能だけど理解が難しいので後で詳しく説明します。
    public class Foo
    {
         // デリゲート型の定義
         public delegate void OnDamageDelegate(GameObject victim, float amount);
         // 定義した型のイベントを宣言
         public event OnDamageDelegate onDamage;
         // ダメージ処理
         float hp { get; private set; }
         public void DoDamage(float amount) {
              hp -= amount;
              onDamage(gameObject, amount); // onDamageに登録されてるメソッドを呼ぶ
         }
    }
    // 例えばダメージを喰らったタイミングでこのメソッドを呼んで欲しい場合
    void PrintDamage(GameObject victim, float amount)
    {
         print(victim.name + " got damage " + amount);
    }
    // イベントにデリゲートを登録
    Foo f = new Foo();
    f.onDamage = PrintDamage;
    // ダメージ処理を呼ぶと、イベントの機能でPrintDamageメソッドが呼ばれる
    f.DoDamage(10);


                                                                                   19/57
Learn C# in Unity
 C#におけるクラス
 ・クラスメンバーとインスタンスメンバー
    一口にメンバーといっても、クラス自体に属して全てのインス
    タンスから同じ値を参照するクラスメンバーと、各インスタン
    ス毎にローカルな値を持つインスタンスメンバーがあります。
    public class Foo : MonoBehaviour
    {
         // クラスフィールドの定義
         public static int classField = 0;
         // インスタンスフィールドの定義
         public int instanceField = 0;
         // クラスメソッドの定義
         public static void Hoge() { print("Hoge! " + classField); }
         // インスタンスメソッドの定義
         public void Mage() { print("Mage! " + classField); }
    }

    // クラスメンバーにはインスタンスがなくてもアクセスできる
    Foo.classField = 10;
    Foo.Hoge(); // "Hoge! 10"
    Foo foo = new Foo();
    print(foo.instanceField); // 0
    foo.Mage(); // "Mage! 10"

                                                                       20/57
Learn C# in Unity
 C#におけるクラス
 ・クラスメンバーしか持たないクラス
    全てのメンバーがstaticなクラスは、クラス自体もstaticにす
    ることで、インスタンスを作ることを明示的に禁止できます。
    public static class Foo
    {
         // クラスフィールドの定義
         public static int classField = 0;
         // クラスメソッドの定義
         public static void Hoge() { print("Hoge! " + classField); }
         // staticなクラスは静的コンストラクター(クラスコンストラクタ)で初期化する
         static Foo()
         {
              classField = 10;
         }
    }



    ちなみに静的コンストラクターは普通のクラスでも使えます。



                                                                       21/57
Learn C# in Unity
 C#におけるクラス
 ・クラス修飾子
    sealed
      sealed修飾子を付けたクラスは継承できなくなります。

 ・メンバーフィールド修飾子
    const
      実行中に値が変わらない(変わってはいけない)フィールド
      につけることで、値を変えようとするコードを書くとコンパ
      イル時にエラーにしてくれます。

    readonly
       意味的にはconstと同じ。constは値型(後述)にしか付け
       られませんが、readonlyは参照型にも付けられます。
                                   22/57
Learn C# in Unity
 C#におけるクラス
 ・メンバーメソッド修飾子
    virtual
        継承先で実装を書き換えて良いことを宣言します。

    override
      継承元で宣言されているvirtualメソッドを上書きして、独自
      に実装し直すことを宣言します。
      ちなみにoverrideしたメソッドから継承元の本来の処理を
      呼び出すには「base.メソッド名()」と書きます。

    extern
       外部で実装されているメソッドを宣言する時に、DllImport
       属性と組み合わせて使います。Unityではネイティブプラグ
       インのメソッドを呼びたい時に使います。
                                    23/57
Learn C# in Unity
 C#におけるクラス
 ・属性(アトリビュート)
    C#はアトリビュートという機能を使って、クラス自体やそのメ
    ンバーにメタデータを付けることができます。Unityでは自分で
    作ったクラスにSerializable属性を付けることでインスペクタ
    に表示できるようにしたり、エディタの拡張で多用します。
    [ExecuteInEditMode()] // この属性を付けると、エディタ上で非実行時もStartとかUpdateが呼ばれる
    public class NewBehaviourScript : MonoBehaviour
    {
         [System.Serializable()] // この属性を付けないと、Foo型のフィールドがインスペクタに表示されない
         public class Foo
         {
              public int hoge;
         }
         public Foo f;
    }




                                                                      24/57
Learn C# in Unity
 C#における構造体
 ・構造体の定義の仕方
    public struct Point
    {
         // メンバーフィールドの宣言
         public int x, y;

        // メンバーメソッドの定義
        public Point(int x, int y)
        {
             this.x = x;
             this.y = y;
        }
    }



    基本的にはクラスの定義と同じだが、構造体はクラスのような
    実装の継承はできない。インターフェース(後述)を継承して、
    自分で実装を書くことはできる。
    またクラスは参照型だが、構造体は値型であるという違いがあ
    る(後述)。

                                     25/57
Learn C# in Unity
 C#におけるインターフェース
 ・インターフェースの定義の仕方
    interface ICollidable
    {
         // メンバーメソッドの宣言(定義はできない)
         bool IsCollide(GameObject other);
    }

    public class NewBehaviourScript : MonoBehaviour, ICollidable
    {
         // インターフェースの実装
         public bool IsCollide(GameObject other)
         {
              if (gameObject.collider == null || other.collider == null)
                   return false;
              return IsCollide(gameObject.collider, other.collider);
         }
    }



    インターフェースはそれを継承したオブジェクトがどんなメン
    バーメソッド、プロパティ、インデクサー、イベントを持つか
    明言するためのもの。メンバーフィールドは宣言できず、実装
    もできないため、単体では機能しない。
                                                                           26/57
Learn C# in Unity
 クラス or 構造体
 ・参照型と値型
    クラスは参照型、構造体は値型です。この2つの違いは、メ
    ソッドの引数や戻り値としてオブジェクトが渡される時の挙動
    に影響します。
    例えばオブジェクトFooからBarへ変数が渡される場合…
     ■参照型                 ■値型
                                    複製
      Transform           Vector3        Vector3




        Foo         Bar    Foo            Bar



                                                   27/57
Learn C# in Unity
 クラス or 構造体
 ・値型の参照渡し
    refキーワードやoutキーワードを使って、構造体のような値型
    の参照を渡す方法もあります。
    public class Foo
    {
         Vector3 myVector;

        public AddVectorTo(ref Vector3 target)
        {
             target += myVector;
        }
    }

    public class Bar
    {
         Foo foo = new Foo();
         Vector3 myVector;
         public Bar()
         {
              foo(ref myVector);
         }
    }




                                                 28/57
Learn C# in Unity
 クラス or 構造体
 ・Null許容型
    値型の変数でも、まだ初期化されていないことを明示したい場
    合があります。そういう時はNull許容型を使います。
    public class Foo
    {
         Vector3? myVector; // 変数の型の後ろに「?」を付けるとNull許容型になる

        public Vector3 GetVector()
        {
             if (myVector == null)
                  myVector = Vector3.up;
             return myVector.Value;
        }
    }




    ただこれはデータベースとのやりとりを簡単にするために用意
    されたものなので、Unityで使うことはあまりない。使わざるを
    えないような状況になったらおそらく設計が悪い。

                                                            29/57
Learn C# in Unity
 クラス or 構造体
 ・参照型
    メリット
     オブジェクトのサイズに関わらず、受け渡しのコストが一定
     受け渡した先で値を変更すると、受け渡した元の値も変わる
    デメリット
     GCで処理する際のコストが高い
 ・値型
    メリット
     GCで処理する際のコストが低い
     受け渡し先で値を変更しても、受け渡し元は影響を受けない
    デメリット
     オブジェクトのサイズに比例して受け渡しのコストが増える
     ボックス化によるパフォーマンスの悪化がわかりづらい
                              30/57
Learn C# in Unity
 クラス or 構造体
 ・ボックス化・ボックス化解除
    値型をobject型に変換することをボックス化、object型を元の
    値型へキャストし直すことをボックス化解除といい、どちらも
    とてもコストが高い操作なので極力避けるようにします(ボッ
    クス化されるとインスタンスのサイズも増えます)。
       object o = 1;   // ボックス化
       int i = (int)o; // ボックス化解除

       // ArrayListは全ての要素をobject型に変換して格納するコレクションクラス
       ArrayList list = new ArrayList();
       list.Add("hoge");   // だから文字列も数値も同じArrayListに格納できるけど…
       list.Add(100);      // ←ここでボックス化が発生!気づきにくい!




    後述するジェネリックという機能を使えば不要なボックス化を
    減らせるので、積極的に使いましょう。


                                                               31/57
Learn C# in Unity
 クラス or 構造体
 ・どちらを選ぶべきか
    基本はクラスを使い、以下の条件を全て満たす場合のみ構造体
    にします(とMicrosoftのドキュメントに書いてある…)。

    1.整数や浮動小数点数のような論理的に単一な値を表す
    2.インスタンスのサイズが16バイト未満である
    3.継承により振る舞いを変更する必要がない
    4.頻繁にボックス化する必要がない

    例えば「二次元座標を表すPointオブジェクト」のようなもの
    は構造体に向いています。



                                 32/57
Learn C# in Unity
 C#の便利な機能
 ・Enum:一連の定数を1つにまとめた型
    public class Foo
    {
         // 関連のある定数群をこう書くよりも…
         const int DamageTypeSlash = 1;
         const int DamageTypeBlunt = 2;
         const int DamageTypeExplosion = 3;

        // enumにしたほうが見通しが良い
        enum DamageType
        {
             Slash = 1, // 値を明言することもできる。明言しないと前の値+1になる。基底は0。
             Blunt,     // 直前の値が1なのでこいつは2。
             Explosion,
        }

        DamageType damageType; // DamageType型のどれか1つの値を取るメンバーフィールド、という意味

        // Enum型の各値には、「型名.名前」という形式でアクセスする。
        public Foo() { damageType = DamageType.Blunt; }

        // 整数へのキャストは保証されているが、あまり感心できる使い方ではない…
        int GetDamageTypeID() { return (int)damageType; }
    }



                                                                          33/57
Learn C# in Unity
 C#の便利な機能
 ・namespace:名前空間
    namespace MyClasses //関連するオブジェクトを1つの名前空間に収めることで検索性を上げたり名前の衝突を防ぐ
    {
         public class Foo { }
         public class Bar { }
    }
    // 名前空間の中の要素にはこのように書いてアクセス
    MyClasses.Foo f;


    // 別のファイルでも同じ名前空間を宣言することができる
    namespace MyClasses
    {
         public class Hoge { }
    }


    // ファイルの先頭でusingディレクティブを使うと、アクセス時の名前空間を省略できる
    using MyClasses;

    Foo f;



    ファイルの先頭でusing UnityEngine;とかやってるのはコレ

                                                                34/57
Learn C# in Unity
 C#の便利な機能
 ・キャスト、型情報の利用
    キャスト(型の明示的な変換)はCの書き方と似ています。
    またis演算子、as演算子という型チェックの簡単な書き方があ
    ります。
       // 普通のキャストは「(目的の型)」で行う
       Collider c = (Collider)gameObject.AddComponent("BoxCollider");

       // is演算子でインスタンスの型が指定したものかどうかチェックできる
       if (c is BoxCollider)
            boxCollider = c as BoxCollider; // インスタンスを指定した型にキャスト、できなかったらnull

       // 型情報を取り出して自分で判定することも
       if (c.GetType() == typeof(BoxCollider))
            print("OK");

       // ジェネリックを使えばそもそもキャストはいらなかったり
       c = gameObject.AddComponent<BoxCollider>();




                                                                         35/57
Learn C# in Unity
 C#の便利な機能
 ・var
    型が明確なローカル変数は、型名をvarで代用することが可能
    public class Foo
    {
         void Hoge()
         {
              // これは
              Transform t = transform;
              // こう書いてもいい
              var t = transform;
              // その1文ではvarが実際何の型になるのかわからない書き方はNG
              var i;
              i = 0;
         }

        public var i = 0; // これもNG。varが使えるのはローカル変数のみ。
    }




                                                        36/57
Learn C# in Unity
 C#の便利な機能
 ・パーシャルクラス・パーシャルメソッド
    1つのクラスの定義を複数のファイルに分けて書くことが可能
    public partial class Foo          public partial class Foo
    {                                 {
         void Hoge()                       void Mage()
         {                                 {
         }                                 }
    }                                 }


    // もちろんクラスFooはHoge、Mage両方のメソッドをメンバに持っている
    Foo f = new Foo();
    f.Hoge();
    f.Mage();



    パーシャルクラスのメソッドにpartial修飾子を付けると、実装
    を別のファイルで定義しても、定義しなくても(!)いい。
    public partial class Foo
    {
         partial void Hoge();
    }


                                                                 37/57
Learn C# in Unity
 C#の便利な機能
 ・拡張メソッド
    staticメソッドをあたかもインスタンスメソッドのように呼べ
    るようにする機能です。クラスにメソッドを追加しているよう
    に見えるけど、継承ではないので既存のメソッドの振る舞いを
    変更したりはできないし、多用すると混乱の元。
    // 本来は以下のようにしか書けないが
    Instantiate(prefabObject);

    // 拡張メソッドの機能を使うと
    static class GameObjectExtension
    {
         // staticメソッドの第一引数の前にthisを付けると拡張メソッドになる
         public static Object Instantiate(this GameObject prefab)
         {
              return Instantiate(prefab);
         }
    }

    // あたかもGameObjectクラスがInstantiate()というインスタンスメソッドを持っているかのように書ける
    prefabObject.Instantiate();



                                                                    38/57
Learn C# in Unity
 C#の制御構文
 ・if文、for文、while・do~while文
    他言語と比べて別段取り立てるような差異はありません。
    contiue、break、gotoなども使えます。
 ・switch文
    caseに文字列が使えたり、基本的にはbreakを省略できなかっ
    たり、他言語と比べて少し特殊です。
       switch (hoge)
       {
       case 0: // case文の間に処理が挟まらない場合だけフォールスルーできる
       case 1: // caseを繋げて書いてあるので、hogeの値が0か1の場合、以下のDoSomething()が呼ばれる
            DoSomething();
            break; // これは省略できない(Cなどでは省略すると処理が下に繋がる)
       case 2:
            DoSomething2();
            goto case 0; // 何か処理をした後どうしても他のcaseに進みたい場合はgotoを使う
       default: // 上記のどのcaseにも当てはまらなかった場合、という書き方
            DoNothing();
            break;
       }


                                                                        39/57
Learn C# in Unity
 C#の制御構文
 ・foreach文
    配列やコレクションなどの全ての要素に対して繰り返し処理を
    行います。
       foreach (AudioSource a in GetComponents<AudioSource>())
       {
            a.Stop();
       }


    後述するLinqと組み合わせるとfor文がほとんど要らなくなる。
       // アタッチされているAoudioSouceのうち、再生中の物に対してだけStop()を呼び出す、という例
       foreach (var a in from a in GetComponents<AudioSource>() where a.isPlaying select a)
       {
            a.Stop();
       }


    ループ文の中でif文を使って分岐させると、ループ処理自体が長
    くなってしまうので、可能ならLinqであらかじめリストを整形
    してからforeach文で処理すると見通しが良くなります。

                                                                                         40/57
Learn C# in Unity
 C#の制御構文
 ・try~catch~finally文
    C#での例外処理を制御する構文だけど、Unityが例外をほとん
    ど意識しなくて良い設計になっているのであまり使いません。
    StreamReader sr = null;
    try
    {
         sr = File.OpenText(path);
         string s = "";
         while ((s = sr.ReadLine()) != null)
              print(s);
    }
    catch (FileNotFoundException e)
    {
         print("File Not Found!");
    }
    finally
    {
         sr.Dispose();
    }




                                               41/57
Learn C# in Unity
 C#の制御構文
 ・using文
    名前空間の省略や型の別名定義にもusingというキーワードを
    使うので混乱しやすいけど、メソッド内に出てくるusing文は
    Dispose()の呼び出しを保証するもの。UnityEngineには
    IDisposableを実装したクラスがないのであまり使わない…。
    ■using版                                         ■非using版
    using (StreamReader sr = File.OpenText(path))   StreamReader sr = null;
    {                                               try
         string s = "";                             {
         while ((s = sr.ReadLine()) != null)             sr = File.OpenText(path);
              print(s);                                  string s = "";
    }                                                    while ((s = sr.ReadLine()) != null)
                                                              print(s);
                                                    }
                                                    finally
                                                    {
                                                         sr.Dispose();
                                                    }




                                                                                        42/57
Learn C# in Unity
 ジェネリック
 ・ジェネリックとは
    不要なキャストを減らし、コンパイル時に型チェックができ、
    コードが最適化されパフォーマンスが良くなる、大変素晴らし
    い機能です。大変素晴らしいです。
       BoxCollider c;

       // 非ジェネリック版(長い。しなくてもいいキャストは見苦しい…)
       c = (BoxCollider)gameObject.AddComponent(typeof(BoxCollider));
       c = gameObject.AddComponent(typeof(BoxCollider)) as BoxCollider;

       // ジェネリック版
       c = gameObject.AddComponent<BoxCollider>();



    しかしUnityはジェネリック版のメソッドをあまり用意していな
    い…ぐぬぬ…。



                                                                          43/57
Learn C# in Unity
 ジェネリック
 ・自分でジェネリック対応する
       // 例えばこんなメソッドをジェネリック版にする場合
       Object FindObjectOf(Type type)
       {
            return FindObjectOfType(type);
       }

       // 型パラメータの名前をTとしてあるのは慣習(スタンダードなルールがある)
       T FindObjectOf<T>() where T: Component // 型パラメータに制限を課すことができる(任意)
       {
            return (T)FindObjectOfType(typeof(T));
       }

       // 上記ジェネリックメソッドの呼び出し方
       var renderer = FindObjectOf<Renderer>(); // 型が自明なのでvarが使えるしキャストもない!

       // 型パラメーターに制限をかけてあるので以下はコンパイル時エラー。素晴らしい!
       int i = FindObjectOf<int>(); // intはComponentを継承していない




                                                                             44/57
Learn C# in Unity
 ジェネリック
 ・非ジェネリックコンテナは使うな
    ボックス化のところで説明したように、ArrayListなどの非ジェ
    ネリックコンテナは、知らないうちにパフォーマンスが悪化し
    たり、本来必要ないキャストが増えたりしてよろしくありませ
    ん(キャストが増えるとバグが増える)。
    明確な理由がない場合は、System.Collections.Generic名前
    空間にある、ジェネリック版のコンテナを使いましょう。
    using System.Collections.Generic;

    List<int> list;
    list.Add(100); // ボックス化無し!
    list.Add("hoge"); // コンパイル時エラー!
    int i = list[0]; // もちろんボックス化解除も無し!




                                          45/57
Learn C# in Unity
 デリゲート
 ・メソッドを参照できる型
    メソッドの参照を保存しておいて後から呼び出したり、複数の
    メソッドを1つの変数に入れておいてまとめて呼び出せるよう
    にする機能です。
    // デリゲート型の定義
    delegate void SomeDelegate(float f);
    // 定義した型の変数を宣言
    SomeDelegate someDelegate;

    // 例えばこんなメソッドを先ほどの変数へ代入することができる
    void Hoge(float value) { } // 型さえあってれば引数の名前は同じじゃなくてもいい
    someDelegate = Hoge;

    // インスタンスメソッドも代入できる
    public class Foo
    {
         public void Bar(float amount) { }
    }
    Foo f = new Foo();
    someDelegate += f.Bar; // 「+=」で追加代入できる

    // デリゲート変数に格納されているメソッドを呼ぶ(Hogeとf.Barが順番に呼ばれる)
    someDelegate(1.0f);

                                                             46/57
Learn C# in Unity
 デリゲート
 ・元々用意されているデリゲート
    引数も取らず戻り値もないデリゲートとか、引数をひとつ取り
    bool値を返すデリゲートなどは頻出するので、System以下に
    あらかじめ用意されています(自分で定義しなくてOK)。
    System.Func<TResult> // TResult Func()

    System.Predicate<T> // bool Predicate(T value)

    System.Action<T> // void Action(T value)

    System.Action<T1, T2> // void Action(T1 p1, T2 p2)




                                                         47/57
Learn C# in Unity
 イベント
 ・デリゲートを外から実行できなくする
    例えばダメージを喰らったタイミングをデリゲートを使って他
    のオブジェクトに通知したい場合、デリゲートむき出しだと外
    から実行できてしまいます。これは困る。
    public class Foo
    {
         // デリゲート型の定義
         public delegate void OnDamageDelegate(GameObject victim, float amount);
         // 定義したデリゲート型の変数を宣言(privateにしちゃうと登録すらできなくなっちゃうので意味なし)
         public OnDamageDelegate onDamage;
         // ダメージ処理
         float hp { get; private set; }
         public void DoDamage(float amount) {
              hp -= amount;
              onDamage(gameObject, amount);
         }
    }
    Foo f = new Foo();
    f.onDamage(1.0f); // 実際にはダメージを喰らってないのにonDamageを直接呼べてしまう


    そういう時はイベントという機能を使いましょう

                                                                                   48/57
Learn C# in Unity
 イベント
 ・イベントは外からは追加と削除だけできる
    デリゲートむき出しではなくイベントにすると、追加と削除は
    クラス外からでもできるけど、実行はクラス内でしかできない
    ようになります。これで変なタイミングで呼ばれなくなる。
    public class Foo
    {
         // デリゲート型の定義
         public delegate void OnDamageDelegate(GameObject victim, float amount);
         // デリゲート型の変数に「event」を付けるだけ
         public event OnDamageDelegate onDamage;
         // ダメージ処理
         float hp { get; private set; }
         public void DoDamage(float amount) {
              hp -= amount;
              onDamage(gameObject, amount);
         }
    }
    Foo f = new Foo();
    f.onDamage(1.0f); // コンパイルエラー!
    f.onDamage += PrintDamage; // 追加は問題なく行える




                                                                                   49/57
Learn C# in Unity
 ラムダ式
 ・名前の無いメソッド
    イベントに登録するメソッドをいちいち全部メンバーメソッド
    として定義していくと、クラスが無駄に巨大化しがちです。
    そこでラムダ式という、名前がない、何かに代入した状態でな
    いと呼び出せないメソッドを定義できる機能を使います。
    public class Foo
    {
         // デリゲート型の定義
         public delegate void OnDamageDelegate(GameObject victim, float amount);
         // デリゲート型の変数に「event」を付けるだけ
         public event OnDamageDelegate onDamage;
         /* 略 */
    }
    Foo f = new Foo();
    // イベントにラムダ式を代入
    f.onDamage = (victim, amount) => print(victim.name + " got damage " + amount);
    // これは以下と同じ
    void PrintDamage(GameObject victim, float amount)
    {
         print(victim.name + " got damage " + amount);
    }
    f.onDamage += PrintDamage;

                                                                                     50/57
Learn C# in Unity
 ラムダ式
 ・コンテナの走査などにも使える
    Listクラスなどは条件判定用デリゲート(リストの要素1つを
    引数に取り、bool値を返すメソッド)を引数に取るFindメソッ
    ドなどをメンバーに持っています。こいつにラムダ式を渡すこ
    とも可能。
    // 例えばGameObjectのリストがあったとして、
    List<GameObject> gameObjects = new List<GameObject>(src);

    // 名前に「Enemy」を含むオブジェクトを探す
    GameObject go = gameObjects.Find(o => o.name.Contains("Enemy")); // returnは省略可能。いい。

    // gameObjectsリストのうち、Y座標が0未満のオブジェクトを全て削除
    gameObjects.RemoveAll(o => o.transform.x < 0); // returnは省略可能。いい。




                                                                                     51/57
Learn C# in Unity
 LINQ
 ・データベース向けに導入された機能だけど
    要するに大量のデータの中から必要な物だけ抽出したり、複数
    のリストをマージしたり、データの塊を整形するための機能で
    す。うまく使うとコードが非常にすっきりする。
    // 例えばこんなループは
    var renderers = GetComponentsInChildren<Renderer>();
    foreach (var r in renderers)
    {
         if (r != null && r.material != null)
              r.material.mainColor = Color.red;
    }

    // LINQだとこう書ける
    var materials = from r in GetComponentsInChildren<Renderer>()
                    where r != null && r.material != null
                    select r.material;
    foreach (Material m in materials)
         m.mainColor = Color.red;



    ループの中で分岐するより先にリストを整形してしまった方が
    間違いが起こりにくい。
                                                                    52/57
Learn C# in Unity
 LINQ
 ・匿名クラスと組み合わせるとさらに強力に
    注目したいメンバーだけ抽出した匿名クラスを作って返せば、
    余計なメンバーにアクセスしてバグることもなくなる。
    var materialSets = from Renderer r in FindObjectsOfType(typeof(Renderer))
                        where r.material != null && r.sharedMaterial != null
                        select new { r.material, r.sharedMaterial };
    foreach (var m in materialSets)
    {
         m.material.mainTexture = texture;
         m.sharedMaterial.mainTexture = texture;
    }




    ただし軽い処理ではないので使いどころに注意する必要がある




                                                                                53/57
Learn C# in Unity
 コルーチン
 ・C#のイテレーター構文を流用している
    C#のyield returnの本来の目的は、イテレータを気軽に実装す
    るためのものです。途中まで処理して一旦メソッドを抜けて、
    次またそこ(メソッドの途中)から再開できる、という特性が
    マルチタスク処理に向いていたので、Unityではコルーチンに流
    用されたようです。コルーチンはC#の機能ではありません。
    // 本来の使い方
    IEnumerator Count10()
    {
         for (int i = 10; i >= 0; --i)
              yield return i;
    }

    foreach (var i in Count1to10())
    {
         print(i); // 10, 9, 8, 7...
    }




                                         54/57
Learn C# in Unity
 コルーチン
 ・コンパイル時に暗黙のクラスが作られている
    yield文が含まれたメソッドは、実はコンパイル時に暗黙の列挙
    用クラスに変換されています。
    IEnumerator CountUp()     class CountUpEnumerator : IEnumerator
    {                         {
         print(1);                 int state = 0;
         yield return null;        bool MoveNext()
         print(2);                 {
         yield return null;             switch (state++)
         print(3);                      {
    }                                   case 0:
                                             print(1);
                                             return null;
                                        case 1:
                                             print(2);
                                             return null;
                                        case 2:
                                             print(3);
                                        }
                                   }
                              }
                              IEnumerator CountUp()
                              {
                                   return new CountUpEnumerator();
                              }

                                                                      55/57
Learn C# in Unity
 コルーチン
 ・コルーチンはMoveNextを毎フレーム呼んでるだけ
    おそらくGameObjectが今実行中のコルーチン(先ほどの列挙
    用クラスのインスタンス)のリストを持っていて、毎フレーム
    それらのMoveNext()メソッドを呼んでいるだけ。似たような
    処理を自分でも書ける。コルーチンごとに停止再開などを管理
    したい場合は、こっちのほうが柔軟性が高い。
    public class Foo : MonoBehaviour
    {
         List<IEnumerator> coroutines = new List<IEnumerator>();
         void Start()
         {
              coroutines.Add(MyCoroutine1());
              coroutines.Add(MyCoroutine2());
         }
         void Update()
         {
              coroutines.RemoveAll(c => !c.MoveNext());
         }
    }




                                                                   56/57
Learn C# in Unity
 おしまい!




           ご清聴ありがとうございました




                            57/57

Unityで覚えるC#

  • 1.
  • 2.
    Learn C# inUnity 初学者にありがちな混乱 ・言語の仕様 ・標準ライブラリの仕様 ・実行環境の仕様 この3つをごっちゃにして「何がわからないのかわからない」 状態になりがち。C#の文法の問題なのか、.Net Framework の問題なのか、Unityの問題なのか切り分けて考えよう。 今回は主にC#の文法の話と、よく使う(知らないとそもそも発 想に至らない).Net Frameworkの機能の話をします。 2/57
  • 3.
    Learn C# inUnity オブジェクト指向 再々(xN)入門 ・その前に、とても眠くなるそもそも論 そもそもプログラムとは「データの加工」です。つまり「デー タ」と「加工」という2つの要素から成り立っています。プロ グラムの用語に直すと「変数」と「関数」です。 オブジェクト指向以前は、データと加工処理が完全に別れてい ました。データ、つまり変数を、それを処理する関数に渡して 加工してもらい、受け取った結果をまた別の関数に渡して、と いう流れを延々繰り返します。 オブジェクト指向はその考え方を1段階抽象化して、関連する 変数と関数を1まとまりにしたオブジェクトという概念を導入 し、プログラムは各オブジェクトが協調動作して動くものだ、 と考えるようになりました。 3/57
  • 4.
    Learn C# inUnity オブジェクト指向 再々(xN)入門 ・オブジェクト指向以前・以後 オブジェクト指向という考え方をした場合としなかった場合、 プログラムの構造がどのようにかわるか、という例。 ■BEFORE ■AFTER int GetFileSize(string fname); class File bytes ReadFromFile(string fname, int size); { void WriteToFile(string fname, bytes data); bytes data; void CloseFile(string fname); void Open(string fname); void Close(); main() } { string fname = "hoge.txt"; main() int size = GetFileSize(fname); { bytes data = ReadFromFile(fname, size); File f = new File(); for (int i = 0; i < size; i++) f.Open("hoge.txt"); data[i] += 1; for (int i = 0; i < f.data.size; i++) WriteToFile(fname, data); f.data[i] += 1; CloseFile(fname); f.Close(); } } 4/57
  • 5.
    Learn C# inUnity オブジェクト指向 再々(xN)入門 ・一般的な意味での用語の定義(≠C#の用語) ■オブジェクト データや処理が1まとまりになった最小のプログラム片 ■クラス/構造体 オブジェクトの設計図 ■インスタンス メモリ上に配置されたクラス/構造体の実体 ■メンバー変数/メンバーフィールド/メンバープロパティ オブジェクトが持つ情報 ■メンバー関数/メンバーメソッド オブジェクトの動作を定義した関数 ■インターフェース オブジェクトがどんなメンバーを持っているか、どう振る舞 うかの定義。実は一番大事な概念。 5/57
  • 6.
    Learn C# inUnity オブジェクト指向 再々(xN)入門 ・プログラムの設計=インターフェースの設計  オブジェクト指向に限らず、プログラムの設計とはインターフェー スの設計に他なりません。オブジェクト指向ならそもそも何をオブ ジェクトに見立てて、それがどんなメンバーを持っているのか、つま りどんなインターフェースにするのかを決めることが最大の関心事で あり、一番難しい問題です。  ライブラリの場合だと、ユーザーのプログラムと、どんなデータを どんな関数を介してやりとりするのか決めること=インターフェース の設計です。  ゲームだってユーザーに対してどんな情報を見せるのか、あるいは 見せないのか、つまりユーザーインターフェースの設計がキモであ り、難しい部分です。  インターフェースが適切になっているか、常に意識しながらプログ ラミングしましょう。 6/57
  • 7.
    Learn C# inUnity オブジェクト指向 再々(xN)入門 ・Unityの代表的なオブジェクト ■Vector3 X,Y,Zの値を持つベクトル。位置情報などを表す。 ■Input ユーザーからの入力を一元管理している。 ■GameObject ゲーム空間(シーン)の中に存在するものは全てこれ。 ■Component GameObjectにアタッチすることで、GameObjectがどう 振る舞うか定義できる。 ■Transform Componentの一種で、ゲーム空間中での位置や向きなどを 管理する。全てのGameObjectに付いている。 7/57
  • 8.
    Learn C# inUnity オブジェクト指向 再々(xN)入門 ・継承 あるオブジェクトの機能を引き継ぎ、より具体的な機能を追加 した新しいオブジェクトを作る仕組み。 Component Transform Camera BoxCollider Collider SphereCollider MonoBehaviour PlayerBehaviour EnemyBehaviour 8/57
  • 9.
    Learn C# inUnity オブジェクト指向 再々(xN)入門 ・多態(ポリモーフィズム) あるオブジェクトが継承先で何をやっているか意識することな く、継承元のインターフェースでアクセスできる仕組み。 BoxCollider bool Raycast(ray) Collider { bool Raycast(ray) return 線と立方体の判定結果; } Ray Bool SphereCollider bool Raycast(ray) { return 線と球の判定結果; GameObject } 9/57
  • 10.
    Learn C# inUnity Unityの設計から学ぶオブジェクト指向 ・継承が全てではない! Unityのゲームシステムは、オブジェクト指向を一歩進めたコン ポーネント指向という考え方で作られています。 ■古いオブジェクト指向 GameCharacter PlayerCharacter GameObject MeshData mesh; Controller controller; EnemyCharacter AI ai; ■コンポーネント指向 MeshData MeshData GameObject GameObject Controller EnemyAI 10/57
  • 11.
    Learn C# inUnity C#におけるクラス ・クラスの定義の仕方 using UnityEngine; // 使うライブラリの宣言 // MonoBehaviourを継承したNewBehaviourScriptクラスの宣言 public class NewBehaviourScript : MonoBehaviour { // メンバーフィールドの宣言 public Vector3 velocity; // メンバーメソッドの定義 void Update() { transform.Translate(velocity * Time.deltaTime); } } メンバーの宣言に、publicを付けるとクラスの外からも見える ようになる。privateを付けるか何も付けないと、クラスの外か らは見えないようになる。内部で何をやっているかはできるだ け見せない方がよい。 11/57
  • 12.
    Learn C# inUnity C#におけるクラス ・クラスがメンバーに持てるもの ■フィールド 変数。通常あまり生のメンバー変数を公開するものではない が、Unityの場合パブリックフィールドにしないとインスペ クタに表示されない。 ■メソッド/コンストラクタ/デストラクタ 関数。コンストラクタとデストラクタは少し特殊な関数。 ■プロパティ アクセサの簡易表記法 ■インデクサー オブジェクトを配列のように扱えるようにするための機能 ■イベント 処理の一部を外部に委譲したり、何かのタイミングを通知す るための機能。 12/57
  • 13.
    Learn C# inUnity C#におけるクラス ・コンストラクタ オブジェクトの初期化処理を行うためのメソッド。ただしUnity はMonoBehaviourを継承したクラスがコンストラクタを実装 することを推奨していません。Awake/Startを使いましょう。 public class Foo : Bar { int i; // コンストラクタの定義。戻り値のない、クラス名とまったく同じ名前のメソッドにする。引数は取れる。 public Foo(int i) { this.i = i; } // 同じクラスの別のコンストラクタを呼ぶ書き方。 public Foo() : this(0) { } // 継承元のコンストラクタを明示的に呼び出す書き方。省略すると継承元の引数なしコンストラクタが呼ばれる。 public Foo(float f) : base(f) { } } Foo f = new Foo(1.0f); // 3つめのコンストラクタが呼ばれる 13/57
  • 14.
    Learn C# inUnity C#におけるクラス ・デストラクタ オブジェクトの破棄処理を行うためのメソッド。C#はGCが動 いていて明示的な破棄はしなくてよいので、通常意識する必要 はありません。 public class Foo { // デストラクタの定義。戻り値も引数もアクセス指定もない、クラス名の最初に~を付けた名前のメソッドにする。 ~Foo() { } } C#で破棄のタイミングを管理したいクラスはIDisposableを継 承して、Dispose()メソッドを実装し、using文と合わせて使 います。ただDispose()の実装は慣れてないと難しい…。 14/57
  • 15.
    Learn C# inUnity C#におけるクラス ・プロパティ:アクセサの簡易表記法 public class NewBehaviourScript : MonoBehaviour { // メンバープロパティの定義 public Vector3 velocity { get { return rigidbody.velocity; } set { rigidbody.velocity = value; } } // 上記は、以下の様なアクセサの定義を簡単にできるようにしたもの Vector3 GetVelocity() { return rigidbody.velocity; } void SetVelocity(Vector3 value) { rigidbody.velocity = value; } // プロパティへは普通のメンバーフィールドと同じようにアクセスできる void Start() { velocity = Vector3.zero; } } 15/57
  • 16.
    Learn C# inUnity C#におけるクラス ・プロパティをもっと便利に使う プロパティは実装を省略してもOK public class NewBehaviourScript : MonoBehaviour { public Vector3 velocity { get; set; } // 上記は、以下のような暗黙のメンバーフィールドを持ったプロパティが作られていると考えてOK Vector3 m_velocity; public Vector3 velocity get { return m_velocity; } set { m_velocity = value; } } } プロパティはget/setごとにアクセス指定を付けることも可能 public class NewBehaviourScript : MonoBehaviour { public Vector3 velocity { get; private set; } } 16/57
  • 17.
    Learn C# inUnity C#におけるクラス ・プロパティをもっともっと便利に使う 読み取りのみ許可するプロパティの場合は、setを省略できる public class NewBehaviourScript : MonoBehaviour { public Vector3 velocity { get { return rigidbody.velocity; } } } 17/57
  • 18.
    Learn C# inUnity C#におけるクラス ・インデクサー public class NewBehaviourScript : MonoBehaviour { int[] data; // インデクサーの定義 public int this[int index] { get { return data[index]; } set { data[index] = value; } } // インデクサーをメンバーに持つオブジェクトへは、配列のようにアクセスできる void Start() { NewBehaviourScript nbs = new NewBehaviourScript(); print(nbs[0]); } } 18/57
  • 19.
    Learn C# inUnity C#におけるクラス ・イベント 重要な機能だけど理解が難しいので後で詳しく説明します。 public class Foo { // デリゲート型の定義 public delegate void OnDamageDelegate(GameObject victim, float amount); // 定義した型のイベントを宣言 public event OnDamageDelegate onDamage; // ダメージ処理 float hp { get; private set; } public void DoDamage(float amount) { hp -= amount; onDamage(gameObject, amount); // onDamageに登録されてるメソッドを呼ぶ } } // 例えばダメージを喰らったタイミングでこのメソッドを呼んで欲しい場合 void PrintDamage(GameObject victim, float amount) { print(victim.name + " got damage " + amount); } // イベントにデリゲートを登録 Foo f = new Foo(); f.onDamage = PrintDamage; // ダメージ処理を呼ぶと、イベントの機能でPrintDamageメソッドが呼ばれる f.DoDamage(10); 19/57
  • 20.
    Learn C# inUnity C#におけるクラス ・クラスメンバーとインスタンスメンバー 一口にメンバーといっても、クラス自体に属して全てのインス タンスから同じ値を参照するクラスメンバーと、各インスタン ス毎にローカルな値を持つインスタンスメンバーがあります。 public class Foo : MonoBehaviour { // クラスフィールドの定義 public static int classField = 0; // インスタンスフィールドの定義 public int instanceField = 0; // クラスメソッドの定義 public static void Hoge() { print("Hoge! " + classField); } // インスタンスメソッドの定義 public void Mage() { print("Mage! " + classField); } } // クラスメンバーにはインスタンスがなくてもアクセスできる Foo.classField = 10; Foo.Hoge(); // "Hoge! 10" Foo foo = new Foo(); print(foo.instanceField); // 0 foo.Mage(); // "Mage! 10" 20/57
  • 21.
    Learn C# inUnity C#におけるクラス ・クラスメンバーしか持たないクラス 全てのメンバーがstaticなクラスは、クラス自体もstaticにす ることで、インスタンスを作ることを明示的に禁止できます。 public static class Foo { // クラスフィールドの定義 public static int classField = 0; // クラスメソッドの定義 public static void Hoge() { print("Hoge! " + classField); } // staticなクラスは静的コンストラクター(クラスコンストラクタ)で初期化する static Foo() { classField = 10; } } ちなみに静的コンストラクターは普通のクラスでも使えます。 21/57
  • 22.
    Learn C# inUnity C#におけるクラス ・クラス修飾子 sealed sealed修飾子を付けたクラスは継承できなくなります。 ・メンバーフィールド修飾子 const 実行中に値が変わらない(変わってはいけない)フィールド につけることで、値を変えようとするコードを書くとコンパ イル時にエラーにしてくれます。 readonly 意味的にはconstと同じ。constは値型(後述)にしか付け られませんが、readonlyは参照型にも付けられます。 22/57
  • 23.
    Learn C# inUnity C#におけるクラス ・メンバーメソッド修飾子 virtual 継承先で実装を書き換えて良いことを宣言します。 override 継承元で宣言されているvirtualメソッドを上書きして、独自 に実装し直すことを宣言します。 ちなみにoverrideしたメソッドから継承元の本来の処理を 呼び出すには「base.メソッド名()」と書きます。 extern 外部で実装されているメソッドを宣言する時に、DllImport 属性と組み合わせて使います。Unityではネイティブプラグ インのメソッドを呼びたい時に使います。 23/57
  • 24.
    Learn C# inUnity C#におけるクラス ・属性(アトリビュート) C#はアトリビュートという機能を使って、クラス自体やそのメ ンバーにメタデータを付けることができます。Unityでは自分で 作ったクラスにSerializable属性を付けることでインスペクタ に表示できるようにしたり、エディタの拡張で多用します。 [ExecuteInEditMode()] // この属性を付けると、エディタ上で非実行時もStartとかUpdateが呼ばれる public class NewBehaviourScript : MonoBehaviour { [System.Serializable()] // この属性を付けないと、Foo型のフィールドがインスペクタに表示されない public class Foo { public int hoge; } public Foo f; } 24/57
  • 25.
    Learn C# inUnity C#における構造体 ・構造体の定義の仕方 public struct Point { // メンバーフィールドの宣言 public int x, y; // メンバーメソッドの定義 public Point(int x, int y) { this.x = x; this.y = y; } } 基本的にはクラスの定義と同じだが、構造体はクラスのような 実装の継承はできない。インターフェース(後述)を継承して、 自分で実装を書くことはできる。 またクラスは参照型だが、構造体は値型であるという違いがあ る(後述)。 25/57
  • 26.
    Learn C# inUnity C#におけるインターフェース ・インターフェースの定義の仕方 interface ICollidable { // メンバーメソッドの宣言(定義はできない) bool IsCollide(GameObject other); } public class NewBehaviourScript : MonoBehaviour, ICollidable { // インターフェースの実装 public bool IsCollide(GameObject other) { if (gameObject.collider == null || other.collider == null) return false; return IsCollide(gameObject.collider, other.collider); } } インターフェースはそれを継承したオブジェクトがどんなメン バーメソッド、プロパティ、インデクサー、イベントを持つか 明言するためのもの。メンバーフィールドは宣言できず、実装 もできないため、単体では機能しない。 26/57
  • 27.
    Learn C# inUnity クラス or 構造体 ・参照型と値型 クラスは参照型、構造体は値型です。この2つの違いは、メ ソッドの引数や戻り値としてオブジェクトが渡される時の挙動 に影響します。 例えばオブジェクトFooからBarへ変数が渡される場合… ■参照型 ■値型 複製 Transform Vector3 Vector3 Foo Bar Foo Bar 27/57
  • 28.
    Learn C# inUnity クラス or 構造体 ・値型の参照渡し refキーワードやoutキーワードを使って、構造体のような値型 の参照を渡す方法もあります。 public class Foo { Vector3 myVector; public AddVectorTo(ref Vector3 target) { target += myVector; } } public class Bar { Foo foo = new Foo(); Vector3 myVector; public Bar() { foo(ref myVector); } } 28/57
  • 29.
    Learn C# inUnity クラス or 構造体 ・Null許容型 値型の変数でも、まだ初期化されていないことを明示したい場 合があります。そういう時はNull許容型を使います。 public class Foo { Vector3? myVector; // 変数の型の後ろに「?」を付けるとNull許容型になる public Vector3 GetVector() { if (myVector == null) myVector = Vector3.up; return myVector.Value; } } ただこれはデータベースとのやりとりを簡単にするために用意 されたものなので、Unityで使うことはあまりない。使わざるを えないような状況になったらおそらく設計が悪い。 29/57
  • 30.
    Learn C# inUnity クラス or 構造体 ・参照型 メリット オブジェクトのサイズに関わらず、受け渡しのコストが一定 受け渡した先で値を変更すると、受け渡した元の値も変わる デメリット GCで処理する際のコストが高い ・値型 メリット GCで処理する際のコストが低い 受け渡し先で値を変更しても、受け渡し元は影響を受けない デメリット オブジェクトのサイズに比例して受け渡しのコストが増える ボックス化によるパフォーマンスの悪化がわかりづらい 30/57
  • 31.
    Learn C# inUnity クラス or 構造体 ・ボックス化・ボックス化解除 値型をobject型に変換することをボックス化、object型を元の 値型へキャストし直すことをボックス化解除といい、どちらも とてもコストが高い操作なので極力避けるようにします(ボッ クス化されるとインスタンスのサイズも増えます)。 object o = 1; // ボックス化 int i = (int)o; // ボックス化解除 // ArrayListは全ての要素をobject型に変換して格納するコレクションクラス ArrayList list = new ArrayList(); list.Add("hoge"); // だから文字列も数値も同じArrayListに格納できるけど… list.Add(100); // ←ここでボックス化が発生!気づきにくい! 後述するジェネリックという機能を使えば不要なボックス化を 減らせるので、積極的に使いましょう。 31/57
  • 32.
    Learn C# inUnity クラス or 構造体 ・どちらを選ぶべきか 基本はクラスを使い、以下の条件を全て満たす場合のみ構造体 にします(とMicrosoftのドキュメントに書いてある…)。 1.整数や浮動小数点数のような論理的に単一な値を表す 2.インスタンスのサイズが16バイト未満である 3.継承により振る舞いを変更する必要がない 4.頻繁にボックス化する必要がない 例えば「二次元座標を表すPointオブジェクト」のようなもの は構造体に向いています。 32/57
  • 33.
    Learn C# inUnity C#の便利な機能 ・Enum:一連の定数を1つにまとめた型 public class Foo { // 関連のある定数群をこう書くよりも… const int DamageTypeSlash = 1; const int DamageTypeBlunt = 2; const int DamageTypeExplosion = 3; // enumにしたほうが見通しが良い enum DamageType { Slash = 1, // 値を明言することもできる。明言しないと前の値+1になる。基底は0。 Blunt, // 直前の値が1なのでこいつは2。 Explosion, } DamageType damageType; // DamageType型のどれか1つの値を取るメンバーフィールド、という意味 // Enum型の各値には、「型名.名前」という形式でアクセスする。 public Foo() { damageType = DamageType.Blunt; } // 整数へのキャストは保証されているが、あまり感心できる使い方ではない… int GetDamageTypeID() { return (int)damageType; } } 33/57
  • 34.
    Learn C# inUnity C#の便利な機能 ・namespace:名前空間 namespace MyClasses //関連するオブジェクトを1つの名前空間に収めることで検索性を上げたり名前の衝突を防ぐ { public class Foo { } public class Bar { } } // 名前空間の中の要素にはこのように書いてアクセス MyClasses.Foo f; // 別のファイルでも同じ名前空間を宣言することができる namespace MyClasses { public class Hoge { } } // ファイルの先頭でusingディレクティブを使うと、アクセス時の名前空間を省略できる using MyClasses; Foo f; ファイルの先頭でusing UnityEngine;とかやってるのはコレ 34/57
  • 35.
    Learn C# inUnity C#の便利な機能 ・キャスト、型情報の利用 キャスト(型の明示的な変換)はCの書き方と似ています。 またis演算子、as演算子という型チェックの簡単な書き方があ ります。 // 普通のキャストは「(目的の型)」で行う Collider c = (Collider)gameObject.AddComponent("BoxCollider"); // is演算子でインスタンスの型が指定したものかどうかチェックできる if (c is BoxCollider) boxCollider = c as BoxCollider; // インスタンスを指定した型にキャスト、できなかったらnull // 型情報を取り出して自分で判定することも if (c.GetType() == typeof(BoxCollider)) print("OK"); // ジェネリックを使えばそもそもキャストはいらなかったり c = gameObject.AddComponent<BoxCollider>(); 35/57
  • 36.
    Learn C# inUnity C#の便利な機能 ・var 型が明確なローカル変数は、型名をvarで代用することが可能 public class Foo { void Hoge() { // これは Transform t = transform; // こう書いてもいい var t = transform; // その1文ではvarが実際何の型になるのかわからない書き方はNG var i; i = 0; } public var i = 0; // これもNG。varが使えるのはローカル変数のみ。 } 36/57
  • 37.
    Learn C# inUnity C#の便利な機能 ・パーシャルクラス・パーシャルメソッド 1つのクラスの定義を複数のファイルに分けて書くことが可能 public partial class Foo public partial class Foo { { void Hoge() void Mage() { { } } } } // もちろんクラスFooはHoge、Mage両方のメソッドをメンバに持っている Foo f = new Foo(); f.Hoge(); f.Mage(); パーシャルクラスのメソッドにpartial修飾子を付けると、実装 を別のファイルで定義しても、定義しなくても(!)いい。 public partial class Foo { partial void Hoge(); } 37/57
  • 38.
    Learn C# inUnity C#の便利な機能 ・拡張メソッド staticメソッドをあたかもインスタンスメソッドのように呼べ るようにする機能です。クラスにメソッドを追加しているよう に見えるけど、継承ではないので既存のメソッドの振る舞いを 変更したりはできないし、多用すると混乱の元。 // 本来は以下のようにしか書けないが Instantiate(prefabObject); // 拡張メソッドの機能を使うと static class GameObjectExtension { // staticメソッドの第一引数の前にthisを付けると拡張メソッドになる public static Object Instantiate(this GameObject prefab) { return Instantiate(prefab); } } // あたかもGameObjectクラスがInstantiate()というインスタンスメソッドを持っているかのように書ける prefabObject.Instantiate(); 38/57
  • 39.
    Learn C# inUnity C#の制御構文 ・if文、for文、while・do~while文 他言語と比べて別段取り立てるような差異はありません。 contiue、break、gotoなども使えます。 ・switch文 caseに文字列が使えたり、基本的にはbreakを省略できなかっ たり、他言語と比べて少し特殊です。 switch (hoge) { case 0: // case文の間に処理が挟まらない場合だけフォールスルーできる case 1: // caseを繋げて書いてあるので、hogeの値が0か1の場合、以下のDoSomething()が呼ばれる DoSomething(); break; // これは省略できない(Cなどでは省略すると処理が下に繋がる) case 2: DoSomething2(); goto case 0; // 何か処理をした後どうしても他のcaseに進みたい場合はgotoを使う default: // 上記のどのcaseにも当てはまらなかった場合、という書き方 DoNothing(); break; } 39/57
  • 40.
    Learn C# inUnity C#の制御構文 ・foreach文 配列やコレクションなどの全ての要素に対して繰り返し処理を 行います。 foreach (AudioSource a in GetComponents<AudioSource>()) { a.Stop(); } 後述するLinqと組み合わせるとfor文がほとんど要らなくなる。 // アタッチされているAoudioSouceのうち、再生中の物に対してだけStop()を呼び出す、という例 foreach (var a in from a in GetComponents<AudioSource>() where a.isPlaying select a) { a.Stop(); } ループ文の中でif文を使って分岐させると、ループ処理自体が長 くなってしまうので、可能ならLinqであらかじめリストを整形 してからforeach文で処理すると見通しが良くなります。 40/57
  • 41.
    Learn C# inUnity C#の制御構文 ・try~catch~finally文 C#での例外処理を制御する構文だけど、Unityが例外をほとん ど意識しなくて良い設計になっているのであまり使いません。 StreamReader sr = null; try { sr = File.OpenText(path); string s = ""; while ((s = sr.ReadLine()) != null) print(s); } catch (FileNotFoundException e) { print("File Not Found!"); } finally { sr.Dispose(); } 41/57
  • 42.
    Learn C# inUnity C#の制御構文 ・using文 名前空間の省略や型の別名定義にもusingというキーワードを 使うので混乱しやすいけど、メソッド内に出てくるusing文は Dispose()の呼び出しを保証するもの。UnityEngineには IDisposableを実装したクラスがないのであまり使わない…。 ■using版 ■非using版 using (StreamReader sr = File.OpenText(path)) StreamReader sr = null; { try string s = ""; { while ((s = sr.ReadLine()) != null) sr = File.OpenText(path); print(s); string s = ""; } while ((s = sr.ReadLine()) != null) print(s); } finally { sr.Dispose(); } 42/57
  • 43.
    Learn C# inUnity ジェネリック ・ジェネリックとは 不要なキャストを減らし、コンパイル時に型チェックができ、 コードが最適化されパフォーマンスが良くなる、大変素晴らし い機能です。大変素晴らしいです。 BoxCollider c; // 非ジェネリック版(長い。しなくてもいいキャストは見苦しい…) c = (BoxCollider)gameObject.AddComponent(typeof(BoxCollider)); c = gameObject.AddComponent(typeof(BoxCollider)) as BoxCollider; // ジェネリック版 c = gameObject.AddComponent<BoxCollider>(); しかしUnityはジェネリック版のメソッドをあまり用意していな い…ぐぬぬ…。 43/57
  • 44.
    Learn C# inUnity ジェネリック ・自分でジェネリック対応する // 例えばこんなメソッドをジェネリック版にする場合 Object FindObjectOf(Type type) { return FindObjectOfType(type); } // 型パラメータの名前をTとしてあるのは慣習(スタンダードなルールがある) T FindObjectOf<T>() where T: Component // 型パラメータに制限を課すことができる(任意) { return (T)FindObjectOfType(typeof(T)); } // 上記ジェネリックメソッドの呼び出し方 var renderer = FindObjectOf<Renderer>(); // 型が自明なのでvarが使えるしキャストもない! // 型パラメーターに制限をかけてあるので以下はコンパイル時エラー。素晴らしい! int i = FindObjectOf<int>(); // intはComponentを継承していない 44/57
  • 45.
    Learn C# inUnity ジェネリック ・非ジェネリックコンテナは使うな ボックス化のところで説明したように、ArrayListなどの非ジェ ネリックコンテナは、知らないうちにパフォーマンスが悪化し たり、本来必要ないキャストが増えたりしてよろしくありませ ん(キャストが増えるとバグが増える)。 明確な理由がない場合は、System.Collections.Generic名前 空間にある、ジェネリック版のコンテナを使いましょう。 using System.Collections.Generic; List<int> list; list.Add(100); // ボックス化無し! list.Add("hoge"); // コンパイル時エラー! int i = list[0]; // もちろんボックス化解除も無し! 45/57
  • 46.
    Learn C# inUnity デリゲート ・メソッドを参照できる型 メソッドの参照を保存しておいて後から呼び出したり、複数の メソッドを1つの変数に入れておいてまとめて呼び出せるよう にする機能です。 // デリゲート型の定義 delegate void SomeDelegate(float f); // 定義した型の変数を宣言 SomeDelegate someDelegate; // 例えばこんなメソッドを先ほどの変数へ代入することができる void Hoge(float value) { } // 型さえあってれば引数の名前は同じじゃなくてもいい someDelegate = Hoge; // インスタンスメソッドも代入できる public class Foo { public void Bar(float amount) { } } Foo f = new Foo(); someDelegate += f.Bar; // 「+=」で追加代入できる // デリゲート変数に格納されているメソッドを呼ぶ(Hogeとf.Barが順番に呼ばれる) someDelegate(1.0f); 46/57
  • 47.
    Learn C# inUnity デリゲート ・元々用意されているデリゲート 引数も取らず戻り値もないデリゲートとか、引数をひとつ取り bool値を返すデリゲートなどは頻出するので、System以下に あらかじめ用意されています(自分で定義しなくてOK)。 System.Func<TResult> // TResult Func() System.Predicate<T> // bool Predicate(T value) System.Action<T> // void Action(T value) System.Action<T1, T2> // void Action(T1 p1, T2 p2) 47/57
  • 48.
    Learn C# inUnity イベント ・デリゲートを外から実行できなくする 例えばダメージを喰らったタイミングをデリゲートを使って他 のオブジェクトに通知したい場合、デリゲートむき出しだと外 から実行できてしまいます。これは困る。 public class Foo { // デリゲート型の定義 public delegate void OnDamageDelegate(GameObject victim, float amount); // 定義したデリゲート型の変数を宣言(privateにしちゃうと登録すらできなくなっちゃうので意味なし) public OnDamageDelegate onDamage; // ダメージ処理 float hp { get; private set; } public void DoDamage(float amount) { hp -= amount; onDamage(gameObject, amount); } } Foo f = new Foo(); f.onDamage(1.0f); // 実際にはダメージを喰らってないのにonDamageを直接呼べてしまう そういう時はイベントという機能を使いましょう 48/57
  • 49.
    Learn C# inUnity イベント ・イベントは外からは追加と削除だけできる デリゲートむき出しではなくイベントにすると、追加と削除は クラス外からでもできるけど、実行はクラス内でしかできない ようになります。これで変なタイミングで呼ばれなくなる。 public class Foo { // デリゲート型の定義 public delegate void OnDamageDelegate(GameObject victim, float amount); // デリゲート型の変数に「event」を付けるだけ public event OnDamageDelegate onDamage; // ダメージ処理 float hp { get; private set; } public void DoDamage(float amount) { hp -= amount; onDamage(gameObject, amount); } } Foo f = new Foo(); f.onDamage(1.0f); // コンパイルエラー! f.onDamage += PrintDamage; // 追加は問題なく行える 49/57
  • 50.
    Learn C# inUnity ラムダ式 ・名前の無いメソッド イベントに登録するメソッドをいちいち全部メンバーメソッド として定義していくと、クラスが無駄に巨大化しがちです。 そこでラムダ式という、名前がない、何かに代入した状態でな いと呼び出せないメソッドを定義できる機能を使います。 public class Foo { // デリゲート型の定義 public delegate void OnDamageDelegate(GameObject victim, float amount); // デリゲート型の変数に「event」を付けるだけ public event OnDamageDelegate onDamage; /* 略 */ } Foo f = new Foo(); // イベントにラムダ式を代入 f.onDamage = (victim, amount) => print(victim.name + " got damage " + amount); // これは以下と同じ void PrintDamage(GameObject victim, float amount) { print(victim.name + " got damage " + amount); } f.onDamage += PrintDamage; 50/57
  • 51.
    Learn C# inUnity ラムダ式 ・コンテナの走査などにも使える Listクラスなどは条件判定用デリゲート(リストの要素1つを 引数に取り、bool値を返すメソッド)を引数に取るFindメソッ ドなどをメンバーに持っています。こいつにラムダ式を渡すこ とも可能。 // 例えばGameObjectのリストがあったとして、 List<GameObject> gameObjects = new List<GameObject>(src); // 名前に「Enemy」を含むオブジェクトを探す GameObject go = gameObjects.Find(o => o.name.Contains("Enemy")); // returnは省略可能。いい。 // gameObjectsリストのうち、Y座標が0未満のオブジェクトを全て削除 gameObjects.RemoveAll(o => o.transform.x < 0); // returnは省略可能。いい。 51/57
  • 52.
    Learn C# inUnity LINQ ・データベース向けに導入された機能だけど 要するに大量のデータの中から必要な物だけ抽出したり、複数 のリストをマージしたり、データの塊を整形するための機能で す。うまく使うとコードが非常にすっきりする。 // 例えばこんなループは var renderers = GetComponentsInChildren<Renderer>(); foreach (var r in renderers) { if (r != null && r.material != null) r.material.mainColor = Color.red; } // LINQだとこう書ける var materials = from r in GetComponentsInChildren<Renderer>() where r != null && r.material != null select r.material; foreach (Material m in materials) m.mainColor = Color.red; ループの中で分岐するより先にリストを整形してしまった方が 間違いが起こりにくい。 52/57
  • 53.
    Learn C# inUnity LINQ ・匿名クラスと組み合わせるとさらに強力に 注目したいメンバーだけ抽出した匿名クラスを作って返せば、 余計なメンバーにアクセスしてバグることもなくなる。 var materialSets = from Renderer r in FindObjectsOfType(typeof(Renderer)) where r.material != null && r.sharedMaterial != null select new { r.material, r.sharedMaterial }; foreach (var m in materialSets) { m.material.mainTexture = texture; m.sharedMaterial.mainTexture = texture; } ただし軽い処理ではないので使いどころに注意する必要がある 53/57
  • 54.
    Learn C# inUnity コルーチン ・C#のイテレーター構文を流用している C#のyield returnの本来の目的は、イテレータを気軽に実装す るためのものです。途中まで処理して一旦メソッドを抜けて、 次またそこ(メソッドの途中)から再開できる、という特性が マルチタスク処理に向いていたので、Unityではコルーチンに流 用されたようです。コルーチンはC#の機能ではありません。 // 本来の使い方 IEnumerator Count10() { for (int i = 10; i >= 0; --i) yield return i; } foreach (var i in Count1to10()) { print(i); // 10, 9, 8, 7... } 54/57
  • 55.
    Learn C# inUnity コルーチン ・コンパイル時に暗黙のクラスが作られている yield文が含まれたメソッドは、実はコンパイル時に暗黙の列挙 用クラスに変換されています。 IEnumerator CountUp() class CountUpEnumerator : IEnumerator { { print(1); int state = 0; yield return null; bool MoveNext() print(2); { yield return null; switch (state++) print(3); { } case 0: print(1); return null; case 1: print(2); return null; case 2: print(3); } } } IEnumerator CountUp() { return new CountUpEnumerator(); } 55/57
  • 56.
    Learn C# inUnity コルーチン ・コルーチンはMoveNextを毎フレーム呼んでるだけ おそらくGameObjectが今実行中のコルーチン(先ほどの列挙 用クラスのインスタンス)のリストを持っていて、毎フレーム それらのMoveNext()メソッドを呼んでいるだけ。似たような 処理を自分でも書ける。コルーチンごとに停止再開などを管理 したい場合は、こっちのほうが柔軟性が高い。 public class Foo : MonoBehaviour { List<IEnumerator> coroutines = new List<IEnumerator>(); void Start() { coroutines.Add(MyCoroutine1()); coroutines.Add(MyCoroutine2()); } void Update() { coroutines.RemoveAll(c => !c.MoveNext()); } } 56/57
  • 57.
    Learn C# inUnity おしまい! ご清聴ありがとうございました 57/57