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

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

ただいまの
回答率

89.06%

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

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 1,363

tamamo

score 13

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

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

BallPrefab
   .ボールのPrefab
   .BallContoroller.csがアタッチされている。


BallGenerator
   ・空のオブジェクト
   ・BallGenarator.csがアタッチされている。
   ・Prefabもアタッチ済み。

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

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BallController : MonoBehaviour
{
    public void Shoot(Vector3 vector3)
    {
        GetComponent<Rigidbody>.AddForce(vector3);
    }

    private void OnCollisionEnter(Collision other)
    {
        GetComponent<Rigidbody>().isKinematic = true;
        GetComponent<ParticleSystem>().Play();
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BallGenerator : MonoBehaviour
{
    public GameObject igaguriPrefab;

    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButton(0)) {
            GameObject Ball = Instantiate(BallPrefab) as GameObject;
            Ball.GetComponent<BallController>().Shoot(new Vector3(0, 200, 2000));
        }
    }
}

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

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

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

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

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

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BallController : MonoBehaviour
{
    private Rigidbody _rigidBody;

    public void Shoot(Vector3 vector3)
    {
        _rigidBody.AddForce(vector3);
    }

    private void OnCollisionEnter(Collision other)
    {
        GetComponent<Rigidbody>().isKinematic = true;
        GetComponent<ParticleSystem>().Play();
    }

    private void Awake()
    {
        _rigidBody = GetComponent<Rigidbody>();
    }
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BallGenerator : MonoBehaviour
{
    public GameObject ballPrefab;
    private GameObject _ball;
    private BallController _ballController;

    // Start is called before the first frame update
    void Start()
    {
        // ここでInstantiateを使うと、クリック前にオブジェクトが生成されてしまう。
        // _ball = Instantiate(ballPrefab) as GameObject;
        // _ballController = _ball.GetComponent<BallController>();
    }


    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButton(0)) {
            _ball = Instantiate(ballPrefab) as GameObject;
            _ball.GetComponent<BallController>().Shoot(new Vector3(0, 200, 2000));
        }
    }
}

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

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

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

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+4

今回実現したい動作は「マウスをクリックする度に、 新しく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 14:13

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

    キャンセル

  • 2019/08/12 14:15

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

    キャンセル

  • 2019/08/12 14:21

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

    キャンセル

+3

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

public class BallGenerator : MonoBehaviour
{
    public BallController ballPrefab;

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/08/12 14:31

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

    キャンセル

  • 2019/08/12 14:41

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

    キャンセル

  • 2019/08/12 14:46

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

    キャンセル

  • 2019/08/12 15:12

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

    キャンセル

checkベストアンサー

+2

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

・この場合as GameObjectは不要

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

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

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BallGenerator : MonoBehaviour
{
    public BallController ballPrefab;

    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButton(0)) {
            Instantiate(ballPrefab).Shoot(new Vector3(0, 200, 2000));
        }
    }
}

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

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

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

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

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BallController : MonoBehaviour
{
    private Rigidbody _rigidBody;
    private ParticleSystem _particleSystem;

    public void Shoot(Vector3 vector3)
    {
        _rigidBody.AddForce(vector3);
    }

    private void OnCollisionEnter(Collision other)
    {
        _rigidBody.isKinematic = true;
        _particleSystem.Play();
    }

    private void Awake()
    {
        _rigidBody = GetComponent<Rigidbody>();
        _particleSystem = GetComponent<ParticleSystem>();
    }

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/08/12 15:11

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

    キャンセル

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

  • ただいまの回答率 89.06%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る