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

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

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

Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

Unity

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

Q&A

解決済

3回答

2872閲覧

【Unity】Instantiate(Prefab)で生成したオブジェクトの関数を呼び出したい(Updateの中で)

tamamo

総合スコア13

Unity3D

Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

Unity

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

0グッド

1クリップ

投稿2019/08/12 01:01

編集2019/08/12 01:04

とあるUnityの参考書を進めています。その中でどうしても解決できない問題があり、知恵を貸していただきたいです。今回の質問にはあまり関係ないと思いますが、3Dで制作している前提です。

まず、オブジェクトが二つあります。

text

1BallPrefab 2 .ボールのPrefab 3 .BallContoroller.csがアタッチされている。 4 5 6BallGenerator 7 ・空のオブジェクト 8 ・BallGenarator.csがアタッチされている。 9 ・Prefabもアタッチ済み。

次に参考書に書いてあるスクリプトです。(変数名などは変えてます)

BallController

1using System; 2using System.Collections; 3using System.Collections.Generic; 4using UnityEngine; 5 6public class BallController : MonoBehaviour 7{ 8 public void Shoot(Vector3 vector3) 9 { 10 GetComponent<Rigidbody>.AddForce(vector3); 11 } 12 13 private void OnCollisionEnter(Collision other) 14 { 15 GetComponent<Rigidbody>().isKinematic = true; 16 GetComponent<ParticleSystem>().Play(); 17 } 18}

BallGenerator

1using System.Collections; 2using System.Collections.Generic; 3using UnityEngine; 4 5public class BallGenerator : MonoBehaviour 6{ 7 public GameObject igaguriPrefab; 8 9 // Update is called once per frame 10 void Update() 11 { 12 if (Input.GetMouseButton(0)) { 13 GameObject Ball = Instantiate(BallPrefab) as GameObject; 14 Ball.GetComponent<BallController>().Shoot(new Vector3(0, 200, 2000)); 15 } 16 } 17}

やってることとしては、マウスをクリックしたらボールのRigidbodyに力を加えて奥に飛ばしているという感じです。

ただ、このスクリプトには問題があります。それはもちろんUpdateの中でGetComponentを使っているところです。上記のコードでは、

  • 生成したオブジェクトの関数を呼び出す時
  • 呼び出した関数内(Rigidbodyコンポーネントを呼び出す時)

この2点で GetComponentを使っています。

こちらの記事Update内で使う場合はキャッシュ化した方が良いとあったので、自分なりにキャッシュ化してみました。それが下記のコードです。

BallController

1using System; 2using System.Collections; 3using System.Collections.Generic; 4using UnityEngine; 5 6public class BallController : MonoBehaviour 7{ 8 private Rigidbody _rigidBody; 9 10 public void Shoot(Vector3 vector3) 11 { 12 _rigidBody.AddForce(vector3); 13 } 14 15 private void OnCollisionEnter(Collision other) 16 { 17 GetComponent<Rigidbody>().isKinematic = true; 18 GetComponent<ParticleSystem>().Play(); 19 } 20 21 private void Awake() 22 { 23 _rigidBody = GetComponent<Rigidbody>(); 24 }

BallGenerator

1using System.Collections; 2using System.Collections.Generic; 3using UnityEngine; 4 5public class BallGenerator : MonoBehaviour 6{ 7 public GameObject ballPrefab; 8 private GameObject _ball; 9 private BallController _ballController; 10 11 // Start is called before the first frame update 12 void Start() 13 { 14 // ここでInstantiateを使うと、クリック前にオブジェクトが生成されてしまう。 15 // _ball = Instantiate(ballPrefab) as GameObject; 16 // _ballController = _ball.GetComponent<BallController>(); 17 } 18 19 20 // Update is called once per frame 21 void Update() 22 { 23 if (Input.GetMouseButton(0)) { 24 _ball = Instantiate(ballPrefab) as GameObject; 25 _ball.GetComponent<BallController>().Shoot(new Vector3(0, 200, 2000)); 26 } 27 } 28}

呼び出した関数内(Rigidbodyコンポーネントを呼び出す時)のキャッシュ化はできました。しかし色々考えてはみたのですが、生成したオブジェクトの関数を呼び出す時のキャッシュ化ができませんでした。Start に書いてしまうと、マウスをクリックする前にオブジェクトが生成されてしまい、期待する動きではなくなってしまいます。

割と特殊?な状況だと思うのですが、こういった場合はどのようにキャッシュ化すれば良いでしょうか?それともこのまま
妥協してUpdate内にGetComponentを書いてしまった方が良いのでしょうか?

※情報が足りない場合は、追加いたしますので指摘お願いします。

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

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

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

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

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

guest

回答3

0

このコードだけで言うならば、そもそもキャッシュ化する必要が無いと思います。
例えば_ballを生成してShootした後、二度と_ballにアクセスすることが無いなら以下のような書き方でも出来ます。

C#

1public class BallGenerator : MonoBehaviour 2{ 3 public BallController ballPrefab; 4 5 // Update is called once per frame 6 void Update() 7 { 8 if (Input.GetMouseButton(0)) { 9 BallController _ball = Instantiate(ballPrefab); 10 _ball.Shoot(new Vector3(0, 200, 2000)); 11 } 12 } 13}
  • _ballは二度と使わないのでローカル変数でいい
  • 直接BallControllerとして生成すればGetComponentは要らない(まぁ内部で同じことやってると思うので負荷は変わらなさそうですが)
  • 2発目以降も新しいボールを普通に発射するだけの想定(1発しか撃てない仕様なら何かしら追記が必要)

Update内でGetComponentの是非についてはmokemokechickenさんの説明通りなので省略します。

投稿2019/08/12 03:24

編集2019/08/12 05:44
sakura_hana

総合スコア11425

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

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

tamamo

2019/08/12 05:31

Instantiate時にBallControllerでキャストする考えは思いつきませんでした。というより、C#のキャストについての知識がそこまでなかったので、勉強不足ですね・・・。回答ありがとうございました!
Ram.Type-0

2019/08/12 05:41

この回答の要点はballPrefabがBallController型で定義されていることだと思います。 下の回答で書きましたが、キャスト自体は冗長な記述です。 GetComponentの代わりにキャストを利用することは出来ません。
sakura_hana

2019/08/12 05:46

ご指摘の通り、as BallControllerは不要ですね(古いUnityを使っていた人間なので未だに書いちゃってました)。 これだったらワンライナーでいいじゃんという気もしますが、Ram.Type-0さんの回答と被っちゃうので他はそのままにしておきます。
tamamo

2019/08/12 06:12

>Ram.Type-0 さん ありがとうございます。理解しました。
guest

0

今回実現したい動作は「マウスをクリックする度に、 新しくBallを生成して飛ばすこと」だと思います。
Instantiate(ballPrefab) as GameObject は Prefabをベースに新しくObjectを生成する意味を持っていたと(うろ覚えながら)記憶しています。

ので、マウスが押される度に(ボールを新しく生成したいときに)呼び出す必要がどうしてもあるのではないでしょうか。
つまり、今のままのコードで良いのではないでしょうか。


ちなみに、「最初に1つだけInstanceを生成してあとは使いまわしたい」「でも、最初に使われるまで生成を遅らせたい」というような場合は、以下のような書き方をすることはよくあります(どんな言語でも)。

GameObject _ball; GameObject getBall() { if (_ball == null) { // is null の書き方を覚えてないので間違っているかも... _ball = Instantiate(ballPrefab) as GameObject; } return _ball; } void Update() { if (Input.GetMouseButton(0)) { // 最初に getBall() されるまで _ball は nullのまま。 getBall().GetComponent<BallController>().Shoot(new Vector3(0, 200, 2000)); } }

■ 追記1

GetComponent のパフォーマンスについて気にされているようなので、
私はUnity素人ですがちょっとコメントしておきますと、

  • Updateは毎フレーム呼ばれるので避けるべきだが、今回は マウスを押したときと限定的なので影響は大きくないだろう
  • そもそも生成した直後のObjectについているComponentなので、キャッシュしようがない

と思いました。

投稿2019/08/12 01:24

編集2019/08/12 01:34
mokemokechicken

総合スコア948

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

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

Ram.Type-0

2019/08/12 05:13

一応is nullはUnityだと地雷ですのであまり使わないほうが良いかと・・・ (UnityEngine.Objectを継承したクラスは全て obj == null はtrueになるがobj is nullがfalseになる  状態(主にInspectorではMissingあるいはNoneと表示される)である可能性があり、実際にしょっちゅう遭遇するため)
mokemokechicken

2019/08/12 05:15

そうなんですね。ありがとうございます!
tamamo

2019/08/12 05:21

回答ありがとうございます。Update内でGetComponentを使うのが、どれくらいの規模感でダメなのか微妙に理解し切れていなかったので、とても参考になりました。
guest

0

ベストアンサー

メインの質問については既に他の方が十分な回答をされているようですが、
パフォーマンスを上げたい、というのが趣旨のようですのでいくつか。

・この場合as GameObjectは不要

Instantiate(ballPrefab)の戻り値はballPrefabと同じ型なので、今回の場合キャストは不要です。
無駄な型チェックが入り、パフォーマンスが低下します。
(かなり古いUnityのバージョンではObject型だったのでキャストが必要だったとか・・・)

・Ballはローカル変数にしましょう(あるいはワンライナー)
Ballはメソッドの呼び出しを跨いで利用されていないので、var Ballと言った形でメソッド内で定義すれば良いです。
GC負荷等の観点からしても多少ローカル変数のほうが少し軽量です。
また、今回の場合であればワンライナーでも呼び出せます。
このパターンだとアセンブリレベルで一時変数ができないはずなので恐らくワンライナーのほうが
数ns高速ですがこれに意味があるかと言うと・・・
また、ワンライナーになったとしてもアセンブリレベルで一時変数が出来ないとは限りません。

C#

1using System; 2using System.Collections; 3using System.Collections.Generic; 4using UnityEngine; 5 6public class BallGenerator : MonoBehaviour 7{ 8 public BallController ballPrefab; 9 10 // Update is called once per frame 11 void Update() 12 { 13 if (Input.GetMouseButton(0)) { 14 Instantiate(ballPrefab).Shoot(new Vector3(0, 200, 2000)); 15 } 16 } 17}

・BallControllerをできる限りGameObjectの上の方につけましょう

GetComponentした際のComponentの取得は、貼り付けられている全てのComponentに対して上から順に検索を行います。ですので、より上につけたComponentはより早くGetComponentで取得することができます。

・BallContorller内部でちゃんとキャッシュをしましょう

多分今回一番パフォーマンスが上がるのはこれで、BallController内では明らかに可能なキャッシュが省かれているので、これをしましょう。

C#

1using System; 2using System.Collections; 3using System.Collections.Generic; 4using UnityEngine; 5 6public class BallController : MonoBehaviour 7{ 8 private Rigidbody _rigidBody; 9 private ParticleSystem _particleSystem; 10 11 public void Shoot(Vector3 vector3) 12 { 13 _rigidBody.AddForce(vector3); 14 } 15 16 private void OnCollisionEnter(Collision other) 17 { 18 _rigidBody.isKinematic = true; 19 _particleSystem.Play(); 20 } 21 22 private void Awake() 23 { 24 _rigidBody = GetComponent<Rigidbody>(); 25 _particleSystem = GetComponent<ParticleSystem>(); 26 } 27

投稿2019/08/12 05:09

Ram.Type-0

総合スコア424

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

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

tamamo

2019/08/12 06:11

回答ありがとうございます。particleSystemのキャッシュは、Update内じゃないから別にいいだろうと高をくくってそのままにしてました。キャッシュできる部分はキャッシュした方が良さそうですね。その他のパフォーマンス面についても教えてくださってありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問