質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

3回答

3882閲覧

コライダーと階層構造。

退会済みユーザー

退会済みユーザー

総合スコア0

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

1グッド

1クリップ

投稿2018/11/28 16:30

編集2018/11/28 16:43

前提・実現したいこと

衝突や検知などでゲームオブジェクトのコライダーを取得したとき、
そのコライダーのゲームオブジェクトが単一のゲームオブジェクトでなく、
キャラクターなどの階層構造でコライダーも階層の中にあり、
その取得した相手のゲームオブジェクトにスクリプトで何かしらの振る舞いをさせたい場合、
どういった設計がよいのかご教示お願い致します。

試したこと

例えば、キャラクターの手の部分のゲームオブジェクトにコライダーがアタッチしていて、
その手の部分のゲームオブジェクトは、キャラクターのルートオブジェクトでない場合を想定します。

このような場合、キャラクターのゲームオブジェクトの設計と、
その取得した手のゲームオブジェクトから、キャラクターに何かしらの振る舞いをさせる方法としては、
次のようなものがよいのかと考えました。

・キャラクターの振る舞いをするスクリプトはキャラクターのルートオブジェクトにアタッチさせる。 ・取得したコライダーの手のゲームオブジェクトからルートのゲームオブジェクトを取得して、  GetComponentでキャラクターのスクリプトにアクセスし、そのスクリプトで振る舞いを実行する。

このような設計が良いと考えているのですが、いかがでしょうか?
もしくは他にも良い設計がある場合、ご教示お願い致します。

また、GetComponentは負荷がかかるという認識がある為、なるべく避けたいのですが、
コライダーからスクリプトを取得する場合は、GetComponentしか方法がないと思うのですが、いかがでしょうか?

すみません、もう1点質問です。
今回のコライダーに限らず、そしてキャラクターにも限らず、基本的に階層構造のゲームオブジェクトは、
基本的にルートのゲームオブジェクトにスクリプトをアタッチすべきと考えているのですが、いかがでしょうか?

ko_yu👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

最終的には**「プロジェクトによる」**が結論になります。

コンポーネント志向では、出来るだけ単独クラス内で処理が完結することが原則です。
例えば「手に当たった物を吹き飛ばす(rigidbody.AddForceする)」という処理があった場合、それは「手オブジェクトに付けたスクリプト」で処理すべきだと思います。
(特にColliderの場合は原則として「OnCollisionEnterが呼ばれるのは、そのColliderが付いたGameObjectに付いたスクリプトが対象」なのでこの方針が顕著)

ただこのようにスクリプトを複数オブジェクトに分散させた場合、ヒエラルキー上で見通しが悪くなるのは確かなので、**「必要が無ければ1つのオブジェクトに集約する」**というのも良いやり方です。

なおその場合、参照さえ取れていれば動くので、必ずしも「キャラクターのルートオブジェクト」である必要もありません。
よくある手法は、シーン直下に「〇〇Manager」という単独オブジェクトを置いておき、そこで管理する方法です。(1つ集約させすぎると逆に見にくくなるので、何事も適度に、ですが)

この辺はインスタンスをどう持つのか?(プレハブにしてるか等)にもよるので、結論としては**「プロジェクトによる」**なのです。


あるオブジェクトから別オブジェクトのコンポーネントを取得する方法は大きく分けて以下の通り。
なのでGetComponentを使いたくないなら他の方法を検討になります。

  • GetComponent系列のスクリプトで取得して変数に格納
  • publicな変数にインスペクターからドラッグ&ドロップ
  • 別のスクリプトから渡す
  • InstantiateまたはAddComponentしたインスタンスを変数に格納

ただ、GetComponentも最小限に使う分には問題無いです(というか使わないとどうしようもないケースが実際ある)。
AwakeやStartなどで一度だけ呼ぶ他、使用頻度が低いなら「必要になったタイミングの最初の1回だけ取得」等にすることで負荷を減らせます。以下一例。

C#

1public class Sample : MonoBehaviour { 2 Hoge hoge; 3 4 // 滅多に呼ばれない処理 5 public void HogeHoge () { 6 if (hoge == null) hoge = GetComponent<Hoge>(); 7 // 以下、hogeを使った処理 8 } 9}

コメントを受けて追記。

検知エリア内にプレハブから取得した不特定多数の敵キャラがいる状況でGetComponentを使わない方法の一例。
(GetComponentを使う方法とどっちがいいかはプロジェクトによる。単純にルートのクラス取りたいだけならGetComponentの方が楽チン。一方、今回のように事前にリストを持つ方法の利点は、任意のタイミングで「全ての敵に何かする」等の処理が楽。但し敵が消える場合は下手に組むとnullエラーを引き起こすので要注意。あくまでテスト用の例なので実際に使う場合は適宜手を加えてください)

C#

1//敵キャラ生成スクリプト 2public class EnemyManager : MonoBehaviour { 3 //敵のプレハブの一番親(ルート)にEnemyクラスが付いている想定 4 public Enemy prefab; //プレハブ作ってインスペクタからセットしておく 5 6 //生成した敵のリスト 7 public Dictionary<string, Enemy> enemyList; 8 9 //敵キャラ生成 10 public void Make () { 11 //とりあえずDictionaryの初期化をここでやっているが実際はちゃんと考えるべき 12 enemyList = new Dictionary<string, Enemy> (); 13 14 //敵を10体作る。GameObject名を「Enemy_数字」にしておく。そしてDictionaryに入れる。 15 //(最初の回答内で挙げた「InstantiateまたはAddComponentしたインスタンスを変数に格納」を使っている) 16 for (int i = 0; i < 10; i++) { 17 Enemy enemy = Instantiate(prefab); 18 enemy.name = "Enemy_"+i; 19 enemyList.Add(enemy.name, enemy); 20 } 21 } 22} 23 24//検知エリアスクリプト 25public class Area : MonoBehaviour { 26 public EnemyManager enemyManager; //インスペクタからセットしておく 27 28 void OnTriggerEnter (Collider other) { 29 //DictionaryにあるかチェックしてからEnemy取得 30 if (enemyManager.enemyList.ContainsKey(other.transform.root.name)) { 31 Enemy enemy = enemyManager.enemyList[other.transform.root.name]; 32 //以下、enemyに対する処理 33 } 34 35 //「transform.root」はそのオブジェクトのルートオブジェクトを示す。 36 //DictionaryのKeyには「Enemyクラスが付いているオブジェクト(つまり衝突対象のルートオブジェクト)の名前」、valueには「Enemyクラス」が入っている。 37 //なので上記の方法で「衝突対象のルートオブジェクトに付いているEnemyクラス」が取得出来る。 38 } 39} 40

投稿2018/11/29 02:25

編集2018/12/01 02:31
sakura_hana

総合スコア11425

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/11/29 17:48 編集

ご回答ありがとうございます。 すみません、自分の質問の説明が下手で、もしかしたら間違って伝わっているかもしれないので、 念の為、質問のコライダーについてなのですが、 手オブジェクトのコライダーで検知するのではなく、検知したものが手オブジェクトのコライダーだったら、その手を持っているキャラクターに何らかの振る舞いをさせたいという状況です。 例えば、検知エリア内に侵入者の手のコライダーを検知したら、侵入者の行動を変えるとかです。 >なおその場合、参照さえ取れていれば動くので、必ずしも「キャラクターのルートオブジェクト」 >である必要もありません。 >よくある手法は、シーン直下に「〇〇Manager」という単独オブジェクトを置いておき、 >そこで管理する方法です。 これは、一般的なオブジェクトの設計としてのお話ですか?(自分が最後に質問した方のご回答ですか?) 衝突や検知などによって、相手のコライダーを取得した場合は、動的に取得となるので、 「参照さえ取れていれば」は不可能と捉えてよいですか? 別コンポーネントを取得する4点の方法のうち、 >別のスクリプトから渡す の方法がわからなかったのですが、どういったコードの書き方になりますか?(他3点はわかりました。) 必要になったタイミングで取得するコードのご提示ありがとうございます。 勉強になりました。
sakura_hana

2018/11/30 01:03 編集

> 例えば、検知エリア内に侵入者の手のコライダーを検知したら、侵入者の行動を変えるとかです。 なるほど、誤解してました。 ですがその場合でも、 ・検知エリアのスクリプトで手Colliderを接触検知→手Colliderの親オブジェクトにあるベースクラスをGetComponent→ベースクラスのメソッドを呼ぶ ・手のスクリプトで検知エリアColliderを接触検知→参照取得済みのベースクラスのメソッドを呼ぶ のどちらも考えられます。(どっちがよいかは他の要件次第だと思います) > シーン直下に「〇〇Manager」という単独オブジェクトを置いておき〜 一般的なオブジェクトの設計としての話です。 そしてColliderが絡む場合でも、用途によっては事前に参照を取った方が楽な場合があります。 例えば「シーン上に必ずプレイヤーは1人、シーンの最初から存在していて削除はされない」という前提で「プレイヤーとエリアが接触したらプレイヤーを停止させる」という実装をする場合、エリア側のスクリプトは以下のように書けます。 //接触の度に動的に参照を取る必要が無いケースの例 public class Area : MonoBehaviour { public Player player; //今回はインスペクターから設定しておく void OnTriggerEnter (Collider other) { //今回はタグで判定:衝突対象がPlayerタグを持っていたら if (other.tag == "Player") { player.Stop(); //プレイヤーを停止させる } } } > 別のスクリプトから渡す izmktrさんの回答にある「hit.player = player;」の部分が正にそれです。 親で取得したコンポーネントを子に渡すことで、GetComponent自体は一度で済むようになります。
退会済みユーザー

退会済みユーザー

2018/11/30 16:55

ご回答ありがとうございます。 ご提示いただいたコードのような方法があるのですね。 これならば確かに、Colliderを取得しつつも、GetComponentを使用せずにスクリプトの呼び出しができますね。斬新でした。勉強になります。 >・手のスクリプトで検知エリアColliderを接触検知→参照取得済みのベースクラスのメソッドを呼ぶ この参照取得済みのベースクラスも、今回ご提示いただいコードのような方法で参照取得済みになるということでしょうか? 別のスクリプトから渡す方法のご教示ありがとうございます。 検知エリア内に、今回の条件のようにプレイヤーが1人で限定されていたら、ご提示いただいたように参照を予め設定しておくことができることがわかりましたが、 一応、確認の為、質問させていただきたいのですが、 これがもし、検知エリア内に不特定多数の敵キャラ(しかもプレハブから生成したとかだったら)、 >・検知エリアのスクリプトで手Colliderを接触検知→手Colliderの親オブジェクトにあるベースクラスを>GetComponent→ベースクラスのメソッドを呼ぶ のように、GetComponentを使う方法しかないですよね?
sakura_hana

2018/12/01 02:42

> この参照取得済みのベースクラスも、今回ご提示いただいコードのような方法で参照取得済みになるということでしょうか? 敢えて確認するということは「参照取得済み」という状態の定義がよく分からないということでしょうか? 「あるオブジェクトから別オブジェクトのコンポーネントを取得する方法」の2つ目「publicな変数にインスペクターからドラッグ&ドロップ」を使っただけですが? > 検知エリア内に不特定多数の敵キャラ GetComponentを使わない方法も無くはないです。回答欄に追記しました。 Unityは「1つのやりたいこと」に対し「複数の解決方法」がある場合が多いです。 今回の件に限らず、「この方法しか無い」と決め付けるのではなく「他に方法は無いだろうか?」「この方法は使えないだろうか?」と常に考えるといいと思います(もちろん考えて検証した結果「この方法しか無いわ」となる場合もありますが)。
退会済みユーザー

退会済みユーザー

2018/12/01 05:45

ご回答ありがとうございます。 >敢えて確認するということは「参照取得済み」という状態の定義が >よく分からないということでしょうか? 「publicな変数にインスペクターからドラッグ&ドロップ」以外にも方法があるか確認したく、質問致しました(質問の仕方が下手ですみません)。 しかもよく考えたら、それがGetComponentとインスペクタの参照以外だったら、 >・別のスクリプトから渡す >・InstantiateまたはAddComponentしたインスタンスを変数に格納 の2点でしたね。失礼しました。 追記のコードありがとうございます。 このような方法もあるのですね。目からウロコでした。 本題と少しずれますが、まず複数のキャラ管理は自分はListを使っていたのですが、Dictionaryを使えば、ゲームオブジェクト名でキャラ管理ができるのですね。 Dictionaryについて調べましたが、Removeメソッドがあるみたいなので、敵が消える場合は、Removeを使って管理するということですね。 なるほど、そしてDictionaryでゲームオブジェクト名で管理することと、コライダーからもルートのゲームオブジェクト名が取って来れるので、GetComponentを使わない方法で実装できますね。 コライダーからはGetComponentするしか主な情報は取って来れないという固定観念で頭が固かったことを自覚しました。 コライダーから、ゲームオブジェクト名やタグを取ってきて、それを利用すればGetComponentをしなくてもスクリプトを取得できるんですね。 とは言え、Bongo様がおっしゃったようにGetComponentも悪ではないみたいなので、 sakura様のおっしゃるようにプロジェクトに合わせて、その時々で適切な使い方をしていきたいと思います。 今回のご教示で自分の中の設計方法の幅が広がりました。 これからUnityの開発において色々な方法を模索していきたいと思います。 ありがとうございました。
sakura_hana

2018/12/01 06:11

念の為の補足。 Dictionaryは「好きな型のKey(今回はGameObject名=string型)」と「好きな型のValue(今回はEnemy型)」を持った配列の一種です。 「ゲームオブジェクト名でキャラ管理ができる」というのは「そういう設計にしたから」なので、それ専門という訳ではありません。 何となく分かりやすいかなと思って例に使いましたが、クラス(インスタンス)から自身のGameObject名も取れるのでListでも同じような作りに出来るでしょう。 Dictionaryの仕様(Listと違って順番を持たない、同じKeyがあってはいけない等)や負荷が足を引っ張る場合があるので、単純な「配列」も含めてどれを使うか色々検討・検証が必要ですね、というお話なのでした。
退会済みユーザー

退会済みユーザー

2018/12/01 06:16

ご回答ありがとうございます。 なるほど、クラス(インスタンス)からも自身のGameObject名が取得できましたね。 やはり、これもどれを使うか色々検討ということですね、勉強になります。 ありがとうございます。
guest

0

GetComponentは排除するべきか...という点が気になりましたので、ご参考までに思いついた手を比較してみました。あくまでも一例ですので、条件が変われば優劣も変わる可能性があります。

下図のように、青球と赤キューブが接触すると、赤キューブが衝突を検出し緑シリンダーのメソッドを実行するという状況を用意しました。

シーン

緑シリンダーのスクリプト

C#

1using UnityEngine; 2using UnityEngine.EventSystems; 3 4public class MessageResponder : MonoBehaviour, MessageResponder.IHitHandler 5{ 6 public int HitCount; 7 8 public void OnHit() 9 { 10 this.HitCount++; 11 } 12 13 public interface IHitHandler : IEventSystemHandler 14 { 15 void OnHit(); 16 } 17}

赤キューブのスクリプト

C#

1using System; 2using UnityEngine; 3using UnityEngine.EventSystems; 4 5public class MessageSender : MonoBehaviour 6{ 7 public enum MessageMode 8 { 9 DirectCall, 10 DirectCallGetTagAndCompare, 11 DirectCallCompareTag, 12 ReferRoot, 13 TraverseParent, 14 GetComponentInParent, 15 SendMessageUpwards, 16 ExecuteHierarchy, 17 Find, 18 FindObjectOfType 19 } 20 21 private const int MessageCount = 1024 * 1024; 22 23 public MessageMode Mode; 24 public SceneResetter Resetter; 25 private MessageResponder responder; 26 27 private void Awake() 28 { 29 this.responder = GameObject.Find("Player").GetComponent<MessageResponder>(); 30 } 31 32 private void OnCollisionEnter(Collision collision) 33 { 34 if (this.Resetter.Reset) 35 { 36 return; 37 } 38 39 Debug.LogFormat("Hit! Mode:{0}", this.Mode.ToString()); 40 this.responder.HitCount = 0; 41 var time = Time.realtimeSinceStartup; 42 switch (this.Mode) 43 { 44 case MessageMode.DirectCall: 45 // 衝突相手が誰であろうと、事前に取得したresponderを直接参照する 46 // タイム0.12秒 47 48 // 相手がシーンで唯一ならいいですが、色々な相手と当たりうるのなら相手判定が必要になるでしょう 49 // 柔軟性に欠けますが、さすがに直接呼び出しだけのことはあって高速です 50 for (var i = 0; i < MessageCount; i++) 51 { 52 this.responder.OnHit(); 53 } 54 55 break; 56 case MessageMode.DirectCallGetTagAndCompare: 57 // 相手が特定のタグを持っているかを調べ、その上で事前に取得したresponderを直接参照する 58 // タイム2.6秒 59 60 // タグ比較を一つ入れただけで、速度はだいぶ低下してしまいました 61 for (var i = 0; i < MessageCount; i++) 62 { 63 if (collision.gameObject.tag == "Player") 64 { 65 this.responder.OnHit(); 66 } 67 } 68 69 break; 70 case MessageMode.DirectCallCompareTag: 71 // 相手が特定のタグを持っているかを調べ、その上で事前に取得したresponderを直接参照する 72 // 特定のタグを持っているか調べる部分をCompareTagに変更したバージョン 73 // タイム1.9秒 74 75 // 少しだけ高速になりました 76 // ==による比較は相手のタグをC#側に取り寄せて比較するのに対し、CompareTagは比較対象のタグを 77 // ネイティブコード側に送りつけてそちらで比較するらしいですが、その辺の違いが影響するのかもしれません 78 for (var i = 0; i < MessageCount; i++) 79 { 80 if (collision.gameObject.CompareTag("Player")) 81 { 82 this.responder.OnHit(); 83 } 84 } 85 86 break; 87 case MessageMode.ReferRoot: 88 // ルートトランスフォームからMessageResponderの取得を試みる 89 // タイム3.1秒 90 91 // ルートをいきなり参照するという都合上、プレイヤーは他のオブジェクトの子であってはならないという制限が生じます 92 // MessageResponderの取得のために1回GetComponentを使っており、合計約100万回のGetComponentが行われます 93 // 留意事項として、GetComponentの速度はゲームオブジェクトにアタッチされたコンポーネントの数に影響されうるらしいです 94 // とはいえ、一つのオブジェクトに数百個のコンポーネントをアタッチするなんてことをしない限りは大丈夫ではないでしょうか 95 for (var i = 0; i < MessageCount; i++) 96 { 97 var r = collision.transform.root.GetComponent<MessageResponder>(); 98 if (r != null) 99 { 100 r.OnHit(); 101 } 102 } 103 104 break; 105 case MessageMode.TraverseParent: 106 // 相手からMessageResponderの取得を試みて、失敗したら順次親をたどり、見つかるまで取得を試みる 107 // タイム64秒 108 109 // 急に遅くなってしまいました 110 // プレイヤーがルートでなくても対応可能ですが、階層が深い場合何度もGetComponentを行わねばならないのが難点かと思います 111 // 今回の場合、合計約400万回のGetComponentが行われるはずです 112 for (var i = 0; i < MessageCount; i++) 113 { 114 var t = collision.transform; 115 var r = t.GetComponent<MessageResponder>(); 116 while (r == null) 117 { 118 t = t.parent; 119 if (t == null) 120 { 121 break; 122 } 123 124 r = t.GetComponent<MessageResponder>(); 125 } 126 127 if (r != null) 128 { 129 r.OnHit(); 130 } 131 } 132 133 break; 134 case MessageMode.GetComponentInParent: 135 // 相手、またはその親へと再帰的にMessageResponderの取得を試みる 136 // タイム3.0秒 137 138 // TraverseParentと同様の動作になるはずですが、見た目もシンプルでずっと高速です 139 // どうやらネイティブコード側で探索処理を行っているようです 140 for (var i = 0; i < MessageCount; i++) 141 { 142 var r = collision.gameObject.GetComponentInParent<MessageResponder>(); 143 if (r != null) 144 { 145 r.OnHit(); 146 } 147 } 148 149 break; 150 case MessageMode.SendMessageUpwards: 151 // 相手にOnHitメッセージを送信する 152 // SendMessageUpwardsを使っているので、メッセージがヒエラルキーを上って伝達されていきプレイヤーまで到達する 153 // タイム4.0秒 154 155 // ヒエラルキーをさかのぼるのは先の二つと同様ですが、メッセージ伝達を行っているという意図が 156 // コードから読み取りやすく、見た目もシンプルです 157 // また、こちらはヒエラルキー上流のすべてのオブジェクトにメッセージが伝達されるのに対し、先の方法では 158 // 最初に見つかったMessageResponderにしかOnHitが発生しないという違いがあります 159 for (var i = 0; i < MessageCount; i++) 160 { 161 collision.gameObject.SendMessageUpwards("OnHit", SendMessageOptions.DontRequireReceiver); 162 } 163 164 break; 165 case MessageMode.ExecuteHierarchy: 166 // 相手にOnHitメッセージを送信する 167 // ExecuteHierarchyを使っているので、メッセージがヒエラルキーを上って伝達されていきプレイヤーまで到達する 168 // タイム53秒 169 170 // こちらはSendMessage系メソッドよりも新しいメッセージ伝達機構で、より高機能です 171 // メッセージが文字列でなくなったので、プログラマーのタイプミスなどに対して強そうですね 172 // 他にも渡せる引数に制限がなかったり便利ですが、C#側で処理している部分が多いためか、今回の対決では 173 // SendMessageUpwardsにだいぶ負けてしまいました 174 for (var i = 0; i < MessageCount; i++) 175 { 176 ExecuteEvents.ExecuteHierarchy<MessageResponder.IHitHandler>( 177 collision.gameObject, 178 null, 179 (responder, _) => responder.OnHit()); 180 } 181 182 break; 183 case MessageMode.Find: 184 // シーン内のどこかにあるであろうプレイヤーを探し、GetComponentでMessageResponderを取得する 185 // タイム3.0秒 186 187 // あまり実用的な場面が思いつきませんが、Find系メソッドを乱発した場合どうなるかを試してみようと思いました 188 // 遅い遅いと言われるFindですが、かなり高速に処理を終えることができました 189 // 今回は検索対象がヒエラルキーのルートにいますが、もしヒエラルキーの深い場所にいる場合は速度が変わってくるかもしれません 190 // あるいは内部的に検索結果をキャッシュする機構があるのでしょうか(未確認です)? 191 for (var i = 0; i < MessageCount; i++) 192 { 193 var p = GameObject.Find("Player"); 194 if (p == null) 195 { 196 continue; 197 } 198 199 var r = p.GetComponent<MessageResponder>(); 200 if (r != null) 201 { 202 r.OnHit(); 203 } 204 } 205 206 break; 207 case MessageMode.FindObjectOfType: 208 // シーン内のどこかにあるであろうMessageResponderを探す 209 // タイム30秒 210 211 // Findと似た結果になるはずですが、FindObjectOfTypeに変えるとむしろ遅くなってしまいました 212 for (var i = 0; i < MessageCount; i++) 213 { 214 var r = FindObjectOfType<MessageResponder>(); 215 if (r != null) 216 { 217 r.OnHit(); 218 } 219 } 220 221 break; 222 default: 223 throw new ArgumentOutOfRangeException(); 224 } 225 226 time = Time.realtimeSinceStartup - time; 227 Debug.LogFormat("Finished! Count:{0} Time:{1}", this.responder.HitCount, time); 228 this.Resetter.Reset = true; 229 } 230}

試行を終えるたびに腕を元に戻すためのスクリプト

C#

1using System; 2using System.Collections; 3using UnityEngine; 4 5public class SceneResetter : MonoBehaviour 6{ 7 public Rigidbody Forearm; 8 public Rigidbody Hand; 9 public Rigidbody UpperArm; 10 public MessageSender Sender; 11 public bool Reset; 12 private Vector3 forearmPosition; 13 private Vector3 handPosition; 14 private Vector3 upperArmPosition; 15 16 private IEnumerator Start() 17 { 18 this.handPosition = this.Hand.position; 19 this.forearmPosition = this.Forearm.position; 20 this.upperArmPosition = this.UpperArm.position; 21 var modeIndex = 0; 22 while (true) 23 { 24 this.UpperArm.isKinematic = true; 25 this.Forearm.isKinematic = true; 26 this.Hand.isKinematic = true; 27 this.UpperArm.MovePosition(this.upperArmPosition); 28 this.Forearm.MovePosition(this.forearmPosition); 29 this.Hand.MovePosition(this.handPosition); 30 if (!Enum.IsDefined(typeof(MessageSender.MessageMode), modeIndex)) 31 { 32 break; 33 } 34 this.Sender.Mode = (MessageSender.MessageMode)Enum.ToObject(typeof(MessageSender.MessageMode), modeIndex); 35 modeIndex++; 36 yield return new WaitForSeconds(5.0f); 37 38 this.UpperArm.isKinematic = false; 39 this.Forearm.isKinematic = false; 40 this.Hand.isKinematic = false; 41 yield return new WaitUntil(()=>this.Reset); 42 this.Reset = false; 43 } 44 } 45}

実行時間は下記のようになりました。

方法タイム
衝突相手に関わらず直接呼び出し0.12秒
タグ比較を行ってから直接呼び出し2.6秒
CompareTagでタグ比較を行ってから直接呼び出し1.9秒
相手のルートを取得しGetComponent3.1秒
親をたどってGetComponent64秒
GetComponentInParent3.0秒
SendMessageUpwards4.0秒
ExecuteHierarchy53秒
Findでプレイヤーを探しGetComponent3.0秒
FindObjectOfType30秒

私もsakura_hanaさんのご意見に同意いたします。明らかに同じオブジェクトが取得されると分かっているのに何度もFindやGetComponentを行うのは非効率的に思いますが、かといってGetComponentを見つけるたびに代替手法に置き換えようとするのはやりすぎな気がします。やりようによってはかえって遅くなってしまうかもしれません。
GetComponentは十分高速に作られているようですので、あんまり毛嫌いせずに使ってしまっていいんじゃないでしょうか。

【Unity】GameObject.Find 系関数の処理速度の検証結果 - コガネブログではFind系メソッド同士の比較を行っており、FindGameObjectWithTagが圧倒的に高速でFindObjectOfTypeは惨敗だったようですね。同じ「Find...」という名前の付いたメソッドでも違いがあって面白いです。
VS GetComponentおじさん - QiitaではGetComponentとDictionary引きを対決させているようです。これも興味深い結果だと思いました。

投稿2018/12/01 02:51

Bongo

総合スコア10807

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sakura_hana

2018/12/01 03:32

詳しいデータありがとうございます! やっぱり検証大事ですね。 「VS GetComponentおじさん」も面白かったです。まぁDictionary/ListのライバルとなるのはGetComponentではなくFindやFindObjectsOfTypeとかでしょうから……それよりは早い、と思いたい……(未検証)
退会済みユーザー

退会済みユーザー

2018/12/01 05:06

ご回答ありがとうございます。 ご検証ありがとうございます。 いろいろな方法があるのですね。 GetComponentInParentとかSendMessageUpwardsとかExecuteHierarchyとか、知らなかった方法がたくさんありました。勉強になります。 GetComponentが思いのほか速かったということもわかり、必要に応じて使っていきたいと思いました。 ありがとうございます。
guest

0

GetComponentは重いので、複数回取得するのなら、変数に入れておきましょう、ということでしょう

とりあえず、私なら親のStartで自分自身に処理を移譲してくれるようにかきますかね

public class Hit : MonoBehaviour { public Player player; void OnTriggerEnter(Collider collision){ player.TriggerEnter(collision); } void OnCollisionEnter(Collision collision){ player.CollisionEnter(collision); } } 親のスタート void Start(){ var player = GetComponent<Player>(); foreach(var c in GetComponentsInChildren<Collider>()){ var hit = c.gameObject.AddComponent<Hit>() as Hit; hit.player = player; } }

投稿2018/11/29 02:26

izmktr

総合スコア2856

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/11/29 18:17

ご回答ありがとうございます。 そのような移譲の方法ができるのですね、勉強になりました。 ちなみにAddComponent<Hit>()にasを付けるとどのような効果になりますか? 普段、asを使わずAddComponentしていました。
退会済みユーザー

退会済みユーザー

2018/11/30 17:04

ご回答ありがとうございます。 asは不要ということで勉強になりました。 ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問