リモートコマンド

ねことプログラマーとXNAの友好な関係の為に デバッグコマンドはゲーム開発する上では欠かせないツールの一つですがXbox 360上で使用するにはUSBキーボードが必要になります。私の場合は新し物好きで定期的にキーボードを買い換えるので家の中には使っていないキーボードが幾つかあり、いならくなったキーボードを使っていました。 ですが、最近になって11年物のTVが写らなくなってしまい、ようやっとHDTVというものを購入しました。新しいTVの画面上で自分の作ったゲームが動いているのを見るのは楽しいので、リビングルームでソファに座りながらプログラムするという機会が増えました。 そこで、新たに起きた問題が発生しました。その問題を図式化したのが以下の図です。 ソファーに座っていると、ねこさん達が私の膝の上に乗ってきます。これから寒くなってくる時期には暖かいねこが膝の上に乗ってくるというのはありがたいものです。ですが、デバッグコマンドを使おうとするときに問題が発生します。デバッグコマンドを使用するときにはXbox 360に接続されたキーボードがあるところまで移動しないといけません。その為には膝の上のねこをどける必要があるのですが、せっかく寄ってきたねこさんを無下にする訳にもいきません。 USB延長ケーブルで手元にキーボードを持ってくるという方法もありますが、コントローラーも無線、Xbox 360へのゲームの転送もWiFi使っているので無線という環境なので、延長ケーブルを使うというのはスマートではありません。   リモートコマンド そこで新たに追加したデバッグコマンドがremoteコマンドです。remoteコマンドはWindowsとXbox 360上で同じゲームを実行している時にNetworkSession機能を使い、Windows上で動作しているゲームで入力したコマンドをXbox 360で実行するというものです。 下図はWindows上でremoteコマンドを実行した状態のものです。 リモートコマンドの実行が成功するとデバッグコマンドがリモートコマンドモードへと移行し、今まではCMD>と表示されていたものが[Client]と表示されます。この状態で入力されたコマンドはXbox 360側へと送られ実行され、実行された結果はWindows側へと送られます。リモートコマンド状態から抜け出すにはquitコマンドを使用します。 ゲームへの追加以下のようにコンポーネントを追加するだけです。Windwos Phoneには対応していないので、#ifdefで括っておくと良いでしょう。 #if WINDOWS || XBOX // リモートデバッグコマンド「remote」の追加 Components.Add(new RemoteDebugCommand(this)); #endif NetworkSessionを使っているのでGamerServicesを初期化する必要があるのですが、remoteコマンドはGamerServicesを使っていない場合は自動で初期化するので、GamerServicesを使っていないゲームでも特別なコードを書く必要がなく使えます。また、既にNetworkSessionを使っている場合にはゲーム側で作ったNetworkSessionをRemoteDebugCommand.NetworkSessionへ設定し、文字列データを受け取った場合にRemoteDebugCommand.ProcessRecievedPacketメソッドを呼び出すようにすることで共存できるようになっています。 ただし、最低でもローカルプロファイルがサインインしている必要があります。 ここで見逃しがちなのが、ローカルプロファイルの作るにはプロファイルの生成を選択した後の画面で下の方にスクロールさせた場所にあるリンクをクリックする必要があるということです。   3.1の頃からあった 実のことを言うと、remoteコマンドはデバッグサンプルの3.1版を公開したときに既にあった機能でしたが、いままで説明するのをすっかり忘れていました(汗)。そんな訳で、このリモートコマンド機能は前日に公開した4.0版のデバッグサンプルに既に含まれており、リモートコマンドも登録してあるので、そのまま使えるようになっています。   これで私は膝の上に乗っているねこさんを降ろすことなくデバッグコマンドが使えるようになりました。 と、いうのは半分冗談で、実はリモートコマンドにはもう一つの重要な役割があります。 つづく

0

デバッグコマンド

注:今回紹介するコンポーネントはデバッグサンプルに入っています。 開発中にいろんなものを実行したい ゲーム開発中には様々な情報が欲しくなる場面が沢山あります。シンプルな情報であればブレークポイントを設定して変数を調べたりすることができますが、3Dゲームで画面に表示されている数十体もある敵のうちからひとつの敵の情報をデバッガを使って得るのは大変です。また、スタンドアローン実行している状態、例えばVisual StudioからCtrl+F5で実行した時や、友達の家でテストプレイしているときにバグが発生したときにも、ある程度の情報を知りたい場面があります。それ以外にも開発中に実行したいことはパッと思いつくだけでも以下のものがあります。 シーン全体のポリゴン数 指定したシェーダーのみの表示 カメラの視錐体表示してビューカリングのチェック シーン内を自由に移動できるデバッグ用カメラへの切り替え 敵の数の表示 シーン内のコリジョン情報の表示 イベントトリガー等の表示 AIテスト用に任意の敵を出現させる 任意の敵を即死させる AIステートの表示 指定した武器を出現させる 指定したステージへの移動 プレイヤーのHP設定 Godモード (無敵モード) これだけの機能を実現するシステムとして、以下の方法が考えられます。 欲しい機能ごとにソースコードを変更してコンパイルする コントローラーのボタンに割り当てる 簡単なデバッグメニューを作る WindowsみたいなUIを表示させるAPIを作る 1は単純な方法ですが、いちいちソースコードをコンパイルするというのは面倒だし、スタンドアローン実行時には使えません。2は表示、非表示といったことには使えますが、数に限りがあり管理するのも大変です。3も表示のコントロールなどには使えますが、任意の敵を指定した場所に出現させるといったことには不向きです。4については、Windowsの場合は既存のUIを使えば良いのですが、Xbox 360上では自作する必要があるし、仮にそういったAPIがあったとしても機能ごとのGUIを作るのに手間が掛かります。また、GUIの弱点としてはバッチ処理をするのが難しいという面もあります。 デバッグコマンド こういった要求を満たすためにゲーム開発現場で昔から使われているのがデバッグコマンドです。本来は開発時のみの機能で商品化されたゲームで見かけることは少ないのですが、PCゲームではDOOM時代からあるものなので知っている人も居ると思います。 デバッグコマンドの利点は文字列を入力実行し、出力するというシンプルさにあります。ゲーム中にコマンドを入力して実行するのはもちろん、ネットワークを介して文字列を転送して実行することができます。このことを利用して開発PCとXbox 360間をNetworkSessionで繋ぎ、Windows上ではGUIを使い、その情報を文字列にしてXbox 360に転送するという使い方もできます。この手法はPC上で作ったマップエディターから実機上への通信にも利用されています。特にPC画面上と開発機から出力された画面では色が違うのでBrute Force開発時にはライティングの最終調整などで使っていました。 デバッグコマンドを使う サンプルではDebugCommandUIコンポーネントを使用します。DebugCommandUIコンポーネントはDebugManagerにあるフォントを使用するので、DebugCommandを追加する前にDebugManagerコンポーネントを追加する必要があります。// デバッグマネージャーの初期化と追 debugManager = new DebugManager( this ); Components.Add( debugManager ); // デバッグマコマンドUIの初期化と追 debugCommandUI = new DebugCommandUI( this ); // デバッグコマンドUIを最上面に表示させる為にDrawOrderを変更する debugCommandUI.DrawOrder…

1

タイムルーラー

注:今回紹介するコンポーネントはデバッグサンプルに入っています。 時を測る 前回紹介したFPSカウンターではゲーム全体のパフォーマンスを測定するには使えますが、どの処理がどれだけ時間が掛かっているかを判定するには不向きです。 例えば、ねこが大勢の敵をなぎ倒す「ねこ無双」というゲームを作っていて、敵が沢山出てきた時に処理落ちになった場合に最適化をする必要があったとします。 最適化ルールその3:「最も負荷の高い部分(ボトルネック)の処理を最適化する」 これは当然の話で、1フレーム内で10%しか消費していない処理よりも、50%消費している部分を最適化する方が効果的です。では、この高負荷の処理を発見するにはどうしたら良いでしょうか?ねこ無双の場合だと、敵が増えると処理落ちがするんだから真っ先に思いつくのは敵AI、敵のアニメーション、敵のコリジョン判定、敵の描画処理部分です。 しかし、必ずしもそうとは限りません。もしかしたら、敵の数ではなく戦っている場面の描画部分がもともと負荷が多かったのかもしれないし、敵を表示するレーダー部分かもしれません。 このボトルネックを発見するのに有用なのがリアルタイムプロファイラー、TimeRuler(タイムルーラー)です。TimeRulerでは、複数の処理時間を測定することができ、その結果をグラフでリアルタイムに表示してくれます。 上図の例では、青い部分が更新処理、黄色い部分が描画処理になっています。この状態で最適化する場合は更新処理部分を最適化するのが効果的だと言うことが一目で判ります。 アルゴリズムのチェック 理想的なアルゴリズムは計算量のオーダーがO(n)、もしくはO(n log n)になることです。例えば、敵を10体出現させたときの処理時間が1msだった場合、敵が100体になった時の処理時間が10ms~20ms以内に収まっていれば理想的なアルゴリズムを使っているということになります。逆に敵100体の処理時間が1,000msになってしまうのは、計算量オーダーがO(n^2)になっているアルゴリズムを使っている可能性があります。良くあるケースでは、コリジョン判定を単純な多重ループにしている時などです。 特に最近のゲームでは大量のオブジェクト数を処理するので、アルゴリズムの選択を間違えると、あっという間に処理が間に合わなくなってしまうので注意が必要です。 TimeRulerを使ってオブジェクトの数を10,20,…100と順々に増やしていき、それぞれの処理時間を測定してグラフにすることで問題のあるアルゴリズム部分を発見することができます。 瞬間最大負荷を測る TimeRulerはリアルタイムプロファイラーなので、瞬間的な負荷も視覚的に見ることができます。例えば同じフレーム内で大量の敵を一気に発生させると、敵の初期化処理に時間が掛かりすぎて、そのフレームだけ処理落ちするということがあります。こういった瞬間的な負荷はFPSカウンターで発見することは不可能ですが、TimeRulerを使うとグラフが一瞬大きくブレるので判ります。 また、TimeRulerを表示した状態でビデオに録画したり、動画をキャプチャーしておけば、こういった瞬間的な負荷を細かく調べることもできます。 TimeRulerを使う為の準備 TimeRulerコンポーネントはDebugManagerにあるフォントを使用するので、TimeRulerを追加する前にDebugManagerコンポーネントを追加する必要があります。// デバッグマネージャーの初期化と追 debugManager = new DebugManager( this ); Components.Add( debugManager ); また、必須ではありませんが、TimeRulerはインスタンス生成時にGame.ServicesにIDebugCommandHostインターフェースが追加されていると、trデバッグコマンドを登録します。DeubgCommandUIはIDebugCommandHostインターフェースを実装しているので、このコンポーネントを追加した後にTimeRulerを追加することでtrコマンドを使うことができます。// デバッグマコマンドUIの初期化と追 debugCommandUI = new DebugCommandUI( this ); // デバッグコマンドUIを最上面に表示させる為にDrawOrderを変更する debugCommandUI.DrawOrder = 100; Components.Add( debugCommandUI ); デバッグコマンドUIをTabキーを押して表示させた後に、trとタイプするとTimerRulerの表示/非表示の切り替え、”tr on”で表示、”tr off”で非表示にすることができます。 また、trコマンドには以下のオプションがあります。 log ログの表示/非表示の切り替え。 例) tr log…

7

FPSカウンター

注:今回紹介するコンポーネントはデバッグサンプルに入っています。 正確な測定には注意が必要 FPSカウンターは、一定時間内に(数秒程度)何フレーム更新できたかを計測した結果から、1秒間のフレーム数、FPS(Frame Per Second)を表示するという、非常に簡単な機能です。 ですが、正確なFPSを測定するには幾つかの注意が必要です。Game.IsFixedTimeStepの既定値はtrueになっているので、ゲームの更新と描画に掛かる時間が1msだとしても、見かけ上は16.6ms、つまり60FPSになってしまいます。また、描画時にV-Syncに同期するようになっているので、IsFixedTimeStepだけをfalseにしてもやはり60FPSになってしまいます。 FPSを正しく測定するにはゲームクラスのコンストラクタ内で以下の様なコードを実行する必要があります。graphics = new GraphicsDeviceManager( this ); graphics.SynchronizeWithVerticalRetrace = false; IsFixedTimeStep = false; これでFPS自体は正しく測定することができますが、IsFixedTimeStepをfalseに変更するということは、ゲームの更新の仕方が固定更新から、可変更新に変えるということです。もし、キャラクターを移動するコードが以下の様になっている場合に可変更新に変更すると、キャラクターの移動が速すぎてゲームがプレイ不可能になる場合があります。pos += speed; この問題を解決するには以下の様に、経過時間によって移動するようなコードになっている必要があります。float dt = (float)gameTime.ElapsedGameTime.TotalSeconds; pos += speed * dt; 固定更新、可変更新の振る舞いの違いについては、この投稿が参考になると思います。もし、既に固定更新でゲームを作っている場合にはFPSカウンターを使う意味はあまりありません。 GPUとCPUバランスに注意 また、CPUとGPUの負荷バランスが違う場合には、FPS値は参考になりません。例えば60フレームのゲームを作っている時にFPS値が丁度60フレームになっているからといって、必ずしもこれ以上の処理を追加できないと言うわけではありません。例えば、1フレームあたりの処理時間が、CPUが50%、GPUが100%近く消費している場合、CPU側にはまだ50%の余力があるにも関わらずFPSの値は60になるので、余力が無いように見えてしまいます。逆にCPU100%、GPU50%の状態でも同じです。 CPU、GPUバランスを調べる簡単な方法は、GPUの負荷を測定するにはUpdate内では何も処理をせずにDrawだけ実行し、CPUの負荷を測定するには逆にDrawの処理をしないようにする方法があります。ただし、Draw内にもCPU部分の処理があるので、もっと正確にCPUの負荷を測定するにはDrawPrimitiveのメソッドのみをコメントアウトする必要があります。 60FPSにならないんだけど IsFixedTimeStepとSynchronizeWithVerticalRetraceをtrueにした状態で、明らかにゲーム全体の処理が16.6ms以内で済んでいるのにも関わらず、FPSにはいつも59.xxしか表示されない。というのはおかしいと思う人もいるかもしれません。 これには3つの理由があります。ひとつめはTVの60フレームというのは実際には59.94(厳密に言えば60/ 100.1%)になるので、理論的に60と言う数値にはならないこと。ふたつめは、測定する場合に1フレーム以下の余りの時間分で誤差がでるということ。そして、ハードウェア的には59.94でフレームを更新していても、ソフトウェア的に測定ポイントまで戻ってくるまでの時間が微妙に変わってくるということです。 ですから、59.xxと言う数値を見たときには約60FPSと考えても問題ありません。 FpsCounterコンポーネントの使い方 FpsCounterコンポーネントはDebugManagerにあるフォントを使用するので、FpsCounterを追加する前にDebugManagerコンポーネントを追加する必要があります。// デバッグマネージャーの初期化と追 debugManager = new DebugManager( this ); Components.Add( debugManager ); また、必須ではありませんが、FpsCounterはインスタンス生成時にGame.ServicesにIDebugCommandHostインターフェースが追加されていると、fpsデバッグコマンドを登録します。DeubgCommandUIはIDebugCommandHostインターフェースを実装しているので、このコンポーネントを追加した後にFpsCounterを追加することでfpsコマンドを使うことができます。 デバッグコマンドUIをTabキーを押して表示させた後に、fpsとタイプするとFPSの表示/非表示の切り替え、”fps on”で表示、”fps off”で非表示にすることができます。//…

1

デバッグサンプル

2010/09/17 追記: XNA Game Studio 4.0用のサンプルをhttp://higeneko.net/hinikeni/sample/xna40/DebugSample.zipにアップしました。詳細は「サンプルコードをXNA 4.0向けに更新」を見てください。 2009/06/25 追記: XNA GS 3.1用のサンプルを http://higeneko.net/hinikeni/sample/xna31/DebugSample.zipにアップしました 2009年1月24日追記: サンプルプログラムのキーボード処理部分でテンキーの0を押すと1が入力されるバグを修正、アップデートしました。バグがあった場所はGameDebug/KeyboardUtils.csファイル内のInitializeKeyMapメソッド内の初期化コード部分です。AddKeyMap( Keys.NumPad0, "10" )となっているのをAddKeyMap( Keys.NumPad0, "0" )にする事で修正できます。連絡してくださったDERNAさんに感謝します。   デバッグ3種の神器 ゲーム開発現場で最も使われているゲーム内で動作するデバッグ用の機能といえば以下の3つがあります。 FPSカウンター リアルタイムプロファイラー デバッグコマンド 市販されているPCゲームの中にはオプション画面でFPSカウンターを表示できるようになっているものもあるので、パフォーマンスの指標として有名です。後のふたつについては一般にはあまり知られていませんが、ゲーム開発現場、特に北米のゲーム開発現では良く目にする機能です。 前回紹介したGamefest Japan 2008のデモでも、これらの機能を使っています。そこで、今回は前回のデモからデバッグ機能だけを抜き取ったサンプルを作りました。  http://higeneko.net/hinikeni/sample/DebugSample.zip 前回と同じく、フォントはConsolasを使用しています。Consolasフォントが無い場合はビルドエラーになるので、以下のURLからConsolasフォントをダウンロードするか、フォント名を変更するようにしてください。 https://www.microsoft.com/downloads/details.aspx?familyid=22e69ae4-7e40-4807-8a86-b3d36fab68d3&displaylang=en 使い方 サンプルを起動すると、画面右上に使い方が表示されます。 FPSカウンターの表示 Aボタン、またはAキー TimeRulerの表示 Bボタン、またはBキー TimeRulerログ表示 Xボタン、またはXキー デバッグコマンドUIの表示 Tabキー サンプルプログラムの終了 Backボタン、またはEscapeキー デバッグコマンドUIはキーボード入力からデバッグ用コマンドを実行します。Xbox 360でもUSBキーボードを本体に繋げることで使うことができます。デバッグコマンドUIには元々使えるコマンドとして画面をクリアするclsコマンド、登録されているコマンドを表示するhelpコマンドがあります。基本的にWindowsにあるコマンドシェルと使い方は一緒で、上下キーで最近使ったコマンドのヒストリを表示する機能もあります。 このデモでは、DebugSampleGame.cs内には独自のデバッグコマンドを追加するサンプルコードが含まれていて、ここではposコマンドを追加して、画面上に表示される「+」マークの位置を表示したり、変更できるようになっています。下の動画では見づらいですが、最初は左上にあった白い点がposコマンドによって画面下中央に移動しているのが判ると思います。 サンプルに含まれるファイル このサンプルに含まれるファイルには、デバッグ機能の使い方のサンプルコードが入ったDebugSampleGame.csファイルの他に、デバッグ機能を提供する以下の8つのファイルがあります。 DebugSample/GameDebug/DebugCommandUI.cs デバッグコマンドUIクラス DebugSample/GameDebug/DebugManager.cs デバッグ用のコンテントを格納する為のゲームコンポーネント…

7

PIXを活用する その1

PIXとは 3Dゲームを開発している時に必ずといってあるのが、モデルが意図しない状態で表示されるという問題です。「色が変」や「形が変」と言う比較的原因が予想しやすい問題から「画面になにも出ない」といった原因を判断するのが困難な場合まで様々な問題があります。特にシェーダープログラムを使っているとこういった問題に直面する機会は多くなります。 こういった問題の原因を見つけるのに非常に有用なツールとして、Direct X SDKに付属するPIX for Windowsがあります。このツールは元々、初代Xbox用に開発されたPerformance Investigator for Xbox (PIX)だったのですが、これを使った人達からの強い要望でWindows版が作られました。 XNA FrameworkはDirect X上で動作しているので、XNA GS用に作られたゲームもPIX for Windowsを使うことができます。 PIXの主な機能としては 1フレーム、もしくは複数のフレームのレンダリング情報のキャプチャ レンダリングに使われたDirect Xの描画関連命令の表示 リソース使用量の表示 各段階でのグラフィクスリソースの状態表示 レンダーステート テクスチャ、レンダーターゲット 頂点情報 シェーダー情報 シェーダーデバッガ があります。 PIXの基本的な使い方 Direct X SDKをインストールした状態で(ここではAugust 2008を例にします)、スタートメニューからMicrosoft DirectX SDK (August 2008)/DirectX Utilities/PIX for Windowsを選択してPIX for Windowsを起動します。 FileメニューからNew Experimentを選択すると以下の画面が表示されます。 Program pathに対象となる実行ファイルを指定し、”A single-frame capture of Direct3D whenever F12 is pressed”のラジオボタンを選択して、右下にある”Start…

2