オンラインゲーム開発のいにしえの技術
開発本部の堀口です。昨年は git による分散作業パターン を書き、つい先月は 札幌での講演 を行い、当文章ではゲーム開発の設計に関するネタです。
昔話交じりのポエムですが20日目としてよろしくお願いします。
オンラインゲームとは
20 年ちかく前に Quake というゲームがリリースされ、一見すると単なる一人称視点のシューティングゲームにしか見えないが、プレイヤー自身の Quake の世界を公開し、来場者と遊ぶことができた。当時でいえば、自分のホームページに CGI 掲示板を設置するのと似ていたと思う。
Quake は、ゲーム世界のふるまいと世界の変化を伝える入出力が非常に良く分離されており、参加するプレイヤーはその世界の中に現れた自分の分身となるアバターの行動のみを制御し、アバターの目の代わりに世界の変化を、わずかな情報にしてプレイヤーに伝えた。プレイヤーの目の前にある端末では、その情報をネットワークから受け取り、世界の状態や変化を 3D ポリゴン、 HUD 、サウンドで表現することに成功していた。
今ではブラウザのページ遷移しないアプリケーションのように、 DOM の変化を HTML 以外の情報によって、ブラウザできれいに再現するような考え方が Quake では確立されていた。
もうひとつ、 Quake のわずか後にリリースされた Diablo というオンラインゲームがあった。ゲーム本体そのものは従来のサーバを必要としないゲームと同じだが、世界中のプレイヤーが集まり、その中の不特定プレイヤーと遊ぶことができた。ひとつの世界で同時に遊べるのは4人までだが、他のプレイヤーの世界を比較的容易に探したり、選ぶことができた。また、ゲーム開始前のロビー機能が豊かで一晩中攻略や雑談のチャットで夜を明かすこともできた。
またそのすこしあとに、 UltimaOnline というゲームがリリースされ、 Quake のようにゲームのふるまいはインターネット越しのサーバに集約され、 Diablo のように不特定多数と遊べるようなゲームになっていた。こちらはロビーに相当する部分がすでにゲーム内に出来ており、街で仲間と騒ぎ、そのまま歩いて目的地に向かう事ができた。
名前のとおり、 UltimaOnline あたりからオンラインプレイを前提としたゲームをオンラインゲームと呼ぶようになり、 Multiplayer Online (MO) または Massive を付け MMO という風に修飾されるようになった。 PlayOnline という日本の雑誌もこういったゲームに焦点を当てた物だった。
以降、敵を倒すことやクエスト達成を重視するような EverQuest や、ターン制ではないリアルタイム戦略ゲームとして WarCraft や Age of Empires というようにオンラインゲームが増えていった。日本からもいくつかオンラインゲームがリリースされた。 20 世紀が終わる直前にインターネットと同じく急速に進化した遊び方であると思う。
その時の技術とは
当時の環境はダイヤルアップ接続による 33.6kbps あたりが普及帯だったかと思う。深夜通話が定額制になるテレホーダイは必須サービスで、プレイヤーは深夜型になり、国内の運営も深夜に活発になった。昨今の帯域制限されたモバイル回線の数割程度の速度しかないので UDP 利用やパケットの最適化等が著しく行われていた。また、ルータ等は普及していなく、端末同士の直接接続も取り入れられていた。
プログラムサイズやパケットサイズが小さく、暗号化等のコストも掛けられない事から解析されやすく、それを悪用したチートツールも多く普及していた。 Quake や UltimaOnline のようにゲームのふるまいを決めるサーバのプログラムと、プレイヤーへの表現に集中するクライアントプログラムが分かれているゲームでは、不具合は修正しやすいように見えた。実際 Quake では後にソースコードがすべて公開され、サーバプログラムに対してゲームのふるまいを破綻させるような攻撃の余地が無いことが分かっている。このようにゲームのふるまいをサーバに実装し、プレイヤーへの表現やプレイヤーからの入力のみを端末のプログラムに任せる設計を、クライアント・サーバ(C/S)型というように呼ばれている。対してゲームのふるまいも端末で行う場合はピアツーピア(P2P)型と呼ばれている。
P2P 型は従来のゲーム開発の延長で出来るため当時から現在まで多く採用されている。ただし、サーバコストの低下、ルータやモバイル端末普及による直接接続コスト上昇、チート対策、ゲームのふるまいが複雑になるにつれ実装が困難になっていく、などのことから目にする機会は減っている。特に Diablo では著しいチート行為が蔓延し、オンラインゲームとしての世界が破たんしていた。
P2P 型でも Diablo のようにプレイヤーのだれか一人がサーバプログラムを動作させる仕組みと、 Age of Empires のように全員がお互いを監視、同期しあう仕組みが普及していた。開発を進めること自体は直感的にでき、それほど難しくないが、新しい体験を与え面白いゲームとして開発を完成させるのは困難であるように感じる。
C/S 型のゲームで、ゲーム開発するということはサーバプログラム開発が主体になり、入力や描画に関することとゲームロジックを適切に分離させる技術が必要になる。ゲームクライアントは、 Web ブラウザのように実装し、飾りとしての動作に徹する。ゲームサーバはゲーム本体になるが、サーバを持たないゲームのように目で見て確認することが困難なので、ユニットテストや結合テストの技術が重要になる。グラフィックスとして画面に出力せずにゲームの動作を確認することが出来れば問題なく開発を続けられる。
本文章では、確実に完成させられる C/S 型ゲームの技術について説明しておこうかと思う。
そのまえに、なぜ「いにしえ」扱いなのか
オンラインゲームの技術は 21 世紀になった途端にあまり進化しなくなったように思う。比較的高価な PC 環境が必要で、課金しないとオンラインゲームは全く遊べなかった。対して PlayStation2 が発売され、家庭用ゲーム機の性能が著しく進化し、開発者もプレイヤーも主流は家庭用ゲーム機だった。また、オンラインゲームは開発を完了させるのはもちろん、商業的に成功させるのは果てしなく困難であるように見えた。
実際その後もオンラインゲーム開発に挑戦する者たちは多かったが一部のみが成功し、その間に家庭用ゲーム機が進化し、 Web2.0 という言葉が出来るころにはこれからはそういう時代かのように見え、携帯電話の普及に合わせて新しい「ソーシャルゲーム」ができ、プラットフォームも SNS からの Flash ゲームというのは記憶に新しいのではないかと思う。
この間、オンラインゲームの新しい体験は乏しく、見た目が綺麗になるか、 WoW のクローンか、 StarCraft の UMS スピンオフか、だいたいどれかに当てはまる状況だった。常時接続が普及し、夜型人間が減るかと思いきや廃人の量産化に拍車がかかり、社会的に制限が与えられるようになった。
絶対数も他のゲームジャンルに比べると少ないまま、昨今の C/S 型オンラインゲームは強固なチート対策と大きなプログラムサイズから解析の難易度が高い上に、商業的に成功する可能性が低いので開発の機会も乏しい。学習困難となれば滅んでしまうかもしれないので、いにしえの技術とする。
ここからは技術的な話
前置きが長すぎたので、本題へ。まずそれぞれのサーバ関連技術をざっくりまとめると、
- Quake
- 自分の行動を伝える
- 他人や世界の状態をもらう
- 当たり判定、視野の判定を行う
- オフラインプレイでは、サーバを同じマシンで動かす
- UltimaOnline
- マップデータは視界の分だけもらう
- 2D のセルなので、行動に関する情報は限定している
- ゲームサーバの動作レートは低い
- Diablo
- ロビーで不特定多数と集える
- ロビーチャットがある
- 公開されているゲームの一覧が見れる
だいたいこれらの要素が達成できれば C/S 型の MO ゲームとしては完成させることが出来る。人数制限を取り除き、通知方法を工夫するだけで MMO としても出来る。
当文章では筆者が 12 年ほど前に作った MO リアルタイムパズルゲームを元に解説したいと思う。
図にするまでも無いぐらい乏しいものだが、サーバが Windows だったので、所謂 WAMP 環境の構築に苦労した記憶がある。この世代 (02年ごろ) あたりからすべての要素をゲームクライアントで表示ではなく、 Web ブラウザで表現出来るものは CGI で実装されているケースが目立ってきた。
ゲーム本体の部分
2ch からのアクセスがあったのでもしかしたら見たことがある人がいるかもしれない。
MO ゲームを開発しようとしたとき、開発を完了できるであろう技術はそろったので、その技術をふるまえるゲームデザインは何かという、技術ドリブンで開発することにした。幾つかの既存のゲームを参考に、これは他の技術ではオンラインゲーム化出来ないということで、画面写真のようなゲームにした。
ゲームのルールはおおむね次のとおり。
- サイコロが定期的に浮かんでくる。
- 浮き始めの低いときは、階段を上がるかのように乗ることが出来る。
- 完全に浮き上がったとき、他のサイコロに移動することができる。
- 乗っているサイコロのフチに突っ込むと転がすことが出来る。
- 転がったとき、サイコロの目の数だけ繋がるとだんだん沈んでいく。
- フロアに立ってるときはサイコロを押したり、他のプレイヤーに潰されたりする。
- 3D 表現によってサイコロの側面を見ることが出来る。
非常にシンプルなルールだがプレイヤーがこのゲームの世界に与えられる影響は、アバターをどの向きにどのぐらい動かすかだけになる。リアルタイムで動くのでサイコロの奪い合いの影響や踏みつぶされたときのふるまいを考えると P2P 型では実現することが大変困難であることが分かる。また、レベルのデータは比較的少量で記述することが出来た。
具体的にどういうプロトコルを組んだかというと、次の通りのものになる。(原文ママ)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
enum MSG_CLIENT//request { MSG_C_CONNECT=1, MSG_C_KEYPASS, MSG_C_DISCONNECT, //実際にClientから来るのはここから。 MSG_C_PLAYERDATA, MSG_C_PING, MSG_C_READY, MSG_C_CHAT, MSG_C_MOVING, MSG_C_STOP, MSG_C_ACCOUNTDATA, MSG_C_VER, }; enum MSG_SERVER { MSG_S_KEYPASS=1, MSG_S_PLAYERSDATA, MSG_S_JOIN, MSG_S_LEAVE, MSG_S_PONG, MSG_S_FLOORDATA, MSG_S_ERRMSG, MSG_S_CHAT, MSG_S_MOVECHAR, MSG_S_PREPARATION, MSG_S_READY, MSG_S_GAMESTART, MSG_S_PLAYERSCORE, MSG_S_GAMESCORE, MSG_S_GAMELEVEL, MSG_S_START, MSG_S_GAMEOVER, MSG_S_NEWDICE, MSG_S_DICEDATA, MSG_S_ROTATIONDICE, MSG_S_MOVEDICE, MSG_S_REMOVEDICE, MSG_S_FALLDICE, MSG_S_FALLDETAIL, MSG_S_ITEM, MSG_S_REMOVEITEM, MSG_S_USEITEM, MSG_S_VER, }; |
上記の定義を元に前者をクライアントからサーバへ、後者をサーバからクライアントへのメッセージとし、それぞれで実装すればリアルタイムパズルオンラインゲームの出来上がりだ。 RFC であればそれぞれのメッセージ内のペイロードまで細かく載せるところだが、ゲーム開発者であれば十分補間できるだろう。
CONNECT KEYPASS DISCONNECT
はセッション管理に関するメッセージになる。プロトコルを覗かれたとき、そのままだと恥ずかしかったのでとりあえず XOR 暗号のための鍵を送っていた。ゲームのふるまいとは関係ないところである。
MSG_C_PLAYERDATA
と MSG_S_PLAYERSDATA
はゲームに参加するときのプレイヤーに関する情報だ。当時のゲームではアカウント管理システムのようなものは MO では存在しないことが多く、見た目(スキン)や名前などは自己申告で自由に設定することができるゲームが多かった。
MSG_S_JOIN
と MSG_S_LEAVE
は他のプレイヤーが参加してきたとき、または退出したときに通知される。先ほどの PLAYERDATA は新参者が利用するだけで、こちらはすでにゲームに参加済みのプレイヤーに向けて使われる。
MSG_C_PING
と対照の MSG_S_PONG
はクライアントセッションの生存管理に使われる。また当時は回線の遅延を表示するゲームも多かったので、 PING 送出から PONG 返送までの時間を計る事で遅延を計算した。遅延をクライアントで計算することで補間の足しにすることもできたが、プレイヤーへの参考程度に表示することが多かったように思う。当ゲームでも遅延は表示するにとどまった。
MSG_C_READY
と MSG_S_READY
はそれぞれのプレイヤーの準備完了の通知である。まずはプレイヤーはゲーム内に参加するとゲーム画面のようなフロアに飛ばされ、チャットをしつつ、ゲームレベルの設定などを行う。各々が準備できたら MSG_S_GAMESTART
が通知されゲームのタイマーが動き出し、サイコロが浮かび上がってくる。こういった仕組みも当時のゲームでは多かった。
MSG_C_MOVING MSG_C_STOP
そして MSG_S_MOVECHAR
は移動操作に関するプロトコルである。当パズルゲームの場合は、アバターをリアルタイムに操作してパズルを解いていくのでアバターを制御するための情報だけで済んだ。移動するときに向きを伝え、止まりたいときに停止を伝える。サーバからは移動したプレイヤーの座標のみ送られてきて、適当に補間しつつ表示する。
マウスで操作する場合、移動したい位置をクリックさせ、その座標を送る方が遅延に強く、プレイヤーが狙った位置に移動させやすい。三人称視点のゲームではほぼ移動先座標を送る設計だ。しかし、当パズルゲームでは「サイコロを転がす向き」のほうが重要であったので、 FPS のように向きを操作する方式になっている。方向キーまたはプレイヤーからクリック位置への向きを計算してサーバに送る。
サーバからの通知も同じ要素で再現することも出来たが、開始と停止の時間で距離が決まってしまうため、遅延に非常に弱く、また実装方法も変えやすいよう位置を伝える方式になっている。両方を組み合わせるとカーブも滑らかに動かすことが出来る。
移動は操作感と見た目両方にも影響し、工夫の余地も多いので良く考え色々な実装を試してみるのが良いだろう。とある 3D MMORPG では位置だけでなく、移動方向と移動速度と回転速度がサーバから通知される。
プレイヤーがゲームの世界に与えられる影響は以上のプロトコルだけであり、クライアントプログラムが解析されようと移動開始と停止のみでは破壊的なチートは不可能であることが分かる。サイコロの動作に関する通知は比較的多く占めているが、クライアントからは直接操作するメッセージが全くない。スコアに関する情報も同様である。
サーバから通知されるプロトコルは多数あり、それぞれリアルタイムで受信から状態が変わっていくので BOT 等を利用するためのクライアントエミュレーションも困難であることが想像できる。例えそれができても、 AI を実装しなければならず、リアルタイムパズルというルールでは、麻雀はもちろん、 MMORPG に比べても困難ではないかと思われる。
ロビー実装のところ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
enum MSG_LBY_CLIENT { MSG_LC_CONNECT=1, MSG_LC_KEYPASS, MSG_LC_DISCONNECT, //実際にClientから来るのはここから。 MSG_LC_LOGIN, MSG_LC_CHAT, MSG_LC_CREATE, MSG_LC_JOINFAIL, MSG_LC_JOINSUCCESS, MSG_LC_DISCONNECTGAME, MSG_LC_JOIN, MSG_LC_GAMELIST, MSG_LC_GETGAMERANK, MSG_LC_GETGAMERANKDETAIL, MSG_LC_VER }; enum MSG_LBY_SERVER { MSG_LS_KEYPASS=1, MSG_LS_LOGINFAIL, MSG_LS_LOGINPASS, MSG_LS_PLAYERS, MSG_LS_JOINPLAYER, MSG_LS_LEAVEPLAYER, MSG_LS_PLAYERSTATECOLOR, MSG_LS_CHAT, MSG_LS_SRVMSG, MSG_LS_JOIN, MSG_LS_GAMELIST, MSG_LS_GAMERANK, MSG_LS_GAMERANKDETAIL, MSG_LS_VER }; |
ロビーとはゲームのコア部分と別のプロトコル設計になっている。殆どのプロトコルが対照的であり、リアルタイムではないので Web の技術が十分にある昨今ではネイティブプログラムで作る意味はあまりないように思う。当ゲームではランキングを実装したころ、その GUI をゲーム内に組み込む事が面倒になり、 Web での表示にシフトしていった。
ロビーとゲームを分けることによって、ロビーが稼働していなくてもアドレスの直接入力で遊ぶことができる。これも当時は常時接続環境があまり普及していなかったので当然のように採用されていた設計だ。一人で遊ぶときは先に述べたようにサーバモードで一つプログラムを起動して自分のマシンに接続するだけで済む。
こういった仕組みは、より古くから普及していた IRCnet というチャットツールとの相性が良く、 IRC によって相手のアドレスが分かるので、 IRC クライアントを拡張させることでゲームロビーの代わりにも出来た。もし MO を作るときは既存システムに相乗りしてしまうのも良い手ではないかと思う。ゲームに特化した GameSpy というシステムをはじめ、各種プラットフォームにはだいたい備わっているのではないかと思われる。
機能としてはわずかだったが、やはり動作するコードが分散すると開発時間も伸びてくる。当ゲームではゲームの本体部分は四カ月程度で概ね出来たが、こちらのロビーも機能の割には同じぐらい手間がかかったように記憶している。ネイティブのロビープロセス、 CGI 、 DB 設計、運用が絡むと格段に開発が難しくなる。
Web サーバを用意したことでオンラインアップデートのためのパッチシステムも想定の範囲内で実装できた。
今回挙げたプロトコルは昨今のスマートフォンを用いたオンラインゲームでも耐えうるかと思う。むしろ当時のオンラインゲーム環境よりもずっと良いのでもっと盛り込むことも十分できるだろう。
いにしえからのまとめ
わずかな仕組みでオンラインゲームはだいたい開発できてしまう。そのためには、
- クライアントからサーバへ送るプロトコルはなるべく少なくすると良い。
- サーバへのメッセージが増えるということは不正行為の可能性が増えることになる。
- 入力とゲームロジックの分離が不適切である場合、コード複雑になりやすい。
- 世界の変化をクライアントへ伝えることにすると良い。
- 状態よりも変化のほうが少ない情報量で済むため。
- 世界の状態を伝えるのはラグの影響が大きいときだけにすると良い。
- 可能な限り Web 前提の仕組みにすると良い。
- ゲームクライアントを動作させずとも動作確認できるように作ると良い。
- 特に IDE とテストとデバッガを柔軟に扱えると良い。
- グラフィックス等の出力との分離が出来ない場合、コードが複雑になりやすい。
- アイデアではなく、持っている技術でゲームデザインすると良い。
そうでない場合、開発が困難になる可能性が高くなるのではないかと思う。
チート行為に関するネタはこちらもどうぞ。
明日はみんなに愛されている QA チームのお話です。おたのしみに!