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

PvP バトル

MCE Online は、PvP マッチメイキング、野生/トレーナーバトルの検証、観戦、レイド協力プレイ、捕獲検証をカバーする、完全なサーバー権威のバトルシステムを提供します。すべてのバトルコマンドは Nakama RPC を経由して振り分けられるため、サーバーが真実の源になります。

アーキテクチャ概要​

クラスファイル責務
NetworkedBattleManagerBattle/NetworkedBattleManager.csMCE のローカル BattleManager とオンラインレイヤー間の MonoBehaviour ブリッジ
ServerBattleAuthorityBattle/BattleServices.csすべてのバトルアクション(技、入れ替え、アイテム、逃走)を Nakama RPC 経由で送信
BattleMatchmakerBattle/BattleServices.csNakama のマッチメイカーを使った ELO ベースの PvP マッチメイキング
SpectatorServiceBattle/BattleServices.csライブバトルを読み取り専用モードで観戦
RaidBattleServiceBattle/BattleServices.csリアルタイムマッチを介した 5対1 の協力ボスバトル

サーバー権威のバトルフロー​

NetworkedBattleManager は、NakamaManager.IsConnected に基づいてオンライン検証を使うかどうかを決定します。オフラインの場合、または forceOffline が設定されている場合、バトルは完全にローカルの BattleManager を通じて実行されます。

バトルの開始​

野生エンカウント(IV/色違いの操作を防ぐため、サーバーが野生モンスターを生成します):

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 を呼び出します。

アクションの送信​

すべてのアクションは、NetworkedBattleManager.SubmitActionAsync() を介して ServerBattleAuthority を経由します:

// 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 は、ELO ベースのフィルタリングを備えた Nakama の組み込みマッチメイカーを使用します。

検索の開始​

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 が発火し、次をトリガーします:

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 が処理)が2人のプレイヤーをペアにします:

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

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

呼び出し側は opponentUserId を渡します。サーバーは共有のサーバー権威 pvp_battle マッチを作成し、参加するためにその matchId を返します。相手には同じ id を運ぶ pvp_match_ready 通知(コード 111)がプッシュされ、両クライアントが1つの権威あるマッチに収束します。両方のロスターがサーバー側でシードされなければならず、そうでなければ 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)。

レイドバトル(5対1 協力)​

RaidBattleService は、最大 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 のとき、すべてのバトル結果はサーバー側で決定されます:

  • 野生エンカウント:サーバーが野生モンスター(種族、レベル、IV、色違い)を生成し、クライアント側の操作を防ぎます。
  • 捕獲の試行:捕獲率はサーバー側で計算されます。
  • 経験値/お金/アイテム:BattleResult の報酬はサーバーから来て、検証後にのみ適用されます。
  • ELO の変化:ランキング調整はサーバーによって計算・適用されます。

OnlineCommandBridge は、バトルから得られたアイテムやお金が mce_give_item および mce_give_money RPC を介して検証されることを保証します。