Twitter @HigenekoTwitter #XNA
2010/09/17 XNA Game Studio 4.0用サンプルをhttp://higeneko.net/hinikeni/sample/xna40/SimpleMessage.zipにアップしました。詳細は「サンプルコードをXNA 4.0向けに更新」を見てください。
2009/06/25 XNA GS 3.1用のサンプルをhttp://higeneko.net/hinikeni/sample/xna31/SimpleMessage.zipにアップしました。
以前紹介した英数字以外の文字列表示方法はコンテントパイプラインの全体の流れを説明するという目的もあったので複雑なつくりになっていました。RPGやテキストベースのアドベンチャーゲームなどの大量のテキストデータを扱う場合は、こういったものは必須になってきますが、今回はXNA 2.0から導入されたプロセッサパラメーターを使ってのシンプルな日本語を含む多言語文字列表示方法を紹介します。
とりあえず、前回のサンプルを2.0用に書き直しておきました。
MessageTextSample 2.0
Windowsアプリケーションプログラミングを経験した人達にとってXNAに同様の文字列描画APIがないというのは疑問に思うかも知れません。なぜ、普通のWindowsのアプリケーションのようにOSにインストールされたフォントを指定して文字列を簡単にかけないのでしょうか?それには主に以下の理由があります。
1については、日本語フォントの殆どは約7,000文字近くのデータを持っています。私のマシンにインストールされているMSゴシックのファイルサイズは8MB近くあります。殆どのゲームでは500~1,500文字程度の文字しか使わないので7,000文字のデータを持つのは効率的ではありません。更に、キャラクターや状況によって複数のフォントを使い分けるのでファイルサイズが大きいというの問題になります。
2は、通常のフォントはTrueTypeと呼ばれるデータ形式で、これは一文字一文字のポリゴンデータを持っているようなものでテクスチャにあらかじめ描画された文字をひとつの四角形ポリゴンで表示するより処理するのに時間が掛かります。また、フォントデータは必要になったときにHDDから読み込むのでリアルタイム性の高いゲームを作ってるときにはその遅延時間を考慮しなければいけないという問題もあります。
3は面倒な問題で、フォントには著作権があり、その使用許諾の形式もフォントを作っている会社によってさまざまなものがありますが、その多くはテクスチャとして文字を使うのは良くてもフォントファイル自体の再配布を禁じているものがあります。それ以外にもいろんなライセンス契約形式があることに注意してください。
4については、フォントと言うのはゲームの雰囲気を伝えるために重要なもので、キャラクターやその場の雰囲気によって複数のフォントを使うことが多いです。例えばおどろどろしい雰囲気を出すために古印体というフォントを使ったりしますが、そういった特徴的なフォントがOSにインストールされていることは殆どありません。また、テクスチャにすることで
のように普通の文字列の間にビットマップで描いた絵を文字として組み合わせることもできます。
以上の理由からXNAではSpriteFontを採用しています。
今回のサンプルの基本アイディアは
と、シンプルなものです。カスタムプロセッサのプロセスメソッドは以下のようになっています。
public override SpriteFontContent Process(FontDescription input, ContentProcessorContext context) { // MessageFilenameで指定されたファイル内の文字を追加する AppendCharacters(input, context); // 文字列を追加した後は単純にFontDescriptionProcessorのプロセスを呼ぶだけ return base.Process(input, context); }
MessageFilenameをプロセッサ・パラメーターとして以下のように宣言しています。詳細はこの記事が参考になると思います。ここではプロパティ画面上で読みやすいようにDisplayNameとDescriptionアトリビュートを使っています。
/// <summary> /// プロセッサーパラメーター /// ここに読み込むメッセージファイル名を指定する /// </summary> // プロパティ画面で表示される文字列の指定 [DisplayName("メッセージファイル名")] // プロパティ画面でのパラメーターの説明 [Description("メッセージテキストが含まれているテキストファイル名")] public string MessageFilename { get { return messageFilename; } set { messageFilename = value; } } string messageFilename;
Processメソッドから呼んでいるAppendCharactersメソッド内では単にファイルから文字列を読み込み、FontDescription.Characters.Addメソッドを使って使用する文字を追加しています。
/// <summary> /// FontDescriptionにMessageFilenameで指定されたファイル内の文字を追加する /// </summary> void AppendCharacters(FontDescription input, ContentProcessorContext context) { // MessageFilenameは有効な文字列か? if (String.IsNullOrEmpty(MessageFilename)) return; if (!File.Exists(MessageFilename)) { throw new FileNotFoundException( String.Format( "MessageFilenameで指定されたファイル[{0}]が存在しません", Path.GetFullPath(MessageFilename))); } // 指定されたファイルから文字列を読み込み、 // FontDescription.Charctarsに追加する try { int totalCharacterCount = 0; using (StreamReader sr = File.OpenText(MessageFilename)) { string line; while ( ( line = sr.ReadLine() ) != null ) { totalCharacterCount += line.Length; foreach( char c in line ) input.Characters.Add( c ); } } context.Logger.LogImportantMessage("使用文字数{0}, 総文字数:{1}", input.Characters.Count, totalCharacterCount); // CPにファイル依存していることを教える context.AddDependency(Path.GetFullPath(MessageFilename)); } catch (Exception e) { // 予期しない例外が発生 context.Logger.LogImportantMessage("例外発生!! {0}", e.Message); throw e; } }
このカスタムプロセッサをコンパイルすると、以下のようにプロパティ画面上でメッセージプロセッサという名前のプロセッサが追加され、そこにメッセージファイル名というプロセッサ・パラメーターが表示されます。
ここにはテキストファイルであればどんなファイル名でも指定できますが、使っている環境に依存しないように相対パスを指定するようにしましょう。通常、コンテントパイプライン実行時のルートパスはContentフォルダになっています。また、多言語が指定できるようにファイルのエンコーディングはUTF-8を指定します。
さて、どんなファイルでも指定できるわけですが、前回の時にも書いたように私がプログラムするときには「エラーが起きにくく、起きたとしても直ぐにエラーの原因が特定、修正ができる」ということを心がけています。ですから、メッセージファイルと実際にランタイム時に使う文字列を別のファイルであるソースコードと別々にすると問題が起きやすく、見つけづらいという状態になってしまいます。
じゃあどうすればいいのか?また前回のように面倒なコーディングをしないといけないのか?と思ってしまいますが、今回はシンプルな解決方法をとります。それはソースコードをメッセージファイルとして指定するというものです。
ソースコードも立派なテキストファイル。そこに使われる文字は殆どが英数字なので日本語のメッセージに使われる文字数に比べると少ないし、既に英数字を使用する文字に指定しているのであれば無駄はありません。気をつけるとすれば、メッセージファイルが更新する度にコンテントビルドが行われるので、頻繁に書き換えるソースコードをメッセージファイルとして指定しないということです。
そこで、今回はサンプル内で使う文字列をまとめたMessageTable.csというソースコードを指定しています。
namespace SimpleMessage { static class MessageTable { public static string[] Messages = { "日本語メッセージテスト\n"+ "キーボードのEnterキー、コントローラーのAボ���ンで\n"+ "次のメッセージを表示します。", ... "最初のメッセージに戻ります。", }; } }
カスタムプロセッサは100行程度のプログラム。ランタイム側は特別なデータタイプを持つことなく、SpriteFontを追加し、そのプロセッサを変更してパラメーターを設定するだけなので、簡単に日本語メッセージを表示できるのではないでしょうか?
簡単(かもしれない)日本語表示サンプル
冒頭で紹介したサンプルも、このサンプルもXNA 2.0用です。XNA 2.0のプロジェクトファイルはC# Express 2005、Visual Studio 2005のどちらでも使えるようになっています。
と、いうわけでXbox LIVE community gamesが発表されました。
http://blogs.msdn.com/xna/archive/2008/02/20/announcing-xbox-live-community-games.aspx
Xbox Live コミュニティゲームを一言で表せばYouTubeやニコニコ動画のゲーム版といった感じです。
そう、今まではXNA Game Studioを使って作ったゲームを開発者以外の人に遊んで貰うのは色々と面倒でしたが、コミュニティーゲームの登場によって簡単に色んな人達があなたの作ったゲームを遊ぶことができるようになります。なぜなら、コミュニティゲームはクリエータークラブ会員でなくとも遊ぶことができるからです。
ゲームの登録手順の概略は以下のようなっています。
ピアレビュー(Peer Review)はクリエータークラブ会員によるゲーム評価プロセスのことです。動画と違ってゲームの場合は、プログラムの���グといった問題があるので、ある程度プログラム知識のある人達によってレビューされる必要があります。
ここで重要なのはマイクロソフトはシステムを提供するだけでピアレビュー自体には関与せず、ピアレビューはクリエータークラブ会員のみによって行われるので、より自由な発想のゲームが登場する可能性があるということです。
また、詳細は決まっていませんがゲーム登録するときに、無料、有料を選択することができ、有料にすることで登録者が収入を得ることもできます。
Xbox LIVE コミュニティゲームのサービス開始時期は今年のホリデーシーズンで、ベータ版は春頃開始を予定しています。
正式運用されるまでの10ヵ月はゲームを遊びたい人達にとっては長いかもしれません。
でも、ゲーム開発者の皆さんにとっては後10ヶ月しかありません。コミュニティゲームのローンチタイトルとして面白いゲームを作ってみてはどうでしょうか?
GDC2008で、XNA Game Studio 3.0の新機能の1つが発表されました。それはWindows、Xbox 360に加えて新たにZuneがサポートされるようになったことです。
日本では未発売のものですが、Zuneは携帯メディアプレイヤーですが他のプラットフォームと同じようにXNA Game Studioを使ってゲーム開発をすることができます。Xbox 360のように贅沢なGPUとかは搭載していませんし、スクリーンの解像度は違いますが、2Dベースのゲームであれば他のプラットフォームで書いたコードがほぼそのまま使えるようになっています。
そして、Zune独自の機能として自分のお気に入りの音楽をゲーム中にBGMとして流したり、映像データをテクスチャとして使うことができるメディアAPIが追加されました。
XNA Game Studio 3.0は春頃にレビューリリース予定で、最終リリースは今年のホリデーシーズンを予定しています。
クライアント/サーバーとピア・ツー・ピア型のどちらのネットワーク形態が優れているのかを議論するのが好きな人たちがいます。しかし、個人的にこの議論は間違ったものだと思います。誰が「どちらか一方のネットワーク形態を選ばなくてはいけない」と言ったんですが?私は両方の長所を活かしたハイブリット形式の大ファンです。
ネットワークプログラミングは妥協との戦いです。100%の正確さと、ラグがまったく無いという2つの状態を両立することは不可能で、トレードオフをしなければいけません。
時にはラグが多少大きくっなっても正確さをとる場合もあれば、逆に正確さを犠牲にして高いレスポンスを必要とする場合もあります。
100万ドルの問題: どのマシンがどのデータを管理するか?
ゲーム内のそれぞれのデータについてのコスト/実益の分析を以下の質問に答えることでできます。
この質問の答えによって、どのようにデータを管理すればいいのかが判ります。
1と2の質問はどのマシンがデータを管理するかの指標になり、3と4の予測アルゴリズムの適用についての質問は別々のものだということに注意
例えば:
スペースシップの移動
結論:それぞれのスペースシップはそれぞれのローカルマシンによって管理されるべき。予測アルゴリズムを適用する。
レースの勝敗
結論:1台のマシンがレース結果を決めるべき。予測アルゴリズムは適用しない
死亡判定
結論:それぞれのマシンがローカルプレイヤーの死亡判定をするべき。他のマシンは死亡判定の予測をしてはいけない。
追記:ここでは視覚的な予測を適用することができる。例えば、私のマシンがヘッドショットだと判定するが、ヘッドショットされたプレイヤーの位置が100%確実ではないので死亡アニメーションを再生することはできない。その代わりに「ダメージを受けた」というアニメーションを再生することができ、視覚的なフィードバックを即座に得ることができる。もし、ヘッドショットを決めたプレイヤーから死亡か確定したという連絡が届いた場合は死亡アニメーションへスムースに変化させることができる。仮にこの予測が外れた場合でもダメージアニメーションをキャンセルして通常のアニメーションに戻すことができる。
ビークル(乗り物)に乗る
私は一度、キャラクターが徒歩で歩き回り、いろんなビークルに乗り降りすることができるゲームのプロトタイプ製作をしたことがありました。ビークルに乗っている間は「スペースシップの移動」の例で説明したのとまったく一緒です。ただビークルに乗ると言うのは違います。一度に複数のプレイヤーが運転することはできませんから!
この問題を解決するために、まずどのマシンがどのオブジェクトに対して所有権を持っているのかを表すデータをつくりました。これで動的にビークルの所有権の変更ができます。次にどのマシンが所有権を持っているのかを決めなければいけません。
ビークルのステート(操作、物理シム、衝突判定等)は、そのビークルを運転しているプレイヤーがいるマシンによって管理されます。それ以外に、この所有権を誰が持つべきを決める管理マシンを決めました。もしビークルに誰も乗っていない場合は、管理しているビークルの数が最も少ないマシンに所有権があります。
ビークルのとなりに立ち「乗る」ボタンを押した時に以下の処理をします。
ビークルを運転しているときはピア・ツー・ピア形式ですが、大事な決定をするのはクライアント/サーバー形式になっています。
アイディアとしては大事な決定を下す単一の管理マシンが全てのデータを管理する必要がないということです。マシンAはビークルの乗り降りを管理して、マシンBはパワーアップアイテムの管理、そしてマシンCはセッション終了時に誰が勝敗を決定するといった感じに管理するものを割り振ることができます。
これで私がネットワークに関して言わなければならないことの全てです。
THE END
原文:http://blogs.msdn.com/shawnhar/archive/2008/01/03/network-object-ownership.aspx
今まで紹介してきた狡猾な圧縮方法より効果的なデータ圧縮方法があります。それはデータ自体を送らないということです。
もちろん、まったくデータを送らないのでは相手側との同期ができません。でも、時には同期すること自体が重要ではない場合があります。
2つのルール
効果音やアニメーションは殆どの場合は同期する必要がありません。もし、ネットワークを介してキャラクターが前に走って移動する場合、それぞれりマシンではその情報を元に、走るアニメーションを再生させ、そのアニメーションに合わせて靴音を鳴らすことができます。靴音が鳴るタイミングが多少ずれていてもゲームプレイには関係ないのでネットワークを介して足音を同期させる必要はありません。
ケーススタディ:MotoGPでは5%の確立で相手を抜き去った場合、抜いた相手に向かって手を振り上げるアニメーションを再生します。クラッシュした場合、ライダーが吹き飛ぶ複数のアニメーションの中からランダムで再生され、ネットワークにはどのアニメーションを再生したかという情報は送られません。 あるプレイヤーから見ると手を振り上げたアニメーションをしているように見えますが、他のプレイヤー視点からはアニメーションが再生されていないという場合があります。クラッシュシーンでライダーの転がるアニメーションはそれぞれのプレイヤーによって違うアニメーションが再生されます。 これらをネットワークを介して同期するにはネットワーク帯域が足りませんでした。しかし、これらのアニメーションはゲームプレイには関係無いものなので、誰も同期していないということには気づきませんでした。
ケーススタディ:MotoGPでは5%の確立で相手を抜き去った場合、抜いた相手に向かって手を振り上げるアニメーションを再生します。クラッシュした場合、ライダーが吹き飛ぶ複数のアニメーションの中からランダムで再生され、ネットワークにはどのアニメーションを再生したかという情報は送られません。
あるプレイヤーから見ると手を振り上げたアニメーションをしているように見えますが、他のプレイヤー視点からはアニメーションが再生されていないという場合があります。クラッシュシーンでライダーの転がるアニメーションはそれぞれのプレイヤーによって違うアニメーションが再生されます。
これらをネットワークを介して同期するにはネットワーク帯域が足りませんでした。しかし、これらのアニメーションはゲームプレイには関係無いものなので、誰も同期していないということには気づきませんでした。
同じように、FPS等のゲームでは撃った弾の位置、壁に当たったときにできる弾痕の位置、飛び散るガラス片や薬莢の位置を同期する必要はありません。ここでネットワークに送る情報は単に「私はゲーム内で銃を撃っている」というブーリアン(bool)の情報だけです。このシンプルな情報を元に、各マシンではそれぞれにシミュレーションを行います。多少の違いが起きても、それらの平行世界で起きている事柄がほぼ同じである限りは問題がありません。
一度、この「データをネットワークに送らなくても良いんだ」という考え方が身につくと、いろんな事ができるようになります。
ケーススタディ:Moto GPで広告看板やパイロンはコース脇に配置されています。もしバイクがこれらのものと衝突した場合、バイクと一緒に吹き飛びます。もし、ゆっくりとした速度でぶつかった場合は看板やパイロンを押し動かすことができます。 殆どのプレイヤーはレースに勝つことが目的で真剣にレースに参加しますが、中にはレースの邪魔をしようとするグリファー(訳注:オンラインゲームで嫌がらせをするプレーヤーのこと、英語ではGriefer、griefには深い悲しみ、悲痛といった意味があります)がいます。グリファー達はコースを逆走したりして、どうやったらレースをメチャクチャにできるのかを競っています。 グリファー達は広告看板やパイロンをコースの中央に動かしてバリケードを築けることに気付きました。これで真剣にレースをしているライダー達がバリケードに衝突して派手にほこりを撒き散らしながら吹き飛ぶ筈です。 でも、実際にはグリファー達の思い通りにはいきませんでした。 広告看板やパイロンの位置情報はネットワークを介して送られてはいませんでした。単に充分なネットワーク帯域が無かったからです。 以下は実際にあったことです。 あるグリファーはバイクを前後に動かしながら、ゆっくりと障害物を押し進めます ピア・ツー・ピアを使っていたので、そのグリファーは自分のバイクの動きを完全にコントロールできます。 他のマシンでは予測アルゴリズムを使っているので、グリファーのバイクの座標は大体一緒ではありますが、完全に一緒ではありません。 グリファーのマシン上ではグリファーの思い通りに障害物はコースの真ん中へ移動します。 他のマシンでは最初の押しが少しだけ左にずれました。この段階では障害物の位置はグリファーが移動させた位置に近いですが一致している訳ではありません。更にグリファーが障害物を押し進めるうちにグリファーが実際に移動させた位置とはズレが生じ、最終的に他のマシン上では障害物はグリファーと当たらない位置に移動して、そのまま動かなくなります。それらの障害物の位置は同期されていないので、この矛盾は修正されません。 結果 グリファーのマシン上では立派なバリケードが完成、他のマシン上ではバリケードができていない レースを真剣にしている人達のマシン上ではバリケードが無いので問題なくレースを楽しむことができます。 一方、グリファーのマシン上では立派なバリケードがあるので他のプレイヤー達はグリファーの思惑通りにバリケードに衝突してライダーは派手に中に舞い、バイクは火花を飛ばしながら滑っていきます。そのしばらく後に新しい情報がネットワークから届きます。あれ?実際には衝突は起きていなかった?この時、吹き飛んだはずのプレイヤーはコース上に配置され、問題なくレースは続行されます。 100万人ものプレイヤー達が何時間もプレイしたにも関わらず、誰もこの矛盾には気づきませんでした。 グリファーは盛大なクラッシュシーンを見て喜び、 真剣にレースをしている人達は無事にレースを終えることができ、 みんないつまでも幸せにレースを楽しんだとさ、めでたしめでたし
ケーススタディ:Moto GPで広告看板やパイロンはコース脇に配置されています。もしバイクがこれらのものと衝突した場合、バイクと一緒に吹き飛びます。もし、ゆっくりとした速度でぶつかった場合は看板やパイロンを押し動かすことができます。
殆どのプレイヤーはレースに勝つことが目的で真剣にレースに参加しますが、中にはレースの邪魔をしようとするグリファー(訳注:オンラインゲームで嫌がらせをするプレーヤーのこと、英語ではGriefer、griefには深い悲しみ、悲痛といった意味があります)がいます。グリファー達はコースを逆走したりして、どうやったらレースをメチャクチャにできるのかを競っています。
グリファー達は広告看板やパイロンをコースの中央に動かしてバリケードを築けることに気付きました。これで真剣にレースをしているライダー達がバリケードに衝突して派手にほこりを撒き散らしながら吹き飛ぶ筈です。
でも、実際にはグリファー達の思い通りにはいきませんでした。
広告看板やパイロンの位置情報はネットワークを介して送られてはいませんでした。単に充分なネットワーク帯域が無かったからです。
以下は実際にあったことです。
結果
100万人ものプレイヤー達が何時間もプレイしたにも関わらず、誰もこの矛盾には気づきませんでした。
グリファーは盛大なクラッシュシーンを見て喜び、
真剣にレースをしている人達は無事にレースを終えることができ、
みんないつまでも幸せにレースを楽しんだとさ、めでたしめでたし
原文:http://blogs.msdn.com/shawnhar/archive/2008/01/01/network-compression-just-say-no.aspx
算術符号化は解りづらく、めったに使われないツールのひとつですが、時々その威力を発揮します。例えるならネットワークデータ圧縮における変なサイズの六角レンチです。
算術符号化はクールです。なぜなら、あまり知られていないし、一見すると動かないように思えるものだからです。パーティーで女の子に好印象を持たせるのに活躍します(訳注:そうか?)
以下のデータがあったとします。
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に収まりそうです。
ここでは4ビットを動物の種類に割り当てていますが実際は11種類必要なところに16種類を表すだけのスペースを用意してしまうというのはビットフィールド圧縮の問題です。この中途半端な組み合わせをコンパクトにする方法があれば良いと思いませんか?
前の記事のビットフィールド圧縮の部分を、乗算、除算、そして剰余算に変更することで解決することができます。
void ArithmeticEncode( ref int encoded, int valueRange, int Value ) { encoded *= valueRange; encoded += value; } int encoded = 0; ArithmeticEncode( ref encoded, 11, (int)animalA ); ArithmeticEncode( ref encoded, 11, (int)animalB ); ArithmeticEncode( ref encoded, 2, whoWon? 1: 0 ); packetWriter.Write((byte)encoded);
そして、読み込み側は、ビットフィールドの時と同じようにパラメーターを逆順に読み込みます。
void ArithmeticDecode( ref int encoded, int valueRange ) { int value = encoded % valueRange; encoded /= valueRange; return value; } int encoded = packetReader.ReadByte(); whoWon = ArithmeticDecode( ref encoded, 2 ) != 0; animalB = (Species)ArithmeticDecode( ref encoded, 11 ); animalA = (Species)ArithmeticDecode( ref encoded, 11 );
どう?クールでしょ?
原文: http://blogs.msdn.com/shawnhar/archive/2007/12/30/network-compression-arithmetic-encoding.aspx
ビットフィールドは古くから知られている素晴らしいデータパッキング手法です。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といった値にまとめるメソッドを作るといいでしょう。
ここでは、先の例では3バイトを送っていたところを1バイトにまとめて送ることができます(まだ2ビット余ってる)
void AddToBitfield( ref int bitfield, int bitCount, int value ) { bitfield <<= bitCount; bitfield |= value; } int bitfield = 0; AddToBitfield( ref bitfield, 1, isAlive? 1: 0 ); AddToBitfield( ref bitfield, 1, isFiring? 1: 0 ); AddToBitfield( ref bitfield, 4, (int)species ); packetWriter.Write((byte)bitfield);
AddToBitfieldメソッドは2つのことをします
読み込むためには、逆のプロセスを行います。
int ReadFromBitfield( ref int bitfield, int bitCount ) { int value = bitfield & ((1 << bitCount) - 1 ); bitfield >>= bitCount; return value; } int bitfield = PacketReader.ReadByte(); species = (Species)ReadFromBitfield( ref bitfield, 4 ); isFiring = ReadFromBitfield( ref bitfield, 1 ) != 0; isAlive = ReadFromBitfield( ref bitfield, 1 ) != 0;
書き込んだ順番とは逆に読み込んでいることに注意してください。
ReadFromBtfieldメソッドはAddToBitfieldと逆のことをします。
ビットフィールドはenumやブーリアンをまとめるのに有効ですが、他の数値データも前回紹介した量子化の手法と組み合わせることで格納することができます。
原文:http://blogs.msdn.com/shawnhar/archive/2007/12/28/network-compression-bitfields.aspx