ネットワーク その10 オブジェクト所有権

クライアント/サーバーとピア・ツー・ピア型のどちらのネットワーク形態が優れているのかを議論するのが好きな人たちがいます。しかし、個人的にこの議論は間違ったものだと思います。誰が「どちらか一方のネットワーク形態を選ばなくてはいけない」と言ったんですが?私は両方の長所を活かしたハイブリット形式の大ファンです。 ネットワークプログラミングは妥協との戦いです。100%の正確さと、ラグがまったく無いという2つの状態を両立することは不可能で、トレードオフをしなければいけません。 時にはラグが多少大きくっなっても正確さをとる場合もあれば、逆に正確さを犠牲にして高いレスポンスを必要とする場合もあります。 100万ドルの問題:  どのマシンがどのデータを管理するか? ゲーム内のそれぞれのデータについてのコスト/実益の分析を以下の質問に答えることでできます。 そのデータは独立したものですか、もしくはそれ以外のデータと状態を一致させる必要がありますか? 全プレイヤーがそのデータのラグに対して同等に気にする必要がありますか、それともひとりのプレイヤーが他のプレイヤーよりも気にしなければいけませんか? そのデータのラグはどれだけ重要なものですか?ゲームデザインを変更することによって、その重要度を低くはできませんか? データ変化を予測することはできますか?間違った予測をした場合、問題になりますか、それともエラーをスムースに訂正することができますか? この質問の答えによって、どのようにデータを管理すればいいのかが判ります。 沢山のデータの整合性が必要な場合、それらは1台のマシンによって管理されるべき もしひとりのプレイヤーがデータのラグに対して他のプレイヤーより気にしなくてはいけない場合、そのデータはそのプレイヤーのローカルマシンによって管理されるべき ラグが大きな問題となる場合は、予測アルゴリズムを適用するべき 予測ミスが大きな問題となる場合は、予測アルゴリズムは適用すべきではない 1と2の質問はどのマシンがデータを管理するかの指標になり、3と4の予測アルゴリズムの適用についての質問は別々のものだということに注意   例えば: スペースシップの移動 スペースシップを操作しているプレイヤーにとってレイテンシは重要な問題。 それぞれのマシンでスペースシップの位置が多少ずれていても、大体の位置があっているのなら問題はない。最悪ケースとしては2つのスペースシップがぶつかったときに、それぞれのマシンでは多少違った方向に跳ね返るが、ゲーム的には問題はない。 多少の予測ミスは問題にならない。間違った位置から正しい位置へと、誰にも気づかれること無く補正することができる。 結論:それぞれのスペースシップはそれぞれのローカルマシンによって管理されるべき。予測アルゴリズムを適用する。   レースの勝敗 レイテンシは特に重要ではない。あなたがゴールラインを通過してから0.5秒後に結果が表示されても大丈夫(カッコイイアニメーションやカメラワークで、このレイテンシを隠すことができる)。 それぞれのマシンでレース結果が違うのはダメ。勝者は常にひとり。 間違った予測結果は大問題。「優勝おめでとう!!」と表示されて少し経ってから「やっぱり2位でした~」と表示されるのはひどすぎる。 結論:1台のマシンがレース結果を決めるべき。予測アルゴリズムは適用しない   死亡判定 殺されたプレイヤーにとってレイテンシは重要だが、他のプレイヤーにとっては特に重要ではない。 マシン毎に違った結果になるのは問題。もし私が死んでいるのなら、他のマシンでも死んでいなくてはならない。 予測ミスは許されない。「ぐはぁ、やられたー」と叫びながら派手なアニメーションが再生され、血飛沫を撒き散らしながら地面を転がった挙句に「ぐふぅ」と事切れた数秒後に何事もなかったのように生き返るのは問題。 結論:それぞれのマシンがローカルプレイヤーの死亡判定をするべき。他のマシンは死亡判定の予測をしてはいけない。 追記:ここでは視覚的な予測を適用することができる。例えば、私のマシンがヘッドショットだと判定するが、ヘッドショットされたプレイヤーの位置が100%確実ではないので死亡アニメーションを再生することはできない。その代わりに「ダメージを受けた」というアニメーションを再生することができ、視覚的なフィードバックを即座に得ることができる。もし、ヘッドショットを決めたプレイヤーから死亡か確定したという連絡が届いた場合は死亡アニメーションへスムースに変化させることができる。仮にこの予測が外れた場合でもダメージアニメーションをキャンセルして通常のアニメーションに戻すことができる。   ビークル(乗り物)に乗る 私は一度、キャラクターが徒歩で歩き回り、いろんなビークルに乗り降りすることができるゲームのプロトタイプ製作をしたことがありました。ビークルに乗っている間は「スペースシップの移動」の例で説明したのとまったく一緒です。ただビークルに乗ると言うのは違います。一度に複数のプレイヤーが運転することはできませんから! この問題を解決するために、まずどのマシンがどのオブジェクトに対して所有権を持っているのかを表すデータをつくりました。これで動的にビークルの所有権の変更ができます。次にどのマシンが所有権を持っているのかを決めなければいけません。 ビークルのステート(操作、物理シム、衝突判定等)は、そのビークルを運転しているプレイヤーがいるマシンによって管理されます。それ以外に、この所有権を誰が持つべきを決める管理マシンを決めました。もしビークルに誰も乗っていない場合は、管理しているビークルの数が最も少ないマシンに所有権があります。 ビークルのとなりに立ち「乗る」ボタンを押した時に以下の処理をします。 クライアントマシンは「乗せてください」というメッセージをビークル所有権管理マシンに送ります クライアントマシンではキャラクターがビークルに乗り込むアニメーションが再生されますが、まだ所有権がないので運転することはできません。 通常 ビークル所有権管理マシンは要求があったクライアントマシンに所有権を渡します ビークル所有権管理マシンからの「乗って良いよ」というメッセージはキャラクターんがビークルに乗り込むアニメーションが終わるまでに届き、ビークルを問題なく運転することができます。この乗り込むアニメーションはラグを隠蔽するわけです。 二人同時に乗り込もうとしたとき ビークル所有権管理マシンは「すまん、君は乗れん」というメッセージを一方に送ります 乗り込み要求を断られたキャラクターは乗り込みアニメーションをキャンセルしてビークルに乗り込む前の位置にもどされます。 先に乗り込み要求を申請したキャラクターはビークルを運転することができます。   ビークルを運転しているときはピア・ツー・ピア形式ですが、大事な決定をするのはクライアント/サーバー形式になっています。 アイディアとしては大事な決定を下す単一の管理マシンが全てのデータを管理する必要がないということです。マシンAはビークルの乗り降りを管理して、マシンBはパワーアップアイテムの管理、そしてマシンCはセッション終了時に誰が勝敗を決定するといった感じに管理するものを割り振ることができます。    …

1

ネットワーク その9 究極の圧縮方法

今まで紹介してきた狡猾な圧縮方法より効果的なデータ圧縮方法があります。それはデータ自体を送らないということです。 もちろん、まったくデータを送らないのでは相手側との同期ができません。でも、時には同期すること自体が重要ではない場合があります。 2つのルール ゲームプレイに関連するものは同期しなければならない 殆どの物はゲームプレイに関与しない   効果音やアニメーションは殆どの場合は同期する必要がありません。もし、ネットワークを介してキャラクターが前に走って移動する場合、それぞれりマシンではその情報を元に、走るアニメーションを再生させ、そのアニメーションに合わせて靴音を鳴らすことができます。靴音が鳴るタイミングが多少ずれていてもゲームプレイには関係ないのでネットワークを介して足音を同期させる必要はありません。 ケーススタディ:MotoGPでは5%の確立で相手を抜き去った場合、抜いた相手に向かって手を振り上げるアニメーションを再生します。クラッシュした場合、ライダーが吹き飛ぶ複数のアニメーションの中からランダムで再生され、ネットワークにはどのアニメーションを再生したかという情報は送られません。 あるプレイヤーから見ると手を振り上げたアニメーションをしているように見えますが、他のプレイヤー視点からはアニメーションが再生されていないという場合があります。クラッシュシーンでライダーの転がるアニメーションはそれぞれのプレイヤーによって違うアニメーションが再生されます。 これらをネットワークを介して同期するにはネットワーク帯域が足りませんでした。しかし、これらのアニメーションはゲームプレイには関係無いものなので、誰も同期していないということには気づきませんでした。 同じように、FPS等のゲームでは撃った弾の位置、壁に当たったときにできる弾痕の位置、飛び散るガラス片や薬莢の位置を同期する必要はありません。ここでネットワークに送る情報は単に「私はゲーム内で銃を撃っている」というブーリアン(bool)の情報だけです。このシンプルな情報を元に、各マシンではそれぞれにシミュレーションを行います。多少の違いが起きても、それらの平行世界で起きている事柄がほぼ同じである限りは問題がありません。   一度、この「データをネットワークに送らなくても良いんだ」という考え方が身につくと、いろんな事ができるようになります。 ケーススタディ:Moto GPで広告看板やパイロンはコース脇に配置されています。もしバイクがこれらのものと衝突した場合、バイクと一緒に吹き飛びます。もし、ゆっくりとした速度でぶつかった場合は看板やパイロンを押し動かすことができます。 殆どのプレイヤーはレースに勝つことが目的で真剣にレースに参加しますが、中にはレースの邪魔をしようとするグリファー(訳注:オンラインゲームで嫌がらせをするプレーヤーのこと、英語ではGriefer、griefには深い悲しみ、悲痛といった意味があります)がいます。グリファー達はコースを逆走したりして、どうやったらレースをメチャクチャにできるのかを競っています。 グリファー達は広告看板やパイロンをコースの中央に動かしてバリケードを築けることに気付きました。これで真剣にレースをしているライダー達がバリケードに衝突して派手にほこりを撒き散らしながら吹き飛ぶ筈です。 でも、実際にはグリファー達の思い通りにはいきませんでした。 広告看板やパイロンの位置情報はネットワークを介して送られてはいませんでした。単に充分なネットワーク帯域が無かったからです。 以下は実際にあったことです。 あるグリファーはバイクを前後に動かしながら、ゆっくりと障害物を押し進めます ピア・ツー・ピアを使っていたので、そのグリファーは自分のバイクの動きを完全にコントロールできます。 他のマシンでは予測アルゴリズムを使っているので、グリファーのバイクの座標は大体一緒ではありますが、完全に一緒ではありません。 グリファーのマシン上ではグリファーの思い通りに障害物はコースの真ん中へ移動します。 他のマシンでは最初の押しが少しだけ左にずれました。この段階では障害物の位置はグリファーが移動させた位置に近いですが一致している訳ではありません。更にグリファーが障害物を押し進めるうちにグリファーが実際に移動させた位置とはズレが生じ、最終的に他のマシン上では障害物はグリファーと当たらない位置に移動して、そのまま動かなくなります。それらの障害物の位置は同期されていないので、この矛盾は修正されません。 結果 グリファーのマシン上では立派なバリケードが完成、他のマシン上ではバリケードができていない レースを真剣にしている人達のマシン上ではバリケードが無いので問題なくレースを楽しむことができます。 一方、グリファーのマシン上では立派なバリケードがあるので他のプレイヤー達はグリファーの思惑通りにバリケードに衝突してライダーは派手に中に舞い、バイクは火花を飛ばしながら滑っていきます。そのしばらく後に新しい情報がネットワークから届きます。あれ?実際には衝突は起きていなかった?この時、吹き飛んだはずのプレイヤーはコース上に配置され、問題なくレースは続行されます。 100万人ものプレイヤー達が何時間もプレイしたにも関わらず、誰もこの矛盾には気づきませんでした。 グリファーは盛大なクラッシュシーンを見て喜び、 真剣にレースをしている人達は無事にレースを終えることができ、 みんないつまでも幸せにレースを楽しんだとさ、めでたしめでたし 原文:http://blogs.msdn.com/shawnhar/archive/2008/01/01/network-compression-just-say-no.aspx

1

ネットワーク その8 算術符号化圧縮

算術符号化は解りづらく、めったに使われないツールのひとつですが、時々その威力を発揮します。例えるならネットワークデータ圧縮における変なサイズの六角レンチです。 算術符号化はクールです。なぜなら、あまり知られていないし、一見すると動かないように思えるものだからです。パーティーで女の子に好印象を持たせるのに活躍します(訳注:そうか?) 以下のデータがあったとします。 enum Species // 種類 { Camel, // ラクダ Cat, // ねこ Caterpillar, // いもむし Cheetah, // チーター Chimpanzee, // チンパンジー Cobra, // コブラ Cormorant, // 鵜(う) Cougar, // クーガー Coyote, // コヨーテ Crab, // カニ Crocodile, // ワニ } Species animalA; Species animalB; bool whoWon; (そう、私たちは世界中の男の子が一度は思う「クーガーとワニはどっちが強いか?」という疑問に答えるためのゲームを作るんだ(訳注:ねこが最強))   ビットフィールド圧縮を使った場合、animalAとanimalBの変数には11種類の動物がいるので、その情報を格納するのにそれぞれ4ビットが必要になり、誰が勝ったのかという情報で1ビット必要になるので合計で8ビットを超えてしまいます。 でも、ちょっと待って animalAに格納する値の組み合わせは11種類、animalBも11種類、そしてwhoWonには2種類の値です。全ての組み合わせを考えると11× 11× 2の合計242通りになります。これならbyteに収まりそうです。…

3

ネットワーク その7 ビットフィールドで圧縮

ビットフィールドは古くから知られている素晴らしいデータパッキング手法です。C#プログラマーがビットフィールドを使う機会は非常に少ないですが、ネットワークパケットの圧縮にはもってこいなので、この機会に使ってみましょう。 バイトは8ビット、intは32ビット。でも、送るべきデータが8ビットや32ビットの倍数にならないときはどうします?例えば以下のようなデータを送るとします。 bool isAlive; // 生きているか? bool isFiring; // 撃っているか? enum Species // 種類 { Camel, // ラクダ Cat, // ねこ Caterpillar, // いもむし Cheetah, // チーター Chimpanzee, // チンパンジー Cobra, // コブラ Cormorant, // 鵜(う) Cougar, // クーガー Coyote, // コヨーテ Crab, // カニ Crocodile, // ワニ } packetWriter.Write(isAlive); packetWriter.Write(isFiring); packetWriter.Write((byte)species)   これで3バイトになりますが、実際にはそんなにいりません。ブーリアン型は1ビットしか必要としませんし、生物の種類も11種しか居ないので4ビットで足ります。 それぞれのフィールドにどれだけのビット数が必要かが判れば、ビットシフトすることによって複数のビット群を1つbyteやintにまとめることができます。使いやすいように、指定されたビット群をbyteやintといった値にまとめるメソッドを作るといいでしょう。…

2

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

ビット数の少ない方が多いものより消費するスペースは小さくなります。 もし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

1

ネットワーク その5 圧縮

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

0

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

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

2

ネットワーク その3 帯域

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

1

XNAカスタムコントロール

以前から、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のタスクを実行することで実現しています。 ある程度規模の大きなゲームになってくると、ゲームエディタなどのツールが必要になってきますが、上の二つのサンプルが参考になるのではないでしょうか?

2

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

ネットワークは信頼できません。 データーパケットを送ったときにはいろいろな事がおこります。 相手側に届くかもしれない 届かないかもしれない 届いたとしても、送った順番とは違う順番で届くかもしれない 届いたとしても、その内容が壊れてるかもしれないし、誰かによって変更されているかもしれない 4つ目はXNAフレームワークベースのゲームでは問題になりません。XNAフレームワークではLiveのライブラリを使っているので送られるパケットは全て自動的に暗号化されているので、データ自体が改ざんされたり、壊れたりということを気にする必要はありません。 2と3の問題についてはSendDataOptions.ReliableとSendDataOptions.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

1