PvP 战斗
MCE Online 提供一套完整的服务器权威战斗系统,涵盖 PvP 匹配、野生/训练家战斗校验、观战、团战合作和捕捉验证。所有战斗命令都经由 Nakama RPC 路由,因此服务器是唯一真相来源。
架构概览
| 类 | 文件 | 职责 |
|---|---|---|
NetworkedBattleManager | Battle/NetworkedBattleManager.cs | MCE 本地 BattleManager 与在线层之间的 MonoBehaviour 桥梁 |
ServerBattleAuthority | Battle/BattleServices.cs | 通过 Nakama RPC 提交所有战斗行动(招式、切换、道具、逃跑) |
BattleMatchmaker | Battle/BattleServices.cs | 使用 Nakama 匹配器的基于 ELO 的 PvP 匹配 |
SpectatorService | Battle/BattleServices.cs | 以只读模式观看实时战斗 |
RaidBattleService | Battle/BattleServices.cs | 通过实时匹配进行 5v1 合作 Boss 战 |
服务器权威的战斗流程
NetworkedBattleManager 根据 NakamaManager.IsConnected 决定是否使用在线校验。如果离线或设置了 forceOffline,战斗将完全通过本地 BattleManager 运行。
开始一场战斗
野生遭遇(服务器生成野生怪物以防止个体值/闪光操纵):
string battleId = await networkedBattleManager.StartWildBattleAsync(
mapId, encounterTableId, playerLevel);
这调用 battle_start_wild RPC。服务器返回一个带 battleId 和 matchId 的 BattleStartResponse。客户端加入战斗匹配以获取实时状态更新。
训练家/NPC 战斗:
string battleId = await networkedBattleManager.StartTrainerBattleAsync(
trainerId, mapId);
调用 battle_start_trainer RPC。
提交行动
所有行动都经由 ServerBattleAuthority 通过 NetworkedBattleManager.SubmitActionAsync():
// Use a move
await networkedBattleManager.SubmitActionAsync(
BattleActionType.Move, moveIndex: 0, target: 0);
// Switch monster
await networkedBattleManager.SubmitActionAsync(
BattleActionType.Switch, index: 2);
// Use an item (pass the inventory slot index; the server resolves the item ID)
await networkedBattleManager.SubmitActionAsync(
BattleActionType.Item, index: itemSlot, target: targetSlot);
// Attempt to flee
await networkedBattleManager.SubmitActionAsync(
BattleActionType.Flee, index: 0);
每种行动类型向 battle_submit_action RPC 发送不同的载荷:
| 行动 | 载荷 | 服务器校验 |
|---|---|---|
| Move | battleId、moveIndex、targetSlot、actionType: "move" | 招式存在、PP 剩余、目标有效 |
| Switch | battleId、switchTo(槽位)、actionType: "switch" | 怪物存活、未被困 |
| Item | battleId、itemId、targetSlot、actionType: "item" | 道具在库存中、使用有效 |
| Flee | battleId、actionType: "flee" | 逃跑检查(无法从训练家 PvP 逃跑) |
回合结果
在双方玩家提交行动后,服务器结算该回合并返回一个 TurnResult:
networkedBattleManager.OnTurnProcessed += (TurnResult result) =>
{
// result.turnNumber, result.playerMoveName, result.enemyMoveName
// result.playerDamageDealt, result.enemyDamageDealt
// result.effectiveness, result.statusApplied, result.weatherChange
// result.playerFainted, result.enemyFainted
};
战斗结束
当战斗结束时,OnBattleEnded 随一个 BattleResult 触发:
networkedBattleManager.OnBattleEnded += (BattleResult result) =>
{
// result.playerWon, result.expGained, result.moneyGained
// result.itemsGained, result.evGained, result.enemyCaught
// result.eloChange (PvP only)
};
匹配会自动离开,状态通过 EndBattleAsync() 清理。
在线捕捉系统
捕捉尝试经过服务器校验以防止捕捉率操纵:
CaptureResult result = await networkedBattleManager.AttemptCaptureAsync("pokeball");
// result.Success -- whether the capture succeeded
// result.ShakeCount -- number of shakes (0-3)
// result.IsCriticalCapture -- whether it was a critical capture
battle_attempt_capture RPC 接收 battleId 和 ballId,服务器计算捕捉率、摇晃次数和会心一捕几率。