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

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

ただいまの
回答率

90.48%

  • Unity

    5638questions

    Unityは、ユニティテクノロジーが開発したゲームエンジンです。 主にモバイルやブラウザ向けのゲーム製作に利用されていましたが、3Dの重力付きゲームが簡単に作成できることから需要が増え、現在はマルチプラットフォームに対応しています。 言語はC言語/C++で書かれていますが、C#、JavaScript、Booで書かれたコードにも対応しています。

Unity シューティングゲームにオブジェクトプールを導入したい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 2,680

DiG5219

score 17

今作成中のシューティングゲームがInstantiateとDestroyを繰り返し、モバイル端末だと特に動きがカクカクになってきました。何かいい手はないかと思って調べているとオブジェクトプールという手段を知り、3日ほど挑戦していますが、うまくできなかったので質問させていただきます。

作成中のシューティングゲームは基本的にUnityの公式のチュートリアルを参考にしています。公式のオブジェクトプールのチュートリアルも試してみたんですが、結構変更している部分があり、公式のようにはなりませんでした。

タップして動かし連続して弾を撃つプレイヤーと、生成されると一定のスピードで動き、一定の間隔で弾を撃ってくるエネミーがあります。
エネミーには弾を撃つものと撃たないものがあり、撃つものはshotPositionを子要素として持ちます。エネミーは何種類かあり、撃つ弾の種類も、shotPositionの数や位置も変わってきます。

using UnityEngine;
using System.Collections;

public class Enemy : MonoBehaviour
{

    Spaceship spaceship;

    public int hp;

    IEnumerator Start()
    {
        spaceship = GetComponent<Spaceship>();

        spaceship.Move(transform.up * -1);

        if (spaceship.canShot == false)
        {
            yield break;
        }

        while (true)
        {
            for (int i = 0; i < transform.childCount; i++)
            {
                Transform shotPosition = transform.GetChild(i);

                spaceship.Shot(shotPosition);
            }
            yield return new WaitForSeconds(spaceship.shotDelay);
        }

    }
    void OnTriggerEnter2D(Collider2D c)
    {
        if (c.tag != "Bullet(Player)") return;

        Transform playerBulletTransform = c.transform.parent;

        Bullet bullet = playerBulletTransform.GetComponent<Bullet>();

        hp = hp - bullet.power;

        Destroy(c.gameObject);


        if (hp <= 0)
        {

            spaceship.Explosion();

            Destroy(gameObject);
        }
    }
}

このエネミースクリプトから

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

[RequireComponent(typeof(Rigidbody2D))]
public class Spaceship : MonoBehaviour {

    public float speed;

    public float shotDelay;

    public GameObject bullet;

    public bool canShot;

    public GameObject explosion;


    public void Shot(Transform origin)
    {
        Instantiate(bullet, origin.position, origin.rotation);
    }
    public void Move(Vector2 direction)
    {
        GetComponent<Rigidbody2D>().velocity = direction * speed;
    }
    public void Explosion()
    {
        Instantiate(explosion, transform.position, transform.rotation);
    }

}

スペースシップスクリプトのShot(transform origin)を呼び出してエネミーごとに自分の子要素であるshotPositionのtransformを渡して、その位置から弾を撃っています。
Shot文で呼び出されたbulletはバレットスクリプトを持っており、

using UnityEngine;
using System.Collections;

public class Bullet : MonoBehaviour
{

    public int speed = 10;

    public int power;


    void Start()
    {
        GetComponent<Rigidbody2D>().velocity = transform.up.normalized * speed;
    }
}


バレットスクリプトでは、生成されると同時にまっすぐ進むだけです。

プレイヤーが撃った弾はエネミーにあたるとDestroyされます。
ステージの四方をDestroyAreaに囲まれており、敵に当たらなかったプレイヤーの弾やエネミーの弾はDestroyAreaに当たることで、破壊されるようになっています。

[http://megumisoft.hatenablog.com/entry/2016/06/25/133033](URL)

調べたところ、こちらのサイトが一番わかりやすく、アレンジできそうでしたので試行錯誤しましたができませんでした。サイトの方にあるBulletGeneratorクラスにあることをこちらのSpaceshipに書けば実装できそうな気がしましたが、こちらでのエネミーごとに違うshotPositionから発射するということが障害になっているのか、うまくできませんでした。

情報等足りないところや、わかりにくいところ等あれば追記します。
ぜひ回答よろしくお願いします。

以下追記になります。
kaka1102さんの回答を参考にいろいろ変更してみました。
プレイヤーはエネミーの弾は管理していなく、プレイヤーとエネミーが共通して持っているスペースシップというスクリプトで弾の撃つ撃たないを管理しています。
なので、参考サイト、kaka1102さんが仮に作成したBulletGeneratorを組み合わせて、スペースシップに追加してみました。

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

[RequireComponent(typeof(Rigidbody2D))]
public class Spaceship : MonoBehaviour {

    List<Bullet> list_Bullets = new List<Bullet>();

    const int MAX_BULLETS = 20;


    public float speed;

    public float shotDelay;

    public GameObject bullet;

    public bool canShot;

    public GameObject explosion;

    void Start()
    {
        Bullet bulleta;
        // 最初に一定数の弾を備蓄しておく
        for (int i = 0; i < MAX_BULLETS; i++)
        {
            // 弾の生成
            bulleta = Instantiate(bullet).GetComponent<Bullet>();

            bulleta.transform.parent = this.transform;
            // 発射前は非アクティブにしておく
            bulleta.gameObject.SetActive(false);
            // Listに追加
            list_Bullets.Add(bulleta);
        }
    }


    public void Shot(Transform origin)
    {
        for (int i = 0; i < list_Bullets.Count; i++)
        {
            {
                if (!list_Bullets[i].gameObject.activeSelf)
                {
                    list_Bullets[i].transform.position = origin.position;
                    list_Bullets[i].transform.rotation = origin.rotation;
                    list_Bullets[i].gameObject.SetActive(true);
                    break;
                }
            }



            //Instantiate(bullet, origin.position, origin.rotation);
        }
    }
    public void Move(Vector2 direction)
    {
        GetComponent<Rigidbody2D>().velocity = direction * speed;
    }
    public void Explosion()
    {
        Instantiate(explosion, transform.position, transform.rotation);
    }

}

イメージ説明
すると、最初の一発は弾を発射するんですが、次からはこのように止まってしまいました。

この状態では、エラーは出ませんが、画像にあるDestroyAreaという画面外のオブジェクトに当たりエネミーが破壊され、次のエネミーが出てきたところで

UnassignedReferenceException: The variable bullet of Spaceship has not been assigned.
You probably need to assign the bullet variable of the Spaceship script in the inspector.
UnityEngine.Object.Instantiate[GameObject] (UnityEngine.GameObject original) (at C:/buildslave/unity/build/Runtime/Export/UnityEngineObject.cs:189)

という同じエラーが3つ出てきます。
このエラーをダブルクリックした先はスペースシップスクリプトの
bulleta = Instantiate(bullet).GetComponent<Bullet>();
になります。

画像中でプレイヤーか弾が出ていませんが、プレイヤーの弾はDestroyAreaにあたると、非表示になった状態で、上へ永遠に飛んで行っていました。

DestroyAreaのスクリプトはこちらです。

using UnityEngine;
using System.Collections;

public class DestroyArea : MonoBehaviour {

    void OnTriggerEnter2D(Collider2D c)
    {
        if (c.tag == "Bullet(Player)")
        {
            c.gameObject.SetActive(false);
        }
        else if (c.tag == "Bullet(Enemy)") { 
            c.gameObject.SetActive(false);
        }
        else
        {
            Destroy(c.gameObject);
        }
    }
}

以上kaka1102さんの回答を参考に改良した点になります。

よろしくお願いします。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

checkベストアンサー

0

Playerがエネミーのショットを管理したりするというところは少し違和感がありますが参照先のような実装をしていればおそらく動くものと思います。

うまくいかなかった箇所をもう少し具体的に教えていただければと思います。
例えば
Bulletの出現箇所が想定していたところと違うところに出た。
そもそもBullet自体が動いていない
等...

それとBulletGeneratorの動作を実装し、うまくいかなかったソースも載せていただけると答えやすいと思います。

僕の方でもそれっぽいものは作成ししてみました。
実装の詳細は多少違いますが一応載せておきます。

まずはBulletGeneratorから、これは子要素にBulletオブジェクトを複数持つオブジェクトです。
子要素の中からGetActive()でfalseのものを探し、あればtrueにし弾として動かします。

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


//------------------------------------------------------------------------------
//==============================================================================
// 弾プール.
public class BulletGenerator : MonoBehaviour {


    [SerializeField] private GameObject[] bullets = null;    // 子要素に入っている弾プール.


    //------------------------------------------------------------------------------
    void Start() {

        // 子要素の弾たちを配列にしている.
        Queue< GameObject > q = new Queue< GameObject >();
        for ( int i = 0; i < this.transform.childCount; ++i ) {
            q.Enqueue( this.transform.GetChild( i ).gameObject );
        }
        this.bullets = q.ToArray();
        q.Clear();

    }


    //------------------------------------------------------------------------------
    void Update() {}


    //------------------------------------------------------------------------------
    public void shot(
            Transform origin ) {

        // 子要素の配列の中から使えるものを探し、あれば使いまわす.
        for ( int i = 0; i < this.bullets.Length; ++i ) {
            if ( !this.bullets[ i ].GetActive() ) {
                this.bullets[ i ].SetActive( true );
                this.bullets[ i ].transform.position = origin.position;
                this.bullets[ i ].transform.rotation = origin.rotation;
                break;
            }
        }
    }

}

プレイヤーがショットを撃つ場合は以下のような感じです。
PCでAキーを押したらショットを撃ちます。
これでオブジェクトプール(BulletGenerator)にショットを依頼します。
同様にエネミーのときもBulletGeneratorにショット依頼をすると弾が打てるようになると思います。
あとはBullet.csで何かに当たったときや画面買いに出たときにSetActive( false )でそのオブジェクトを無効にすれば弾オブジェクトの使いまわしができます。

using UnityEngine;
using System.Collections;

public class Player : MonoBehaviour {

    public BulletGenerator bg = null;

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {

        if ( Input.GetKeyDown( KeyCode.A ) ) {
            this.bg.shot( this.transform );
        }

    }
}

一応僕の環境ではうまくいきました。
ヒエラルキーの配置はこんな感じです。
イメージ説明

追加の回答です。

二発目以降の弾が出ない原因はすみません、わからないです。

このエラーは初期化されていない変数を利用しようとしたときに出るものです。
Spaceshipのbulletが初期化されていないために発生したものと思われます。
このエラー自体初めて見たので対処方法はいまいちわからないのですが、
念のため弾オブジェクトを新たに生成(Instantiate)しない方法で、あらかじめエネミーの子要素として弾を必要数配置し、プレハブ化すればそのエラーは出ないものと思います。
なので新たに弾を生成せずにすべての子要素にSetActive( false )を入れ、Listに登録するだけで済みます。

具体的にはSpaceshipのスクリプトのStart()内の処理を

void Start()
    {
        Transform t;
        //Bullet bulleta;
        // 最初に一定数の弾を備蓄しておく
        // 子要素全てにアクセスし、弾オブジェクトだけをリストに登録する.
        for (int i = 0; i < transform.childCount; i++)
        {
            // 弾の生成
            //bulleta = Instantiate(bullet).GetComponent<Bullet>();
            t = transform.GetChild( i );
            if ( t.gameObject.name != "EnemyBullet" )
                continue;
            Bullet bulleta = t.GetComponent< Bullet >();


            // 発射前は非アクティブにしておく
            bulleta.transform.parent = this.transform;
            bulleta.gameObject.SetActive(false);
            // Listに追加
            list_Bullets.Add(bulleta);
        }
    }

に変更し、すでにあるEnemy以下の弾のプールから登録するだけになります。
弾の名前がEnemuyBullet(Clone)ではないのはInstantiateしてないためです。

弾がSetActive( false )で移動し続けるのも原因はいまいち判然としませんが停止処理を挟めば大丈夫かと思います

なのでDestroyAreaに当たったら別途弾を止める処理を入れる必要があるかと思います。
例えば、Bulletスクリプトに停止用のメソッドを追加し以下のように呼び出してからSetActive( false )する等です。

void OnTriggerEnter2D(Collider2D c)
    {
        if (c.tag == "Bullet(Player)")
        {
            c.GetComponent< Bullet >().stopBullet();
            c.gameObject.SetActive(false);
        }
        else if (c.tag == "Bullet(Enemy)") { 
            c.GetComponent< Bullet >().stopBullet();
            c.gameObject.SetActive(false);
        }
        else
        {
            Destroy(c.gameObject);
        }
    }

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2016/12/16 03:38

    回答ありがとうございます。参考に改良させていただきました。しかし、またもや正常に動作しなかったので質問内容に追記しました。お気づきの点等ありましたらよろしくお願いします。

    キャンセル

  • 2016/12/26 03:14

    追加ありがとうございます。試行錯誤を重ねていたんですが、やはりうまくいかず、参考サイトの通りのものをもう一度2Dにして作成してみました。すると、スペースを10回連打すれば、思ったように弾が非表示から表示になりうまく発射できたのですが、10発撃ち終わって一度非表示になったものをもう一度発射させようとすると非表示から表示にはなりました。しかし、表示されたその場から動かなくなりました。kaka1102さんは参考サイトの通りに作ると、綺麗に動きましたか??

    キャンセル

  • 2016/12/27 16:56

    僕の方ではうまく動作していました、
    今は出先にいるので帰宅したのちにこちらもソースを確認してみます

    キャンセル

  • 2016/12/28 20:46

    遅れました以下のURLにプロジェクトごと配置したのでソースを確認していただけたらと思います。
    https://github.com/DevelopFRM/TeratailShitsumon
    Resourcesフォルダ内のBulletGeneratorシーンを開くとテストができます。
    シーンを再生してスペースキーでショットを撃つことができます

    キャンセル

  • 2017/01/12 00:07

    回答いただいたのに返信が遅くなってしまい、申し訳ありません。他用で忙しく、手を付けられていませんでした。kaka1102さんに作成していただいたプロジェクトですと、綺麗に動作しました。ありがとうございます。私は2Dで作っていますので、そちらでも対応できるようにまた試してみたいと思います。

    キャンセル

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

  • Unity

    5638questions

    Unityは、ユニティテクノロジーが開発したゲームエンジンです。 主にモバイルやブラウザ向けのゲーム製作に利用されていましたが、3Dの重力付きゲームが簡単に作成できることから需要が増え、現在はマルチプラットフォームに対応しています。 言語はC言語/C++で書かれていますが、C#、JavaScript、Booで書かれたコードにも対応しています。