charatbeatHDXでは3Dモデルの描画時に一部のテクスチャを自分で用意した画像に変更することが出来ます。ここではこれを利用して、某筐体の3Dモデルのモニター部分をゲーム画面に置き換えることで、あたかも3Dモデル内でゲームが動作しているような演出を行ってみます。
これにはレンダーテクスチャという機能を使用します。
通常は画像などを描画すると直接画面に描画されますが、まずはこの描画先をレンダーテクスチャという画面には見えない専用のメモリ領域(テクスチャ)に描画します。そして、最後に3Dモデルの一部のマテリアルをこのレンダーテクスチャに置き換えることで、結果的にその部分だけ見た目を変えることが出来ます。
このサンプルでは、ネットでDLしたMMD用のモデルデータを自作ソフトMMDTestを使ってXファイルにコンバートしたものを使用しています。また、この中の一部のテクスチャを変更しています。
※元データにもXファイルが入っていますが、こちらはスケールが1/10になっていたため使用していません
まずはゲーム画面の描画先をレンダーテクスチャに変更します。これによりゲーム画面はそのレンダーテクスチャに描画されるため、画面には何も表示されなくなります。
次にモデルを画面に表示しますが、このモデルは4番目のマテリアルがモニター部分のテクスチャとなっているので、マテリアル4の描画時に先ほど作成したレンダーテクスチャに変更することで、上記のようにモニター部分だけをゲーム画面にすることが出来ます。
マテリアルごとに描画を制御するには
hdxDrawModel2()関数を使用します。この関数の引数にコールバックを指定することで、マテリアルの描画ごとにそのコールバックが呼び出されるので、その中で必要ならテクスチャを変更します。
1点重要な問題として、実はモニター部分は周りのフレームを含んだ状態でモデリングされているため、ゲーム画面をそのまま貼ってしまうと周囲がフレーム部分に隠れてしまいます。このため、実際に表示される領域に合わせてテクスチャ内の画像を縮小してあげなければなりません。
このサンプルではシステムスキンをベースとしているため、ゲーム画面は1280x720となっています。そして上記のフレームを含めた大きさが1280x720だったとすると、実際にゲーム画面が表示される領域はおよそ1000x500(赤い面)となるので、最終的にゲーム画面をこのサイズに縮小したレンダーテクスチャを作成します。
charatbeatHDXの内部ではレンダーテクスチャが2枚存在しており、
hdxBeginRenderTexture()と
hdxEndRenderTexture()を呼び出すごとに切り替わるようになっています。また、現在対象になっていないレンダーテクスチャは以前描画した内容が残っている状態となります。
ここで再び
hdxBeginRenderTexture()を呼び出すと、今度はもう1枚のレンダーテクスチャが描画先となりますが、ここで
hdxDrawRenderTexture2()を呼び出すことで1つ前に描画したレンダーテクスチャから現在のレンダーテクスチャに対して、任意の領域を指定の位置とサイズで描画することが出来ます。
※レンダーテクスチャを開始していない場合は実際の画面に描画されます
これを利用することで、まずはゲーム画面を1280x720で最初のレンダーテクスチャに描画し、次のレンダーテクスチャに以前のレンダーテクスチャの内容を縮小描画することで、最終的に3Dモデルで使用するテクスチャが作成できます。
※最初から1000x500で全てを描画出来ればこのような処理は必要ありませんが、ここでは既存の描画処理を変更したくないためこのようにしています
最初のレンダーテクスチャ |
|
次のレンダーテクスチャ |
|
→ |
|
|
|
※上記の白い部分は見えない部分なので特に何も描画していません |
ゲーム画面の描画はシステムスキンの処理をそのまま利用するため、実装はさほど難しくはありません。
また、ここではゲーム画面のみを変更するため、変更するスクリプトは「_06_game.lua」となります。
※script.luaも改変されていますが、ゲーム画面以外の必要の無いスクリプトのincludeを削除しているだけです
このサンプルではカメラを移動出来るようにカメラ用の変数が定義されています。この変数はグローバル変数として以下のように定義しています。
g_iCamState = 0;
g_iCamMoveCount = 0;
g_fModelRotCount = 0;
また、ゲーム画面に入るごとにこれらの変数を初期化するため、OnStartMainGame()にて同じく初期化します。
function OnStartMainGame()
:
g_iCamState = 0;
g_iCamMoveCount = 0;
g_fModelRotCount = 0;
※localが付いていないためわざわざグローバル変数として定義する必要はありませんが、ここではどの変数を使っているのかを見た目で分かるようにしたいため敢えてグローバル領域にも定義しています
さらに3Dモデルのロードと、使用するレンダーテクスチャのサイズをセットして初期化を終わります。
hdxLoadModel( 0,"model.x",-1 );
hdxSetRenderTextreSize( 1280,720 );
end
次にゲーム画面の描画処理部分を変更します。
まずはゲーム画面の描画を全てレンダーテクスチャに対して描画するように設定します。
function OnRunGame( upd_frm )
:
hdxBeginRenderTexture();
hdxSetFilterMode( FILTER_LINEAR );
:
if( g_dwMainState==MNST_FINISH ) then
hdxDrawAnime( 2, 640, 360, 1.0, 1.0 );
end
hdxEndRenderTexture();
描画処理を
hdxBeginRenderTexture()と
hdxEndRenderTexture()で囲むことで、本来画面に描画されるものが全てレンダーテクスチャに描画されます。また、初期化時にレンダーテクスチャを1280x720に設定しているので、レンダーテクスチャいっぱいにゲーム画面が描画されたことになります。
次にもう1枚のレンダーテクスチャに対して、上記で描画した画像を1000x500に縮小して描画します。
hdxBeginRenderTexture()
hdxSetFilterMode( FILTER_LINEAR );
hdxDrawRenderTexture2( 139,105,1000,500, 0,0,1280,720, 1 );
hdxSetFilterMode( FILTER_POINT );
hdxEndRenderTexture();
再び
hdxBeginRenderTexture()を呼び出すことで、もう1枚のレンダーテクスチャを描画先に設定し、ここで
hdxDrawRenderTexture2()を使って縮小表示しています。描画が終わったら
hdxEndRenderTexture()を呼び出してレンダーテクスチャを終わらせています。
最後に3Dモデルを描画しますが、このサンプルでは上下左右キーでカメラを移動させることが出来るようになっています。
まずモデルを表示するための初期設定を行います。
hdxSetBlendMode( BLEND_NORMAL );
hdxSetZBuffer( TRUE );
hdxClearZBuffer();
hdxSetLightDirection( 0,1,1, 0,0,0 );
hdxSetLightAmbient( 0.5,0.5,0.5 );
hdxSetLight( TRUE );
hdxSetViewport( 0,0,1280,720 );
3Dモデルは陰面消去が必要となるため、始めに
hdxSetZBuffer()を使ってZバッファを有効にし、次に
hdxClearZBuffer()を使ってZバッファをクリアしています。
本来はZバッファへの描画をしていなければ特にクリアする必要はありませんが、2Dアニメでマスクを使用している場合は内部でZバッファが使用されるため、ここでは明示的にZバッファのクリアを行っています。
あとは平行光源を使用するためライトの位置や向き、環境光などをセットしたり、全画面への描画を明示的に指定するため
hdxSetViewport()を呼び出しています。
次にカメラの制御を行うため、グローバル変数として定義したカメラ用変数を使ってキーの入力を制御します。
if( upd_frm==TRUE ) then
if( g_iCamState==0 ) then
elseif( g_iCamState==1 ) then
g_fModelRotCount = g_fModelRotCount + 0.5;
g_iCamMoveCount = g_iCamMoveCount + 1;
if( g_iCamMoveCount>=60 ) then
g_iCamState = 2;
end
elseif( g_iCamState==2 ) then
local key = hdxSysGetInput();
if( key["is_press_up"]==1 ) then
g_iCamMoveCount = g_iCamMoveCount - 1;
if( g_iCamMoveCount<0 ) then
g_iCamMoveCount = 0;
end
elseif( key["is_press_down"]==1 ) then
g_iCamMoveCount = g_iCamMoveCount + 1;
if( g_iCamMoveCount>300 ) then
g_iCamMoveCount = 300;
end
end
if( key["is_press_left"]==1 ) then
g_fModelRotCount = g_fModelRotCount + 1;
if( g_fModelRotCount>=360 ) then
g_fModelRotCount = 0;
end
elseif( key["is_press_right"]==1 ) then
g_fModelRotCount = g_fModelRotCount - 1;
if( g_fModelRotCount<=0 ) then
g_fModelRotCount = 360;
end
end
end
end
このサンプルでは、
ゲームが開始した直後、
ゲームがスタートした状態、
ユーザーがキーで操作可能な状態を把握するため、
g_iCamStateの値を見て処理を変えています。ちなみにカメラが移動するのはゲームが開始した直後からのため、ゲームが開始されたら
g_iCamStateを1にする制御が計算処理部分に追加されています。
:
if( hdxGetTime()>=g_dwWaitTitleTime ) then
hdxStartGame();
g_iCamState = 1;
g_dwMainState = MNST_GAME;
end
カメラの移動は回転と移動カウントを変化させているだけで、このあたりは適当な処理になっています。
上下左右キーの入力状態は
hdxSysGetInput()から取得出来ます。この関数は本来はキーの押下状態を表示するために使用するものですが、今回の用途ではゲームの進行には影響しないため、裏技としてこれをキー入力処理に使用しています。あとは押されたキーによりカメラの距離を変化させたり、モデルの回転角度を変化させています。
なおVSYNCを切っている場合、フレームレートが60以上になると
OnRunGame()もそれに合わせて呼ばれてしまいキーの処理も早くなってしまうため、カメラの移動やモデルの回転が高速に動いてしまいます。これを防ぐため60fps間隔で1回だけTRUEとなる引数
upd_frmを見て、TRUEの時だけキー入力処理を行うようにしています。
次にカメラの位置から実際にモデルを画面に表示する処理を行います。
hdxMatrixTranslation( 0, 0, 0, 4.5 );
hdxMatrixRotationY( 1,hdxToRadian(g_fModelRotCount) );
hdxMatrixMultiply2( 0,1 );
hdxSetLookAt( 0, 19.02+g_iCamMoveCount/10, g_iCamMoveCount/4+6.4,
0, 19.02-g_iCamMoveCount/10, 0,
0, 1, 0 );
hdxSetFilterMode( FILTER_LINEAR );
hdxDrawModel2( 0,0,"_DrawModelCallback" );
hdxSetFilterMode( FILTER_POINT );
hdxSetLight( FALSE );
hdxSetZBuffer( FALSE );
ここではモデルのモニター部分が原点からZ方向に-4.5の位置にあるため、これを原点に戻すために
hdxMatrixTranslation()にてZ方向を+4.5移動するための行列をマトリクスバッファ0にセットしています。
次にモデルをY軸方向(縦)に対して回転するため、Y軸の回転行列を
hdxMatrixRotationY()にてマトリクスバッファ1にセットしています。
そして移動してから回転をするため
hdxMatrixMultiply2()を使って行列を掛け算し、結果を再びマトリクスバッファ0にセットしています。
※ここでは
mat[0] = mat[0] * mat[1]の計算を行っています
これで3Dモデルをワールド原点に置いた場合のモニター部分の中心が
(0.0, 19.02, 0.0)となるので、次にこの場所を見るように
hdxSetLookAt()を使ってカメラを設定します。
全ての設定が終わったら実際にモデルを表示するため
hdxDrawModel2()を呼び出します。この時コールバックとして
_DrawModelCallback()関数を指定しており、これで
hdxDrawModel2()内でマテリアルごとに
_DrawModelCallback()が呼び出されるようになります。
コールバックは以下のように定義しています。
function _DrawModelCallback( id )
if( id==4 ) then
hdxSetTexture( 1 );
return TRUE;
end
return FALSE;
end
この3Dモデルのモニター部分のマテリアルは4なので、4の時だけ
hdxSetTexture()にてレンダーテクスチャを指定してTRUEを返し、それ以外はFALSEを返すことでモデル自体のマテリアルで描画するように制御しています。
最後に全ての描画が終わったら念のためライトとZバッファをOFFにして終了です。