Open1

陣地広げていく系のゲーム作る

0y00y0

ゲーム概要

まずは小規模で動かしたい

ジャンル

  • 3D陣取りアクションゲーム

目的

  • プレイヤーはマップ上のタイルを爆弾で自陣の色に染め、敵よりも多くのタイルを占領することを目指す。
  • 制限時間内に、より多くのタイルを占領した陣営が勝利となる。
  • 敵キャラクターはプレイヤーのタイル占領を妨害し、プレイヤーや自陣営のタイルを攻撃してくる。キャラクターが倒されても一定時間後に復活する(プレイヤーも同様)。

2. ゲームの構成要素と仕様

2.1. プレイヤーキャラクター

  • 操作
    • ユーザーがキーボードまたはゲームパッドで操作
    • 移動
      • 上下左右に移動
    • アクション
      • 爆弾を設置
  • 能力
    • HP
      • CharacterStatsSOで定義された最大HPを持つ。
      • HPが0になっても、LevelConfigSOで定義されたリスポーン時間後に初期位置付近で復活。
    • HPの変動
      • ReactiveProperty<int> で管理・通知。
    • 移動速度
      • CharacterStatsSOで定義された速度で移動。
    • 爆弾設置
      • BombStatsSO で定義されたクールダウン時間 (playerCooldown) を経て、自身の足元に爆弾を設置。
  • ビュー
    • PlayerPresenter がアタッチされたGameObjectで表現。
    • PlayerInputView を通じて入力受付。

2.2. 敵キャラクター

  • AI制御
    • EnemyPresenter によって制御。
    • 以下のAIステートに基づき行動:
      • Idle (待機): 周囲に優先ターゲット(プレイヤーや占領すべきタイル)がいない場合のフォールバック状態。短時間で他のステートへ再評価。
      • CapturingTile (タイル占領優先): プレイヤーが安全な距離にいる、または視界にいない場合、最も近い(または戦略的に価値のある)ニュートラルタイルや敵(プレイヤー)タイルに向かい、爆弾を設置して自陣を広げようとする。
      • ApproachingPlayer (プレイヤーへ接近): プレイヤーが中距離にいて、かつタイル占領よりもプレイヤーへの攻撃を優先すべき状況(例:プレイヤーが自陣深くに侵入、敵のHPが十分にある)の場合、プレイヤーに向かって移動。
      • AttackingPlayer (プレイヤー攻撃): プレイヤーが適切な攻撃範囲内(ATTACK_RANGE_MINATTACK_RANGE_MAXで定義)にいる場合、プレイヤーの動きを予測して爆弾設置を試みる。設置後は Repositioning へ。
      • Repositioning (再配置): プレイヤーに近すぎる、爆弾を設置した直後、または危険を察知した場合に、安全な位置へ移動したり、次の行動のための間合いを取ったりする。
  • ステート遷移
    • プレイヤーとの距離、周囲のタイル状況(ニュートラル/敵タイル)、自身のHP、爆弾のクールダウン状況などに基づいて自動で遷移。
  • 能力
    • HP
      • CharacterStatsSO(敵用、LevelConfigSO経由で設定)で定義された最大HP。
      • 0になると撃破され、LevelConfigSOで定義されたリスポーン時間後に初期位置付近で復活。
      • HP変動は ReactiveProperty<int>
    • 移動速度
      • CharacterStatsSO(敵用)で定義された速度。
    • 爆弾設置
      • BombStatsSO で定義されたクールダウン時間 (enemyBaseCooldown) を経て爆弾を設置。
  • ビュー
    • EnemyPresenter がアタッチされたGameObjectで表現。
    • PoolManager によって管理(復活時はプールから再取得または再アクティブ化)。

2.3. タイル

  • 構成
    • ゲームマップはグリッド状に配置されたタイルで構成される。
  • 所有権
    • 各タイルは Neutral (中立)、Player (プレイヤー所有)、Enemy (敵所有) のいずれかの状態を持つ。
    • この状態はReactiveProperty<Owner> で管理され、変更時に TileView が自動的に色を更新。
  • 占領
    • 爆弾が爆発すると、その爆風範囲内のタイルが爆弾の所有者のものになる。

2.4. 爆弾

  • 性能
    • 半径・信管時間
      • BombStatsSO で定義された値に基づき生成。
    • ダメージ
      • BombStatsSO で定義されたダメージ量を、爆風範囲内の敵対するキャラクターに与える。
  • ライフサイクル
    • 設置後、一定の信管時間(fuseTime)が経過すると爆発。
    • 爆発後は消滅し、ビューオブジェクトは PoolManager に返却。

2.5. マップと設定

  • マップ構成
    • LevelConfigSO で定義されたタイル数 (mapTileDimensions) とタイル間隔 (tileSpacing) に基づいて動的に生成。
    • 追加: LevelConfigSO に初期の敵陣地配置パターンや割合を設定可能にする(オプション)。
  • パラメータ管理
    • ゲームの主要な数値(キャラクターのHPや速度、敵の数、爆弾の性能、マップサイズ、制限時間、リスポーン時間など)は LevelConfigSO, CharacterStatsSO, BombStatsSO といったScriptableObjectによって管理。
    • Unity Editorから容易に調整可能。
    • これらの設定は GameLifetimeScope を通じて各PresenterにDI。

3. 主要システム

  • DI (Dependency Injection) とライフサイクル管理 (VContainer)
    • GameLifetimeScope がDIコンテナを構築し、ゲームに必要なオブジェクト(Model、Presenter、Service、設定SOなど)の依存関係を解決・注入。
    • IDisposable を実装したクラスは、VContainerのスコープ終了時に自動的に Dispose() が呼び出され、リソースが解放。
    • ITickable を実装したクラス (GameplayLoopUpdater) は、VContainerによって毎フレーム Tick() メソッドが呼び出される。
  • イベントシステムと状態管理
    • Model層の主要な状態(キャラクターのHP、タイルの所有者など)は ReactiveProperty で管理され、変更がリアクティブストリームとして通知される。
    • キャラクターの死亡(一時的なリタイア)などの離散的なイベントは Subject を通じて通知される。
    • Presenter層はこれらのストリームを購読し、リアクティブに変更に対応(UI更新、ロジック実行など)する。
    • 購読は CompositeDisposable で管理され、適切に破棄される。
  • オブジェクトプーリング (PoolManager)
    • 敵キャラクターのビューGameObjectと爆弾のビューGameObjectは、PoolManager によって再利用される。
    • プレハブはAddressablesキー経由でロードされる。

4. UI (ユーザーインターフェース)

  • 表示 (GameUIView)
    • プレイヤーのHP。
    • (オプション)敵キャラクターのHP(選択ターゲットなど)。
    • 現在アクティブな敵の数。
    • プレイヤーと敵のタイル占有率(プログレスバー形式)。
    • 残り制限時間。
    • ゲーム結果メッセージ(勝利/敗北)。
  • 入力 (PlayerInputView)
    • キーボード(デフォルト)によるプレイヤーの移動(上下左右)と爆弾設置(スペースキー)の入力を受け付ける。
    • Rxストリームとして PlayerPresenter に提供する。

5. ゲームフロー (高レベルな状態遷移)

  • ゲーム全体の流れは GameFlowManager によって管理される。

  • 起動 〜 メインメニュー

      1. アプリケーション起動。
      • Unityシーンがロードされ、GameLifetimeScope がVContainerをセットアップ。
      1. GameFlowManager が初期化。
      • ゲーム状態が GameState.MainMenu に。
      • メインメニューUIが表示される。
  • ゲーム開始

      1. プレイヤーがメインメニューで「スタート」ボタンをクリック。
      1. UIButtonHook 経由で GameFlowManager.StartGame() が呼び出される。
      1. GameFlowManagerGameOrchestratorPresenter.SetupNewGame() を呼び出し、ゲームセッションを初期化(キャラクターのリスポーンタイマーなどもセットアップ)。
      1. ゲーム状態が GameState.Playing に遷移。
      • インゲームUIが表示される。
  • ゲームプレイ中 (GameState.Playing)

    • プレイヤーはキャラクターを操作し、敵は改良されたAIに基づいて行動する(タイル占領も意識)。
    • キャラクターが倒されると、一定時間後にリスポーンする。
    • 爆弾の設置、爆発、タイルの占領、キャラクター間の戦闘がリアルタイムに進行する。
    • プレイヤーは「ポーズ」ボタンでゲームを一時停止 (GameState.Paused) できる。
      • ポーズ中は Time.timeScale = 0 になり、ゲームの進行が停止する。
      • 再度「再開」ボタンで Playing 状態に戻れる。
  • ゲーム終了

    • 制限時間が0になった時点で勝敗判定。
    • ゲームクリア (GameState.GameClear): プレイヤーのタイル占有率が敵のタイル占有率を上回っている場合。
    • ゲームオーバー (GameState.GameOver): 敵のタイル占有率がプレイヤーのタイル占有率を上回っている場合(または同率でプレイヤー不利とする場合など)。
    • ゲームクリア/ゲームオーバーUIが表示される。
  • リトライ / メニューへ戻る

    • ゲームオーバーまたはゲームクリア画面から、プレイヤーは「リトライ」または「メニューへ」を選択できる。
    • 「リトライ」: GameFlowManager.StartGame() が再度呼ばれ、新しいゲームセッションが始まる。
    • 「メニューへ」: GameFlowManager.GoToMainMenu() が呼ばれ、GameState.MainMenu に戻る。

6. 詳細な制御の流れ (ゲームプレイ中)

A. ゲームセッションのセットアップ (GameOrchestratorPresenter.SetupNewGame())

    1. クリーンアップ: 前のセッションのデータを破棄・リセット。
    1. Model初期化: GameState をリセット。
    1. ワールド初期化 (WorldInitializer):
    • LevelConfigSO に基づきマップ境界計算、初期タイル配置(一部敵陣地として設定可能)。
    • プレイヤーの Character Modelと PlayerPresenter を生成・初期化。リスポーンロジックの準備。
    • 敵の Character Model群と EnemyPresenter 群を PoolManager から取得して生成・初期化。リスポーンロジックの準備。
    1. 状態監視Presenterの初期化:
    • PlayerStatusWatcher がプレイヤーのHPやリスポーンを管理。
    • EnemyFleetWatcher が敵群のHPやリスポーンを管理。
    1. UI初期表示: 制限時間、初期HP、占有率などを表示。

B. フレームごとの更新 (GameplayLoopUpdater.Tick())

    1. VContainerによって GameplayLoopUpdater.Tick() が毎フレーム呼び出される。
    1. ゲームが Playing 状態の場合:
    • _bombController.UpdateBombTimers(Time.deltaTime)
    • _gameUIUpdater.UpdateOwnershipBars()
    • _gameUIUpdater.UpdateTimer()(制限時間の更新と表示)。
    • _orchestrator.UpdateCharacterRespawns(Time.deltaTime)(キャラクターリスポーン処理)。
    • _orchestrator.CheckGameResultByTimeLimit()(制限時間による勝敗判定)。

C. プレイヤーの行動と結果

    1. PlayerInputView が入力を検知し、PlayerPresenter に通知 (UniRx)
    1. PlayerPresenter は移動入力に応じて PlayerModel の位置を更新し、自身のGameObjectの transform も更新
    1. PlayerPresenter は爆弾設置入力(クールダウン後)に応じて _orchestrator.RequestBombSpawn() を呼び出す

D. 敵AIの行動と結果

    1. EnemyPresenter.AIBehaviorLoop() が毎フレーム実行。
    1. UpdateAIState():
    • 周囲のタイル状況(最も近い未占領タイル、敵タイルなど)、プレイヤーの位置と状態、自身の状態(HP、爆弾クールダウン)を評価。
    • 優先度に基づきステートを決定 (CapturingTile, ApproachingPlayer, AttackingPlayer, Repositioning, Idle)。
    1. ExecuteCurrentAIState():
    • CapturingTile: 目標タイルへ移動し、爆弾を設置して占領を試みる。
    • ApproachingPlayer: プレイヤーに接近。
    • AttackingPlayer: プレイヤーの動きを予測し(簡易的でも)、爆弾設置を試みる。
    • Repositioning: 安全な位置へ移動。
    1. 移動や爆弾設置はModel更新や _orchestrator.RequestBombSpawn() に繋がる。

E. 爆弾の処理と影響

    1. キャラクターが爆弾を設置すると _orchestrator.RequestBombSpawn() が呼ばれ、BombController が処理
    1. BombController は Bomb Modelを生成し、PoolManager から爆弾View(GameObject)を取得・表示
    • アクティブ爆弾リストでタイマー管理
    1. GameplayLoopUpdater (内部で BombController.UpdateBombTimers) によりタイマーが0になると、BombController_gameRuleServiceModel.ExplodeBomb(bombModel) を呼び出す
    1. GameRuleService.ExplodeBomb()
    • 爆風範囲内の TileModel.CurrentOwner (RP) を更新
      • これにより、TileView の色が自動的に変わる
    • 爆風範囲内の敵対する CharacterModel.Hp (RP) を更新(ダメージを与える)
      • これにより、キャラクターのHPバーUIが更新される
    • GameRuleServiceOnBombExplodedSubject を発行
    1. BombControllerOnBombExploded_PresenterEvent を発行
    1. GameOrchestratorPresenter がこれらの爆発イベントを購読し、視覚・聴覚エフェクトを再生(現状はログ表示)
    1. BombController が爆弾Viewを PoolManager に返却

F. キャラクターの戦闘不能とリスポーン

    1. キャラクターのHPが0になると OnDied Subjectが発行される。
    1. PlayerStatusWatcher / EnemyFleetWatcher がこれを検知。
    • 対応するキャラクター(のPresenterとView)を一時的に非アクティブ化(またはプールに一旦戻す)。
    • リスポーンタイマーを開始(時間は LevelConfigSO で設定)。
    1. GameOrchestratorPresenter.UpdateCharacterRespawns():
    • 各リスポーンタイマーを更新。
    • タイマーが0になったキャラクターをリスポーンさせる。
      • PoolManager からViewを再取得(または再アクティブ化)。
      • ModelのHPを最大にリセット。
      • 初期位置付近の安全な場所に再配置。
      • 対応するPresenterを再初期化。

G. 勝敗判定のトリガー (GameOrchestratorPresenter.CheckGameResultByTimeLimit())

    1. GameplayLoopUpdater.Tick() 内から定期的に呼び出される。
    1. LevelConfigSO で設定された制限時間を監視。
    1. 制限時間が0になった場合:
    • _gameStateModel.GetOwnership() でプレイヤーと敵のタイル占有率を取得。
    • 占有率を比較。
      • プレイヤー優位なら GameFlowManager.Instance.Clear() を呼び出す。
      • 敵優位(または同率で不利扱いなど)なら GameFlowManager.Instance.Over() を呼び出す。