【Unity】GetComponentの使い方と注意点

こんにちは。
京都でエンジニアをしている江藤(@as00812145)といいます

ゲーム開発において、ある特定のスクリプトから別のスクリプトへアクセスして操作することは頻繁にあります。
そして、そのときに非常に重宝するのがUnityが提供している関数のGetComponentです。
これを使うことによって必要なオブジェクト数やスクリプトの量を減らすことができる他、ゲームロジックの実装の幅が劇的に広がります。

今回はGetComponentの使い方とその注意点について解説していきます。

Unityのゲームの考え方

最初にUnityのゲームの考え方について解説しましょう。
Unityのすべての要素は componentという単位で作られています。
Unityにおける、カメラ、プログラム、画像、もろもろはすべて「component」で構築されているのです。
ただし、コンポーネントというのはあくまでも、追加される属性でしかありません。なので、
属性が付与される対象となる本体があります。これがゲームオブジェクトです。
実際に見てみましょう。

↑のimage や script などがコンポーネントになります。
先程も行ったとおり、 componentはあくまでもゲームオブジェクトに付与される属性なので、
簡単に取り外しができます。
実際にエディター上で操作してみましょう。

など色々操作ができました。
これで componentの考え方がわかってきたと思います。

UnityにおけるGetComponentの使い方

さて、ここからが本題です。先ほどまで手動で行っていたcomponentの作業をスクリプトから実行するのが、「GetComponent」になります。

これは名前の通り、特定のコンポーネントを取得することができる関数。

ここまでのUnityの考え方から GetComponentを使うにはまず、ゲームオブジェクトを取得しないといけません。
なぜなら、コンポーネントは あくまでも「属性」なので、属性だけだとどのコンポーネントを取得していいのかわからなくなるのです。
なので、ゲームオブジェクト -> 付与されているコンポーネント というプロセスで取得しないいけません。

実際に、GetComponentのドキュメントを見てみると、
GameObject.GetComponent

`GameObject.GetComponent`のようにGameObjectクラスのメソッドとしてGetComponentが存在しています。

さて、これでは、わかりにくいので、別のスクリプトの関数を実際にコンポーネントで取得して、
実行するというサンプルを作りながら、解説していきたいと思います。

準備

今回はシンプルにエディター上からオブジェクトを取得してみます。
なので以下二つのソースコードが必要です。

  • 実際に実行したい関数を持ってるスクリプト
  • 実際に対象のスクリプトの関数を実行するスクリプト

まずは、実際に実行させる関数をもっているスクリプトを書きましょう。
それが以下のスクリプトなので以下のソースコード(以下sayHello.cs)を適当なオブジェクトにアタッチしてください。
シンプルにログを出力する関数を実装しており、今回はこれを別のスクリプトから実行させてみます。

[code language=”java”]
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace say {
public class sayHello : MonoBehaviour {

public void Hello()
{
Debug.Log(“hello”);
}
}
}
[/code]

ここでの注意点は namespaceで外からこのクラスを参照できるようにしておきましょう。
外部からスクリプトを取得する場合は、その取得する対象の型情報が必要になるため、外部から読み込めるようにしていないといけないからです。

続いて、実際に対象のスクリプトの関数を実行するスクリプト(以下TestScript.cs)を書きます。

[code language=”java”]
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using say; // 対象のスクリプトの情報を取得
public class TestScript : MonoBehaviour {

public GameObject targetObject; // エディターから取得できるようにpublicにしておく
// Use this for initialization
void Start () {
sayHello say = targetObject.GetComponent<sayHello>(); //コンポーネントを取得
say.Hello(); // 対象のコンポーネントのスクリプトを実行
}

// Update is called once per frame
void Update () {

}
}

[/code]

こちらも、さっきとは別のオブジェクトにアタッチしてください。
ソースコードに関してはいろいろなことが詰まっているので実際に手を動かしながら解説していきます。

ゲームオブジェクトの取得

では実際にUnity上で操作していきましょう。

さて、TestScript.csをアタッチしたら、エディター上に一つフォームが追加されたと思います。
Unityはpublicでの変数はエディター上から操作することが可能になります。そして、
それは、コンポーネントやゲームオブジェクトであっても例外ではないのです。
なので、ここに、実際に実行させたいオブジェクトをドラッグアンドドロップしましょう。
これで、ゲームオブジェクトが取得できました。

これはTestScript.csの

[code language=”java”]
public GameObject targetObject; // エディターから取得できるようにpublicにしておく
[/code]

部分で実装しています。

コンポーネントの取得

では、これからコンポーネントを取得して実際に関数を実行してみます。
先程のドキュメントの通り、
GetComponentはGameObjectのメソッドなので、先程取得した、GameObjectから
GetComponentを呼びます。
この関数は、コンポーネントの型を渡して、コンポーネントの実態を返すため、
TestScript.csでは以下の部分になります。

[code language=”java”]
using say; // 対象のスクリプトの情報を取得
public class TestScript : MonoBehaviour {
public GameObject targetObject; // エディターから取得できるようにpublicにしておく
// Use this for initialization
void Start () {
sayHello say = targetObject.GetComponent(); //コンポーネントを取得
say.Hello(); // 対象のコンポーネントのスクリプトを実行
}
[/code]

getComponentは先に対象の型、つまり、コンポーネントの情報を渡さないといけないので、
ジェネリクスを使っています。そのために、最初にusing からその情報を取得しています。
そして、getComponentの結果、コンポーネントとして、実際にコンポーネントの実態が、帰ってきます。
今回であれば、スクリプトなので、スクリプトのクラスの参照が返ってきます。
そしてその参照から関数を読んでいるのが、

[code language=”java”]
say.Hello(); // 対象のコンポーネントのスクリプトを実行
[/code]

部分となるわけです。

ではUnityを実行してみてください。
期待通りログとして実行された思います。

実際の使い方(よく使う関数などを交えた使い方と注意点)

さて、実際に[get component]を使えたわけですが、ゲームオブジェクトを
いちいち最初に設定しておくのは面倒なので、スクリプトから動的に取得する方法
解説しておきましょう。
これにはGameObject.Findとそれに類する関数を使うのでそれらを紹介しましょう。

GameObject.Find

GameObject.Find

これは、シーン内に存在するゲームオブジェクトの名前で取得します。
名前は完全一致で取得するので、動作の負荷としては高いです。

GameObject.FindWithTag

GameObject.FindWithTag
ゲームオブジェクトのタグ名で取得します。この場合はそのオブジェクトにタグ名を設定しておかないといけません。

GameObject.FindObjectsWithTag

GameObject.FindGameObjectsWithTag
これはGameObject.FindWithTagの複数オブジェクトを取得するバージョンです。
タグの性質上、複数のオブジェクトに同一のタグを保有することがよくあるので、検索するタグを持つオブジェクトをすべて取得します。
なので、返り値は配列となっており、該当するものがない場合はからの配列が帰ってきます。

注意点

これらで、初心者が陥りがちなのが、パフォーマンスの問題です。
GameObject.Findは名前を完全一致でシーン内のオブジェクトを全部走査するので非常に重いです。
なので、連続してこの関数を使用するのはゲームの画面がカクつく原因になります。

かと言って、タグ名で全部管理して、FindWithTagで操作しようとすると、
タグ名が爆発的に増殖していき、管理が難しくなって、ミスとバグの増産することになり、開発の遅れの遠因になります。

なので、自分が作るゲームとソノゲームオブジェクトに持たせる役割によって使い分けるようにしましょう。

私の場合は、
インスペクターから設定:その役割が単一で途中で他のオブジェクトに切り替えることはない。
GameObject.Find:極力使わないか、初期化時など、一度切りすむような場合で取得
GameObject.Find && FindObjectsWithTag : 頻繁にゲームオブジェクトを取得しないといけない。一度に複数のゲームオブジェクトに変更を与えたい場合

といった形である程度の使い方を決めています。
ただ、これも開発するゲームよってちょこちょこ境界がかわりますが、最初からある程度線を引いていれば、
ゲームが巨大化しても、そう簡単にはパフォーマンスで死んでしまうことななくなります。

GetComponentのまとめ

ここらへんは、基本的なことですが一歩まちがえるとゲームの挙動が非常に重くなったりします。
そして、ここらの境界や線引きは実際の経験や試行回数がものをいいます。
開発において全てに通じる銀の弾丸はそうそう存在しないので、実際に使って、複数の場面で対応できるようにしていきましょう。