Unityでインディゲームを作る!

Unityでのゲーム制作を目指し、それに関わる話題についてのブログ

UnityのGetComponentInParentとGetComponentInChildrenの挙動おかしくなってないですか?と思ったら勘違いでした。

 少し気になったことがあったので書きます。環境はWindows10Unity2020.3.22です。お前の使い方、環境が悪い!案件だったらすいません。と、この記事を書いた後に調べてみたら、どうやら自分の勘違いでした!

 結論から言えば、GetComponentInParentGetComponentInChildrenは指定したコンポーネントヒエラルキー上で探索するメソッドだが、自オブジェクトもその対象になるので、スクリプトが装着されているオブジェクトにそのコンポーネントがある場合は、そのコンポーネントを返す、ということのようです。

 

↓以後の文章はきちんと調べずに、自分の勘違いで勝手に怒りながら書いたものであり、自戒のために残しておきたいと思います。

 

はじまり

 Unityでのコーディングでよく使うメソッドと言えば、GetComponentであり、自分も以前にこれについての記事を書いたりしました。

 

 実際、コンポーネントを組み合わせてゲームオブジェクトを組み立てていくUnityにおいては、欠かすことのできない重要なメソッドです。かつそれなりに重いので扱いに注意が必要なモノでもあります。

 GetComponentにはいくつかバリエーションがあり、今回の議題となるGetComponentInParentGetComponentInChildrenがその中の一つです。

 Unityではゲームオブジェクト同士でヒエラルキー(階層)構造を作ることが出来て、これらのメソッドはそうした階層における親オブジェクト、子オブジェクトから、コンポーネントを取得するというメソッドになります。

 

本題

 というわけで、ようやく今回の本題ですが、このGetComponentInParentやGetComponentInChildrenが上手く機能していないようなのです。具体的には親オブジェクトのコンポーネントを取得しようとしても、実際には自オブジェクトのコンポーネントを取得してしまう、という状況です。

 自分の記憶が正しければ、当然InParentなのだから、自オブジェクトは除外されていたはずだし、しかも複数コンポーネントを取得するGetComponentsInChildrenなどでも、自オブジェクトのコンポーネントが含まれてしまっています。

 

 この現象って自分だけなんですかね?それとも以前からこういう仕様だったのか?うーーーん、というわけで、実例を元にこの現象を説明していきたいと思います。

GetComponentInParentTest_SceneView

 

通り抜けられる足場

 実例としては、アクションゲームでよくある、プレイヤーが下からジャンプですり抜けられる足場を作ってみます。

GetComponentInParentTest_TesstCube

 通常のコライダーはプレイヤーを弾いてしまうので、下からすり抜けようとする時だけ、トリガーモードにして、すり抜けたらまたコライダーに戻す、という方法にします。なので、コライダーとトリガーがそれぞれ2つ必要ということになります。

 

GetComponentInParentTest_TriggerInspector

 というわけで、このようにキューブの階層下にトリガーをつけたオブジェクトをセットします。ここにスクリプトも装着して、キューブのコライダー・コンポーネントをGetComponentInParentで取得する、というまぁ至極普通の手段を実行します。

 

GetComponentInParentTest_Script01

 自オブジェクトのトリガーでプレイヤーを検知し、GetComponentしたコライダーをトリガーモードに切り替え、プレイヤーを通すという処理です。しかし、これが上手くいかない!なぜか!GetComponentInParentが自オブジェクトのBoxCollider(トリガーモード)を取得しているからです。 これおかしくないですか?これ俺が悪いんすか!?

ここでスクリプトを少し改造します。

GetComponentInParentTest_Script02

 GetComponentsInParentに変更し、それが取得したコンポーネントを持つゲームオブジェクトをコンソールに表示して見ます。

GetComponentInParentTest_Console

 なぜか!Triggerオブジェクトも取得されちゃってます。なんで!自オブジェクトのコンポーネントも取っちゃうのは流石におかしいでしょ・・・

 

 いろいろあって、今回のケースでの解決策としては、もう階層構造を作らずに一つのオブジェクトにコライダーとトリガーを両方着けてしまって、GetComponentでコライダーの方だけ取得するという方法にしました。

GetComponentInParentTest_Collider&Trigger

 GetComponentには、ゲームオブジェクトに複数そのコンポーネントがある場合は、インスペクターにおいて上の方にあるコンポーネントを優先して取得する、という特性があります。それを利用して、目的のコンポーネントを取得させます。

GetComponentInParentTest_FinalScript

 つまり、最終的なスクリプトは逆にこのようなシンプルなものとなりました。結果として、プレイヤーも無事足場を通過して、その足場に着地することが出来ました。

GetComponentInParentTest_Resolve

 しかし、このやり方はGetComponentInParentの問題を解決したわけではなく、回避したにすぎません。今回に単純に足場のコライダーをトリガーモードにする、という解決法ならば、このやり方の方がむしろシンプルで良いと思いますが、場合によっては階層構造を作った方が良い場合もあるはずで、そうした場合はGetComponentInParentを使いたくなる状況も出てくるかもしれません。

 今自分が取り組んでいるプロジェクトでは同様な問題に直面し、ひとまずPublicで対象のゲームオブジェクトを選択するという方法を取ってます。これもまたシンプルな解決法ですが、いちいち手動で選択しないとダメだし、インスペクターに表示が増えます。とは言え、プレハブを活用するのであれば、楽な手段でもあります

まとめ?

 結局、GetComponentInParentが何故か、自オブジェクトのコンポーネントも対象にしてしまう現象の理由は分からず、じゃあ検索しろよって話なのですが、もしこの現象が他の人にも起こってたら結構な問題になっているはずです。

 ちょっと調べてみたいと思います。

 

最後に

 というわけで、ちゃんと調べる前に書いた文章でした。実際には昔から、自オブジェクトも含むという仕様だったようで、勘違いでした。

 しかし、InParentとしている以上、自オブジェクトも対象に含むのはAPIとしてどうなんだ、とも思うわけで、自オブジェクトのコンポーネントを取得したいなら、ただのGetComponentを使うんだから、そこおかしくないですか?とは思いました。