Unity学習#14では、リストの要素数でサイズが変わるリストの作成手順について書きました。この記事は、その前段として、リストの各階層にアクセスするために必要な GameObject、Component、Transform の関係性についてまとめます。
先に結論を書き、その後で経緯をまとめます。
- GameObject、Component、Transform の関係
- MonoBehaviour からのアクセス方法
- GameObject、Component、Transform の関係(クラス図)
- Unity 画面との関係
GameObject、Component、Transform の関係
先に結論を書くと、Scene は GameObject の一覧(コレクション)を持ち、GameObject は Component の一覧を持っています。Transform は Component の一つですが、Transform 同士で親子の関係(階層)を持っています。
そのため、自分の属するGameObjectから親または子のGameObjectにアクセスするには、一旦 GameObject が持っている Transform にアクセスします。(詳細は後述しますが、Transform から GameObject への参照は、Transform が Component の継承クラスであることを利用します)
尚、自分自身と階層関係を持たない GameObject へのアクセスは、Scene を使ってアクセスすることになりますが、通常は [SerializeField] で特定の GameObject への参照をあらかじめ渡しておく方法が使われると思います。
MonoBehaviour からのアクセス方法
MonoBehaviour(C#スクリプトに使用する最小クラス)から GameObject、Component、Transform にアクセスする方法を以下にまとめます。
MonoBehaviour → GameObject
(this.)gameObject;
MonoBehaviour → Component
(this.)gameObject.GetComponent
MonoBehaviour → Transform
(this.)gameObject.transform;
MonoBehaviour → 親GameObject
(this.)gameObject.transform.parent.gameObject;
MonoBehaviour → 子GameObject
(this.)gameObject.transform.GetChild(int).gameObject;
子ゲームオブジェクトの並び順は、Hierarchyウィンドウの並び順
GameObject、Component、Transform の関係(クラス図)
ここからは、上記の結論にたどり着いた経緯を簡単にまとめます。
以下は、Unityのリファレンスから分かる範囲で読み解いた GameObject、Component、Transform の関係です(クラス図)。変数や関数はお互いの関係性を知るために必要なものだけをピックアップしています。
Unity に限らず、オブジェクト指向のマニュアルは、継承された機能の出元を追うには少し分かりづらい書き方になっています。これはオブジェクト指向の最大の特徴であるカプセル化のためで、オブジェクトが継承した変数や関数を、その出元を知らなくても使えるようにするためです。今回はリファレンスから読み解ける範囲で、継承された変数や関数の出元をまとめました。
継承ライン
クラスの継承は先ほどのクラス図の縦のラインです。スクリプトの中心となるMonoBehaviourから見た継承、Transform、GameObjectから見た継承をそれぞれ追ってみると、以下のことが分かります。
Object → Component → Behaviour → MonoBehaviour
まず、スクリプトの基本になる MonoBehaviour の継承関係を見てみます。MonoBehaviour は Component の継承クラスで、Component は Object の継承クラスです。MonoBehaviour が Component の一つとして扱えるようになっていることが分かります。
GameObject とは直接の継承関係にありませんが、Component が GameObject を持っています。後述しますが、これは Component が自身の配列の親である GameObject を参照できるようにしています。
Component → Transform
Transform は Component を継承しています。Transform が Component の継承クラスであるということは、Transform も Component の機能を使って GameObject にアクセスできることを意味します。
Object → GameObject
GameObject は、Component や Transformとは継承関係にはなく、ただ単に Object の継承クラスです。
ここまで継承関係を見ましたが、継承だけでは Component と GameObject の関係が分からないので、所持ラインを見る必要があります。
個人的には GameObject が基底クラスでないことに驚きました。GameObject の役割を知るまでは、Object → GameObject → Component、のような継承があるのではと予想していたからです。
所持ライン
継承ラインから、Component と Transform が GameObject を参照できることが分かりましたが、GameObject からComponent や Transform を参照するルートが分からないので、所持ラインを見ていきます。
所持ラインは先ほどのクラス図の横のラインです。
Scene → GameObject → Component
GameObject は Component をコレクションとして持ちます。GameObject が GetComponent() という、いかにもな関数を持っていることから、GameObject が Component をコレクションとして持っていると推測できます。
尚、GameObject 自体も、Sceneのコレクション要素になっています。本稿の最後に書きますが、Scene は 全てのGameObject でなく、一番上の階層にある(Root)GameObject のコレクションだけを持っています。
違和感があるのは、GameObject を継承していない Component が、GameObject だけが使えそうな GetComponent() を持っていることです。Component は GameObject のコレクションなので、本来は Component から他の Component を参照できないはずです。しかしながら、Component もまた、自分の属する GameObject にぶら下がる仲間の Component を探せるようになっています。
この場合、少なくとも 2 つの実装方法が考えられるかと思います。
方法1)Component が GameObject の GetComponent() を呼んでいる(ラッパー関数)
方法2)Component が、次または前の Component への参照を持っている(Component 同士がお互いをリンクする)
単純に考えると、Component が自分の属する GameObject への参照を持っているため、方法1の方が軽装で済みます。Component に次または直前の Component へのリンクを持たせると、Component の並べ替えをする時にひと手間増えるからです。また、コレクションの子要素にコレクション機能を持たせることは、クラスの責務が増えるので通常はあまりしないかと思います(コレクション機能は通常、 ArrayList や LinkedList など、コレクション機能だけのクラスを定義する)。ただ、Rect Transform 関連のコンポーネントは、コンポーネント同士でお互いを制御することがあります。そのため、方法2も考えられなくはないのですが、それでも方法1で実現できるので、おそらく方法1で実装されていると考えます。
GameObject → (Component) → Transform
Transform は Component の継承のため、これまでの情報から、GameObject は Component の一つとして Transform にアクセスできそうです。しかし、GameObject は、わざわざ Transform への参照を持っています。これは Transform が Component の中でも別格と推測されます。
参照パス
継承と所持を確認したところで、先ほどの参照についてルートを確認します。基本的に、スクリプトのいる位置である MonoBehaviour から 1) GameObject、2) 同じGameObjectに属する他のComponent、3) 同じGameObjectに属するTransform、4) 子GameObject、5) 親GameObject への参照ルートを確認します。
MonoBehaviour → GameObject
MonoBehaviour から 自分の属する GameObject の参照は、Component が持っている GameObject への参照を利用します。
MonoBehaviour → Component
MonoBehaviour から 同じ GameObject に属する他の Component を参照する時は、自分が属する GameObject の GetComponent() を利用します。
MonoBehaviour → Transform
MonoBehaviour から同じ GameObject に属する Transform への参照は、先述の MonoBehaviour → Component への参照の他に、GameObject が Transform への直接参照を持っていることを利用します。
GetComponent() は検索が必要なので、GameObject が持っている Transform への参照を利用する方が早いと思われます。
MonoBehaviour → 親GameObject
MonoBehaviour から自分の属する GameObject の親 GameObject を参照するには、Transform に着目します。
1) GameObject が他の GameObject を参照するためには、GameObject をコレクションとして持っている Scene を使えば良いのでは、と考えましたが、Scene が持っているのは Root GameObject だけなので、親GameObjectを取得できない可能性もあります。
2) 次に、GameObject が static 関数で持っている Find() は、このクラス図を眺める限り少し不自然です。Find が他のGameObjectを見つける方法が、このクラス図の範囲からは見つけられないからです。
3) Transform に着目すると、Transform には変数 parent と 関数 GetChild() が見つかります。これは、Transform 自身が親子関係を記述できることを意味します。Transform は リファレンスからは、Component との継承以外、リストに関わる Interface なども実装していません。そのため、parent と GetChild() は Transform が出元です。
4) おそらく、GameObject.Find() は、Scene が持っている Root GameObject と、Transform が持っている GetChild() を組み合わせて、目的のGameObject を探索するものと思われます。(そのうち調べたいと思います)
5) また、このパスには Transform から GameObject へのアクセスが必要ですが、これは Transform が Component の継承クラスで、Component が 自分の属する GameObject への参照を持っていることを利用します。
MonoBehaviour → 子GameObject
MonoBehaviour から子GameObject への参照は、親GameObjectとほぼほぼ同じルートになりますが、親一つに対して子は一つでないため、最後は Transform の GetChild() で探す必要があります。GetChild() は index でしか検索できないので、目的のGameObjectかどうかを特定するために、GameObject の基底クラスである Object.name を活用することになりそうです。
Unity 画面との関係
Hierarchy ウィンドウ = Sceneの配列 + Transform
これまで見てきた内容から、HierarchyウィンドウはSceneが持っているRoot GameObject のコレクションと、各Root GameObject が持っている Transform の親子関係の両方を俯瞰できる機能だと分かります。
以下に、Hierarchyウィンドウと、Sceneが持つRoot GameObject のリストを示します。Hierarchy と、Root GameObject の並び順が一致していることが分かります。
また、Hierarchy と、Transform が持つ 子Transform の並び順も一致しています。
Inspector ウィンドウ = GameObjectの配列
また、Inspectorウィンドウは、GameObjectが持っているComponentのコレクションのゲーム中の状態を俯瞰できる機能だと分かります。Inspectorは「監視役」の意味で、Wikipediaではプログラム実行中のデータの状態などを表示して確認するツールとあります。GetComponents() をコールしてみると、Inspectorウィンドウの並び順に一致していることが分かります。
(以上)