System4.0 とDirectX の共存
はじめに
System4.0とDirectX(Direct3D)を共存させる方法を説明します。前回のGDIを用いた描画は、非常にイレギュラーな方法でしたが、今回の方法は比較的まともな内容です。
System4ひそひそ話に述べられている通りの実現方法を用いて実現しています。GetRenderTargetDataを用いるので、グラフィックボードがDirectX9対応でなければ、相当低速になってしまいます。 高速に動作するかどうかのテストには、Rance6の動作デモをDirect3Dモードで動かしてみるという方法があります。
あと、Akt.は、C++プログラマじゃないですし、C++でDirectXを扱うのは始めたなので、間違いがあっても責めちゃ駄目です。
それから、プログラムの骨格として、Masafumi's LABORATORYさんのサンプルを使用しています。
自分が分かりづらかった場所を中心に説明していきます。ここに書いてあることだけでは、不足していることがあるかもしれませんが、それは自分で調べてください。
この方式の利点
System4.0(SACT2)には音楽再生、効果音再生、キーボードとジョイパッドの押下状態の取得、マウスの状態取得、スプライトを用いた画像管理、画面変更時のエフェクトなどの豊富な機能があります。このような機能を自分でプログラミングして作成せずに、自由に使用できます。
スプライトを用いたゲームの作成など、大変便利で生産性も高いです。そしてこの方法によりDirectXと共存すると、DirectXの処理内容をスプライトの一つとして処理できます。また、CG素材と音楽素材も自由に使えるようになります。
Direct3D
Direct3Dの初期化の流れなどは、Masafumiさんのところのサンプルコードを見て頂くとして、それをどうするかをまず述べたいと思います。一言で言うと、DLLにしなければなりません。System4.0から利用できる形式はDLLの形式でなければならないため、EXE形式では意味がありません。そのため、プロジェクトのプロパティの構成から、ダイナミックライブラリ(DLL)を選択してください。また、自分の環境では、d3dx9dt.libが存在しないと言われましたので、そのようなエラーが出る場合は、d3dx9d.libにしておきます。
そして、重要な事ですが、Direct3Dはウィンドウが無ければデバイスを作成できないようです。そのため、必ずウィンドウを作成しなければなりません。しかしながら、ウィンドウを表示していては意味がありません。目的はSystem4.0の管理する画面にDirect3Dで画像を送りたいことなのですから。そのため、ShowWindowとUpdateWindowの呼び出しをコメントアウトしておきます。また、アプリケーションループは不要です。こちらもコメントアウトしてください。何故ならば、ウィンドウはダミーであり、ウィンドウが閉じられるまで実行するわけではないからです。System4.0が終了されるまで実行されなければなりません。UnregisterClassは終了直前に呼ばれなければなりません。Clearnupの方へ移動させます。Direct3Dの初期化時は、バックバッファのサイズを指定して初期化しましょう。その際のフォーマットは、画面の色深度と一致していないと駄目なようなので、UNKNOWNを指定しておきます。また、BackBufferCountに1を代入しておきます。
初期化の最後に、CreateOffscreenPlainSurfaceを、D3DPOOLSYSMMEMで作成しておきます。このSurfaceを保存しておきましょう。
次にRender関数の引数にBYTE型のポインタを追加しましょう。ここで指定されたポインタへDirect3Dで描画したデータを流し込むという仕組みです。Presentが呼び出される場所をコメントアウトし、バックバッファの取得、GetRenderTargetDataでバックバッファから作成しておいたSurfaceにデータのコピー、SurfaceのLockRectと行います。
ここで注意が必要ですが、LockRectしたデータ列のフォーマットは、画面のフォーマットに等しいです。そのため画面が16bitならばR5G6B5、32bitならX8R8G8B8といったようになっています。しかしながら、System4.0の管理するSurfaceは(おそらく)常に24bitのR8B8G8です。24bitであれば、memcpyで縦x横x3バイトコピーすれば良いだけの話ですが、32bitの場合は、X8に相当する部分を切り捨てながらコピーします。つまり、3つずつコピーしなければなりません。16bitの場合は、さらに話が複雑ですが、ここでは述べません。そして、最後にUnlockRectを行います。
DrawPlugin
DrawPluginとは、System4.0のSACT_UPDATEが呼ばれるごとに、DLLが呼び出されるというPluginの事です。詳しくは、マニュアル[ Sys42SDK_20041224\熟練者向けセット\マニュアル\Develop\DrawPlugin.html ]や、うぃんすろうさんとこを参照してください。
また、DrawPlugin単独では終了時イベントを検出することができません。System4ひそひそ話の方に終了時コールバックについての記述がありますが、確認していません。終了を検出するには、クラスを作成し、そのクラスをグローバルで宣言しておきます。このクラスのデストラクタが呼び出された時が終了時です。この検出方法で、おそらく、大丈夫だと思います。
自分の実装方法としては、IDrawPluginを継承したクラス、Direct3Dを管理するクラス、終了検出用のクラス、という3つを使用しました。グローバルで、終了検知用のクラスは実体として、そのほかの2つはポインタとして宣言します。次に、CreateInterfaceにて、IDrawPluginを継承したクラスのインスタンスを作成しポインタに格納し戻り値に返します、次に外部に公開する関数として初期化用のものを作成し、そこでDirect3Dを管理するクラスの初期化を行いポインタに格納します。終了検出用のクラスのデストラクタでは、Direct3Dを管理するクラスのポインタが実体を持っていれば終了処理をします。
同様にスプライトを登録する関数を外部に公開します。この関数が呼ばれた場合は、SetInterfaceで取得しておいたIChangeNumToISurfaceを用いてSurfaceに変換します。このSurfaceを保持しておき、SACT_UPDATEにより、DrawPluginのUpdateが呼び出された時に、SurfaceのGetPixel(0,0);から、先頭のポインタを取得し、それを上で追加したRenderの引数に入れます。後は同様に取得しておいた、IAddInvalidateRectにて画面全てを更新したとSystem4.0に伝えれば完成です。
DrawDungeon
中級者向けの3Dダンジョンの描画の中の開発用のフォルダに、DrawDirectX.dllを作成するためのソースファイル一式が格納されています。このダンジョン描画用のプロジェクトのうち、game.cppとgame.hのみがダンジョン描画用に作成されているファイルで、他の用途に使いたい場合、この2つを変更すれば良いように設計しました。(ただし、難しいことをやろうとしたり、そもそもDirect3Dをもっと弄くりたい場合は他にも変更が必要です)
game.hを開くと、CGameSystemクラスの中身が表示されますが、必須である関数はGameUpdate、GameRun、GameBuild、GameClear、GameGetState、コンストラクタ、デストラクタです。GM_StructBoolCheckはあれば便利という関数で、コメント欄の下にあるものは、ダンジョン描画で必要な変数や関数です。
・コンストラクタ
ポインタにNULLをいれておきましょう。この地点ではDirect3Dのデバイスを取得できません。・デストラクタ
確実に終了処理させるために、様々な物を解放するようにしてください。・GameBuild
Sys40からDrawDirectX.Buildを実行した時に呼び出されるものです。引数はSystem4.0の構造体で、任意の型を渡せます。構造体からのデータの取得は、GetDataByNameを用いると便利です。普通はDirectXを用いる処理の開始時に一回だけ呼び出すものです。・GameRun
Sys40からDrawDirectX.Runを実行したときに呼び出されるものです。引数はSystem4.0の構造体で任意の型を渡せます。普通これはゲームの実行中にループないで毎フレーム呼び出す物です。普通引数にキー情報を入れることが多いと思います。・GameClear
Sys40からDrawDirectX.Clearを実行したときに呼び出されるものです。引数はありません。普通は終了時に呼びます、が、DirectXの実行中にSystem4.0を終了した場合には、この関数が呼ばれずにデストラクタが呼ばれます。・GameUpdate
SACT_UPDATEを発行した際に、System4.0によって自動的に呼び出されます。ここでDirectXの描画を行います。BeginSceneとEndSceneの間で呼ばれます。青色Clearも行われています。・GameGetState
GameRunでは足りないデータの送受信を行うための関数です。この関数の引数は文字列です。参照呼び出しになっているため、文字列の変更が有効です。また、整数の戻り値を返すことも可能です。
・GM_StructBoolCheck
これは、上記の構造体と、変数名を渡すと、構造体に変数名のものが存在した場合は、そのデータをbool値で返すというものです。キー情報を簡単に取得するために作成してあります。
結論
問題点は、DirectX9対応のグラフィックボードじゃなければ、非常に遅いこと。この一点に尽きます。