メインコンテンツまでスキップ

バトル内部構造詳解

このガイドは完全な MCE C# ソースコードにアクセスできる Source ティア ユーザー向けです。

このドキュメントでは、BattleManager アーキテクチャ、15以上のモジュール、BattleStateMachine、新しいバトル機能の追加方法を詳細に解説します。

BattleManager アーキテクチャ

BattleManagerモジュラーコンポジションパターンに従っています。数千行のモノリシッククラスではなく、バトルロジックはフォーカスされたモジュールに分割され、BattleManager ハブを通じて通信します。

BattleManagerModule 基底クラス

すべてのモジュールは BattleManagerModule を継承します:

public abstract class BattleManagerModule : MonoBehaviour
{
protected BattleManager BattleManager { get; private set; }

public virtual void Initialize(BattleManager manager)
{
BattleManager = manager;
}

protected T GetModule<T>() where T : BattleManagerModule
{
return BattleManager.GetModule<T>();
}
}

このパターンにより各モジュールは:

  • マネージャーを通じて他のモジュールにアクセス(疎結合)。
  • 独立して初期化。
  • マネージャーをモック化して分離テスト可能。

モジュール詳解

BattleManagerBattlersModule

Battler インスタンスを管理します -- モンスターのバトル中の表現です:

public class Battler
{
public MonsterInstance Monster; // 基礎データ
public MonsterEntry Species; // 種族参照
public int CurrentHP; // バトルHP
public MoveSlot[] Moves; // 4つの技スロット(PP付き)
public int[] StatStages; // ステータスごとに -6 から +6
public StatusCondition Status; // 非一時的状態異常
public List<VolatileStatus> Volatiles; // 一時的状態異常
public bool IsFainted => CurrentHP <= 0;

// 計算された実効ステータス(種族値 + IV + EV + 性格 + 段階)
public int EffectiveAttack { get; }
public int EffectiveDefense { get; }
// ... など
}

BattleManagerMovesModule

コアダメージエンジンです。

ダメージ計算パイプライン:

public int CalculateDamage(Battler attacker, Battler defender, Move move)
{
int level = attacker.Monster.Level;
int power = GetEffectivePower(move, attacker, defender);
int attack = GetAttackStat(move, attacker);
int defense = GetDefenseStat(move, defender);

float baseDamage = ((2f * level / 5f + 2f) * power * attack / defense) / 50f + 2f;

float modifier = 1f;
modifier *= GetCriticalMultiplier(attacker, move);
modifier *= GetRandomFactor(); // 0.85 - 1.0
modifier *= GetSTAB(attacker, move); // 1.0 または 1.5
modifier *= GetTypeEffectiveness(move, defender); // 0, 0.25, 0.5, 1, 2, または 4
modifier *= GetWeatherModifier(move);
modifier *= GetAbilityModifier(attacker, defender, move);
modifier *= GetItemModifier(attacker, move);

return Mathf.Max(1, Mathf.FloorToInt(baseDamage * modifier));
}

BattleManagerHealthModule

すべてのHP変動はこのモジュールを通じて行われます:

  • DealDamage(Battler, int) -- HPを減少、ひんしをチェック。
  • Heal(Battler, int) -- HPを回復、最大値で上限。
  • CheckFainted(Battler) -- HP <= 0 なら true を返す。
  • ProcessFaint(Battler) -- 経験値、努力値を付与、進化をチェック。

BattleManagerStatusesModule

状態異常とターンごとの効果を管理します。

BattleManagerCaptureModule

捕獲率の計算式を実装します。種族捕獲率、現在HP、状態異常、ボールタイプに基づいて修正捕獲率を計算し、シェイクチェック(0-3回揺れてから捕獲または脱出)を行います。

BattleStateMachine

ステートマシンは列挙型ベースのアプローチをコルーチンで使用します:

public class BattleStateMachine : MonoBehaviour
{
public enum BattleState
{
Intro,
TurnStart,
ActionSelection,
ActionExecution,
TurnEffects,
FaintCheck,
ReplaceFainted,
TurnEnd,
BattleEnd,
Results,
Cleanup
}

public IEnumerator RunBattle(BattleParameters parameters)
{
yield return RunState(BattleState.Intro);

while (currentState != BattleState.BattleEnd)
{
yield return RunState(BattleState.TurnStart);
yield return RunState(BattleState.ActionSelection);
yield return RunState(BattleState.ActionExecution);
yield return RunState(BattleState.TurnEffects);
yield return RunState(BattleState.FaintCheck);

if (NeedReplacement())
yield return RunState(BattleState.ReplaceFainted);

if (IsBattleOver())
break;

yield return RunState(BattleState.TurnEnd);
}

yield return RunState(BattleState.BattleEnd);
yield return RunState(BattleState.Results);
yield return RunState(BattleState.Cleanup);
}
}

新しいバトルモジュールの追加

バトルシステムに新しいモジュールを追加するには:

ステップ 1:モジュールクラスの作成

BattleManagerModule を継承する新しいクラスを作成します。

ステップ 2:モジュールの登録

BattleManager のモジュールリストにモジュールを追加します。

ステップ 3:既存モジュールとの統合

MovesModule で、ダメージ計算中に新しいモジュールを呼び出します。

ステップ 4:ステートマシンへのフック

ターンエフェクトフェーズで新しいモジュールの処理を呼び出します。

バトルイベントシステム

モジュールは BattleManager 上のイベントを通じて通信します:

// イベントの定義
public event Action<Battler, Move, int> OnDamageDealt;
public event Action<Battler, StatusCondition> OnStatusApplied;
public event Action<Battler> OnBattlerFainted;
public event Action<Battler, Battler> OnBattlerSwitched;

// モジュールからイベントを発火
BattleManager.OnDamageDealt?.Invoke(target, move, damage);

// 他のモジュールから購読
BattleManager.OnDamageDealt += HandleDamageDealt;

このイベントシステムにより、モジュールは直接結合なしに互いのアクションに反応できます。

バトルロジックのテスト

[Test]
public void DamageCalculation_SuperEffective_DoublesDamage()
{
var attacker = CreateTestBattler(level: 50, attack: 100);
var defender = CreateTestBattler(defense: 100, types: MonsterType.Grass);
var move = CreateTestMove(power: 80, type: MonsterType.Fire);

int damage = movesModule.CalculateDamage(attacker, defender, move);

int neutralDamage = CalculateNeutralDamage(attacker, defender, move);
Assert.AreEqual(neutralDamage * 2, damage, neutralDamage * 0.15f);
}

ベストプラクティス

  1. モジュールは集中させましょう。各モジュールは1つの責務を処理すべきです。
  2. モジュール間の直接参照ではなく、イベントを通じて通信しましょう
  3. 既知の入力と期待される出力でダメージ計算をテストしましょう
  4. Battler の状態を直接変更しないでください -- 必ず適切なモジュールを通じて行ってください。
  5. ターンシーケンスにはステートマシンを使用しましょう。アドホックなコルーチンを追加しないでください。