Programs / 最終更新時間:2003年11月04日 22時46分51秒

NetChairの概要

NetChairはネットワーク&マルチプレイのテストプログラムです。
3Dの空間を各プレイヤーが移動し、チャットできます。

DirectX7以上がインストールされた環境で動きます。

なお、NetChairには全ソースコードが含まれており、使用していないコードも含めて、後述するWheel-Libraryというオープンソースライブラリが付属しています。

画面

ダウンロード

NetChairWithWheel050.lzh(341)

目次

Wheel-Library Version 0.5の概要

Wheel-Library(ホイール・ライブラリ)は、ゲーム等のアプリケーションを作成するためのライブラリです。環境に依存しないライブラリを目指しての第一歩ということで、デザインパターンの実験をかねて作ってみました。

D3DIMをラップしたLowLevelAPIと、その上に実装されたシーングラフAPIが主となるライブラリで、DirectX7SDK + VisualC++6.0以上専用となっています。

オープンソースで、ユーザーによる改変、その後の再配布、作ったアプリの商用利用も自由です。[1]

以下のように各部が暫定的な仕様ながら可能となっています。

  • Xファイルのセーブ/ロード
  • モデルのアニメーション再生
  • ビルボード
  • 簡単なパーティクルシステム
  • レンズフレア
  • 単純な曲線の補間と描画
  • 図形描画
  • 文字列描画
  • RayPick
  • ステージ管理

3D描画以外にも、

  • 参照カウンタとその操作を隠蔽するクラス
  • 簡易文字列クラス
  • キー入力クラス
  • メモリリーク・チェッカー
  • マトリクス・スタック
  • 衝突判定クラス(かなり未完成)
  • ネットワーククラス

いろいろ入っています。部分的に使うのもアリです。

――しかし、他人に読みやすいものとはとても言えません。また、設計と実装にムダが多く、それほど効率もよくないです。そのうちパーツごとにシンプルな形で利用できるものを作るかもしれません。

ライブラリを使った画面例

海面と波しぶきシミュレーションもどき

環境マップとアニメーションテスト


  • [1]Wheel-Libraryの著作権は n_ryota が保持し、Wheel-Library内部で利用しているライブラリの著作権はそれぞれの作者が保持します。また、改変する場合にはソースやヘルプなどに参考にしたライブラリとして書いていただけると嬉しいです。ちなみに、以前はWizard-Libraryという名前でした。大げさなのでWheel-Libraryに改名。
例:
// Reference : Wheel-Library ( http://cafe.eyln.com/ )

NetChairの接続方法

NetChair.exeを起動後、F1で操作ヘルプが出ます。
F3キーで接続です。

インターネットを使う場合は、TCP/IPを選びます。OKを押すと、Gameセッションを選ぶダイアログが出ます。

そこで、Start Searchボタンを押し、接続したい相手のIPアドレスを入力してください。

見つかればリストアップされるので、入るセッションを選択して、Joinボタンを押してください。自分がセッションを作る場合は、Createボタンで作れます。

その後、接続してほしい相手に自分のIPアドレスを教えてあげてください。

IPアドレスは、F4キー押すか、c:\windows\Winipcfg.exeを実行するか、MS-DOSプロンプトでipconfigと入力すれば表示されます。

NetChairの操作方法

カーソルキーで移動、スペースキーでジャンプです。
オマケ:左シフトを押している間はカメラが固定され、テンキー(2,4,6,8,0,5)で移動できます

Cキーにてチャットメッセージを入力できます。

NetChairの仕組み

プログラム内容について少し。

ピアツーピアでデータを送りあう

まず、ピアツーピアです。1秒間に4回、自分の状態を自分以外の全プレイヤーに送信しています(チャットメッセージは別枠)。

送るデータは、基本的に姿勢と位置です。このゲームだといろいろ省略できるものがありますが、これに特化するつもりはないので、3次元のデータをちゃんと送ります。

ただし、姿勢は3x3マトリクス(float*3*3)の1軸を省略したデータ(float*3*2)ではなく、クォータニオン(float*4)を使います。

受信したら、現在の状態が受け取った状態になるよう補間していきます。受け取ってから、0.5秒後には完全に受け取った状態になります。

つまりネットワーク上の遅延が0でも、相手プレイヤーの動きは0.5秒遅れて表示されることになります。

クライアント/サーバーについて

ネットワークでは同期処理をどうするかが大事なポイントです。

クライアント/サーバー(C/S)でエレガントにいくか、ピァツーピア(PP)で強引にいくかということになるのですが、結局、NetChairではピアツーピアでいくことにしました。

C/Sだとクライアントの操作(または操作結果)をサーバーに伝えて、クライアント間の相互関係などゲーム部分の処理をすべてサーバーで行い、結果を各クライアントに送信、クライアントはその情報をもとに画面表示するだけ――というのが基本になると思います。

C/S ○

  • サーバー側でゲーム部分を処理するため、同期処理に頭を悩まさなくてすむ、C++的なスマートな実装ができそう、など嬉しい要素が満載です(基本としてはの話)。

C/S ×

  • しかし、他人の情報がサーバー(ホスト)プレイヤーを経由して伝わるため、クライアントからサーバーへ、サーバーからクライアントへというだけの通信時間がかかるので、その点はマイナスです。

P2P ×

  • これに対し、P2Pの場合は、全プレイヤーがサーバーのようなものなので、各クライアントから他の全クライアントに直接通信する形になります。各クライアントとネットワーク全体をみた負荷が大きい反面…

P2P ○

  • 他のクライアントの情報を得るまでの時間はC/Sにくらべて、理論値では半分。(上記の要素で受信/送信量が増えた場合にはその負荷で遅くなることに注意)

もちろんC/Sでサーバーに強力なものが用意できるのであれば、P2Pなんて使ってる場合ではないですが、並な回線のPCをインターネットで繋げて2〜8人(ムリすればもっといける)くらいでプレイするのであれば、PPのメリットを生かせるかなと思った次第です。

また、「純粋なC/Sでは、サーバーが抜けるとセッションが存続不可能になるけれど、PPでは他プレイヤーがホストになって存続可能なのが安心」とか、「すでにPP用のコードがあるからとか」、「ザコなPCのプレイヤーがサーバーになりたがって困ることがないとか」、いろいろ他の理由もなくはないかも。

ピアツーピアの問題

しかし、P2Pは同期が煩雑。クライアントごとの「現実」のどれを「真実」にするかという問題に頭を悩ませることになります。

以前新坂さんがとある掲示板で書かれていましたが、遅い攻撃は攻撃された側のクライアントで、速い攻撃は攻撃した側のクライアントで処理し、その結果だけを同期させるのがP2Pでは一番スマートなやり方だという気がしました。(結果は同期させるが、時間的にはずれていてもよいとして)

アクション同士が個別に通信

しかし、そのまま実装するとシンプルなうちはよいとして、いずれはソースが…イヤだ! というわけで、ネットワーク上で結果同期されるアクション同士が個々に通信しあえるような仕組み、PacketCommandクラスを作り、利用しています。

通常、キャラクタークラスに自分の移動関数とか、攻撃関数などのメソッドを用意すると思いますが、そのメソッドがそれぞれ別のクラスとして作成する形です。

CharaMovePacketCommand
HomingLaserPacketCommand

とか、そういうノリで。

このPacketCommandクラスには、

  • OriginalMove
  • RemoteMove

というメソッドがあり、そのアクションの所持者となっているクライアントではOriginalMoveメソッドが、他クライアントではRemoteMoveメソッドが実行されます。

たとえば

  • オリジナルの方でキー操作に合わせて位置を動かし、1/10秒に1回、位置情報をSendするようにします。
  • リモートの方では、その情報を受け取った場合には、目標位置を更新し、RemoteMoveメソッドは、現在位置から目標位置へ位置を補間して移動させ続けます。

ホーミングレーザーなどの場合には、オリジナルは発射情報を他クライアントへ伝え、他クライアント上にあるリモートなオブジェクトは当たれば結果を他クライアントへ伝えます。その間、OriginalMoveメソッドでは少し遅めにホーミングさせておき、結果を受け取ったらその結果を再現するように動きます。

結果を受け取った時点(もしくは、一定時間を過ぎた時点)でホーミングレーザーは消えます。もし、不幸にも情報が伝わっていない間に複数の結果が存在することになった場合は、仕方がないので、ダメージなどをどちらかムシするか、どちらとも有効にするかしかないですが……そのあたりは涙を飲んでそのように実装します(各クライアントを並べてみないかぎり、そうそうバレないとは思うけど)。

PacketCommandを継承して、OriginalMoveRemoteMoveを実装するだけで、通信対応のアクションを実装できるわけですね。