🚀

【Unity】敵機の弾生成をオブジェクトプールで再利用するように修正してみました

2024/12/05に公開

現在RAYSERという3Dシューティングゲームを開発していて、今までは敵機の弾を一定時間毎にInstantiateとDestroyを実施して、生成と破棄をしていましたが、オブジェクトプールを用いて敵機ごとに弾のプールを持たせて、再利用するようにしてみました。

敵機撃破後一定時間経過後に敵機の弾のプール情報なども破棄するようにしてみました。挙動確認してみたところ一応ヒエラルキー上では必要以上に敵機の弾は増えてはおらず、破棄も実施されているようなので、こちらで様子を見てみようと思います。

敵機の弾を発射するクラス

EnemyBeamTurret.cs
using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.Pool;

namespace Turret
{
    using UniRx;

    /// <summary>
    /// 敵機の射撃処理
    /// </summary>
    public class EnemyBeamTurret : MonoBehaviour
    {
        /// <summary>
        /// ショットインターバル
        /// </summary>
        [SerializeField] private float shotInterbalTime = 1.5f;
  
        /// <summary>
        /// 敵機のビームのPrefab
        /// </summary>
        [SerializeField] private EnemyBeam enemyBeamPrefab;

        private ObjectPool<EnemyBeam> _enemyBeamPool;

        /// <summary>
        /// アクティブなビームを追跡するリスト
        /// </summary>
        private List<EnemyBeam> _activeBeams = new List<EnemyBeam>();

        /// <summary>
        /// 親の破壊フラグ
        /// </summary>
        private bool _isParentDestroyed = false;

        private void Start()
        {
            // ObjectPoolの初期化
            _enemyBeamPool = new ObjectPool<EnemyBeam>(
                // オブジェクトを生成する方法
                createFunc: () =>
                {
                    var newBeam = Instantiate(enemyBeamPrefab);
                    Debug.Log("New EnemyBeam instantiated."); // 新しいビーム生成
                    return newBeam;
                },
                // プールから取得した際の処理
                actionOnGet: beam =>
                {
                    beam.gameObject.SetActive(true);
                    Debug.Log($"EnemyBeam retrieved from pool: {beam.name}"); // プールから取得された
                },
                // プールに戻す際の処理
                actionOnRelease: beam =>
                {
                    beam.gameObject.SetActive(false);
                    Debug.Log($"EnemyBeam returned to pool: {beam.name}"); // プールに戻された
                },
                // オブジェクトが破棄される際の処理
                actionOnDestroy: beam =>
                {
                    Debug.Log($"EnemyBeam destroyed: {beam.name}"); // 破棄された
                    Destroy(beam.gameObject);
                },
                defaultCapacity: 1, // 初期プールサイズ
                maxSize: 2 // 最大プールサイズ
            );

            // ショットインターバルごとに射撃処理を実行
            Observable
                .Interval(TimeSpan.FromSeconds(shotInterbalTime))
                .Subscribe(_ => { EnemyShot(); })
                .AddTo(this);
        }

        /// <summary>
        /// 射撃処理
        /// </summary>
        private void EnemyShot()
        {
            // 親オブジェクトが非表示の場合は射撃処理停止
            if (_isParentDestroyed || transform.root.gameObject.activeSelf == false) return;

            var beam = _enemyBeamPool.Get();
            beam.transform.position = transform.position;
            beam.transform.rotation = transform.rotation;

            // ビームをリストに追加
            _activeBeams.Add(beam);

            // ビームが非アクティブ化される際にプールへ戻す
            Action releaseAction = null;
            releaseAction = () =>
            {
                _enemyBeamPool.Release(beam);

                // イベント購読を解除
                beam.OnDeactivation -= releaseAction;

                // リストから削除
                _activeBeams.Remove(beam);
            };

            beam.OnDeactivation += releaseAction;
        }

        private async void OnDestroy()
        {
            if (_isParentDestroyed) return;
            _isParentDestroyed = true;

            await UniTask.Delay(5000);

            // アクティブなビームを全て破棄
            foreach (var beam in _activeBeams)
            {
                if (beam != null)
                {
                    Destroy(beam.gameObject);
                }
            }
            _activeBeams.Clear();

            // プールをクリア
            if (_enemyBeamPool != null)
            {
                _enemyBeamPool.Clear();
                _enemyBeamPool = null;
            }
        }
    }
}

敵機の弾のクラス

EnemyBeam.cs
using System;
using Damage;
using UnityEngine;

namespace Turret
{
    /// <summary>
    /// 敵機ビーム用クラス
    /// </summary>
    public class EnemyBeam : MonoBehaviour, IDamageableToPlayer
    {
        public System.Action OnDeactivation;

        [SerializeField] private Rigidbody _rigidbody;

        /// <summary>
        /// ビームのスピード
        /// </summary>
        [SerializeField] private float beamSpeed = 10f;

        /// <summary>
        /// ダメージ量
        /// </summary>
        [SerializeField] private float damage = 3f;

        /// <summary>
        /// 自動消滅時間
        /// </summary>
        [SerializeField] private float disappearTime = 5f;

        private void OnEnable()
        {
            Invoke(nameof(Deactivate), 5f);
        }

        public float AddDamage()
        {
            return damage;
        }

        private void Update(){
            _rigidbody.linearVelocity = transform.forward.normalized * beamSpeed;
        }

        private void Deactivate()
        {
            // 非アクティブ化処理
            if (OnDeactivation != null)
            {
                // イベントを発火
                OnDeactivation.Invoke();

                // イベントのクリア
                OnDeactivation = null;
            }

            gameObject.SetActive(false);
        }

        private void OnDisable()
        {
            CancelInvoke(); // 非アクティブ時にタイマーを解除
        }
    }
}

こちらの仕組みをもとに再利用しやすいように委譲するようにしました
https://zenn.dev/cz_mirror/articles/39b4c765e7252c

さらに複数弾を撃てるように修正してみました
https://zenn.dev/cz_mirror/articles/d3026df676b039

Discussion