Unityのスクリプトの中で他のゲームオブジェクトの取得、自分自身または他のゲームオブジェクトに設定されているコンポーネント(構成要素)を取得し操作したい事があります。
ゲームオブジェクトに設定するスクリプトもコンポーネントとして扱われるので、他のゲームオブジェクトに設定されたスクリプトに何らかの処理をしてもらいたい時にはコンポーネントの取得をする必要があります。
今まではゲームオブジェクト、コンポーネントの取得に関して掘り下げて説明はしていなかったので、何となく使っていた方もいるかもしれません。
まずはゲームオブジェクトの取得方法を見ていきます。
ゲームオブジェクト取得のテストの準備
ゲームオブジェクトの取得は基本的にStartメソッド内で取得し、それをフィールドや変数に入れておきます。使用する時はそのフィールドや変数を介して実行します。
これは使う時に毎回ゲームオブジェクトの取得をしていると処理に時間がかかる為です。
では実際にゲームオブジェクトを用意して、ゲームオブジェクトを取得するスクリプトを組んでみます。
上のようにゲームオブジェクトを階層化しておきます。
1にはObj1タグ
2にはObj2タグ
3にはObj3タグ
4にはObj4タグ
を設定し、それぞれにScript1からScript4までのスクリプトを作成し設定しておきます。
ゲームオブジェクト1は上のようになります。他のゲームオブジェクトにも同じように設定します。
階層の一番上のObj1から子要素を取得するテスト
まずはゲームオブジェクト1から他のゲームオブジェクトを探すスクリプトScript1を作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | using UnityEngine; using System.Collections; public class Script1 : MonoBehaviour { private GameObject obj2; // ゲームオブジェクト2を入れる為の変数 private GameObject obj3; // ゲームオブジェクト3を入れる為の変数 private GameObject obj4; // ゲームオブジェクト4を入れる為の変数 void Start () { // ゲームオブジェクトの名前で検索なければNull obj2 = GameObject.Find("2"); // ゲームオブジェクトに設定されているタグで検索なければNull obj3 = GameObject.FindWithTag("Obj3"); // 自分自身の階層下から検索、なければNull obj4 = transform.Find("2/3/4").gameObject; // ゲームオブジェクトが取得出来てるかどうかの確認、見つからなければエラー Debug.Log(obj2.name); Debug.Log(obj3.name); Debug.Log(obj4.name); } } |
ゲームオブジェクトを検索したい場合は
1 2 3 | GameObject.Find("ゲームオブジェクトの名前") |
を使います。
ヒエラルキー上にある全部のゲームオブジェクトからアクティブなゲームオブジェクトを検索してくれますが、同じ名前のものを作っていると最初に見つかった方を返すようです。
また、ゲームオブジェクトを全部検索していくので処理に時間がかかります。
今回の例ではゲームオブジェクト2を検索しています。
ゲームオブジェクトに設定したタグで検索したい場合は
1 2 3 | GameObject.FindWithTag("ゲームオブジェクトに設定したタグ名") |
を使います。
ゲームオブジェクト固有のタグが設定されていれば、そのゲームオブジェクトの名前が同じでも指定したタグの物だけ検索出来ます。
FindよりFindWithTagを使った方が処理速度は早いみたいです。
次に現在のゲームオブジェクトの階層の下に取得したいゲームオブジェクトがある場合です。
transform.Find(“取得したいゲームオブジェクトまでのパス”).gameObject
transform.Findを使うと取得したいゲームオブジェクトまでのパスを指定すればそのゲームオブジェクトのTransform情報が取得出来ます。
TransformはプロパティとしてGameObjectを表すgameObjectプロパティが宣言されていますので、取得したTransform.gameObjectでゲームオブジェクトを取得する事が出来ます。
今回はScript1を設定したゲームオブジェクト1の階層下であるゲームオブジェクト4を取得する為に
transform.Find(“2/3/4”).gameObject
という記述をしています。
GameObject.FindとTransform.Findの違い
先ほど言及していますが、FindはGameObject.FindとTransform.Findがあるようです。
GameObject.Findの場合は引数に指定した名前でシーン上にある全てのゲームオブジェクトの中から探すので時間がかかります。
Transform.Findの場合は自身の子要素から引数で指定した名前のものを探します。
同じ名前の関数なのでわかりづらいですが子要素から探す場合はtransform.Findを使う方が処理が速いですね。
GameObject.Findはあんまり使う必要がないかも?
Unityの実行をして確認したところ、すべて他のゲームオブジェクトの取得が出来ました。
Obj3から他のゲームオブジェクトを取得する方法
次にゲームオブジェクト3から他のゲームオブジェクトを取得するScript3を記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | using UnityEngine; using System.Collections; public class Script3 : MonoBehaviour { private GameObject obj1; private GameObject obj2; private GameObject obj4; void Start () { // 自身のルートのゲームオブジェクトを取得 obj1 = transform.root.gameObject; // 自身の親のゲームオブジェクトを取得 obj2 = transform.parent.gameObject; // 自身の子の1番目の要素を取得 obj4 = transform.GetChild(0).gameObject; Debug.Log(obj1.name); Debug.Log(obj2.name); Debug.Log(obj4.name); } } |
自身のゲームオブジェクトの一番上の階層のゲームオブジェクトを取得したい時は
1 2 3 | transform.root.gameObject |
とします。
今回の場合はゲームオブジェクト3の一番上の階層であるゲームオブジェクト1を取得しています。
1 2 3 | transform.root |
でルート(一番上の親)のTransformを取得し、プロパティであるgameObjectを得る事でゲームオブジェクトを取得しています。
自身のゲームオブジェクトの親要素を取得する場合
1 2 3 | transform.parent.gameObject |
とします。
今回の場合はゲームオブジェクト3の親要素であるゲームオブジェクト2を取得しています。
自身のゲームオブジェクトの子要素を取得する場合
1 2 3 | transform.GetChild(0) |
を使用します。
自身の子要素が複数ある場合はGetChildの引数に対応する子要素の数値を指定します。
子要素の子要素を取得する場合は
1 2 3 | transform.GetChild(子要素番号).GetChild(子要素番号) |
という感じで取得する事も出来ます。
複数の子要素がある場合はforeachやfor文等の繰り返し分を使って要素を取得する事が多いと思います。
子要素の子要素を取得というような処理を作る場合は再帰を使う事も出来ます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class GetChildTest : MonoBehaviour { // Start is called before the first frame update void Start() { GetChildRecursive(transform); } void GetChildRecursive(Transform tra) { Debug.Log(tra.name); if (tra.childCount != 0) { GetChildRecursive(tra.GetChild(0)); } } } |
GetChildRecursiveはTransform型の引数を受け取りそのゲームオブジェクトの名前をコンソールに出力し、自身の子要素が存在していれば自分のメソッド内で自分のメソッドを呼び出しています。
上のスクリプトをゲームオブジェクトの1に設定すればコンソールに
1
2
3
4
と表示されます。
これでだいたいのゲームオブジェクトの取得の方法がわかりました。
ゲームオブジェクトを探さずインスペクタでゲームオブジェクトを設定しておき、そのゲームオブジェクトを使うという方法もあります。
1 2 3 4 5 | public GameObject obj1; [SerializeField] private GameObject obj2; |
上のようにスクリプトにpublicフィールドでGameObject型を宣言したり[SerializeField]アトリビュートを付けると、インスペクタでそのゲームオブジェクトを設定出来るので、スクリプトで検索する必要はありません。
用途に応じて使い分けるのがいいかもしれません。
Obj2から他のゲームオブジェクトのコンポーネントを取得
ゲームオブジェクトの取得方法がわかったのでそのゲームオブジェクトに設定されているコンポーネントを取得してみます。
Script2を作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | using UnityEngine; using System.Collections; public class Script2 : MonoBehaviour { private Script1 script1; private Script2 script2; private Transform transform3; private Script4 script4; void Start () { script1 = GetComponentInParent<Script1>(); script2 = GetComponent<Script2>(); transform3 = GetComponentInChildren<Transform>(); script4 = GetComponentInChildren<Script4>(); Debug.Log(script1); Debug.Log(script2); Debug.Log(transform3); Debug.Log(script4); } } |
自身の親要素を辿ってコンポーネントを検索する時は
1 2 3 | GetComponentInParent<コンポーネントの型>(); |
を使用します。
自身に同じコンポーネントがあれば自身のコンポーネントが取得出来るみたいです。
今回は自身の親要素のゲームオブジェクトに設定しているScript1スクリプトコンポーネントを取得しています。
1 2 3 | GetComponentInParent<Script1>(); |
自身のゲームオブジェクトに設定されているコンポーネントを取得するには
1 2 3 | GetComponent<コンポーネントの型>(); |
とします。
今回は
1 2 3 | GetComponent<Script2>(); |
としています。
自身のゲームオブジェクトの子の階層を辿ってゲームオブジェクトのコンポーネントを取得するには
1 2 3 | GetComponentInChildren<コンポーネントの型>(); |
とします。自身のゲームオブジェクトに同じ型のコンポーネントがある場合は自身のコンポーネントを取得します。
今回は
1 2 3 4 | obj3 = GetComponentInChildren<Transform>(); obj4 = GetComponentInChildren<Script4>(); |
と自身のTransformコンポーネントと自身の2段階下の階層のScript4を取得しています。
上が実行結果になります。
自身と同じ型のコンポーネントを親要素や子要素から取得したい時は自身のコンポーネントを取得しないよう気をつけなければいけません。不明なバグの原因になるかも!?
JavaScriptは動的型付けなのでコンポーネント取得時は型を指定して取得した方がいいかも!?
最後にJavaScriptで型の動的型付けに対応する為のコンポーネントの取得の仕方をScript4に記述します。
JavaScriptでは型の指定はせず、勝手に型を考慮して変換してくれるスクリプト言語です。
UnityのJavaScriptでも明示的に型指定をしてなくても実行出来ます。
が、変な不具合に遭遇する可能性もあるので、型指定をしてコンポーネントを取得しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | private var obj1 : Script1; private var obj2 : Script2; private var obj3 : Script3; private var obj4 : Script4; function Start () { obj1 = GetComponentInParent.<Script1>(); obj2 = transform.parent.parent.GetComponent.<Script2>(); obj3 = transform.parent.GetComponent.<Script3>(); obj4 = GetComponent.<Script4>(); Debug.Log(obj1); Debug.Log(obj2); Debug.Log(obj3); Debug.Log(obj4); } |
今までは
1 2 3 | GetComponent(Script1) |
としていましたが、
1 2 3 | GetComponent.<Script1>() |
として取得します。
わたくしがスクリプトを組んでいる上でコンポーネントの取得方法で不具合が出たという事はないですが、最初から指定しておくといいかもしれません。
上が実行結果です。
その他のゲームオブジェクト、コンポーネントの取得方法
これまで紹介した方法以外にもゲームオブジェクトの取得、コンポーネントの取得方法はあります。
GameObject.FindGameObjectsWithTag
FindGameObjectsWithTagは引数で指定したタグ名が設定されているゲームオブジェクトをすべて取得する事が出来ます。
同名のタグが設定されているゲームオブジェクトをすべて取得し何らかの処理をしたい時に使用するといいかもしれません。
例えばEnemyタグが設定されている敵すべてに同じ命令を送り、キャラクターを目的地にして攻撃させるとか、
そういった時に使えるかもしれません。
Object.FindObjectOfType
FindObjectOfTypeは引数で指定したタイプの最初に見つけたアクティブなオブジェクトを探す事が出来ます。
見つからない場合はnullが返ってきます。
例えばゲーム内でLightコンポーネントを取得したい時は
1 2 3 4 5 6 7 8 9 10 11 12 | using UnityEngine; using System.Collections; public class GetComponentTest : MonoBehaviour { void Start () { Light light = FindObjectOfType <Light>(); Debug.Log(light); } } |
FindObjectOfTypeは全検索してそのコンポーネントを探していくので、時間がかかる処理になります。
使いどころとしては、現在のシーンに他のシーンを追加してマルチシーンにした時、現在のシーンのスクリプトから今読みこんだシーンにだけあるスクリプトを取得したい、
といった時に使用すると便利です。
Object.FindObjectsOfType
FindObjectsOfTypeを使用すると指定した型の全てのゲームオブジェクトの配列を取得出来ます。
FindObjectOfTypeの複数取得版ですね。
インスペクタでコンポーネントを設定するのとスクリプト中でコンポーネントを取得する違い
ここまででGetComponent系のメソッドを使って自身の階層から該当するコンポーネントの取得をする方法をやってきました。
ですがStartメソッドで階層からコンポーネントを取得する方法の他にフィールドで[SerializeField]アトリビュートを付けたり、publicなフィールドにすればあらかじめ該当するコンポーネントをインスペクタで設定しておく事も出来ます。
なら最初からインスペクタで設定しておいた方が簡単だ!
と思うかもしれませんが、場合によっては階層から探してコンポーネントを取得した方がいい時やしなければいけない時があります。
例えばプレハブからインスタンス化して敵をゲーム内に登場させる時に主人公キャラクターを追いかけさせる為に主人公キャラクターのTransformを使いたいとします。
ですがインスペクタでキャラクターのTransformを設定出来るようにしていても、プレハブのインスペクタにはヒエラルキー上の主人公キャラクターを設定する事が出来ません。
なので敵キャラクターをインスタンス化した後にゲーム内の主人公キャラクターのTransformを取得する必要があります。
後は階層からコンポーネントを取得するようにしておくと設定の間違いが減るというのもあります。
例えば主人公キャラクターの操作スクリプトでStatusというキャラクターのステータスを管理するスクリプトをインスペクタで設定出来るとします。
敵キャラクターも同じStatusスクリプトでステータスを管理していた場合に、主人公キャラクターの操作スクリプトのインスペクタで間違って敵のキャラクターに設定しているStatusスクリプトを設定してしまう可能性もあります。
自身の階層からStatusスクリプトを取得していると間違って敵キャラクターのStatusスクリプトを設定してしまうという間違いはなくなります。
終わりに
これで、Unityのスクリプトでゲームオブジェクト、コンポーネントを取得するやり方がわかりました。
ゲームオブジェクトの名前やタグを使ってゲームオブジェクトを検索したり、自身のゲームオブジェクトから階層を辿って、コンポーネントを取得する方法はいくつもあります。
自分のやりやすいやり方、処理速度を考慮してやり方を変えてみてください。
GetComponentInChildrenやGetComponentInParentでは自分自身のコンポーネントも取得するというのを知らなかったので、
わたくし自身も勉強になりました・・・・(^_^;)