このエントリーは、KLab Advent Calendar 2015 の12/13の記事です。
KLabGames事業部のyasui-sです。よろしくお願いします。
最近、サーバとクライアントが常時接続状態で多数の端末を同期させるゲームが増えてきています。
その中で一番の核であり、キモである同時という部分についての考え方を書こうと思います。
蛇足ですが、今回の説明の内容は、要件によって必要度合いが大きく異なります。
要件によっては基本形で十分であることもありますので適時ご判断ください。
本稿ではわかりやすさを重視し、MMOを題材として解説を行い、各通信時間は1秒単位でかかると設定しています。
また、サーバを含めた各端末上での時刻はすべて統一されているとします。
次のフローをご覧ください。
これは、単純にコマンドの実行を送るフローです。
誰しもが一番初めに作成する形ではないでしょうか。
今回仮定したようなクライアントAからサーバには1秒、サーバからクライアントBには2秒かかるとした場合、
クライアントB上で実行されるのが3秒遅延してしまうということになります。
この3秒が致命的である場合もあり、この3秒をどのようにごまかすかが重要になってきます。
次のフローをご覧ください。
クライアントから送られたコマンドをサーバが全クライアントへ送信しています。
しかし、サーバからクライアントBへの通信時間がサーバからクライアントAへの通信時間より長いため、
結果的にコマンドの実行が1秒ずれてしまっています。
一番の基本形と比べ、クライアントAとクライアントBとのずれが小さくはなっていますが、
それでも要件として満たさない場合も多いはずです。
また、この方法だと致命的な問題があります。
それは、ユーザの操作に対するレスポンスが遅いということです。
上記フローによると、ユーザの入力からコマンドの反映まで2秒かかってしまっており、
その2秒がユーザにとってストレスになってしまう可能性が大いにあります。
ここでコマンドだけの送信・仲介方法を考えることを諦め、
何らかのデータを付け加えて、このずれをどうにかするという方向へ転換してみたいと思います。
今回は実行時刻を加えてみた場合について考えてみます。
しかし、この時刻がくせ者で、サーバを含めたどの端末のどの時点の時刻かによって状況が変わってくるでしょう。
では、それぞれのケースについて詳しく考えてみましょう。
次のフローをご覧ください。
サーバがコマンドの実行を受け取った際に、サーバ基準の時刻を付け加えて全クライアントへ送信しています。
そして、受け取ったクライアントではそのサーバ基準の時刻でコマンドが実行されたものとして実行します。
これなら各クライアント上で実行開始された(とされる)時刻が同じであるため、
各端末上でのずれはそれほど発生しないでしょう。
しかし、クライアントA上では依然としてユーザの入力からの遅延が発生しており、ストレスが発生する可能性をはらんでいます。
次のフローをご覧ください。
クライアントA上ではコマンドの入力後、すぐに実行しています。
これにより先ほど発生していた入力に対する遅延は起こらなくなっています。
また、クライアントB上ではサーバで受け取った時刻を元に実行しており、
(補間処理などは行いつつ)クライアントAとクライアントBとのずれは1秒で収まっています。
多くの要件はこの構成で十分なのではないでしょうか。
しかし、逆を言えば1秒もずれているとも言えます。
タイミングが重要な要件であればこのズレが致命的になる可能性があります。
次のフローをご覧ください。
クライアントAでは自分の時刻を元に実行時刻を決め、コマンドと共に送っています。
サーバではそれを仲介するだけにし、クライアントBではクライアントA上の実行時刻を元に補間し、実行します。
その結果、クライアントAとサーバ、クライアントBでは実行開始されたタイミングまで同期できました。
しかし、クライアントの自己申告による実行時間を信用するということは、改ざんに対して弱くなることと同義であるため、
悪意のあるクライアントからの通信に対応する必要が発生します。
以下に今回挙げた項目についてサマリを作ってみました。
個々の方法について比較する一助になれば幸いです。
方法 | メリット | デメリット | 用途例 |
---|---|---|---|
基本形 | 構造が単純 コーディングが簡単 |
各端末でタイミングがずれる | タイミングが重要でないもの 例:チャットの吹き出し |
サーバがコマンドを受け取ったことを返した後、実行する | 構造が単純 コーディングが簡単 サーバ基準で完全に同期している |
各端末でタイミングがずれる ユーザレスポンスが悪い |
タイミングが重要でないもの サーバ側で何らかの判定を行う必要があるもの 例:アイテムの使用 |
サーバがコマンドの受け取り時刻を加えて返した後、実行する | 各端末でずれが生じにくい サーバ基準で完全に同期している 実行タイミングの改ざんを行いにくい |
ユーザレスポンスが悪い 操作キャラクタを含めた見た目が悪い |
サーバ側で何らかの判定を行う必要があるもの 例:コマンド方式の場合のスキルの先行入力 |
コマンドを実行したクライアント上では即時実行し、 サーバがコマンドの受け取り時刻を加えて他のクライアントへ返した後、実行する |
実行端末以外の各端末でずれが生じにくい ユーザレスポンスが良い |
実行端末とそれ以外の端末でずれが生じる | サーバ側で何らかの判定を行う必要があるもの ユーザの操作に対するレスポンスが早いほうがいいもの 例:エモーション |
コマンドを実行したクライアントでコマンドとクライアント基準の実行時刻を送り 、サーバがコマンドを他のクライアントへ返した後実行する |
各端末でずれが生じにくい ユーザレスポンスが良い |
クライアントからの通信改ざんに対して脆弱になる | ユーザの操作に対するレスポンスが早い必要があるもの 通信が改ざんされても影響が少ないか異常値をフィルタできるもの 例:コマンド入力方式でない場合の移動処理 |
これらの手法はどれも一長一短であり、何度も言いますが要件によって最適な選択肢が違います。
また、行う通信によってそれぞれの手法を選択するということも有力な候補になるでしょう。
(例えば、移動はクライアントから実行時刻を決めて、攻撃はサーバが実行時刻を決めるなど)
更に今回は割愛しましたが、攻撃対象が死亡しているなど、コマンドが実行できないとサーバが判断した際に
どのようにキャンセルを行うかということも含めて考えていく必要があります。
これらの手法を元に、更に効率のよい方法を日々模索していきたいと思います。
明日は hhatto さんです。お楽しみに。
KLabのゲーム開発・運用で培われた技術や挑戦とそのノウハウを発信します。
合わせて読みたい
KLabのゲーム開発・運用で培われた技術や挑戦とそのノウハウを発信します。