いろいろガンガンいこうぜ。体も大事に

普段はJavaでAndroidアプリ開発しているプログラマーのブログです。

GetComponentメソッドについて

GetComponentメソッドについて調べたり,まとめてみたりしました。 (Unity4.3.2f1で試しています。)

3個のオーバーロード

Componentクラスのインスタンスメソッド,GetComponent。

GetComponentのスクリプトリファレンス

このGetComponentメソッドは3個のオーバーロードを持っています。

Component GetComponent(Type type);
T GetComponent<T>();
Component GetComponent(string type);

スクリプトリファレンスによるとパフォーマンスの観点から見て,文字列型を引数にとるオーバーロードより他の物を使った方がいいようです。

またGeneric Functionsにはジェネリクスを用いると,キャストをする手間とタイプする文字数が減るよねという記述があります。

基本的には,ジェネリクス版を使えばいいと思います。が,しかしジェネリクスのオーバーロードではできず,Type型を引数にとるオーバーロードではできるものがありました。

型パラメータにスーパークラスを与えてGetComponent

MonoBehaviourクラスを継承したBaseクラス。そのBaseクラスを継承したDerived0クラスを用意します。

using UnityEngine;

public class Base : MonoBehaviour { }
public class Derived0 : Base { }

そして,Baseクラスを型パラメータに指定してGetComponentします。 そのGetComponentしたコンポーネントのクラス名を表示するクラスを作ります。

using UnityEngine;

public class GetComponentSample : MonoBehaviour
{
    void Start ()
    {
        Base component = GetComponent<Base> ();
        Debug.Log (component.GetType ().Name); 
    }
}

次のようにシーン上のGameObjectにDerived0とGetComponentSampleコンポーネントをつけます。

画像1

これを実行すると,Derived0とデバックログが出力されます。 サブクラスのコンポーネントが付与されているGameObjectに対して,スーパークラスの型を指定して,GetCompoenentでコンポーネントを取得することができました。

次のように,スーパークラスを抽象クラスにすることも可能なようです。

using UnityEngine;

public abstract class Base : MonoBehaviour { }

また,次のようにDerive0とは違うサブクラスを作って,

public class Derived1 : Base { }

それを次のようにGetComponentSampleと一緒にGameObjectに付与して,

画像2

実行した場合,こちらはDerived1と出力されました。

型パラメータにスーパークラスを指定したGetComponentメソッドの呼び出しで,サブクラスのコンポーネントを取得することができることがわかりました。

複数のコンポーネントが付与されてる場合のGetComponent

using UnityEngine;

public class StringContainer : MonoBehaviour
{
    public string value;
}
using UnityEngine;

public class GetComponentSample : MonoBehaviour
{
    void Start ()
    {
        StringContainer component = GetComponent<StringContainer> ();
        Debug.Log (component.value);
    }
}

次の画像のように一つのGameObjectにStringContainerクラスを3個,GetComponentSampleを1個付与します。

画像3

これを実行すると, デバックログにはvalue0と表示されました。 インスペクターのMoveUpなどを用いて,コンポーネントの順番を変えます。

画像4

上の画像のような状態で実行するとデバックログにはvalue1と表示されました。 また順番を変えて以下のようにします。

画像5

この状態で実行するとデバックログにはvalue2と表示されました。

同じクラスのコンポーネントが複数付与されている場合,インスペクターにおける一番上のインスタンス(という言い方でいいのでしょうか?)が取得できるようです。

前の節で作ったBaseクラス,Derived0クラスそしてDerived1クラスを使ってみます。ふたたびBaseクラスでGetComponentして,クラス名を表示します。

using UnityEngine;

public class GetComponentSample : MonoBehaviour
{
    void Start ()
    {
        Base component = GetComponent<Base> ();
        Debug.Log (component.GetType ().Name); 
    }
}

画像6

上の画像のように, GetComponentSampleクラス, Derived0クラス, Derived1クラス の順番でコンポーネントを付与します。

これを実行するとデバックログに,Derived0と表示されました。

順番を変えます。

画像7

上の画像の用に GetComponentSampleクラス, Derived1クラス, Derived0クラス の順番でコンポーネントを付与したら

これを実行するとデバックログに,Derived1と表示されました。

GetComponentでコンポーネントを取得する際,もし指定した型パラメータのクラスで取得出来るコンポーネントが複数ある場合は,インスペクター上で一番上の物が取得できるようです。

GetComponentsについて

複数のコンポーネントを取得するGetComponentsというメソッドがあります。

取得しようとするコンポーネントがGameObjectについていない場合,GetComponentではnullが帰ってきますが,GetComponentsでは空の配列が帰ってくるようです。

前節のStringContainerクラスとGetComponentsメソッドを用いたサンプルを作りました。

using UnityEngine;

public class GetComponentSample : MonoBehaviour
{
    void Start ()
    {
        StringContainer[] stringContainers = GetComponents<StringContainer> ();
        foreach (var stringContainer in stringContainers) {
            Debug.Log (stringContainer.value);
        }
    }
}

画像8

上の画像ようにGetComponentSampleを1個とStringContainerクラスのコンポーネントを複数付けて文字列valueを画像の用に設定し,実行すると

value0
value1
value2
value3

という順番で表示されました。

順番を変えます。

画像9

上の画像のように,順番を変えると実行結果は

value3
value2
value1
value0

となりました。

GetComponentsで取得した配列の要素の順番は,インスペクターで表示される順番と同じ順番のようです。

インタフェースでGetComponent

抽象クラスを指定してGetComponentをすることができたので,インタフェース型を指定してGetComponentをすることもできるのではないかと思いました。

結論から言うと,インタフェース型を指定してGetcomponentは

  • Component GetComponent(Type type)はできる
  • T GetComponent()はできない

ようです。

public interface ISample { }
using UnityEngine;
public class Implemented0 : MonoBehaviour, ISample { }
using UnityEngine;

public class GetComponentSample : MonoBehaviour
{
    void Start ()
    {
        // ISample component = GetComponent<ISample> (); これはコンパイルエラー
        ISample component = GetComponent(typeof(ISample)) as ISample;
        Debug.Log (component.GetType ().Name);
    }
}

このGetCompoentSampleクラスとImplemented0クラスを下の画像のようにGemeObjectに付与します。

画像10

実行するとデバックログにImplemented0と出力されます。

Type型を引数にとるGetComponentのオーバーロードでは,インタフェース型を指定してコンポーネントインスタンスを取得することができました。

ジェネリクスの方のオーバーロードのGetComponentではインタフェース型を指定すると,コンパイルエラーとなってしまいました。

T GetComponent<T>()の制約について

インタフェースで T GetComponent<T>() しようとするとコンパイルエラーになります。この時,次のようなメッセージが表示されます。

The type `ISample' cannot be used as type parameter `T' in the generic type or method `UnityEngine.Component.GetComponent<T>()'. There is no implicit reference conversion from `ISample' to `UnityEngine.Component' (CS0311) (Assembly-CSharp)

Component型への暗黙の参照変換ができないから,型パラメータTとしてISampleはダメだよ。ってことでしょうか。

System.Reflection名前空間のクラスを使って, T GetComponent<T>() の,型パラメータの制約を見てみました。

先に結論を言うと,T GetComponent<T> ()

  public T GetComponent<T> () where T : Component

となっているのではないかと思います。

void Start ()
{
    MethodInfo methodInfo = typeof(GameObject).GetMethod ("GetComponent", new Type[]{});
    Debug.Log ("Method Name : " + methodInfo.Name);
    // Method Name GetComponent

    Debug.Log ("Return Type : " + methodInfo.ReturnType.Name);
    // Return Type : T

    ParameterInfo[] parameterInfos = methodInfo.GetParameters ();
    Debug.Log ("parameter count : " + parameterInfos.Length);
    // parameter count : 0


    Type[] genericArgumentTypes = methodInfo.GetGenericArguments ();
    Debug.Log ("generic argument type count : " + genericArgumentTypes.Length);
    // generic argument type count : 1

    Type genericArgumentType = genericArgumentTypes [0];
    Debug.Log ("generic argument type : " + genericArgumentType.Name);
    // generic argument type : T


    Type[] genericParameterConstraints = genericArgumentType.GetGenericParameterConstraints ();
    Debug.Log ("generic parameter constraints count : " + genericParameterConstraints.Length);
    // generic parameter constraints count : 1

    Type genericParameterConstraint = genericParameterConstraints [0];
    Debug.Log ("type constraint : " + genericParameterConstraint.Name);
    // type constraint : Component
}

Debug.Logの下のコメント行は,表示されたデバックログです。

デバックログの出力内容から見るに,T GetComponent <T> () のT 型パラメータとして指定出来るのは,Component 型もしくはそのサブクラスである必要があるようです。

そのため,T GetComponent <T> ()のTにインターフェースを指定した場合,Component型でもComponent型のサブクラスでもないため,コンパイルエラーになるようです。

まとめとか

よく使うGetComponentメソッド。(あとGetComponentsメソッド) 型パラメータにスーパークラスや抽象クラスを指定するのは,いくつか使い方がありそうですね。インターフェースはどうでしょう。もしかした。

本当は全コンポーネントや,一つのGameObjectについている,全ての自作コンポーネントを取得する方法を調べていたら,細かいところが気になって書いたポスト。 次は,GetComponents<Component>GetComponents<MonoBehavoiour>コンポーネント全取得します。

関連リンク

  • GetComponentのスクリプトリファレンス

http://docs.unity3d.com/Documentation/ScriptReference/Component.GetComponent.html

http://docs.unity3d.com/Documentation/Manual/GenericFunctions.html

  • 暗黙の参照変換

http://msdn.microsoft.com/ja-jp/library/aa691284(v=vs.71).aspx

  • 型パラメータ制約

http://msdn.microsoft.com/ja-jp/library/d5x73970(v=vs.90).aspx