跳到主要内容

战斗内部机制深入解析

本指南适用于拥有完整 MCE C# 源代码访问权限的 Source 版本用户。

本文档详细解析了 BattleManager 架构、其 15+ 模块、BattleStateMachine 以及如何添加新战斗功能。

BattleManager 架构

BattleManager 遵循模块化组合模式。战斗逻辑不是集中在一个数千行的单体类中,而是分散在各个专注的模块中,这些模块通过 BattleManager 中心进行通信。

public class BattleManager : MonoBehaviour, IBattleManager
{
// 模块引用(在 Inspector 中分配或通过 Zenject 注入)
[SerializeField] private BattleManagerBattlersModule battlersModule;
[SerializeField] private BattleManagerHealthModule healthModule;
[SerializeField] private BattleManagerMovesModule movesModule;
[SerializeField] private BattleManagerItemsModule itemsModule;
[SerializeField] private BattleManagerCaptureModule captureModule;
[SerializeField] private BattleManagerAIModule aiModule;
[SerializeField] private BattleManagerAnimationModule animationModule;
[SerializeField] private BattleManagerAudioModule audioModule;
[SerializeField] private BattleManagerStatusesModule statusesModule;
[SerializeField] private BattleManagerBattlerStatsModule statsModule;
[SerializeField] private BattleManagerBattlerSwitchModule switchModule;
[SerializeField] private BattleManagerRostersModule rostersModule;
[SerializeField] private BattleManagerScenariosModule scenariosModule;
[SerializeField] private BattleManagerMegaModule megaModule;
[SerializeField] private BattleManagerCharactersModule charactersModule;
}

BattleManagerModule 基类

所有模块继承自 BattleManagerModule

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

// 在初始化时由 BattleManager 调用
public virtual void Initialize(BattleManager manager)
{
BattleManager = manager;
}

// 通过管理器访问其他模块
protected T GetModule<T>() where T : BattleManagerModule
{
return BattleManager.GetModule<T>();
}
}

此模式允许每个模块:

  • 通过管理器访问其他模块(松耦合)。
  • 独立初始化。
  • 通过模拟管理器进行隔离测试。

模块深入解析

BattleManagerBattlersModule

File: BattleManagerBattlersModule.cs

管理 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; }
// ... etc
}

关键方法:

  • CreateBattler(MonsterInstance) -- 从 MonsterInstance 创建 Battler。
  • GetActiveBattler(side) -- 返回某一方当前活跃的战斗单位。
  • GetAllBattlers() -- 返回所有战斗单位(双方)。

BattleManagerMovesModule

File: BattleManagerMovesModule.cs

核心伤害引擎。这是最大的模块,Battler.cs 文件约 180KB。

伤害计算管线:

public int CalculateDamage(Battler attacker, Battler defender, Move move)
{
// 1. 基础伤害
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;

// 2. 修正值链
float modifier = 1f;
modifier *= GetCriticalMultiplier(attacker, move);
modifier *= GetRandomFactor(); // 0.85 - 1.0
modifier *= GetSTAB(attacker, move); // 1.0 or 1.5
modifier *= GetTypeEffectiveness(move, defender); // 0, 0.25, 0.5, 1, 2, or 4
modifier *= GetWeatherModifier(move);
modifier *= GetAbilityModifier(attacker, defender, move);
modifier *= GetItemModifier(attacker, move);

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

关键方法:

  • ExecuteMove(Battler, Move, Battler) -- 完整的招式执行含效果。
  • CanUseMove(Battler, MoveSlot) -- 检查 PP、禁用、冰冻等。
  • ApplySecondaryEffect(Move, Battler, Battler) -- 能力值变化、状态施加。
  • GetTypeEffectiveness(Move, Battler) -- 属性表查找含双属性。

BattleManagerHealthModule

File: BattleManagerHealthModule.cs

所有 HP 变化经由此模块:

  • DealDamage(Battler, int) -- 减少 HP,检查濒死。
  • Heal(Battler, int) -- 恢复 HP,上限为最大值。
  • CheckFainted(Battler) -- HP <= 0 时返回 true。
  • ProcessFaint(Battler) -- 给予经验值、努力值,检查进化。

BattleManagerStatusesModule

File: BattleManagerStatusesModule.cs

管理状态异常及其每回合效果:

public void ApplyStatus(Battler target, StatusCondition status)
{
// 检查免疫(火系不会灼伤、电系不会麻痹等)
if (IsImmune(target, status)) return;

// 检查是否已有非暂时性状态
if (target.Status != StatusCondition.None) return;

target.Status = status;
// 触发状态施加动画
}

public void ProcessTurnEndStatuses(Battler battler)
{
switch (battler.Status)
{
case StatusCondition.Burn:
DealStatusDamage(battler, battler.MaxHP / 16);
break;
case StatusCondition.Poison:
DealStatusDamage(battler, battler.MaxHP / 8);
break;
case StatusCondition.BadPoison:
badPoisonCounter++;
DealStatusDamage(battler, battler.MaxHP * badPoisonCounter / 16);
break;
}
}

BattleManagerCaptureModule

File: BattleManagerCaptureModule.cs

实现捕获率公式:

public CaptureResult AttemptCapture(Battler wildBattler, CaptureItem ball)
{
float maxHP = wildBattler.MaxHP;
float currentHP = wildBattler.CurrentHP;
float catchRate = wildBattler.Species.CatchRate;
float ballBonus = ball.CatchRateModifier;
float statusBonus = GetStatusBonus(wildBattler.Status);

// 修正捕获率
float modifiedRate = (3 * maxHP - 2 * currentHP) / (3 * maxHP);
modifiedRate *= catchRate * ballBonus * statusBonus;

// 晃动检查(捕获或逃脱前 0-3 次晃动)
int shakeThreshold = Mathf.FloorToInt(65536f / Mathf.Pow(255f / modifiedRate, 0.1875f));

int shakes = 0;
for (int i = 0; i < 4; i++)
{
if (Random.Range(0, 65536) < shakeThreshold)
shakes++;
else
break;
}

return new CaptureResult
{
Shakes = shakes,
Captured = shakes >= 4,
CriticalCapture = CheckCriticalCapture(modifiedRate)
};
}

BattleStateMachine

File: BattleStateMachine.cs

状态机使用基于枚举的方法配合协程:

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

private BattleState currentState;

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:创建模块类

public class BattleManagerWeatherModule : BattleManagerModule
{
private WeatherType currentWeather = WeatherType.None;
private int weatherTurnsRemaining;

public override void Initialize(BattleManager manager)
{
base.Initialize(manager);
currentWeather = WeatherType.None;
weatherTurnsRemaining = 0;
}

public void SetWeather(WeatherType weather, int turns)
{
currentWeather = weather;
weatherTurnsRemaining = turns;
}

public float GetDamageMultiplier(Move move)
{
return currentWeather switch
{
WeatherType.Rain when move.Type == MonsterType.Water => 1.5f,
WeatherType.Rain when move.Type == MonsterType.Fire => 0.5f,
WeatherType.Sun when move.Type == MonsterType.Fire => 1.5f,
WeatherType.Sun when move.Type == MonsterType.Water => 0.5f,
_ => 1f
};
}

public void ProcessTurnEnd()
{
weatherTurnsRemaining--;
if (weatherTurnsRemaining <= 0)
{
currentWeather = WeatherType.None;
// 显示"天气恢复正常"消息
}
}
}

步骤 2:注册模块

将模块添加到 BattleManager 的模块列表中:

// 在 BattleManager.cs 中
[SerializeField] private BattleManagerWeatherModule weatherModule;

步骤 3:与现有模块集成

MovesModule 中,在伤害计算时调用天气模块:

modifier *= GetModule<BattleManagerWeatherModule>().GetDamageMultiplier(move);

步骤 4:挂接到状态机

在回合效果阶段,处理天气:

GetModule<BattleManagerWeatherModule>().ProcessTurnEnd();

战斗事件系统

模块通过 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. 保持模块专注。每个模块应处理单一职责。
  2. 通过事件通信,而非模块间的直接引用。
  3. 测试伤害计算,使用已知输入和预期输出。
  4. 不要直接修改 Battler 状态 -- 始终通过适当的模块操作。
  5. 使用状态机进行回合排序。不要添加临时协程。