ネットワークゲーム技術


Abarsは2002年に、オンラインアクションRPGであるRUINATERRAを発表しました。
その製作の過程では、プログラム的に様々な壁があり、何とか乗り越えようとしました。
ですが、オンラインゲームのノウハウは、今の時代とても貴重な物で、
ネット上には、なかなか有効な情報が無く、試行錯誤して解決していくしかありませんでした。
結果、多数のノウハウが集まりましたが、それをAbarsだけで保有しておくのはもったいないと思い、
今回、ここに公開する事にしました。
ここにある情報が、少しでも次世代のネットワークゲーム作家の方の役にたっていただければ、幸いです。


述べるネットワークゲームの種類
ネットワークゲームタイプ:PEAR2PEAR型(プレイヤー同士を直接つなぐ。サーバーが無い。)
ジャンル:アクションRPG系のゲーム
データ通信に使うプロトコル:DirectPlay(DirectX8)(オススメ書籍
つまり、RUINATERRAのような物。

基本的な考え方
・自分のパソコンでは、自分のキャラクターだけを操作する
・他のキャラクターは、他のプレイヤーからの情報を使って、画面に表示するだけ
・他のキャラクターについては、重力などの計算さえしない
・攻撃の当たり判定も、自分のキャラクターについてだけ行う
・その結果だけを、他のキャラクターに送信する(AがBに攻撃し、10ダメージを与えた、など)
・アイテムはホストが管理し、取る時にはホストに問い合わせをする

データ転送の遅延・送信量の限界
ネットワークゲーム製作には、いろいろな困難がありますが、一番の困難が、
”インターネットを通してデータを送る際の、遅延と、送れる量の限界”の存在です。
例えば、Aという人が、Bという人にメッセージを送ったとしましょう。
すると、Bさんは、Aさんが送ってから、例えば一秒後にそのメッセージを受け取ります。
遅れる量の限界というのは、一秒間に何バイトのメッセージを送れるかという事です。
ネットワークゲーム開発では、64kbpsの回線でも動く事が推奨されます。
なので、一秒間に最大8kバイトしか送・受信できません。
送信・受信を合わせて8kbなので、プレイヤー人数4人の場合は、大体2kバイトしか送れない事になります。
アクションゲームなどでは、リアルタイムにキャラクターの位置や敵の位置などを送信するのですが、
一度、一人のキャラクターの位置を送るのには、データを圧縮しても8バイトほど必要です。
なので、理論値では、一秒に250体分しか送れません。
データは、移動データ以外にも、攻撃データやチャットメッセージなど多岐に渡りますから、
実際は100体分程度。
敵などを考慮すると、やはり、一秒に二回程度しかキャラクターの位置データを送信できません。
そこで、次のテクニックを使う必要があります。

キャラクターの位置の同期を取る方法
A、B、それぞれのパソコンで、キャラクターの位置の同期を方法を取る方法として、
まず考え付くのは、キーボードの入力情報を相手に送る方法ですが、
これだと、一秒間に60回近くデータを送る必要があり、さらにデータの遅延などで、とても使い物になりません。
実際は、上で述べたように、0.5秒に1回程度しかデータを送れません。
なので、どうするかというと、”補完”という技術を使います。
送信するデータは、そのキャラクターの今の位置情報(X,Y)です。
そして、そのデータを受信すると、現在の位置からその位置へ、0.5秒かけて滑らかに移動させればよいのです。

データの圧縮
キャラクターの位置情報として、X座標とY座標をそのまま送ろうとすると、各4バイト必要です。
ですが、実際に、キャラクターの動ける範囲はマップ内と決まっています。
そして、X方向に数ドットキャラクターがずれていても、ユーザーは気にしません。
それを利用して、キャラクターの位置を2で割って送信し、
受信する側で2倍すれば、送信データ量を、半分に減らせます。

データの整合性
マップに落ちているアイテムを、各パソコンで管理していると、問題が生じます。
例えば、Aがアイテムを取って、その直後にBが取ったとしましょう。
Aが取ったというデータを、Bに伝えるには、遅延で約一秒かかります。
なので、その時間の間にBも同じアイテムを取れてしまうのです。
つまり、同じアイテムが二つに増殖してしまい、データの整合性が取れなくなります。

これを解決するために、アイテムはホストとなるプレイヤーが管理します。
アイテムを取る時には、プレイヤーがホストに”取っていい?”っと聞き、
アイテムが存在すれば、プレイヤーに”取っていいよ”っと返し、アイテムを消します。
こうすれば、AとBが同時に”取っていい?”っと聞いてきても、
どちらかにしか”いいよ”っと返さないので、アイテムの整合性は保たれます。

敵に関しても同様です。
各パソコンで敵を動かしていたのでは、各パソコンごとに動作速度が違うので、
敵の位置の整合性が取れなくなる(パソコンによって敵の位置が違うことになる)事になります。
なので、敵の動きはすべてホストが管理し、その敵の行動を全プレイヤーに送信します。

チャットの入力
ネットワークゲームを作る時に、意外と悩むのは、どうやってキーボードからチャット用メッセージを入力するかという事です。
漢字入力などを考えると、とても個人で作れる物ではありません。
DirectX8では、GUIも使えないので、どうしようか迷う所です。
結論としては、IMEを使います。
IMEは、意外と簡単に制御でき、キーボードから入力された文字列を得る事ができます。
プログラミング方法については、検索してみて下さい。
なお、ATOKの時は、半角/全角キーではなく、ALT+半角/全角キーで、入力モードになります。

ロビーの仕組み
ネットワークゲームのロビーは、CGIで構築します。
まず、ゲーム側が、”ルームを作る”という命令をCGIにします。
すると、CGIは、ルームの情報をtxtに書き込みます。
この時、ゲーム側にはルームの作成時刻をキーとして返します。
ゲームが終了すると、ゲーム側は、そのルームの作成時刻をキーにして、
”ルームを削除する”という命令をCGIにします。
CGIは、ルームの作成時刻が同一なルームを探し、削除します。
なお、C言語からCGIにアクセスするには、ソケットとHTTPを使えばできます。
プログラミング方法については、検索してみて下さい。

セーブデータの暗号化
ネットワークゲームでは、チート対策として、セーブデータを暗号化する必要があります。
暗号化の簡単な方法は、エクスクルーシブオアを取る事です。
a~0x5f4aとかとすれば、aは、5f4a(16進数)のビットが反転され、暗号化されます。
もう一度同じ操作をすれば、再度ビットが反転され、元のデータが得られます。
さらに、セーブデータの中に、hp+mp+...などのデータをチェックサムとして入れておき、
その値と、ロードしたセーブデータから求めたhp+mp+...を比較し、値が違っていたら起動しないようにしておくといいでしょう。
hpがある値(ありえない値)を超えたときにも起動しないようにしておくのも、有効でしょう。