📌 この記事は Zenn に投稿した内容のアーカイブです。

はじめに:高速対戦ゲームにおけるネットコードの本質

ゲームプレイ(GamePlay)エンジニアにとって、最重要テーマのひとつが ネット同期(netcode) です。
特に Overwatch のような高速リアルタイムアクションでは、「レスポンスの速さ(responsive)」 がゲーム性を決定づけます。

そのため、プレイヤーの操作は サーバーの応答を待たずに即時反映 する必要があります。
これを実現するのが 予測(prediction / pre-presentation) です。

ただし、クライアントを信頼できない(チート対策)というFPSの前提は20年変わっていません。
そのうえで「即時応答」と「サーバー権威」の両立を図るのがネットコード設計の核心になります。


即時応答が必要な操作

  • 移動

  • スキル

  • 武器(発射や装填などのスキル付き武器)

  • 当たり判定(Hit Registration)

共通原則はただ一つ:
「プレイヤーがボタンを押したら即、見た目の挙動が起きること」

どれほど ping が高くても、遅延を感じさせてはならない。

しかし、予測を行う以上、避けられない問題があります──
それが 予測ミス(misprediction) です。

予測ミスは、
「クライアント側では成功したように見えた操作が、サーバー上では成立していない」
状態を指します。

Overwatch では、予測ミスが起きても「操作を遅延させる」のではなく、
できる限り予測ミスを減らす設計(=確定性 Determinism) が採用されています。


予測ミスの例:サーバーとの食い違い

ping 250ms。
クライアントでは「ジャンプした」つもりでも、サーバー上では Mei の氷結を受けていた
その結果、クライアントは一度ジャンプの予測を行った後、サーバーからの正しい状態(氷結)で「強制的に巻き戻される」。

このように、超高速応答を目指すほど、予測ミスが発生しやすくなります。


▼ ここから:低ミス予測を支える「確定性(Determinism)」

以下では Overwatch が採用している ECS + Deterministic Simulation の要点を整理します。


1. 時間の量子化:Command Frame

確定的なシミュレーションは、

  • 時間の同期

  • 固定更新周期

  • 値の量子化

に依存します。

サーバーとクライアントは、Command Frame(命令フレーム) 単位で同期を取ります。

  • 1フレーム = 16ms(大会時は 7ms)

ECS では、プレイヤー入力を扱うすべての System は通常の Update ではなく
UpdateFixed(固定フレーム用の更新)で動作します。


2. クライアントは常にサーバーより「半RTT + 1フレーム」先を走る

|602x338

例:RTT が 160ms の場合

  • 半分 → 80ms

  • +1 フレーム(16ms)

つまりクライアントは常に 約96ms 未来を走っている 状態です。

なぜ?
クライアントが即時にプレイヤー入力を反映し続けるには、現在時刻(NOW)に限りなく近く動作する必要があるからです。


3. 予測 → サーバー確認 → 一致チェック

クライアントは以下を行います:

  1. 現在の入力を元に予測シミュレーション

  2. その結果をブラッシュアップしつつ、入力をサーバーへ送信

  3. サーバーが後から「この時刻の正しい状態」を送り返す

  4. クライアントは リングバッファに保存した過去の状態 と照合

  5. 一致 → そのまま継続 / 不一致 → Reconciliation(巻き戻し+再計算)


一致していればスムーズに次へ進み、
不一致なら「サーバーの状態へ巻き戻して」その後の入力を全部再適用します。


4. Reconciliation:巻き戻しと再適用

予測ミスが起きた場合:

  1. サーバーから戻ってきた「正しい状態」を採用

  2. その時点から現在までの「入力ログ」を全て再生(リプレイ)

  3. 現在時刻まで追いつく


例:
クライアントは Tracer が走っていると予測していたが、サーバー上ではMcCreeのフラッシュでスタン状態だった。

巻き戻して再計算すると、挙動がサーバーと完全に揃う。


5. ネットワークが不安定なとき:クライアントの「時間膨張」

クライアントの入力パケットはカスタム UDP(信頼制)で送られますが、
それでもパケットロスは起こる

パケットロスが続くと、サーバーは「最後に届いた入力」を元に予測するしかありません。

そこで、Overwatch が採用したのが:

クライアントの Time Dilation(時間膨張)

クライアントが、

  • 通常:1frame = 16ms

  • 膨張:1frame ≒ 15.2ms

のように 僅かに高速でシミュレーション を行い、サーバーより先行幅を広げる技術です。

結果:

  • 入力パケットが「より早く」サーバーに届く

  • サーバー側の入力バッファが太くなる(余裕が出来る)

  • パケットロスの穴を埋めやすくなる

これは、ping やパケットロスが揺れまくる環境(国際回線、宇宙ステーションなど)でも機能します。


6. サーバーが「回復した」と判断したら

ネットワークが安定すると、サーバーは:
「OK、もう大丈夫」
という合図を出します。

クライアントは:

  • 時間膨張を解除(frame 時間を元に戻す)

  • 入力報告頻度を通常に戻す

  • サーバーもバッファを縮小

これにより、遅延・丢包・予測ミスの影響を最小限に抑えつつレスポンス性を維持できます。


7. 入力の「スライディングウィンドウ」送信

サーバーが最後に確認したフレームが例えば 4 で、
クライアントが現在 19 フレームまで進んでいる場合:

クライアントは 4〜19 のすべての入力ログをまとめて送る

これにより:

  • パケットロスが発生しても次のパケットで穴埋め可能

  • サーバーは入力の抜けをほぼ確実に補完できる

  • 移動予測は破綻しにくくなる

この手法は古い FPS(例:Quake)の時代から存在する伝統技術です。


8. すべての要素を同時に起こした最悪ケースのデモ

以下の全要素が同時に発生:

  • ping 変動

  • パケットロス

  • 時間膨張

  • 入力スライディングウィンドウ

  • 予測ミス

  • サーバー側 correction

それでもゲームプレイは成立する──
これが Overwatch のネットコードの強さです。


まとめ

Overwatch のネットコードは次の技術の組み合わせで成り立っています:

  • クライアント予測(Prediction)

  • サーバー権威(Server Authoritative)

  • 巻き戻し(Reconciliation)

  • 確定的シミュレーション(Determinism)

  • スライディングウィンドウ入力

  • ネットワーク不安定時の時間膨張(Time Dilation)

結果として、低遅延・高レスポンス・高一貫性 を同時に実現しています。


※このブログは GDC2017【Overwatch Gameplay Architecture andNetcode 】を参考にしました。