Age of Empires にみる RTS の快適なオンラインプレイのための実装

こんにちは、さいです。

Age of Empires というリアルタイムストラテジーゲーム(RTS)があります。1997年に発売されたPCゲームですが、現在もシリーズが続いているゲームです。シングルプレイと、インターネット回線やLAN経由での多人数プレイが可能で、自身の文明で一定の条件を満たしたプレイヤーが勝利者となる仕組みです。

というわけで、Age of Empires はオンラインプレイができる RTS なわけで、
快適なオンラインプレイのためには、色々と考慮しないといけないことがあります。

特に Age of Empires の初代は、1997年発売なので、インターネット環境がモデムでの通信だったり、メモリが16MBだったり、15FPSしか出ないマシンが存在したりと、様々なスペックが今の時代よりだいぶ低かったりします。

そんな中で快適なオンラインプレイゲーム体験のための Age of Empires の工夫が興味深かったので、まとめます。

なんの情報をクライアント間で通信するのか

RTS というゲームの性質上、Age of Empires のゲーム上にはユニットがたくさん存在します。Age of Empires だとゲーム上に最大250ユニット存在するので、それらユニット1つ1つの (x, y) 座標や、今どんな行動をしているかを各クライアントへネットワークを通して共有しようとすると、大量のパケットを交換しあわないといけません。

そういうわけで Age of Empires では、ユニットの状態をネットワーク経由で共有するのでなく、プレイヤーのコマンドを各クライアントに共有し、各クライアントがネットワーク経由で受け取った各プレイヤーのコマンドを元にゲームを処理して描画するようになっています。


すべてのクライアントが同じコマンドを受け取って、コマンドを元に同じ処理を行えば、それぞれのクライアントが描画する画面は同じ結果になるはずなので、プレイヤーのコマンドを共有するだけで、理論上はオンラインプレイのゲームを成り立たせることができます。

ただしこの場合、ネットワークの遅延やパケット損失があって、どれかのクライアントが受け取るコマンドが欠けたり、受け取る順番がかわったりすると、クライアント毎にゲームの状態が変わってしまって、ゲームが成り立たなくなってしまいます。

同期して通信する

前述の通りインターネット回線というのはとても不安定なので、ネットワークの状況により、容易にパケットの順番が入れ替わったり、損失したりすると、ゲームが成り立たなくなってしまいます。そのため、各クライアントが同期してパケットを送受信し合う必要があります。一体どうしているのでしょうか。

Age of Empires はRTSなので、ゲームの仕様としてはターンの概念が存在しません。プレイヤー同士はリアルタイムに、好きなタイミングでゲームの各種行動を行い、そのプレイヤーの行動同士が即時反映されることで、ゲームが進みます。

一方 Age of Empires では実装レベルではターンの概念が存在します。これを deterministic lockstep networking model と呼びます。これは RTS ではよく使われる手法で、例えば Star Craft シリーズでも使われています。(Star Craft 1 では P2P 方式で、Star Craft 2 ではクラサバ方式でこのモデルを使用しています)

deterministic lockstep とは

前述の通り、Age of Empires では実装レベルでは、ターンの概念が存在します。このターンは 200ms ごとに 1 ターン進みます。

ゲームクライアントは、自分のプレイヤーがコマンドを入力すると、「現在のターン + 2ターン後のターンに、そのコマンドを実行する」よう予約します。

Age of Empires のオンラインプレイでは、実はプレイヤーのコマンドはすぐ実行されるわけではないのです。ただし 1ターンは 200ms なので、2ターン後に実行されても、プレイヤーはそこまで大きく違和感を感じることはありません。

また、その際に、自分のクライアントでコマンドの実行を予約するだけでなく、ターンの終了時に「○○ターンにこれらのコマンドを実行する」というパケットを各クライアントに向けて送信します。(○○ターンは、自クライアントの現在のターン+2ターン)

この2ターンというのは、パケットの送受信による遅延を待つ時間です。すべてのクライアントが、自プレイヤーの入力したコマンドや、他クライアントから受信したコマンドを即実行するのではなく、2ターン先に実行するようにすることで、パケットの送受信が多少遅延しようが、2ターン以内に各クライアントに届けば、それぞれのクライアントが必ず同じコマンドを実行できるようになります。

2ターンが経過すると、よほどネットワークの状況が悪くない限り、各クライアントが予約したコマンドを、自クライアントは既に受信していて知っているはずです。よって、予約されているそれらの各プレイヤーのコマンドを実行します。

もしここで、大きなネットワーク遅延などの理由で、どれかのクライアントのコマンドの予約が欠けている場合、欠けていると判断したクライアントは、ゲームの進行を止めて待ちます。大抵の場合、その数ターンあとには欠けてたクライアントのコマンドが届いて、ゲームの進行が再開するため、プレイヤーから見ると、ラグったように見えます。一方で、数ターン立ってもコマンドが届かないクライアントや、あるいは頻繁にコマンドの受信が遅れてゲームの進行を止めるクライアントを、各クライアントはゲームから除外する判断をします。

このように、ゲームの状態ではなく、プレイヤーの入力をネットワーク経由で送受信することで、各クライアントで同じ画面になることを成り立たせ、かつ各クライアントが協調してターンを進めていく方式を、deterministic lockstep と呼びます。

deterministic = 決定的、つまりプレイヤーが同じ入力をすれば必ず同じゲーム画面になるという意味。

lockstep = 密集行進、move in lock-step with で足並みをそろえて移動するいう意味。

なお、おの2ターン後というターン数や、あるいは 1ターンが 200ms という数字はゲームが調整可能です。もしラグが頻繁に発生するようなら、ターンを200ms->300msにするか、2ターン先の予約を、3ターン先の予約にするなどの調整が必要になります。(当然のことながら、その場合は、コマンド入力から実際にゲーム上で実行されるまでの待ちが大きくなることになります。)

ゲームによっては、これらの数値を固定値で使用するのではなく、オンラインプレイ開始後に、ネットワーク状況などを測りながら、動的に変更するゲームもあります。

参考

1500 Archers on a 28.8: Network Programming in Age of Empires and Beyond

Multiplayer Game Programming: Architecting Networked Games

この記事が気に入ったらサポートをしてみませんか?