Unityでのスクリプト作成、コーディングにおいて重要であり、かつ基本的なメソッドである"GetComponent"について、まとめてみます。
そして、そもそも"参照(リファレンス)を得る"ということはどういうことか?という基本的な部分についても書いてみたいと思います。
※2017/11/22少し加筆修正
GetComponentはオブジェクトの参照を得るためのメソッド
2018_10_15追記 オブジェクト指向について、また書きました。たぶんコレがオブジェクト指向そのものに対する自分の最終的な見解になります。
オブジェクトとは
まず"オブジェクト"とは"クラス"の変数です。"クラス"は設計図のようなモノであり、そこから作られた実体がオブジェクト、みたいな説明がありますが、結局は"クラス"は複数の変数、メソッドを保有 できる特殊なデータ・タイプ(いわゆる"型")なワケで、そのタイプのデータを格納するための特殊な変数が"オブジェクト"です。
変数とは少し違うんだぞ!という区別をするために"オブジェクト"あるいは"インスタンス"といっているのであって、データを格納するための器のようなものという性質は同じです。私達人間も様々なデータを内部に保有 しているという意味ではオブジェクトと言えます。
つまり、より複雑で抽象的なモノ(形があるとは限らない)をコンピュータ上でデータとして扱うためのデータタイプがクラス、ということです。
オブジェクト指向についてはコチラの記事にまとめてみました。
Unityでコードのコンポーネントをスクリプトと呼んでいますが、要は"クラス化された、あるひとつのコード"であるということに注意が必要です。つまり、ゲームはコードの集合体であり、その全体のコードの内の一部分ということになります。またUnityのスクリプト上で、スクリプトを含む他のコンポーネントのオブジェクトを作るということは、他のクラスのオブジェクトを作るということにもなります。(ここは後で詳しく説明します。)
変数である以上、宣言して使うわけですが、それだけでは"中身"は入っていません。
int year = 2017;
なので、まずこのように値を代入する必要があります。
オブジェクト指向とは"クラス"で分割し、そこからオブジェクトを作り、オブジェクトを中心にプログラムを動かしていくことで、現実世界の概念や自然言語に近づける、というものです。当然、オブジェクトには中身が必要です。
問題はUnityにおけるオブジェクトに入れる中身とは何か?ということです。つまり、それは実際のゲームオブジェクトにアタッチされているコンポーネントになります。
(もちろんプログラミングにおける一般的な意味でのオブジェクトも作られます。)
Unityにおける"コンポーネント"
直訳すれば『部品』となりますが、Unityでは何かしらの機能のまとまり、ゲームオブジェクトに機能を持たせるための部品になります。
要は物理演算のための"Rigidbody"や当たり判定の"コライダー"、音を出すための"オーディオ・ソース"、などのUnityで(比較的)手軽にゲームを作ることの出来る、有難い部品の数々です。
と同時にプログラミング的にこれらは全て"クラス"でもあります。つまり、スクリプト上でUnityのコンポーネントを動すには、そのコンポーネントの"クラス"からオブジェクトを作る必要があるということです。
そして、上記で書いたようにオブジェクトには中身が必要です。繰り返しますが、それがゲームオブジェクトに実際にアタッチされているコンポーネントになるわけです。
つまり、その中身がなければ、
Rigidbody rb; // Rigidbodyのオブジェクト、インスタンス
rb.AddForce( Vector3.up * jumpPower );
とコードを書いても、Unity上では何も起こらないわけです。ここでいう"rb"というRigidbodyのオブジェクトに中身を入れる必要があります。
つまり、オブジェクトにアタッチされているコンポーネントの"参照"(リファレンス)を得る必要があります。(例えばプレイヤーのゲームオブジェクトにアタッチされているRigidbodyのことになります。)
そもそも"参照"とは?
"参照する"とは単純に言えば、結びついている、関連しているということです。具体的な行為としては、何らかの本を参考にするみたいなことです。より詳しく言うと、"どこ"にある"どの本"の"何ページ目"の"何行目"を明示した上で、参考にする、というのが"参照する"ということです。
プログラミングにおいても同様で、より正確な所在地を指し示しつつ、参考にしている、引用している、ということになります。
プログラミングにおける"参照"(リファレンス)
少しプログラミングそのものに関する話です。C#では変数のデータの格納の仕方には二種類あります。値そのものを格納するデータ・タイプ、実際の値は格納せず、データ群が置かれたメモリ上のアドレスを格納するリファレンス・タイプの二つです。
オブジェクトはリファレンスタイプになります。つまり、オブジェクトは正確には該当する中身の"ある場所"を格納しているということになります。オブジェクトはメモリの住所を指し示している、とも言えます。どこにあるか分かっているからこそ、データやメソッドを動かすことができるということです。
GetComponentの働き
というわけで、いよいよ本題です。つまりGetComponentはUnity上の該当するコンポーネントがどこにあるかをオブジェクトに示す(返す)ためのメソッドです。
スクリプト上のオブジェクトが真の実体を持つには、オブジェクトがUnity上のコンポーネントを指し示すことが出来なければいけないのです。
先ほどの例で言えば、
Rigidbody rb;
void Start(){
rb = GetComponent<Rigidbody>();
}
これはRigidbodyのオブジェクトを作り、このスクリプトがアタッチされているゲームオブジェクトに同じくアタッチされているRigidbodyコンポーネントの場所を"rb"に指し示す、という内容になります。
注意としては、GetComponentの効力は、基本そのスクリプトがアタッチされているコンポーネントに限る、ということです。(ChildやParentは指定すれば可)
上の例が、プレイヤーキャラにアタッチされた"PlayerController"スクリプトだとすると、そのプレイヤーキャラにアタッチされたRigidbodyを呼び出していることになるのです。つまりrbというオブジェクトが、当コンポーネントの参照を得て、このコード上において実体をもった、ということになります。
アタッチされたゲームオブジェクト以外の参照
しかし、それだけでは参照を得るのに限界があります。例えば、敵キャラにアタッチされた"EnemyContoroller"等の他のスクリプト(他の"クラス")の参照を得ることはできないのでしょうか?でなければ、"PlayerController"側から、敵キャラを動かすことができないことになります。(攻撃したら吹っ飛ばす、みたいな)
スクリプトの中で、そのスクリプトのオブジェクトを作り、きちんと"参照"を得れば可能です。それにはいくつかの方法があります。
・publicとして宣言して、インスペクター上にドラッグする。
public Rigidbody playerRb;
これは便利ですが、実際にシーンにおかれているゲームオブジェクトである必要があります。例えば、必ずシーンに存在するプレイヤーを参照する際に便利かもしれません。しかしドラッグし忘れるとnull(参照ないですよ!)エラーが発生します。
・GameObject.FindWithTag()で引っ張ってくる。
タグの設定をする必要がありますが、確実な方法です。基本的にプレイヤーには、Playerタグを設定しておけば、FindWithTagでそのプレイヤーのゲームオブジェクトの参照を得られます。つまり、一回、ゲームオブジェクトのオブジェクトにそれを移して、そのオブジェクトからGetComponentする、という形になります。
GameObject playerObject = GameObject.FindWithTag("Player");
if(playerObject != null)
rb = playerObject.GetComponent<Rigidbody>();
・接触したコライダーから参照を得る。
これはダイナミックな方法です。OnCollisionEnterなどで検出したコライダーからGetComponentするという方法です。応用するとPhysic.OverlapSphere等で検出したコライダーからを与えて、バクダンの爆発力を与えて吹き飛ばす、なんてこともできます。(爆弾のスクリプト上に各キャラクターにアタッチされたrigidbodyと結びついたrigidbodyのオブジェクトを作ることで、爆弾から各キャラへのrigidbodyに対してAddForceで物理エネルギーを加えることができるわけです。)rigidbodyの場合は、collider.attachedRigidbodyから直接アクセスも可能です。
まとめ!
というわけで、Unity上で何かする際に必須な各コンポーネントをスクリプト上でコントロールするためには、その実際のコンポーネントの参照を得る必要があり、そのためにGetComponentは必要である、という話でした。
基本ではありますが、大事な部分ではあると思うし、Unity上のC#を使う上での特徴的な所だと思うので、自分なりにまとめてみました。