📝

【Unity】敵機の弾の処理を委譲で実装しなおしました

2024/12/12に公開

現在RAYSERという3Dシューティングゲームを開発しております。敵機の弾を撃つ構造で、オブジェクトプールで作り直しましたが、他のclassでも再利用しやすいように委譲する仕組みで作り直しました。

前回の記事
https://zenn.dev/cz_mirror/articles/c2ae295feccb36

まずは委譲するための砲台の制御クラスを作成しました、こちらのclassでは前回のオブジェクトプールで弾を生成する処理と、停止する処理を主に責務としています。

TurretController.cs
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;
using UniRx;

namespace Turret
{
    /// <summary>
    /// 砲台の制御クラス
    /// </summary>
    public class TurretController
    {
        private readonly GameObject owner;
        private readonly EnemyBeam enemyBeamPrefab;
        private readonly float shotInterval;
        private ObjectPool<EnemyBeam> beamPool;
        private List<EnemyBeam> activeBeams = new List<EnemyBeam>();
        private IDisposable shotSubscription;
        private readonly Func<Vector3> getPosition;
        private readonly Func<Quaternion> getRotation;

        public TurretController(
            GameObject owner,
            EnemyBeam enemyBeamPrefab,
            float shotInterval,
            Func<Vector3> getPosition = null,
            Func<Quaternion> getRotation = null)
        {
            this.owner = owner;
            this.enemyBeamPrefab = enemyBeamPrefab;
            this.shotInterval = shotInterval;
            this.getPosition = getPosition ?? (() => owner.transform.position);
            this.getRotation = getRotation ?? (() => owner.transform.rotation);

            // その他の初期化
            InitializePool();
        }

        private void InitializePool()
        {
            beamPool = new ObjectPool<EnemyBeam>(
                createFunc: () => GameObject.Instantiate(enemyBeamPrefab),
                actionOnGet: beam => beam.gameObject.SetActive(true),
                actionOnRelease: beam => beam.gameObject.SetActive(false),
                actionOnDestroy: beam => GameObject.Destroy(beam.gameObject),
                defaultCapacity: 10,
                maxSize: 20
            );
        }

        public void StartShooting()
        {
            StopShooting(); // 既存の購読を停止して重複を防止

            shotSubscription = Observable
                .Interval(TimeSpan.FromSeconds(shotInterval))
                .Subscribe(_ => Shoot())
                .AddTo(owner);
        }

        public void StopShooting()
        {
            shotSubscription?.Dispose();
        }

        private void Shoot()
        {
            if (!owner.transform.root.gameObject.activeSelf) return;

            var beam = beamPool.Get();
            beam.transform.position = getPosition();
            beam.transform.rotation = getRotation();

            activeBeams.Add(beam);

            Action releaseAction = null;
            releaseAction = () =>
            {
                beamPool.Release(beam);
                beam.OnDeactivation -= releaseAction;
                activeBeams.Remove(beam);
            };

            beam.OnDeactivation += releaseAction;
        }

        public void Cleanup()
        {
            StopShooting();

            foreach (var beam in activeBeams)
            {
                if (beam != null)
                {
                    GameObject.Destroy(beam.gameObject);
                }
            }

            activeBeams.Clear();
            beamPool?.Clear();
        }
    }
}

敵機の弾を撃つEnemyBeamTurretクラスにTurretControllerを移譲するように修正してみました。弾を発射する処理は委譲されたクラスから実行するように修正しています。

EnemyBeamTurret.cs
using UnityEngine;

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

        /// <summary>
        /// 敵機のビームのPrefab
        /// </summary>
        [SerializeField] private EnemyBeam enemyBeamPrefab;

        private TurretController _turretController;

        private void Start()
        {
            // TurretControllerの初期化
            _turretController = new TurretController(gameObject, enemyBeamPrefab, shotInterbalTime);

            // 通常の敵タレットは開始時から発射を開始
            _turretController.StartShooting();
        }

        private async void OnDestroy()
        {
            // TurretControllerのリソースをクリア
            _turretController?.Cleanup();
        }
    }
}

こちらの移譲する仕組みを用いて他のTurret系のクラスも修正してみました。こちらのボスキャラの弾発射処理と、EnemyBeamTurretクラスとの差異は、ステージ1のボスキャラ登場のシグナルのみ異なるので、シグナルのよって発火する処理のみ手直しするだけで実装できるようになりました

EnemyBeamTurret.cs
using Event.Signal;
using UI.Game;
using UniRx;
using UnityEngine;

namespace Turret
{
    public class Stage1BossTurret : MonoBehaviour
    {
        /// <summary>
        /// ショットの自動消滅時間
        /// </summary>
        [SerializeField] private float shotInterbalTime = 1.5f;

        /// <summary>
        /// 敵機のビームのゲームオブジェクト
        /// </summary>
        [SerializeField] private EnemyBeam enemyBeamPrefab;

        private TurretController _turretController;

        private void Start()
        {
            _turretController = new TurretController(gameObject, enemyBeamPrefab, shotInterbalTime);

            MessageBroker.Default.Receive<Stage1BossEncounter>()
                .Where(x => x._talk == TalkEnum.TalkEnd)
                .Subscribe(_ => _turretController.StartShooting())
                .AddTo(this);
        }

        private void OnDestroy()
        {
            _turretController?.Cleanup();
        }
    }
}

こちらの仕組みをさらに改良して複数の弾を発射できるようにしてみました
https://zenn.dev/cz_mirror/articles/d3026df676b039

Discussion