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

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

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

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

Q&A

解決済

1回答

5478閲覧

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

DiG5219

総合スコア26

Unity

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

0グッド

0クリップ

投稿2016/12/14 09:52

編集2016/12/15 18:35

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

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

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

lang

1using UnityEngine; 2using System.Collections; 3 4public class Enemy : MonoBehaviour 5{ 6 7 Spaceship spaceship; 8 9 public int hp; 10 11 IEnumerator Start() 12 { 13 spaceship = GetComponent<Spaceship>(); 14 15 spaceship.Move(transform.up * -1); 16 17 if (spaceship.canShot == false) 18 { 19 yield break; 20 } 21 22 while (true) 23 { 24 for (int i = 0; i < transform.childCount; i++) 25 { 26 Transform shotPosition = transform.GetChild(i); 27 28 spaceship.Shot(shotPosition); 29 } 30 yield return new WaitForSeconds(spaceship.shotDelay); 31 } 32 33 } 34 void OnTriggerEnter2D(Collider2D c) 35 { 36 if (c.tag != "Bullet(Player)") return; 37 38 Transform playerBulletTransform = c.transform.parent; 39 40 Bullet bullet = playerBulletTransform.GetComponent<Bullet>(); 41 42 hp = hp - bullet.power; 43 44 Destroy(c.gameObject); 45 46 47 if (hp <= 0) 48 { 49 50 spaceship.Explosion(); 51 52 Destroy(gameObject); 53 } 54 } 55} 56

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

lang

1using UnityEngine; 2using System.Collections; 3using System.Collections.Generic; 4 5[RequireComponent(typeof(Rigidbody2D))] 6public class Spaceship : MonoBehaviour { 7 8 public float speed; 9 10 public float shotDelay; 11 12 public GameObject bullet; 13 14 public bool canShot; 15 16 public GameObject explosion; 17 18 19 public void Shot(Transform origin) 20 { 21 Instantiate(bullet, origin.position, origin.rotation); 22 } 23 public void Move(Vector2 direction) 24 { 25 GetComponent<Rigidbody2D>().velocity = direction * speed; 26 } 27 public void Explosion() 28 { 29 Instantiate(explosion, transform.position, transform.rotation); 30 } 31 32} 33 34

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

lang

1using UnityEngine; 2using System.Collections; 3 4public class Bullet : MonoBehaviour 5{ 6 7 public int speed = 10; 8 9 public int power; 10 11 12 void Start() 13 { 14 GetComponent<Rigidbody2D>().velocity = transform.up.normalized * speed; 15 } 16}

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

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

http://megumisoft.hatenablog.com/entry/2016/06/25/133033

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

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

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

lang

1using UnityEngine; 2using System.Collections; 3using System.Collections.Generic; 4 5[RequireComponent(typeof(Rigidbody2D))] 6public class Spaceship : MonoBehaviour { 7 8 List<Bullet> list_Bullets = new List<Bullet>(); 9 10 const int MAX_BULLETS = 20; 11 12 13 public float speed; 14 15 public float shotDelay; 16 17 public GameObject bullet; 18 19 public bool canShot; 20 21 public GameObject explosion; 22 23 void Start() 24 { 25 Bullet bulleta; 26 // 最初に一定数の弾を備蓄しておく 27 for (int i = 0; i < MAX_BULLETS; i++) 28 { 29 // 弾の生成 30 bulleta = Instantiate(bullet).GetComponent<Bullet>(); 31 32 bulleta.transform.parent = this.transform; 33 // 発射前は非アクティブにしておく 34 bulleta.gameObject.SetActive(false); 35 // Listに追加 36 list_Bullets.Add(bulleta); 37 } 38 } 39 40 41 public void Shot(Transform origin) 42 { 43 for (int i = 0; i < list_Bullets.Count; i++) 44 { 45 { 46 if (!list_Bullets[i].gameObject.activeSelf) 47 { 48 list_Bullets[i].transform.position = origin.position; 49 list_Bullets[i].transform.rotation = origin.rotation; 50 list_Bullets[i].gameObject.SetActive(true); 51 break; 52 } 53 } 54 55 56 57 //Instantiate(bullet, origin.position, origin.rotation); 58 } 59 } 60 public void Move(Vector2 direction) 61 { 62 GetComponent<Rigidbody2D>().velocity = direction * speed; 63 } 64 public void Explosion() 65 { 66 Instantiate(explosion, transform.position, transform.rotation); 67 } 68 69}

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

この状態では、エラーは出ませんが、画像にある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のスクリプトはこちらです。

lang

1using UnityEngine; 2using System.Collections; 3 4public class DestroyArea : MonoBehaviour { 5 6 void OnTriggerEnter2D(Collider2D c) 7 { 8 if (c.tag == "Bullet(Player)") 9 { 10 c.gameObject.SetActive(false); 11 } 12 else if (c.tag == "Bullet(Enemy)") { 13 c.gameObject.SetActive(false); 14 } 15 else 16 { 17 Destroy(c.gameObject); 18 } 19 } 20} 21

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

よろしくお願いします。

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

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

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

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

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

guest

回答1

0

ベストアンサー

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

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

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

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

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

C#

1using UnityEngine; 2using System.Collections; 3using System.Collections.Generic; 4 5 6//------------------------------------------------------------------------------ 7//============================================================================== 8// 弾プール. 9public class BulletGenerator : MonoBehaviour { 10 11 12 [SerializeField] private GameObject[] bullets = null; // 子要素に入っている弾プール. 13 14 15 //------------------------------------------------------------------------------ 16 void Start() { 17 18 // 子要素の弾たちを配列にしている. 19 Queue< GameObject > q = new Queue< GameObject >(); 20 for ( int i = 0; i < this.transform.childCount; ++i ) { 21 q.Enqueue( this.transform.GetChild( i ).gameObject ); 22 } 23 this.bullets = q.ToArray(); 24 q.Clear(); 25 26 } 27 28 29 //------------------------------------------------------------------------------ 30 void Update() {} 31 32 33 //------------------------------------------------------------------------------ 34 public void shot( 35 Transform origin ) { 36 37 // 子要素の配列の中から使えるものを探し、あれば使いまわす. 38 for ( int i = 0; i < this.bullets.Length; ++i ) { 39 if ( !this.bullets[ i ].GetActive() ) { 40 this.bullets[ i ].SetActive( true ); 41 this.bullets[ i ].transform.position = origin.position; 42 this.bullets[ i ].transform.rotation = origin.rotation; 43 break; 44 } 45 } 46 } 47 48}

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

C#

1using UnityEngine; 2using System.Collections; 3 4public class Player : MonoBehaviour { 5 6 public BulletGenerator bg = null; 7 8 // Use this for initialization 9 void Start () { 10 11 } 12 13 // Update is called once per frame 14 void Update () { 15 16 if ( Input.GetKeyDown( KeyCode.A ) ) { 17 this.bg.shot( this.transform ); 18 } 19 20 } 21} 22

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

追加の回答です。

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

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

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

C#

1void Start() 2 { 3 Transform t; 4 //Bullet bulleta; 5 // 最初に一定数の弾を備蓄しておく 6 // 子要素全てにアクセスし、弾オブジェクトだけをリストに登録する. 7 for (int i = 0; i < transform.childCount; i++) 8 { 9 // 弾の生成 10 //bulleta = Instantiate(bullet).GetComponent<Bullet>(); 11 t = transform.GetChild( i ); 12 if ( t.gameObject.name != "EnemyBullet" ) 13 continue; 14 Bullet bulleta = t.GetComponent< Bullet >(); 15 16 17 // 発射前は非アクティブにしておく 18 bulleta.transform.parent = this.transform; 19 bulleta.gameObject.SetActive(false); 20 // Listに追加 21 list_Bullets.Add(bulleta); 22 } 23 }

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

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

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

C#

1 void OnTriggerEnter2D(Collider2D c) 2 { 3 if (c.tag == "Bullet(Player)") 4 { 5 c.GetComponent< Bullet >().stopBullet(); 6 c.gameObject.SetActive(false); 7 } 8 else if (c.tag == "Bullet(Enemy)") { 9 c.GetComponent< Bullet >().stopBullet(); 10 c.gameObject.SetActive(false); 11 } 12 else 13 { 14 Destroy(c.gameObject); 15 } 16 }

投稿2016/12/15 05:00

編集2016/12/16 08:15
退会済みユーザー

退会済みユーザー

総合スコア0

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

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

DiG5219

2016/12/15 18:38

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

2016/12/25 18:14

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

退会済みユーザー

2016/12/27 07:56

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

退会済みユーザー

2016/12/28 11:46

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

2017/01/11 15:07

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.51%

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

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

質問する

関連した質問