UnityでGetComponent<>()メソッドをC#風に記述した場合のサンプル(シミュレート)

2022年8月9日

エンジンの内部の動作を理解することは、Unityの仕組みの理解を深める近道です

シミュレートをしてみよう

Unityでプログラムを制作する場合、Unityエンジンに用意されている各種メソッドを上手に使ってコードを組み上げていきます。

何気なく使うのではなく、いつもどのように動作しているのか?を考えながら進めることで、C#プログラミングの学習にも役立つでしょう

シミュレートしたコード(最終)

最初に完成形を示します
詳細は、以降解説していますので参考にしてください

コードだけで理解ができるのであれば、素晴らしいスキルの持ち主です

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GetComponentSample : MonoBehaviour
{
    void Start()
    {
        /*
        GetComponent<AudioSource>();
        gameObject.AddComponent<Rigidbody>();
        */

        GameObject car = new GameObject();
        car.AddComponent<Rigidbody>();
        car.AddComponent<AudioSource>();

        var carAudio = car.GetComponent<AudioSource>();
    }
}

class GameObject
{
    List<Component> components = new List<Component>();

    public GameObject()
    {
        AddComponent<Transform>();
    }

    public T AddComponent<T>() where T : Component, new()
    {
        T component = new T();
        components.Add(component);
        return component;
    }

    public T GetComponent<T>() where T : Component
    {
        return components.Find(component => component is T) as T;
    }
}

class Component { }
class Transform : Component { }
class Rigidbody : Component { }
class AudioSource : Component { }

コードの説明

コンポーネントを継承している個別のコンポーネントの定義

最後の行のあたりに記述しています
今回は、継承の形だけ欲しかったので、メンバーは入れていません
さらに深くシミュレートするのであれば、たとえばTransformクラスには、positionプロパティを追加したりするとよりエンジンに近づけるでしょう

AudioSourceクラスにPlay()メソッドを追加すると、Start()メソッドの最終行にcarAudio.Play()メソッド呼び出しの追加ができるようになるでしょう

class Component { }
class Transform : Component { }
class Rigidbody : Component { }
class AudioSource : Component { }

AddComponent<>

public T AddComponent<T>() where T : Component, new()

ジェネリックメソッドと呼ばれています
Tは、型が入る仮引数名になります(型パラメータ)
特にTじゃないと動作しないわけではありませんが、慣用としてTが使われます。引数を2つ取る場合、2つ目はUを使うのも慣用です(Addcomponent<T, U>)とすると2つの型パラメータが引数で扱えます

where T : Componentは、Tの型はComponentを継承している必要があるという制約を示しています
where T : new()は、new制約と呼ばれ、ジェネリック クラスかメソッド宣言内の型引数にパブリックでパラメーターなしのコンストラクターが必要であることを指定します

インスタンスの作成ですが、Tに、たとえばRigidbodyを当てはめてみるとわかりやすいですね
Rigidbody component = new Rigidbody(); となりますね。

T component = new T();

componentsはComponent型のリストです。RigidbodyはComponent型を継承しているので代入できます

components.Add(component);

メソッドブロックの最初の行で作成したインスタンスを戻り値として返しています
呼び出し元では、使わないかもしれませんが、使うこともできますね

return component;

GetComponent<>

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

AddComponentの説明と同じです

return components.Find(component => component is T) as T;

componentsリストから、与えられた型パラメータと一致するものを探して戻り値として返します
componnet is Tで一致していればTrueが返ります
as TでT型にキャストしています

このコードでは、ListのFindメソッドを使っていますが、同じことをforeach文で実現すると次のようになります

foreach (var component in components)
{
    if (component is T)
    {
        return component as T;
    }
}
return null;

別の記述方法

次のような記述もできます

return (T)components.Find(component => component.GetType() == typeof(T));

(T)は as Tでも同じです

return components.Find(component => component.GetType() == typeof(T)) as T;

UnityEngine名前空間での実際のコード

AddComponent<>

旧バージョンのUnityでは、ジェネリックを使ったコードになっていませんでした
AddComponent(Rigidbody)といった書き方になっていましたが、今は、ジェネリックのAddComponent<Rigidbody>()が推奨されています

コードには、その時に名残があります
AddComponent<>は、内部ではAddComponent()を呼び出していますね

このAddComponent()は戻り値の型として、Component型を返しています
なので、呼び出し元では、戻り値をキャストする必要がありました
具体的には、

Ridigbody rb = (Ridigbdoy)GetComponent(Rigidbody);

または、

Ridigbody rb = GetComponent(Rigidbody) as Rigidbody;

内部で旧コードのAddComponent()を呼び出しているのがわかる実際のコード

//
// 概要:
//     Adds a component class of type componentType to the GameObject. C# Users can use a generic version.
//     (訳)GameObjectにcomponentType型のコンポーネントクラスを追加する。C#ユーザはジェネリック版を使用することができる。
//
public Component AddComponent(Type componentType)
{
    return Internal_AddComponentWithType(componentType);
}

public T AddComponent<T>() where T : Component
{
    return AddComponent(typeof(T)) as T;
}

GetComponent<>

GetComponent<>メソッドは、GameObjectクラスとComponentクラスの両方に同じコードが定義されています
内部では、同じリストを参照していると考えられます
スクリプトがアタッチされているゲームオブジェクトが同じであれば、同じリスト内になるので、Componentクラスのメソッドが呼び出されます(GetComponent・・・)と始めれらます
別のゲームオブジェクトのコンポーネントを取得したい場合、(ゲームオブジェクトのインスタンス.GetComponent・・・)となります

GameObjectクラスのGetComponent<>

public unsafe T GetComponent<T>()
{
    CastHelper<T> castHelper = default(CastHelper<T>);
    GetComponentFastPath(typeof(T), new IntPtr(&castHelper.onePointerFurtherThanT));
    return castHelper.t;
}

ComponentクラスのGetComponent<>

public unsafe T GetComponent<T>()
{
    CastHelper<T> castHelper = default(CastHelper<T>);
    GetComponentFastPath(typeof(T), new IntPtr(&castHelper.onePointerFurtherThanT));
    return castHelper.t;
}

クラス図

C#,Unity

Posted by hidepon