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

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

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

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

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

Q&A

解決済

1回答

2815閲覧

ObjectPoolが上手く動かない

lkiuxc

総合スコア29

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

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

0グッド

0クリップ

投稿2019/05/08 05:58

こんにちは。

UniRxのObjectPoolを使ってシューティングゲームを作ろうと思いテストコードを書いてみたのですが、使っているオブジェクトを無理矢理持ってきて使ってしまっています。
なぜこうなっているかが良く解りません、どうかよろしくお願いします。
ObjectPoolの宣言側です。

using System.Collections; using System.Collections.Generic; using UniRx.Toolkit; using UnityEngine; public class TestBulletPool : ObjectPool<TestPlayerBullet> { //private field private readonly Transform _PerentTransform; private readonly GameObject _Bullet; //public field //Method /// <summary> /// /// </summary> /// <param name="PerentTransform">生成されるオブジェクトの親オブジェクト。インスペクター散らかし防止用</param> /// <param name="BulletPrefab">実際に打ち出される弾。TestPlayerBulletをコンポーネントにつけたものでないといけない</param> public TestBulletPool(Transform PerentTransform,GameObject BulletPrefab) { _PerentTransform = PerentTransform; _Bullet = BulletPrefab; } protected override TestPlayerBullet CreateInstance() { var k = GameObject.Instantiate(_Bullet); k.transform.parent = _PerentTransform; return k.GetComponent<TestPlayerBullet>(); } }

Poolするオブジェクトのスクリプトです。

using System.Collections; using System.Collections.Generic; using UnityEngine; using UniRx; using UniRx.Triggers; using System; [RequireComponent(typeof(Rigidbody))] public class TestPlayerBullet : MonoBehaviour { //private field private Rigidbody rb; //public field public int damege; //Method void Start() { rb = GetComponent<Rigidbody>(); rb.AddForce(transform.forward * 10, ForceMode.Impulse); } public void SetPosition(Vector3 position) { transform.position = position; } public IObservable<Collider> CollisionToDestroy() { return this.OnTriggerEnterAsObservable(); } }

オブジェクトを生成するスクリプトです。

using System.Collections; using System.Collections.Generic; using UnityEngine; using PlayerStatus.Domain.UseCase; using UniRx; using UniRx.Triggers; using System; using UniRx.Toolkit; using System.Linq; public class TestShoot : MonoBehaviour { IShootInput shootInput; [SerializeField] int BulletLevel; [SerializeField] float _LevelOneShootInterval; //[SerializeField] //float _LevelTwoShootInterval; //[SerializeField] //float _LevelThreeShootInterval; //[SerializeField] //float _LevelFourShootInterval; [SerializeField] GameObject bullet; TestBulletPool bulletPool; string DestroyedGameObject(GameObject gameObject) { if(gameObject == null) { return "null"; } return gameObject.name; } void Awake() { bulletPool = new TestBulletPool(transform, bullet); List<TestPlayerBullet> list = new List<TestPlayerBullet>(); //this.UpdateAsObservable() // .Where(_ => BulletLevel == 1) // .ThrottleFirst(TimeSpan.FromSeconds(_LevelOneShootInterval)) // .Subscribe(_ => // { // var pool = bulletPool.Rent(); // pool.CollisionToDestroy(gameObject.transform.position + new Vector3(0,0,1)) // .Subscribe(x => // { // bulletPool.Return(pool); // }); // }); Observable .Interval(TimeSpan.FromSeconds(_LevelOneShootInterval)) .Subscribe(x => { var pool = bulletPool.Rent(); pool.SetPosition(gameObject.transform.position + new Vector3(0, 0, 1)); list.Add(pool); }); list.ObserveEveryValueChanged(x => x.Count) .Skip(1) .Subscribe(_ => { var peeked = list.Last(); peeked.CollisionToDestroy() .Subscribe(x => { bulletPool.Return(peeked); list.Remove(peeked); Debug.Log(DestroyedGameObject(peeked.gameObject)); }); }); } }

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

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

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

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

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

izmktr

2019/05/08 09:29

UniRx 明るくないんで、素朴な疑問なんですが、 list.ObserveEveryValueChanged の内部でlist.Remove を呼び出していますけど、これって安全ですか?
lkiuxc

2019/05/08 10:01

安全じゃないです!!!!!!!!!!!!!!!!
guest

回答1

0

ベストアンサー

「使っているオブジェクトを無理矢理持ってきて使ってしまっています」とおっしゃるのは、飛んでいる最中の弾が障害物にぶつかったわけでもないのに消えて再利用されてしまうということでしょうか?
もしそうでしたら、弾の再利用時にTestShoot内のbullet.CollisionToDestroy().Subscribeが解除されないまま何度も購読開始されてしまうことによる異常動作かもしれません。
TestShootTestPlayerBulletにいくつか手を加えてみましたが、これならどうでしょうか?

TestShoot

  • 弾をプールから取り出した際に衝突時のプールへの返却を予約することにした。
  • listは何のために用いるのか不明だったが(生きている弾を列挙して何か行う予定でしょうか)念のため残しておいた。

listへの弾追加は元のコードと同じく発射時に行って、さらに衝突時のlistからの除去もこのタイミングで予約することにした。
Countを監視する必要性は薄いように思われたので削除した。

C#

1using System.Collections; 2using System.Collections.Generic; 3using UnityEngine; 4using PlayerStatus.Domain.UseCase; 5using UniRx; 6using UniRx.Triggers; 7using System; 8using UniRx.Toolkit; 9using System.Linq; 10 11public class TestShoot : MonoBehaviour 12{ 13 IShootInput shootInput; 14 [SerializeField] 15 int BulletLevel; 16 17 [SerializeField] 18 float _LevelOneShootInterval; 19 //[SerializeField] 20 //float _LevelTwoShootInterval; 21 //[SerializeField] 22 //float _LevelThreeShootInterval; 23 //[SerializeField] 24 //float _LevelFourShootInterval; 25 [SerializeField] 26 GameObject bullet; 27 TestBulletPool bulletPool; 28 29 string DestroyedGameObject(GameObject gameObject) 30 { 31 if (gameObject == null) 32 { 33 return "null"; 34 } 35 return gameObject.name; 36 } 37 38 void Awake() 39 { 40 bulletPool = new TestBulletPool(transform, bullet); 41 List<TestPlayerBullet> list = new List<TestPlayerBullet>(); 42 Observable 43 .Interval(TimeSpan.FromSeconds(_LevelOneShootInterval)) 44 .Subscribe(_ => 45 { 46 var bullet = bulletPool.Rent(); 47 bullet.Launch(gameObject.transform.position + new Vector3(0, 0, 1)); 48 list.Add(bullet); 49 bullet.CollisionToDestroy().Subscribe(__ => 50 { 51 bulletPool.Return(bullet); 52 list.Remove(bullet); 53 Debug.Log(DestroyedGameObject(bullet.gameObject)); 54 }); 55 }).AddTo(this); 56 } 57}

TestPlayerBullet

  • Start内で弾に初速度を与えるのはやめて、SetPositionの名前をLaunchに変えて初期位置・初速度設定をともにここで行うことにした。こうすることで、弾が新規に生成された場合でも既存の弾をプールから取ってきた場合でも区別せずに扱えるだろうと考えた。

また、弾が再利用された際に以前の速度・角速度が残っていると不都合なので、ゼロにリセットすることにした。

  • Rigidbody取得はStartのタイミングでは遅いのでAwakeに変更した。
  • 発射時に毎回Subscribeを行っているので弾衝突時には購読を解除したいところだが、衝突してもオブジェクトがプーリングされ破壊されないためAddToを使った解除は適さないだろうと考えた。そこでCollisionToDestroyが返すストリームにTake(1)を加えてイベント発行回数が0回または1回に制限されるようにし、1回衝突すればストリームが終了するようにした。

C#

1using System.Collections; 2using System.Collections.Generic; 3using UnityEngine; 4using UniRx; 5using UniRx.Triggers; 6using System; 7 8[RequireComponent(typeof(Rigidbody))] 9public class TestPlayerBullet : MonoBehaviour 10{ 11 //private field 12 private Rigidbody rb; 13 14 //public field 15 public int damege; 16 17 //Method 18 void Awake() 19 { 20 rb = GetComponent<Rigidbody>(); 21 } 22 23 public void Launch(Vector3 position, float impulseMagnitude = 10.0f) 24 { 25 transform.position = position; 26 rb.velocity = Vector3.zero; 27 rb.angularVelocity = Vector3.zero; 28 rb.AddForce(transform.forward * impulseMagnitude, ForceMode.Impulse); 29 } 30 31 public IObservable<Collider> CollisionToDestroy() 32 { 33 return this.OnTriggerEnterAsObservable().Take(1); 34 } 35}

投稿2019/05/10 16:07

Bongo

総合スコア10807

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問