跳到主要内容

PvP 战斗

MCE Online 提供一套完整的服务器权威战斗系统,涵盖 PvP 匹配、野生/训练家战斗校验、观战、团战合作和捕捉验证。所有战斗命令都经由 Nakama RPC 路由,因此服务器是唯一真相来源。

架构概览​

类文件职责
NetworkedBattleManagerBattle/NetworkedBattleManager.csMCE 本地 BattleManager 与在线层之间的 MonoBehaviour 桥梁
ServerBattleAuthorityBattle/BattleServices.cs通过 Nakama RPC 提交所有战斗行动(招式、切换、道具、逃跑)
BattleMatchmakerBattle/BattleServices.cs使用 Nakama 匹配器的基于 ELO 的 PvP 匹配
SpectatorServiceBattle/BattleServices.cs以只读模式观看实时战斗
RaidBattleServiceBattle/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 发送不同的载荷:

行动载荷服务器校验
MovebattleId、moveIndex、targetSlot、actionType: "move"招式存在、PP 剩余、目标有效
SwitchbattleId、switchTo(槽位)、actionType: "switch"怪物存活、未被困
ItembattleId、itemId、targetSlot、actionType: "item"道具在库存中、使用有效
FleebattleId、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,服务器计算捕捉率、摇晃次数和会心一捕几率。

基于 ELO 的匹配​

BattleMatchmaker 使用 Nakama 内建的匹配器,带基于 ELO 的过滤。

开始一次搜索​

await NakamaManager.Instance.Matchmaker.StartSearchAsync(
eloRating: 1200, tier: "any");

匹配器查询在 +/- 200 ELO 范围内寻找对手:

+properties.elo:>=1000 +properties.elo:<=1400

数值属性包括玩家的 elo,字符串属性包括 tier。

匹配器创建一个 2 人匹配(minCount: 2,maxCount: 2)。

找到匹配​

当找到匹配时,ReceivedMatchmakerMatched 在 socket 上触发,进而触发:

NakamaManager.Instance.Matchmaker.OnMatchFound += (matchId, opponent) =>
{
// opponent.UserId, opponent.DisplayName
// matchId -- the battle match to join
};

权威 PvP 匹配(pvp_match_create)​

匹配找到一个对手;随后权威的 PvP 战斗在一个共享的、服务器拥有的匹配中运行。pvp_match_create(RpcIds.PvpMatchCreate,由 src/battle/pvp-engine.ts 中的 rpcPvpMatchCreate 处理)将两名玩家配对:

[Serializable]
public class PvpMatchCreateRequest { public string opponentUserId; }

[Serializable]
public class PvpMatchCreateResponse { public bool success; public string matchId; public string error; }

调用方传入 opponentUserId;服务器创建共享的、服务器权威的 pvp_battle 匹配,并返回其 matchId 以供加入。对手会被推送一个 pvp_match_ready 通知(代码 111),携带相同的 id,因此两个客户端会收敛到一个权威匹配上。两支队伍都必须在服务器端植入,否则该 RPC 会硬拒绝 —— 客户端无法将一支队伍伪造进排位赛。

搜索状态与取消​

bool searching = NakamaManager.Instance.Matchmaker.IsSearching;
float duration = NakamaManager.Instance.Matchmaker.SearchDuration;

await NakamaManager.Instance.Matchmaker.CancelSearchAsync();

OnMatchCancelled 在成功取消后触发。匹配超时通过 OnlineConfig.MatchmakingTimeout 配置(默认:60 秒)。

观战模式​

SpectatorService 让玩家以只读模式观看实时战斗。

列出实时战斗​

List<SpectableBattle> battles =
await NakamaManager.Instance.Spectator.GetLiveBattlesAsync();

foreach (var battle in battles)
{
// battle.BattleId, battle.Player1Name, battle.Player2Name
// battle.SpectatorCount, battle.Duration
}

这调用 get_live_battles RPC。

观看一场战斗​

bool joined = await NakamaManager.Instance.Spectator.SpectateAsync(battleMatchId);
// Receive battle state updates via OnBattleStateUpdate

await NakamaManager.Instance.Spectator.StopSpectatingAsync();

观战模式由 OnlineConfig.EnableSpectator 控制(默认:true)。每场战斗的最大观战人数:MaxSpectatorsPerBattle(默认:20)。

团战(5v1 合作)​

RaidBattleService 提供合作 Boss 战,最多 5 名玩家共同对抗一只强大的怪物。

可用团战​

List<RaidBossInfo> raids =
await NakamaManager.Instance.Raids.GetAvailableRaidsAsync();

foreach (var raid in raids)
{
// raid.BossId, raid.BossName, raid.Difficulty (Normal/Hard/Legendary/Mythic)
// raid.CurrentParticipants, raid.MaxParticipants, raid.RecommendedLevel
}

创建团战大厅​

string raidMatchId = await NakamaManager.Instance.Raids.CreateRaidLobbyAsync(
bossId: "raid_boss_legendary_dragon",
difficulty: RaidDifficulty.Legendary);

这创建一个 Nakama 匹配,并通过 raid_create RPC 以 matchId、bossId、difficulty 和 maxParticipants(来自 OnlineConfig.RaidMaxParticipants,默认:5)在服务器上注册它。

加入并进行​

bool joined = await NakamaManager.Instance.Raids.JoinRaidAsync(raidMatchId);

// Submit actions during the raid
await NakamaManager.Instance.Raids.SubmitRaidActionAsync(raidId, moveIndex);

团战行动使用匹配状态 op 代码 200。

团战事件​

Raids.OnRaidFound += (raidId) => { };
Raids.OnPlayerJoinedRaid += (userId, count) => { };
Raids.OnRaidStarted += (raidId) => { };
Raids.OnRaidCompleted += (raidId, success) => { };

团战由 OnlineConfig.EnableRaidBattles 控制(默认:true)。

难度设置​

配置字段默认值说明
MatchmakingTimeout60sPvP 搜索超时
PvPTurnTimeout45sPvP 中每回合的时间
MaxSpectatorsPerBattle20观战人数上限
RaidMaxParticipants5团战最大玩家数
EnableSpectatortrue切换观战模式
EnableRaidBattlestrue切换团战

战斗情境中的反作弊​

当 OnlineConfig.EnableServerAuthority 为 true 时,所有战斗结果都在服务器端决定:

  • 野生遭遇:服务器生成野生怪物(物种、等级、个体值、闪光度)以防止客户端操纵。
  • 捕捉尝试:捕捉率在服务器端计算。
  • 经验/金钱/道具:BattleResult 中的奖励来自服务器,仅在校验后应用。
  • ELO 变化:排名调整由服务器计算并应用。

OnlineCommandBridge 确保任何从战斗中获得的道具或金钱都通过 mce_give_item 和 mce_give_money RPC 校验。