前回投稿に続いて「別スクリプトの変数やメソッドへの参照」についてUnity Gemsより紹介する:
------
http://unitygems.com/mistakes1/
October 15, 2012
別スクリプトの変数やメソッドへの参照
二つのスクリプトを作成し、相互に参照したいメソッドや変数があるけれどもどうやったらスクリプトを参照する方法や変数を取得する方法が分からなかったとする。用語として、呼び出しを行って変数参照を行うスクリプトを「caller」呼び出される変数やメソッドを保有するスクリプトを「target」と定義する。
「staticを変数に用いないこと」がUnityで陥りやすい「罠」。ついつい変数を保有するスクリプトの参照方法が分からない場合に、簡単に参照出来る方法としてやってしまいがちだ。正確に理解していないかぎりやってはいけない典型パターンだ。staticな変数はグローバル変数の扱いであり使用すべき状況はかぎられていて、かつ上級者向けにシナリオといえる。複雑なゲームのプログラミングにおいて一度も使うことがなかったとしても全く困らないことが殆どだ。
Unity Answers頻出パターンとして「一人の敵を死なせるつもりが全員死なせてしまった」がある。大抵、開発者が参照方法を分からず、staticな変数定義に手をつけてしまったことが原因である。身に覚えのある方は良く読んで正しく理解すること!
「target」スクリプトへの参照を得るためには、まずどのオブジェクトにアタッチされているかを理解する必要がある。方法は5種類ほどある:
1.参照した「target」スクリプトが「caller」スクリプトと同じオブジェクトにある場合
この場合、GetComponent呼び出しを行い、戻り値としてスクリプトがアタッチされているゲームオブジェクトを取得すると良い。GetComponentを明示的に型定義しない場合は同一のゲームオブジェクトにアタッチされたスクリプトが戻される。
Javascriptの場合のGetComponent記述方法は:
GetComponent(TargetScriptName).someVariable = someValue;
GetComponent(TargetScriptName).SomeMethod();
C#記述方法:
GetComponent<TargetScriptName>().someVariable = someValue;
GetComponent<TargetScriptName>().SomeMethod();
GetComponentの戻り値がString型となるパターンは出来るかぎり避けること。GetComponentの戻り値がString型でない場合は呼び出しを行うことで正しい型が戻され、変数やメソッドをすぐに参照することが出来る。一方、GetComponent("「target」スクリプト名")という記述をするとスクリプト名を戻り値として得られるが、コンパイラが正しい型を認識できないため変数やメソッドをすぐに参照することが出来ない。
2.Collider、Triggerを通して「target」スクリプトを参照する
この場合、すでに参照したいオブジェクトを取得できており、OnTriggerの場合はColliderとして、OnCollisionの場合はCollisonとして参照できる。
あとは取得したオブジェクトにGetComponentを使用するだけだ。
Javascriptでは
function OnTriggerEnter(other : Collider)
{
other.GetComponent(TargetScriptName).someVariable = someValue;
}
C#では
void OnTriggerEnter(Collider other)
{
other.GetComponent<TargetScriptName>().someVariable = someValue;
}
OnCollisionの場合、衝突したCollisionオブジェクトを取得できているため、相手オブジェクトを代表するものに対してGetComponentを呼び出しする:
function OnCollisionEnter(collision : Collision)
{
collision.collider.GetComponent(TargetScriptName).someVariable = someValue;
}
C#では
void OnCollisionEnter(Collision collision)
{
collision.GetComponent<TargetScriptName>().someVariable = someValue;
}
また、Trigger, Colliderの相手オブジェクトの子オブジェクトへの参照が必要である場合、GetComponentInChildrenを用いれば良い
GetComponentChildrenは呼び出ししたオブジェクトも検索対象とすることに留意
3.二つのオブジェクトにエディタを通じた関連性がある、あるいはスクリプトで関連性を記述している
この場合、オブジェクトに関連性があり、呼び出したタイミングで相互に作用してはいないものの、何らかの長期的な関連性がある。例をあげると、敵がプレイヤーを追跡している場合、プレイヤーは武器を敵に向けてロックオンしている関連性があるし、別のパターンではプレイヤーが何かの乗りものに乗車している関連性がある場合などがあげられる。
次に「caller」スクリプトの中に変数を通して「target」スクリプトを参照させたい。一番簡単な方法は変数の型定義としてTargetScriptNameとすることであるが、別の方法としてはGameObjectやTranformなど異なる参照変数とすることも出来る。
パフォーマンス観点からGetComponent呼び出しはマイナス効果があるので留意すること。たとえ複数の変数を定義してわざわざ同じオブジェクトを参照する場合であってもパフォーマンス観点からGetComponent呼び出しの回避は検討すべきである。一般にGetComponentから必要オブジェクトへの参照をAwakeやStart時にpublic変数にキャッシュしておくのが良い。複数の変数の場合は、ランタイムで変数のひとつを変更したら必ず他の変数も変更されるように必ずに実装すべきである。
エディタの場合は、Inspectorを用いて変数をセットする、あるいはランタイムにセットする場合は前述の2または後述の4の手法を用いてComponentを取得し、変数をセットアップする必要がある。
Triggerによる長期的な関連性をもった例:
var targetScript : TargetScriptName;
function Update()
{
//If we have a target, and that target is ready to be targeted
if(targetScript && targetScript.someVariable > 100)
{
//Do one thing }
else
{
//Do something else
}
}
function OnTriggerEnter(other : Collider)
{
//Check whether the thing we hit can be a target
var target = other.GetComponent(TargetScriptName);
if(target)
{
//If it can then target it
targetScript = target;
}
}
4.「target」スクリプトを名前かタグの一致で判定する
この方法はパフォーマンス観点で別のオブジェクトを見つけるにあたり最もマイナス効果が大きい。オブジェクトへの参照を一致で判定させることは一度だけにとどめるよう設計を行い、出来ることならばStartかAwakeのなかであらかじめ行うことが肝要である。
名前やタグの一致によりオブジェクトを見つけた後は、前述の2とまったく同様の方法でGetComponentを通じて参照を行えばよい。
なおオブジェクトを見つける手法は複数ある。例えばGameObject.FindWithTagかGameObject.Findでシーン全体に対して検索を行う。あるいはtranform.Findを用いることでゲームオブジェクトの子オブジェクトを検索させることが出来る。見つける手法がどれであったとしても前述の2の方法でスクリプトを参照させ、出来ることならば結果をキャッシュしておき長期的に呼び出しできるように前述の3の方法を用いてその後は継続的に参照を行う。
TargetScriptName targetScript;
void Start()
{
targetScript = GameObject.Find("someObjectName").GetComponent<TargetScriptName>();
}
void Update()
{
if(Vector3.Distance(targetScript.transform.position, transform.position) < 10)
{
targetScript.someVariable -= someValue;
}
}
5.オブジェクトに参照したい別コンポーネントがあるが、そのコンポーネント上に参照したい変数がない場合
階層を辿っていく場合や、同じオブジェクトに複数のスクリプトがある場合に頻出する。本質的には前述の2のパターンと同じといえる。
例えば「caller」スクリプトがアタッチされているオブジェクト子オブジェクト全てへの参照が必要である場合
for(var t : Transform in transform)
target = t.GetComponent(TargetScriptName);
target.DoSomething();
target.someVariable = someValue;
}
---------
内容が濃い!やっぱり情報を整理してある素晴らしいウェブサイトがUnity Gemsだとおもう。
次回はさらに「OnTriggerやOnCollisionの判定条件」をカバーしていきたい。