敵の生成の説明 - CASru-GAME/TeamGameDevBootcamp GitHub Wiki

敵の生成の説明を書く

実際にやるのは初めてですので説明書きます。たぶんここから理解しないと今後参加が難しいと思いますので。。。

大きくは「実際にゲームで動作するパート」と「デバッグパート」に分かれています。上のクラス図では合わせて表示していますが、別々の方がわかりやすいでしょう。

実際にゲームで動作するパート

目的は敵の生成ですのでまずその手順を説明します。

flowchart TD

    View["View レイヤー"]
    Usecase["Usecase レイヤー\n 敵を生成する"]
    Presenter["Presenter レイヤー"]
    Datastore["Datastore レイヤー\n 1. 生成した敵を入れるためのリスト\n 2. リストに敵を入れる/削除する関数\n 3. リストから敵を得る関数"]
    Data["Data レイヤー"]


    View --> Usecase
    Usecase --> Presenter
    Presenter --> Datastore
    Datastore --> Data
Loading

今回は簡単な機能(敵を生成するだけ)ですのでPresenter層には書かなくていいです。このまま実装しましょう。

BattleEnemyDatastore.cs

namespace App.Battle.Datastores
{
    public class BattleEnemyDatastore : IBattleEnemyDatastore, IDisposable
    {
        private Dictionary<string, Enemy> _enemies = new();
        public IEnumerable<Enemy> Enemies => _enemies.Values.ToArray();

        public void AddEnemy(string id, CharacterParameter characterParameter)
        {
            if(characterParameter == null)
            {
                throw new NullReferenceException($"{nameof(CharacterParameter)} is null");
            }

            var enemy = new Enemy(id, characterParameter);
            UnityEngine.Debug.Log($"{enemy} added");

            _enemies.Add(id, enemy);
        }

        public void RemoveEnemy(string id)
        {
            if(!_enemies.ContainsKey(id))
            {
                return;
            }
            _enemies.Remove(id);
        }

        public Enemy GetEnemyBy(string id)
        {
            if(!_enemies.ContainsKey(id))
            {
                return null;
            }

            var enemy = _enemies[id];
            UnityEngine.Debug.Log($"{enemy} got");
            return enemy;
        }

        public void Dispose()
        {
            _enemies.Clear();
        }
    }
}

敵のリストは辞書(Dictionary)でもっておいて、他のスクリプトからは辞書に値だけ配列に変換して参照できるようにしてます。

あと、AddEnemyRemoveEnemyは辞書にキー(id)を持って追加したり削除したりしています。

外部からEnemyインスタンスを欲しいときもあるので、GetEnemyByも用意しました。

これでDatastore層が完成しましたので次はUsecase層です。

BattleEnemyGenerateUseCase.cs

namespace App.Battle.UseCases
{
    public class BattleEnemyGenerateUseCase : IBattleEnemyGenerateUseCase
    {
        private readonly IBattleEnemyDatastore _BattleEnemyDatastore;

        [Inject]
        public BattleEnemyGenerateUseCase(
            IBattleEnemyDatastore battleEnemyDatastore
        )
        {
            _BattleEnemyDatastore = battleEnemyDatastore;
        }

        public void GenerateEnemy()
        {
            _BattleEnemyDatastore.AddEnemy("1", new CharacterParameter());
            UnityEngine.Debug.Log($"{"1"} generated");
        }
    }
}

今は詳しいことは無視して実装したのでGenerateEnemy関数は引数がありません。

敵を生成したら当然敵のリストに入れる必要があるので、DIコンテナからさきのDatastore層のInterfaceを持ってきています(Injectのコンストラクトの部分)。

主要部分はこれで終わりです。次はデバッグです。

デバッグパート

ここが難しいです。

flowchart TD

    View["View レイヤー"]
    Usecase["Usecase レイヤー\n 敵を生成する"]
    Presenter["Presenter レイヤー\n ボタンに敵を生成する機能を入れる"]
    Datastore["Datastore レイヤー"]
    Data["Data レイヤー"]


    View --> Usecase
    Usecase --> Presenter
    Presenter --> Datastore
    Datastore --> Data
Loading

TODO : Usecase と Presenterでの違い

まずはPresenter層から BattleDebugEnemyGeneratePresenter.cs

namespace App.Debug.Battle.Presenters
{
    public class BattleDebugEnemyGeneratePresenter : MonoBehaviour, IBattleDebugEnemyPresenter, IInitializable, IDisposable
    {
        [Header("Enemy")]
        [SerializeField] private Button _generateEnemyButton;

        private readonly Subject<Unit> _onGenerateEnemy = new();
        public IObservable<Unit> OnGenerateEnemy => _onGenerateEnemy;

        private readonly CompositeDisposable _disposables = new();
        public void Initialize()
        {
            _generateEnemyButton.OnClickAsObservable()
            .Subscribe(_ => _onGenerateEnemy.OnNext(Unit.Default))
            .AddTo(_disposables);
        }

        public void Dispose()
        {
            _disposables.Dispose();
        }
    }
}

(Interfaceの名前修正必要)

ボタン(_generateEnemyButton)を用意します。また、イベント(Subject, _onGenerateEnemy)を用意して、このイベントに登録するためのインスタンス(OnGenerateEnemy)も用意します。

Initialize()関数を使ってシーンが始まったらボタンがクリックされたとき(_generateEnemyButton.OnClickAsObservable())にイベントに登録された関数が実行されるようにします(.Subscribe(_ => _onGenerateEnemy.OnNext(Unit.Default)))。なので敵を生成する関数をこのイベント(_onGenerateEnemy)に登録する必要があります。それをUsecaseからやります。

BattleDebugEnemyUseCase.cs(BattleDebugEnemyGenerateUseCaseに修正必要)

namespace App.Debug.Battle.UseCases
{
    public class BattleDebugEnemyUseCase : IInitializable, IDisposable
    {
        private readonly IBattleDebugEnemyPresenter _battleDebugEnemyPresenter;
        private readonly IBattleEnemyGenerateUseCase _battleEnemyGenerateUseCase;

        private readonly CompositeDisposable _disposables = new();

        [Inject]
        public BattleDebugEnemyUseCase(
            IBattleEnemyGenerateUseCase battleEnemyGenerateUseCase,
            IBattleDebugEnemyPresenter battleDebugEnemyPresenter
        )
        {
            _battleEnemyGenerateUseCase = battleEnemyGenerateUseCase;
            _battleDebugEnemyPresenter = battleDebugEnemyPresenter;
        }

        public void Initialize()
        {
            _battleDebugEnemyPresenter.OnGenerateEnemy
                .Subscribe(x => _battleEnemyGenerateUseCase.GenerateEnemy())
                .AddTo(_disposables);
        }

        public void Dispose()
        {
            _disposables.Dispose();
        }
    }
}

登録するには、登録先のイベントと敵を生成する関数が必要ですので、前作った2つのクラスのInterfaceをDIコンテナから持ってきます(Injectの部分)。

Initialize()からイベントに敵を生成する関数を登録します。

        public void Initialize()
        {
            _battleDebugEnemyPresenter.OnGenerateEnemy
                .Subscribe(x => _battleEnemyGenerateUseCase.GenerateEnemy())
                .AddTo(_disposables);
        }

の部分

これで完成しました。

⚠️ **GitHub.com Fallback** ⚠️