战斗内部机制深入解析
本指南适用于拥有完整 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);
}
}