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

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

ただいまの
回答率

90.01%

ObjectPoolが上手く動かない

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 438

lkiuxc

score 26

こんにちは。

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));
                   });
            });
    }
}
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

質問への追記・修正、ベストアンサー選択の依頼

  • izmktr

    2019/05/08 18:29

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

    キャンセル

  • lkiuxc

    2019/05/08 19:01

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

    キャンセル

回答 1

checkベストアンサー

0

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

TestShoot

  • 弾をプールから取り出した際に衝突時のプールへの返却を予約することにした。
  • listは何のために用いるのか不明だったが(生きている弾を列挙して何か行う予定でしょうか)念のため残しておいた。
    listへの弾追加は元のコードと同じく発射時に行って、さらに衝突時のlistからの除去もこのタイミングで予約することにした。
    Countを監視する必要性は薄いように思われたので削除した。
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>();
        Observable
            .Interval(TimeSpan.FromSeconds(_LevelOneShootInterval))
            .Subscribe(_ =>
            {
                var bullet = bulletPool.Rent();
                bullet.Launch(gameObject.transform.position + new Vector3(0, 0, 1));
                list.Add(bullet);
                bullet.CollisionToDestroy().Subscribe(__ =>
                {
                    bulletPool.Return(bullet);
                    list.Remove(bullet);
                    Debug.Log(DestroyedGameObject(bullet.gameObject));
                });
            }).AddTo(this);
    }
}

TestPlayerBullet

  • Start内で弾に初速度を与えるのはやめて、SetPositionの名前をLaunchに変えて初期位置・初速度設定をともにここで行うことにした。こうすることで、弾が新規に生成された場合でも既存の弾をプールから取ってきた場合でも区別せずに扱えるだろうと考えた。
    また、弾が再利用された際に以前の速度・角速度が残っていると不都合なので、ゼロにリセットすることにした。
  • Rigidbody取得はStartのタイミングでは遅いのでAwakeに変更した。
  • 発射時に毎回Subscribeを行っているので弾衝突時には購読を解除したいところだが、衝突してもオブジェクトがプーリングされ破壊されないためAddToを使った解除は適さないだろうと考えた。そこでCollisionToDestroyが返すストリームにTake(1)を加えてイベント発行回数が0回または1回に制限されるようにし、1回衝突すればストリームが終了するようにした。
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 Awake()
    {
        rb = GetComponent<Rigidbody>();
    }

    public void Launch(Vector3 position, float impulseMagnitude = 10.0f)
    {
        transform.position = position;
        rb.velocity = Vector3.zero;
        rb.angularVelocity = Vector3.zero;
        rb.AddForce(transform.forward * impulseMagnitude, ForceMode.Impulse);
    }

    public IObservable<Collider> CollisionToDestroy()
    {
        return this.OnTriggerEnterAsObservable().Take(1);
    }
}

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

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