XNA Frameworkブログ
 

January, 2008

  • ひにけにXNA

    ネットワーク その6 量子化で圧縮

    • 1 Comments

    ビット数の少ない方が多いものより消費するスペースは小さくなります。

    もしint型の値が0~100までの範囲しか取らないと判っているのなら、そのまま4バイトのint型として送るより、byte型にキャストして送ることができます。

    場合によっては値が表す範囲を値をずらすことによって減らすことができます。例えば、キャラクターの高さのデータを送る必要があり、高さはcmで表されるとします。このゲーム中のキャラクターの高さの範囲は、ドワーフ(100cm)から巨人(300cm)まであります。

    300という値はbyteで表現できる範囲(0-255)を超えているので、キャストすることはできません。

    しかし、100cm以下のキャラクターが存在しないと判っているのなら、100cmを基点とすることで値の取る範囲を小さくすることができます。

    PacketWriter.Write( (byte)( height - 100 ) );

     

    受信側では値を使う前に100を足すだけです。これで、キャラクターの高さの範囲は0-200になり、byteにキャストすることができます。

     

    他にもスケーリングすることで値の取る範囲を小さくすることができます。例えば、浮動小数点で表されるラジアン角度を送る場合があるとします。ラジアンで円を表す範囲は0~2πになります。この値を0~255の範囲に収まるようにスケーリングすれば、byteにキャストして送ることができます。

        float rotationEncodeScale = 255.0f / MathHelper.TwoPi;
        PacketWriter.Write( (byte)( rotation * rotationEncodeScale ) );
    

     

    受信側では逆数を掛けることで、元の値にします。

        float rotationDecodeScale = MathHelper.TwoPi / 255.0f;
        float rotation = (float)PacketReader.ReadByte() * rotationDecodeScale;
    

     

    この量子化では、精度が多少失われてしまいますが、通常は4バイトのデータを1/4に減らすことの方が価値があります。

     

    訳注:
    原文のコードでは255ではなく256を使っていますが、この場合に変換できる値は上限値未満の時に動作します。もし、上限値を指定した場合、結果は256になり、byteにキャストすると結果は0になってしまいます。ですが、この例で扱っている値はラジアンなので円を表す場合、値が0とMathHelper.TwoPiの時は結果が同じになるので問題なく動作します。

    同じ手法はシェーダープログラムなどで使うのですが、この場合に良く用いられるのは0~1までの値で1以下の値を取るので、この場合はスケールを255にしないと動作しません。

    以上の理由から、ここではスケール値を255に変更しました。

     

    原文:
    http://blogs.msdn.com/shawnhar/archive/2007/12/24/network-compression-quantization.aspx

  • ひにけにXNA

    ネットワーク その5 圧縮

    • 0 Comments

    限られたネットワーク帯域の中では、送信するデータを圧縮することは非常に重要なことです。

    Zip等の一般的な圧縮アルゴリズムはネットワークゲーム向けではありません。これらの圧縮はある程度のデータ量がある場合は効率良い圧縮が期待できますが、ネットワークパケットのように小さいデータを圧縮するには不向きです。ここで必要なのは20バイトのデータを10バイトにするような圧縮方法です。

    初心者がよく考える手法として、送信側で複数のパケットを続けてバイトストリームして一般的な圧縮アルゴリズムを使って圧縮し、圧縮されたデータをパケットに分割して送受信するというものがあります。確かに圧縮率は高くなるのですが、この手法には致命的名欠点があります。この手法では、圧縮したデータを展開するに全てのパケットが失われること無く、順番に配信される必要があります。この為にはSenDataOptions.ReliableInOrderを使う必要があり、レイテンシが増加する原因になってしまいます。

    また、変化量(前の状態との差)を送ることで全体のデータを送るより少ないデータ量で済ますことができますが、この手法も一般的な圧縮アルゴリズムを使うのと一緒で、全てのデータが順番に送信されるという保障があるときにのみ使える手法です。

     

    通常は古典的なビットフィールドをまとめたり、量子化を使った方が良い結果になります。これらの手法は4KBのRAMを積んだ8ビットマイコン時代に良く使われたものですが、ギガバイト単位のデータを扱える.Netの世界では忘れ去られた手法でもあります。

    例えば、文字列を送るのでなく、整数のIDかenum値を送る。

    もし、行列が回転と移動の組み合わせで、スケールや、せん断、射影をしないと判っているのなら行列データを送らない。変わりに12バイトのVector3であるMatrix.Translateと、16バイトのQuaternion.CreateFromRotationMatrix(matrix)を送ります。これで64バイトの行列データが28バイトに圧縮されたことになります。

    以下、次回に続く。

    原文
    http://blogs.msdn.com/shawnhar/archive/2007/12/22/network-compression.aspx

  • ひにけにXNA

    ネットワーク その4 帯域 ボイスチャットについて

    • 2 Comments

    XNAフレームワークはボイスチャットをサポートしており、ヘッドセットがある場合に自動でチャットができるようになっています。便利な機能ではありますが、使用中はより多くのネットワーク帯域が必要になることに注意が必要です。

    音声データは500 B/s以下の帯域に圧縮され、ヘッドセットに向かって喋った時のみにデータ転送を行います。

    デフォルトの状態では全てのプレイヤー同士で会話することができるようになっています。もし、ひとりが他の15人のプレイヤーに話しかけた場合、

    • 500 * 15 = 7.3KB/s

    ひゃー!! 8KB/sが目標だったことを覚えていますか?これではゲームデータを送る前に殆どの帯域を使い切ってしまいます。

     

    では、この厄介なボイスデータ帯域問題をどのようにして解決することができるのでしょうか?

    • 同時にプレイできるプレイヤー数を少なくする
    • もしくは、LocalNetworkGamer.EnableSendVoiceを状況に合わせて変更する
      • 同じチームにのみ話せるようにする
      • 自分の近くにいるプレイヤーに対してのみ話せるようにする。ただし、頻繁にEnableSendVoiceを変更するのは止めましょう。この値を変更する度にその情報をネットワーク上に転送しないといけないので、頻繁に変更してしまうと逆により多くの帯域を使ってしまうことになってしまいます。
      • MotoGPではゲーム用のデータ転送量の少ないロビー内では16人全員が自由に会話でき、ゲーム内では最も近い3人のプレイヤーと会話ができるようになっています。

     

    原文:
    http://blogs.msdn.com/shawnhar/archive/2007/12/20/network-bandwidth-voice.aspx

  • ひにけにXNA

    ネットワーク その3 帯域

    • 1 Comments

    ネットワーク帯域とは、どれだけの量のデータを送受信できるかを表します。データ量が上限に近づくほどにパケットロスの量が増え、この上限を超えたデータ量を送ろうとした場合、結果的にセッションから切断されることになってしまいます。

    XNA Frameworkでは帯域をバイト/秒で表します。まぎらわしいことにネットワークベンダーはビット/秒で表すのが好きで(多分、より数字を大きく見せるため)、さらにまぎらわしいことにどちらの表記もbpsやkbpsを使うことです。この表記が出てきた場合にはバイト/秒なのかビット/秒のどちらのことを表しているのか注意する必要があります。(訳注:私は個人的にバイト/秒は大文字のB/s、ビット/秒はをbit/sと区別して表記するようにしています)

     

    より多くのユーザーに遊んで貰うために色々な家庭のインターネット接続を考慮してXboxのゲームでは最小で上り、下り8KB/sの帯域で動作することが求められます。

    ネットワーク帯域と言うと、ゲーム内で送受信するデータ量x接続するマシン数と考えがちですが、ここで忘れてはいけないのはデータパケットのヘッダー部分のサイズです。

    • 20バイトのIPヘッダー
    • 8バイトのUDPヘッダー
    • ~22バイトのLIVEやXNA Frameworkで使われる部分(NAT Traversal、暗号化、Reliable/Ordered転送用の情報)

    これで~50バイトのパケットヘッダーサイズになります。

    例えばプレーヤー毎にBoolean値を、60フレーム/秒の間隔で転送すると:

    • 1バイトのペイロードデータ
    • +50バイトのヘッダー = 51バイト
    • *60パケット/秒 = 3060バイト

    ホァ!Boolean値一個のデータ転送だけでも3KB/sになってしまいます(ゴールは8KB/sということを思い出してください)。98%の帯域がパケットヘッダーで無駄に消費されてしまいます。

     

    では、どうやってこの厄介なヘッダー問題を回避することができるのでしょうか?

    • データ転送頻度を下げる
      • 通常のゲームでは毎フレーム毎にパケットを送るのではなく、1秒間に10~20パケットの割合で送っています
      • この転送頻度は動的に変化させることができます。例えば遠くのプレイヤーにはより低頻度でパケットを送ります
      • MotoGPではXbox LIVE上で最大16人同時プレイができます。これだけの人数になると1秒間に4パケットが上限でした。この為により効果的な予測アルゴリズムを使って低レートでも滑らかに動作させる必要がありました。
    • パケットをまとめる
      • 沢山の小さ���パケットを1つの大きなパケットにすることで、パケットヘッダーのサイズの無駄を少なくすることができます。
      • 通知メッセージを即座に送るのではなく、複数のフレームを待たせることによってパケットをまとめる
      • もし同じフレーム内で複数のパケットを同じ相手に送った場合、次にNetworkSession.Updateを呼んだときに自動的まとめてくれます。
      • XNA Frameworkは自動的にボイスデータやrealiable UDPの確認などを既にあるパケットにまとめるので、これらのデータは独立したヘッダーを必要としません。

     

    原文
    http://blogs.msdn.com/shawnhar/archive/2007/12/18/network-bandwidth-packet-headers.aspx

  • ひにけにXNA

    XNAカスタムコントロール

    • 2 Comments

    以前から、XNAを.NetのControl内で使いたいという要望を何度か聞くことがありました。本当はXNA 2.0で導入される予定でしたがテスト期間が間に合わなかったために見送りとなりました。ですが以前書いたようにXNA 2.0でGraphicsDeviceが仮想化されたことによってウィンドウ内でXNAを使う実装が非常にシンプルになったので、Creators Club Onlineにサンプルがアップされました。

    WinForms Series 1: Graphics Device Sample

    このサンプルでは、XNAをWinFormsのカスタムコントロールとして使う方法が実装されています。単体のコントロールを配置したときに動作するのはもちろん、複数のコントロールを配置した場合でも動作するので3Dゲーム用のエディター等で3面図+カメラビューの四つの画面を表示したりというときに便利です。実装的にはひとつのGraphicsDeviceを使いまわしているので、モデルデータ等は別コントロールであっても同じものが使えます。

    WinForms Series 2: Content Loading Sample

    ゲームエディタなどを作るときには、コンテントパイプラインを通る前のfbxファイルやxファイルを直接指定できたほうが便利ですが、一度Content.Load<T>に慣れてしまうと自前でNodeContentやMeshContentからモデル描画するのは非常に面倒ですし、パフォーマンス的な問題もあります。そこで、このサンプルではfbxファイルを直接読むのではなく、ツール上でコンテントパイプラインのビルド処理をすることで普段のゲームと同じようにContent.Load<T>を使えるようにしています。コンテントパイプラインのビルド処理はMSBuildを使っているのですが、このサンプルのContentBuilder.csファイル内でMSBuildのタスクを実行することで実現しています。

    ある程度規模の大きなゲームになってくると、ゲームエディタなどのツールが必要になってきますが、上の二つのサンプルが参考になるのではないでしょうか?

  • ひにけにXNA

    ネットワーク その2 パケットロス

    • 1 Comments

    ネットワークは信頼できません。

    データーパケットを送ったときにはいろいろな事がおこります。

    1. 相手側に届くかもしれない
    2. 届かないかもしれない
    3. 届いたとしても、送った順番とは違う順番で届くかもしれない
    4. 届いたとしても、その内容が壊れてるかもしれないし、誰かによって変更されているかもしれない

    4つ目はXNAフレームワークベースのゲームでは問題になりません。XNAフレームワークではLiveのライブラリを使っているので送られるパケットは全て自動的に暗号化されているので、データ自体が改ざんされたり、壊れたりということを気にする必要はありません。

    2と3の問題についてはSendDataOptions.ReliableSendDataOptions.InOrderフラグを指定することで解決できます。なぜフラグ指定をしないといけないのか疑問に思う人もいるかもしれません。常にSendDataOptions.ReliableInOrderで送ればいいじゃないか、それ以前になぜ他のネットワークアプリケーションの用にTCPを使わないの?

    その理由はゲームでは常にシミュレーション結果をリアルタイムに更新する必要があるからです。TCPやSendDataOptions.ReliableInOrderを指定した場合、以下のような動作をします。

    • 送信側はデータパケットをネットワークに送る
    • 送信側はデータパケットのコピーを内部の送信中キューに保存する
    • パケットが届いた時に、受信側は「データが届いたよ」という確認メッセージを返信をする
    • 送信者は受信側からの確認メッセージが届いたら、そのパケットを送信キューから取り除く
    • 一定時間、確認メッセージが届かなかった場合は、もう一度同じパケットを送信しなおす
    • もしパケットが送信順に届かなかった場合、例えばBとCのパケットがAを受信する前に届いたら、受信側はBとCのパケットをキューに保存する
    • 送信順序を保つために、受信側はAが届くまでの間待たなければいけない

    この仕組みはウェブページのHTMLデータ等をダウンロードする時などには有効な方法です。何故なら、AのパケットにはHTMLのヘッダーやレイアウト部分を含んでいるわけですが、その情報無しに残りのデータを表示するのは意味がありません。

    ゲームの場合を考えてみましょう。例えばプレイヤーの座標をゲームループ内で送る場合は

    • Aの時間の時のプレイヤー座標を送る
    • Bの時間の時のプレイヤー座標を送る
    • Cの時間の時のプレイヤー座標を送る
    • AとBの座標情報が失われる
    • Cの座標が届く
    • 受信側はCの情報が届いているのにも関わらず、AとBの座標が再び送信するまでの間、待つことになる

    なんて間抜けな…。もし受信者がCの時点での座標を知っているのなら、それより過去の時間であるAとBを気にするのは無意味なことです。この状況ではAとBの情報は無視して、最新の座標に更新するのがより自然な方法です。

    SendDataOptions.ReliableInOrderを指定したときにパケットロスした場合、レイテンシは増加します。このフラグを指定しない場合にパケットロスが発生した瞬間だけガクっとしますが、その後は問題なくゲームを続けることができます。しかし、フラグを指定した場合、たった一つのパケットロスでさえ、それに続くパケット全てが失ったパケットが再び届くまでの時間分だけ遅延するこになります。

    SendDataOptions.ReliableかSendDataOptions.InOrderのどちらかを指定したときには問題にならないことに注意してください。この問題は両方のフラグを指定した(TCPの振舞いと同じ)時のみに発生します。

    転送を確実に行いたいが、パケットの送信順を気にする必要が無い(SendDataOptions.Reliableを指定)場合、パケットAがロスしてBが届いた時はゲーム側では単にBを受信したことになり、Aのパケットは送信しなおされますが続くほかのパケットの遅延には影響ありません。

    パケットの送信順は気にするが、パケットロスは気にする必要が無い(SendDataOptions.InOrderを指定)場合、それぞれのパケットには番号がつけられ、番号が古いものが、新しいものより後に届いた場合は古いものは無視されます。この方法が最もレイテンシが少ない方法です。

    ショーン(Shawn)曰く

    • SendDataOptions.InOrderをできるだけ多く使う
    • SendDataOptions.Reliableは必要なときに使う
    • SendDataOptions.ReliableInOrderはできるだけ少なく

     

    原文
    http://blogs.msdn.com/shawnhar/archive/2007/12/14/network-packet-loss.aspx

  • ひにけにXNA

    ネットワーク その1 レイテンシ

    • 1 Comments

    「さて」とセイウチはいった「ネットワークの話をしましょう」

    (訳注:元ネタはルイス・キャロルのセイウチと大工から)

    ネットワークゲームプログラマーには以下の三つの不死の敵がいます

    • レイテンシ データが相手に届くまでに掛かる遅延時間
    • パケットロス データが相手に届かない現象
    • 帯域 送ることのできるデータ量の上限

    以上の三つについて順に話しましょう。

    レイテンシは物理的理由によって決まります。SFの世界では何十年もの昔に実現しているのに、物理学者は未だに光速を超える手段を発見していません。ですから、秒速30万キロ/秒を超える速度でデータを送ることができません。これは物理学者のせいです(そんなに難しいことなのか?)

    十分に速いだろって?

    僕はシアトルに住んでいて、同僚のイーライ(Eli)はニューヨークに住んでいた。その距離はおよそ4,000Km、光の速度で13ミリ秒掛かる。

    僕は以前イギリスに住んでいたけど、イギリスからシアトルまでは7,800Km、光の速度で26ミリ秒掛かることになる。

    60フレーム/秒のゲームの場合、各フレームは16ミリ秒毎に更新されるので、僕がイギリスの友達と遊んでる場合は2フレーム近い差(ラグ)が生じてしまう。

    ちょっと待ってくれ、話はそれだけではないんだ

    • ネットワークデータは真空中を通らない。光の速度といえば普通は真空中での光の速度を言うけど、実際のデータは光ケーブルや銅線ケーブルを通るので6割程度の速度に落ちてしまう
    • ネットワークデータは一本線で繋がっている訳ではない。両サイドにモデムがあり、それぞれ10ミリ秒程度のレイテンシがある。また、友人は私と同じISPやスイッチボード上にいる訳ではないので実際にはルーターを介してデータ送られる。それぞれのルーターでは5~50ミリ秒程のレイテンシがある

    最悪ケースは?

    • Xboxのゲームでは最大200ミリ秒(0.2秒)の遅延が合っても動作するよう奨励されている

    どうやったら、家で再現できるの?

    NetworkSession.SimulateLatency = TimeSpan.FromMilliseconds(200)

    どうすればいいの?

    • 0.2秒の遅延があっても問題のないゲームデザインをする。例えばポーカーゲームなんかの場合は0.2秒程度の遅れは無視してもかまわないよね
    • 予測アルゴリズムを使ってラグを隠蔽する。レイテンシがあるお陰でリモートオブジェクトのその瞬間のゲームステートを知ることは不可能。でも0.2秒前の情報、例えば移動速度、加速しているのか、減速しているのか、旋回しているのかという情報を元に0.2秒後の状態を予測することはできる。
      その予測が間違っていた場合でも、予測した座標から実際の座標へスムーズに補間することによってガクガク動くのを防ぐことができる。予測の90%くらいがあっていればラグが気にならなくなるね。
      ラグを完全に消すことは物理学的に不可能なので、あくまで目的はラグが分からないように隠すということ

    ここまでがShawn Hargreaves氏の投稿の翻訳です。

    ちなみに日本国内の場合は距離的問題は少ないと思います。例えば東京~札幌間の距離は850Kmで3ミリ秒となりますが、記事の中の通り、問題になるのはルーター間でレイテンシが大きな問題となるでしょう。

    予測アルゴリズムですが、以下のサンプルが参考になると思います。

    http://creators.xna.com/Headlines/developmentaspx/archive/2007/01/01/Network-Prediction-Sample.aspx

    追記:

    ゲーム中に表示されるPing値と、この記事で言及しているレイテンシとは違うものです。

    • レイテンシは片道時間 A地点からB地点にデータが届くまで掛かる時間
    • Pingは往復時間  A地点からB地点まで、そしてB地点からA地点までデータを送り返すのに掛かる時間

    行きと帰りが同じ速度と仮定すると0.2秒というレイテンシは0.4秒のPing値に相当することに注意してください。

Page 1 of 1 (7 items)