【Unity】FindAnyObjectByTypeで参照を取得する際は継承にも注意

【Unity】FindAnyObjectByTypeで参照を取得する際は継承にも注意

FindAnyObjectByTypeを使うことで、シーン内のコンポーネントを取得できます。Prefab内から別のゲームオブジェクトにアタッチしたコンポーネントへの参照を取得する際には便利なので、うまく活用していくと実装がしやすくなります。

ただ、継承関係があるクラスに対して使う際にはちょっと注意が必要だったりします。というのも、あるクラスを継承した管理用クラスなどを取得すると、基底クラスの方が取得されたり、同じ基底クラスを継承した別のクラスが取得されたりするので、型の確認をしておくと安心です。

 

 

環境

Mac OS Sequoia

Unity6.3 LTS (6000.3.4f1) Silicon

 

参考資料

 

便利なFindAnyObjectByType

MonoBehaviourを継承しているクラスで使用できるFindAnyObjectByType()のメソッドは、シーン内のコンポーネントやスクリプトを取得するのに便利なメソッドです。

例えばPrefabの中からシーン内のスクリプトを参照したい場合には、インスペクターウィンドウから事前に参照をアサインすることが難しかったりします。こうした時に、動的にシーン内の参照を検索・取得できるFindAnyObjectByType()のメソッドは重宝します。

他にも、シーン内にひとつしかないことを担保したいシングルトンのクラスを作る際にも便利に使えます。例えばインスタンスへの参照を保持するstaticなプロパティが外部から呼ばれた際に、インスタンスの有無を確認して、FindAnyObjectByType()を使って同じ型がシーン内にいるかどうかを確かめたりなどですね。同じクラスを持つゲームオブジェクトが作成された際は、Awake()のタイミングでstaticなフィールドの参照を確認し、もう既に存在しているならDestroy()で破棄する流れになります。

以下はRPGのチュートリアルで作ったフラグ管理のクラスのサンプルコードで、Instanceというプロパティ内でFindAnyObjectByType()を使用しています。

 

都度ゲームオブジェクトを検索して、GetComponentして……という手間が省けるので、管理用クラスに関してはシングルトンにしておくと便利です。

ただ、継承関係があるクラスでFindAnyObjectByType()を使用する際、場合によっては問題が起きることもあるので、型の確認をしておくと安心です。

 

どんな問題が起きるか

上記のようなシングルトンの管理クラスを作った際、継承関係があると取得されるコンポーネントが意図しないものになることがあります。自分自身のクラスを取得しているつもりが、同じ継承元をもつ別クラスが取得されたりも。シングルトンとして動作する場合、片方がDestroyされてゲーム内で動作が止まっていた……なんてこともあります。

FindAnyObjectByType()はシーン内に存在する、指定した型のコンポーネントへの参照を取得してくれますが、継承元のクラスを取得しようとした際に、派生したクラスまで取得の対象になったりします。この辺りは文章で書くと分かりにくいので、コードでサンプルを作ってみましょう。

継承元のクラスをBasePikachu、派生したクラスをDerivedRaichuにしました。派生というよりは進化のような気もしますが、これで進めていきます。

BasePikachuのクラスは以下のように実装しました。鳴き声を出力するシンプルな実装です。

 

続いてDerivedRaichuのクラスは以下の通りです。BasePikachuのクラスを継承しており、ShowMessage()をオーバーライドしました。

 

この2つのクラスに対してFindAnyObjectByType()を使って参照を取得し、ShowMessage()を呼び出していくクラスも作成します。

 

BasePikachuへの参照を取得すると「ピカチュウ!」とコンソールに表示され、DerivedRaichuへの参照を取得すると「ライチュウ!」とコンソールに表示されるはずです。

まずはシーン内にそれぞれのスクリプトをアタッチしたゲームオブジェクトを作成します。

ゲームオブジェクトを作っておく
ゲームオブジェクトを作っておく

 

この状態でゲームを実行すると、以下のように出力されました。先にBasePikachuへの参照を取得しているので「ピカチュウ!」「ライチュウ!」の順番で出力されており、何も問題がないように見えます。

意図した通り……のはず
意図した通り……のはず

 

FindAnyObjectByType()はシーン内に存在する、任意の対象のコンポーネントへの参照を取得します。ドキュメントには、呼び出しごとに同じ対象となるとは限らないと書かれているので、DerivedRaichuがアタッチされたゲームオブジェクトを増やしてみることで、そちらが取得される確率を上げてみたいと思います。

個体値厳選してるのかな?
個体値厳選してるのかな?

 

この状態でゲームを実行してみると、以下のように表示されました。先に出力されるメッセージは「ピカチュウ!」であってほしいのですが、派生した側が取得されているようですね。

なんということでしょう
なんということでしょう

 

このように、継承関係がある場合は継承元のクラスを取得したい場合でも派生クラスが取得される場合があります。

 

対策

継承元のクラスを指定して使いたい場合には、派生クラスが取得されると意図しない動きになったりします。そのため、型の確認をしておくと安心です。

FindAnyObjectByType()で取得したコンポーネントの型を確認する形だと、型が違っていた場合に再取得するというのも大変です。というのも、その型を持ったコンポーネントのうち、取得される対象が保証されていないので、目的のコンポーネントを取得するにはランダム要素が大きくなります。

そこで、対象のコンポーネントをまとめて取得できるFindObjectsByType()を使用して一括で取得後、foreach文やLinqで対象の型のものを取得するのが良いかと思います。というわけで上で紹介したFindAnyTestのクラスに処理を追加しました。(nullチェックは省いています)

foreach文の方は、型を比較して最初に見つかったBasePikachuのオブジェクトを使ってShowMessage()を呼び出しています。

Linqの方はWhereで合致する対象を抜き出した後、FirstOrDefaultで最初のものを取得し、ShowMessage()を呼び出しています。

型の比較については、GetType()で取得した型を == で比較する形にしました。isで比較するとBasePikachuから派生する型の場合もtrueになってしまうので、typeofを使っています。

型の比較を行う形で実行すると、以下のように出力されます。型の比較を行った3, 4番目の出力では、BasePikachunのShowMessage()が呼ばれています。

ピカチュウが鳴いてくれた
ピカチュウが鳴いてくれた

 

こんな感じで、継承元となっているクラスのメソッドを使いたい場合には、型の比較を行なうと良いかと思います。

 

まとめ

FindAnyObjectByType()のメソッドを使ってシーン内のコンポーネントを取得できるのは便利なので、うまく活用していくのが良いかと思いますが、継承関係があるクラスに対して使う場合は、継承元となるクラスを使いたいのに派生クラスが取得されるケースもあります。

その場合はFindObjectsByType()のメソッドを使って、対象となるコンポーネントを全て取得したのち、型の比較を行なってから処理を行なうと安心です。

 

     

ゲーム開発の攻略チャートを作りました!

CTA-IMAGE

「ゲームを作ってみたいけど、何から手を付けていいか分からない!」


そんなお悩みをお持ちの方向けに、todoがアプリをリリースした経験を中心に、ゲーム作りの手順や考慮すべき点をまとめたe-bookを作成しました。ゲーム作りはそれ自体がゲームのように楽しいプロセスなので、「攻略チャート」と名付けています。


ゲームを作り始めた時にぶつかる壁である「何をしたら良いのか分からない」という悩みを吹き飛ばしましょう!