ゲームフレームワーク的なものを作る。(8)〜描画エンジンの抽象化〜
描画エンジンの抽象化
windowsでゲームを作る場合、「Direct3D」か「OpenGL」を選択することになりますが、どちらも使ったことがある人であれば、誰もが一度はインターフェイスを用意して両方扱えるようにしたいと思うはず。
で、今回は上記のような単純なインターフェイスと実装クラスを作るのではなく、「ウィンドウ」と「描画機能」を組み合わせた「Viewクラス」を作ります。
WindowクラスとIGraphicsインターフェイスを継承したView抽象クラス
とりあえずWindowクラスは既存のものを使い、新たにIGraphicsインターフェイスを追加します。
class ITexture; typedef std::shared_ptr< ITexture > TexturePtr; /** * スプライト描画機能 */ class ISprite { public: /** * スプライト描画開始。以降、AddSpriteにより内部バッファにスプライトを追加。 */ virtual void BeginSprite() = 0; /** * スプライト描画終了。内部のバッファに溜めてあるスプライトリストをバックバッファへ出力。 */ virtual void EndSprite() = 0; /** * バッファにスプライトを追加。 */ virtual void AddSprite( const TexturePtr& texture, const Rect& rect, const Color& color ) = 0; }; /** * 行列スタック制御機能 */ class IMatrixStack { public: /** * スタックトップをコピーしてpush */ virtual void Push() = 0; /** * スタックトップをpop */ virtual void Pop() = 0; /** * Ms * top */ virtual void Scale( float x, float y, float z ) = 0; /** * Mr * top */ virtual void Rotate( float x, float y, float z ) = 0; /** * Mt * top */ virtual void Translate( float x, float y, float z ) = 0; }; /** * IGraphicsインターフェイス */ class IGraphics : public ISprite //スプライト描画機能 , public IMatrixStack //行列スタック制御機能 { public: /** * シーンのクリア */ virtual void Clear() = 0; /** * シーンのレンダリング開始 */ virtual bool BeginScene() = 0; /** * シーンのレンダリング終了 */ virtual void EndScene() = 0; /** * 画面の更新 */ virtual void Update() = 0; public: /** * 文字列の描画 */ virtual void DrawString( const Point& p, const String& str, const Color& color ) = 0; /** * 画像ファイルからテクスチャを生成 */ virtual TexturePtr CreateTexture( const String& filename ) = 0; };
次に、上記のWindowクラスとIGraphicsインターフェイスを組み合わせたViewクラスを作ります。
Viewは抽象クラスで、IGraphicsインタフェイスの実装は派生クラスのDirect3DViewとOpenGLViewで行います。
#include "Window.h" #include "graphics/IGraphics.h" /** * Windowと描画機能を組み合わせたView抽象クラス */ class View : public Window , public IGraphics { public: View( const String& title, const Size& client_size ) : Window( title, client_size ) {} virtual ~View(){} };
使い方
main関数、Gameクラスの記述が多少変わります。
int main() { Application::Initialize(); try { //ゲーム用ウィンドウの生成と描画エンジンの初期化 Direct3DView view( "hoge", Size( 720, 480 ) ); //OpenGLView view( "hoge", Size( 720, 480 ) ); //ゲームを初期化して、ゲームループを開始する Game game( &view ); //ゲームループ // ウィンドウが閉じられるまで1フレームの処理を繰り返す while( !view.IsClosed() ) { //1フレームの処理 game.Run(); //メッセージキューにある全てのwindowsイベントを処理する Application::DoEvents(); } } catch( std::exception& e ) { std::cout << e.what() << std::endl; } catch( ... ) { std::cout << "予期せぬ例外" << std::endl; } Application::Finalize(); return 0; }
/** * ゲーム1フレームの処理を行う */ void Game::Run() { //シーンの実行 ; //バックバッファをクリアし、シーンのレンダリングを開始する //レンダリング終了後、画面を更新する view->Clear(); if( view->BeginScene() ) { view->BeginSprite(); //描画 view->Push(); { view->Translate( x, y, 0.0f ); view->AddSprite( texture, rect, color ); } view->Pop(); view->EndSprite(); view->EndScene(); } view->Update(); }
ダウンロード
- framework09(View).zip
- VC++2010 ExpressEdition、DirectX SDK(February 2010)で動作確認
- 「はちゅねミク」の画像は、三次元CG@七葉(http://nanoha.kirara.st/3dcg/)からお借りしたモデルを使用しています。
次回予定
ViewをOpenGLで実装したOpenGLViewクラスを作る予定です。
ゲームフレームワーク的なものを作る。(7)〜Textureインターフェイスとスプライト描画メソッド〜
前回「ゲームフレームワーク的なものを作る。(6)〜スプライト描画〜 - while( c++ );」のサンプルでは、Direct3DのIDirect3DTexture9インターフェイスとID3DXSpriteインターフェイスを直接使って描画を行っていましたが、フレームワーク利用者に対してDirectXなどの具体的な描画エンジンは隠蔽したいですし、今後はOpenGLもサポートしていく予定なので、まずはテクスチャインターフェイスを導入します。
Textureインターフェイス
とりあえず仮想デストラクタとテクスチャサイズを返すメソッドを持たせます。
/** * テクスチャインターフェイス */ class ITexture : public IStringizable //ストリームへ出力可能 { public: /** * デストラクタ */ virtual ~ITexture(){} public: /** * サイズを取得 */ virtual Size GetSize()const = 0; };
Direct3DによるTextureクラスの実装
Graphicsクラス側で生成したテクスチャをコンストラクタで渡して初期化しています。
/** * テクスチャクラス(Direct3Dによる実装) */ class Texture : public ITexture { // ------------------------------------------------------------------------------- // 生成と破棄 // ------------------------------------------------------------------------------- public: /** * コンストラクタ */ Texture( IDirect3DTexture9* instance, const std::string& filename ); /** * デストラクタ */ ~Texture(); // ------------------------------------------------------------------------------- // ITexture実装 // ------------------------------------------------------------------------------- public: Size GetSize()const{ return size; } // ------------------------------------------------------------------------------- // 基本機能 // ------------------------------------------------------------------------------- public: /** * IDirect3DTexture9の実体を取得 */ IDirect3DTexture9* GetInstance()const{ return instance; } /** * ファイル名を取得 */ std::string GetFileName()const{ return filename; } private: IDirect3DTexture9* instance; ///< IDirect3DTexture9の実体 std::string filename; ///< ファイル名 Size size; ///< テクスチャサイズ // ------------------------------------------------------------------------------- // IStringizable実装 // ------------------------------------------------------------------------------- private: /** * 文字列への変換 */ std::string to_string()const; // ------------------------------------------------------------------------------- // コピー禁止 // ------------------------------------------------------------------------------- private: Texture( const Texture& ); Texture& operator=( const Texture& ); };
/** * コンストラクタ * * @param instance 生成済みのテクスチャ * @param filename ファイル名 */ Texture::Texture( IDirect3DTexture9* instance, const std::string& filename ) : instance( instance ) , filename( filename ) { //テクスチャサイズ取得 D3DSURFACE_DESC desc; instance->GetLevelDesc( 0, &desc ); size.width = desc.Width; size.height = desc.Height; } /** * デストラクタ */ Texture::~Texture() { instance->Release(); } /** * 文字列への変換。とりあえずファイル名とサイズを出力 */ std::string Texture::to_string()const { std::stringstream ss; ss << "[ " << filename << " ]" << size; return ss.str(); }
Graphicsクラスにスプライト描画メソッドを追加する
画像ファイルからstd::shared_ptr
- CreateTexture(ファイル名)
- std::shared_ptr
を返す
- std::shared_ptr
- BeginSprite
- スプライト描画開始。以降、AddSprite()で内部のバッファにスプライトを追加しEndSprite()で一気にまとめて描画する。
- AddSprite
- 内部バッファにスプライトを追加する。
- Textureインターフェイス(std::shared_ptr)、Rect、Color、MatrixStackを指定。
- EndSprite
- バッファ内のスプライトをまとめて描画。
typedef std::shared_ptr< ITexture > TexturePtr; /** * Graphicsクラス */ class Graphics { /*略*/ private: /** * スプライト描画オブジェクトの生成 */ ID3DXSprite* CreateSprite(); public: /** * 画像ファイルからテクスチャを生成 */ TexturePtr CreateTexture( const std::string& filename ); // ------------------------------------------------------------------------------- // スプライト描画機能 // ------------------------------------------------------------------------------- public: /** * スプライト描画開始。以降、AddSpriteにより内部バッファにスプライトを追加。 */ void BeginSprite(); /** * スプライト描画終了。内部のバッファに溜めてあるスプライトリストをバックバッファへ出力。 */ void EndSprite(); /** * バッファにスプライトを追加。 */ void AddSprite( const TexturePtr& texture, const Rect& rect, const Color& color, const MatrixStackPtr& matrix_stack ); private: std::shared_ptr< ID3DXSprite > sprite; ///< スプライト描画担当 };
AddSprite()はTextureインターフェイスのshared_ptrでテクスチャを受け取るので、内部の描画時にstd::static_pointer_cast
/** * 画像ファイルからテクスチャを生成 * * @param filename ファイル名 * @return 生成したテクスチャを指すスマートポインタ */ TexturePtr Graphics::CreateTexture( const std::string& filename ) { IDirect3DTexture9* texture = nullptr; HRESULT hr = D3DXCreateTextureFromFile( device.get(), filename.c_str(), &texture ); if( FAILED( hr ) )throw std::runtime_error( "D3DXCreateTextureFromFile[" + filename + "]" ); return TexturePtr( new Texture( texture, filename ) ); } /** * スプライト描画オブジェクトの生成 * * @return スプライト描画オブジェクトを指すスマートポインタ */ ID3DXSprite* Graphics::CreateSprite() { ID3DXSprite* sprite = nullptr; HRESULT hr = D3DXCreateSprite( device.get(), &sprite ); if( FAILED( hr ) )throw std::runtime_error( "D3DXCreateSprite" ); return sprite; } /** * スプライト描画開始。以降、AddSpriteにより内部バッファにスプライトを追加。 */ void Graphics::BeginSprite() { sprite->Begin( D3DXSPRITE_ALPHABLEND ); } /** * スプライト描画終了。内部のバッファに溜めてあるスプライトリストをバックバッファへ出力。 */ void Graphics::EndSprite() { sprite->End(); } /** * バッファにスプライトを追加。 * * @param texture テクスチャ * @param rect 描画元矩形 * @param color 合成する色 * @param matrix_stack 行列スタック */ void Graphics::AddSprite( const TexturePtr& texture, const Rect& rect, const Color& color, const MatrixStackPtr& matrix_stack ) { RECT r = { rect.left, rect.top, rect.right, rect.bottom }; //デフォルトで矩形の中心を回転の中心とする D3DXVECTOR3 center( rect.Width() / 2.0f, rect.Height() / 2.0f, 0.0f ); sprite->SetTransform( matrix_stack->GetTop() ); sprite->Draw( std::static_pointer_cast< Texture >( texture )->GetInstance(), &r, ¢er, nullptr, color.to_A8R8G8B8() ); }
使い方
graphics.Clear(); if( graphics.BeginScene() ) { graphics.BeginSprite(); //背景の描画 matrix_stack->Push(); { matrix_stack->TranslateLocal( 360.0f, 240.0f, 0.0f ); graphics.AddSprite( texture, Rect( 0, 0, 720, 480 ), Color( 1.0f, 1.0f, 1.0f, 1.0f ), matrix_stack ); } matrix_stack->Pop(); //ミクの描画 // マウス位置に描画 matrix_stack->Push(); { matrix_stack->TranslateLocal( static_cast< float >( mouse_pos.x ), static_cast< float >( mouse_pos.y ), 0.0f ); matrix_stack->RotateYawPitchRollLocal( 0.0f, 0.0f, rotate ); matrix_stack->ScaleLocal( 1.0f, 1.0f, 1.0f ); //ネギの描画 // ミクを中心に自転しながら公転する matrix_stack->Push(); { matrix_stack->RotateYawPitchRollLocal( 0.0f, 0.0f, rotate ); matrix_stack->TranslateLocal( 100.0f, 0.0f, 0.0f ); matrix_stack->RotateYawPitchRollLocal( 0.0f, 0.0f, rotate ); matrix_stack->ScaleLocal( 1.0f, 1.0f, 1.0f ); graphics.AddSprite( texture2, Rect( 128, 0, 256, 128 ), Color( 1.0f, 1.0f, 1.0f, 1.0f ), matrix_stack ); } matrix_stack->Pop(); graphics.AddSprite( texture2, Rect( 0, 0, 128, 128 ), Color( 1.0f, 1.0f, 1.0f, 1.0f ), matrix_stack ); } matrix_stack->Pop(); graphics.EndSprite(); //debug std::stringstream ss; ss << "texture:" << texture << std::endl << "texture2:" << texture2 << std::endl ; graphics.DrawString( Point( 0, 0 ), ss.str(), Color( 1.0f, 1.0f, 1.0f, 1.0f ) ); graphics.EndScene(); } graphics.Update();
ダウンロード
- framework08(ITexture).zip
- VC++2010 ExpressEdition、DirectX SDK(February 2010)で動作確認
- 「はちゅねミク」の画像は、三次元CG@七葉(http://nanoha.kirara.st/3dcg/)からお借りしたモデルを使用しています。
Graphicsクラスに行列スタック制御メソッドを追加する
行列スタック用インターフェイスを作るか、Graphicsの機能として行列スタックを制御するか迷ったのですが、とりあえずGraphicsクラスにメソッドを追加しておきます。
- Push()
- topをコピーしてpush
- Pop()
- pop
- Scale( x, y, z )
- topに左からスケーリング行列を掛ける
- Rotate( x, y, z )
- topに左から回転行列を掛ける
- Translate( x, y, z )
- topに左から平行移動行列を掛ける
/** * Graphicsクラス */ class Graphics { /*略*/ // ------------------------------------------------------------------------------- // 行列スタック制御機能 // ------------------------------------------------------------------------------- public: /** * topをコピーしてpush */ void Push(); /** * pop */ void Pop(); /** * Ms * top */ void Scale( float x, float y, float z ); /** * Mr * top */ void Rotate( float x, float y, float z ); /** * Mt * top */ void Translate( float x, float y, float z ); private: std::shared_ptr< ID3DXMatrixStack > matrix_stack; ///< 行列スタック制御 };
/** * スタックトップをコピーしてpush */ void Graphics::Push() { matrix_stack->Push(); } /** * スタックトップをpop */ void Graphics::Pop() { matrix_stack->Pop(); } /** * Ms * top */ void Graphics::Scale( float x, float y, float z ) { matrix_stack->ScaleLocal( x, y, z ); } /** * Mr * top */ void Graphics::Rotate( float x, float y, float z ) { matrix_stack->RotateYawPitchRollLocal( x, y, z ); } /** * Mt * top */ void Graphics::Translate( float x, float y, float z ) { matrix_stack->TranslateLocal( x, y, z ); }
使い方
ちょっとGraphicsクラスに機能が集中しているような気もしますが。
graphics.Clear(); if( graphics.BeginScene() ) { //シーンの描画 ; graphics.BeginSprite(); //背景の描画 graphics.Push(); { graphics.Translate( 360.0f, 240.0f, 0.0f ); graphics.AddSprite( texture, Rect( 0, 0, 720, 480 ), Color( 1.0f, 1.0f, 1.0f, 1.0f ) ); } graphics.Pop(); //ミクの描画 // マウス位置に描画 graphics.Push(); { graphics.Translate( static_cast< float >( mouse_pos.x ), static_cast< float >( mouse_pos.y ), 0.0f ); graphics.Rotate( 0.0f, 0.0f, rotate ); graphics.Scale( 1.0f, 1.0f, 1.0f ); //ネギの描画 // ミクを中心に自転しながら公転する graphics.Push(); { graphics.Rotate( 0.0f, 0.0f, rotate ); graphics.Translate( 100.0f, 0.0f, 0.0f ); graphics.Rotate( 0.0f, 0.0f, rotate ); graphics.Scale( 1.0f, 1.0f, 1.0f ); graphics.AddSprite( texture2, Rect( 128, 0, 256, 128 ), Color( 1.0f, 1.0f, 1.0f, 1.0f ) ); } graphics.Pop(); graphics.AddSprite( texture2, Rect( 0, 0, 128, 128 ), Color( 1.0f, 1.0f, 1.0f, 1.0f ) ); } graphics.Pop(); graphics.EndSprite(); //debug std::stringstream ss; ss << "texture:" << texture << std::endl << "texture2:" << texture2 << std::endl ; graphics.DrawString( Point( 0, 0 ), ss.str(), Color( 1.0f, 1.0f, 1.0f, 1.0f ) ); graphics.EndScene(); } graphics.Update();
ダウンロード
- framework08(MatrixStack).zip
- VC++2010 ExpressEdition、DirectX SDK(February 2010)で動作確認
- 「はちゅねミク」の画像は、三次元CG@七葉(http://nanoha.kirara.st/3dcg/)からお借りしたモデルを使用しています。
ゲームフレームワーク的なものを作る。(6)〜スプライト描画〜
今回はDirect3D側で用意されているID3DXSpriteインターフェイスを使って、画像ファイルからテクスチャを生成し、画像の描画を行います。
スプライト描画オブジェクトの生成
Graphicsクラスにスプライト描画オブジェクト生成メソッドを追加します。前述のデバイスやフォントと同様、std::shared_ptr
typedef std::shared_ptr< ID3DXSprite > SpritePtr; class Graphics { /*略*/ public: /** * スプライト描画オブジェクトの生成 */ SpritePtr CreateSprite(); };
/** * スプライト描画オブジェクトの生成 * * @return スプライト描画オブジェクトを指すスマートポインタ */ SpritePtr Graphics::CreateSprite() { ID3DXSprite* sprite = nullptr; HRESULT hr = D3DXCreateSprite( device.get(), &sprite ); if( FAILED( hr ) )throw std::runtime_error( "D3DXCreateSprite" ); return SpritePtr( sprite, com_deleter() ); }
テクスチャの生成
スプライト描画オブジェクト同様、Graphicsクラスにテクスチャ生成メソッドを追加します。
typedef std::shared_ptr< ID3DXSprite > SpritePtr; class Graphics { /*略*/ public: /** * 画像ファイルからテクスチャを生成 */ TexturePtr CreateTexture( const std::string& filename );
/** * 画像ファイルからテクスチャを生成 * * @param filename ファイル名 * @return 生成したテクスチャを指すスマートポインタ */ TexturePtr Graphics::CreateTexture( const std::string& filename ) { IDirect3DTexture9* texture = nullptr; HRESULT hr = D3DXCreateTextureFromFile( device.get(), filename.c_str(), &texture ); if( FAILED( hr ) )throw std::runtime_error( "D3DXCreateTextureFromFile[" + filename + "]" ); return TexturePtr( texture, com_deleter() ); }
使い方
まずはGameクラスのメンバに持たせて、ID3DXSpriteインターフェイスの機能を直接使ってみましょう。
/** * Gameクラス */ class Game { /*略*/ private: Graphics graphics; SpritePtr sprite; TexturePtr texture; };
Game::Game( const WindowWeakPtr& window ) : graphics( window ) , sprite( graphics.CreateSprite() ) , texture( graphics.CreateTexture( "image\\bg.bmp" ) ) //画像ファイル名を指定 { /*略*/ }
Beginメソッド〜Endメソッド内でDrawメソッドを呼び出すことで、複数のスプライトを描画することができます。(レンダーステートを変更しない描画物は、1度のBegin〜Endで一気に描画した方が効率が良い)
void Game::Run() { //シーンの実行 ; //バックバッファをクリアし、シーンのレンダリングを開始する //レンダリング終了後、画面を更新する graphics.Clear(); if( graphics.BeginScene() ) { //シーンの描画 ; //Begin〜End内で描画処理を記述 sprite->Begin( 0 ); { RECT rect = { 0, 0, 720, 480 }; //描画元画像の矩形を指定 D3DXVECTOR3 pos( 0.0f, 0.0f, 0.0f ); //描画先座標を指定 sprite->Draw( texture.get(), &rect, nullptr, &pos, 0xffffffff ); } //以下同様にDrawメソッドを呼び出す。 { /*略*/ } sprite->End(); sprite->Begin( 0 ); /*略*/ sprite->End(); graphics.EndScene(); } graphics.Update(); }
行列(スケーリング、回転、平行移動)を利用する。
ID3DXSpriteインターフェイスには、いわゆるアフィン変換(スケーリング、回転、平行移動など)を行うためのSetTransformメソッドが用意されています。
行列の設定には
- D3DXMATRIXクラス
- D3DXMatrixScaling
- D3DXMatrixRotationX/Y/Z/YawPitchRoll/Axis
- D3DXMatrixTranslation
- D3DXMatrixMultiply
- D3DXMatrixIdentity
などを使用します。
行列を使用する際、Drawメソッドには描画座標指定せず、矩形と回転の中心座標のみ指定した方が使いやすいです。
sprite->Begin( D3DXSPRITE_ALPHABLEND ); { RECT rect = { 0, 0, 128, 128 }; //描画元画像の矩形を指定 D3DXVECTOR3 center( 64.0f, 64.0f, 0.0f ); //矩形の左上を基準とした回転の中心座標を指定 D3DXMATRIX mat, m; D3DXMatrixIdentity( &mat ); D3DXMatrixScaling( &m, 1.0f, 1.0f, 1.0f ); D3DXMatrixMultiply( &mat, &mat, &m ); D3DXMatrixRotationZ( &m, D3DXToRadian( 30.0f ) ); D3DXMatrixMultiply( &mat, &mat, &m ); D3DXMatrixTranslation( &m, 360.0f, 240.0f, 0.0f ); D3DXMatrixMultiply( &mat, &mat, &m ); sprite->SetTransform( &mat ); sprite->Draw( texture2.get(), &rect, ¢er, nullptr, 0xffffffff ); } sprite->End();
DDSファイルを使用する。
上記の例ではαチャンネル付き画像としてDDSファイルを使用しています。αチャンネル付き画像はPhotoshop、GIMPなどのソフトやDirectXSDK付属のTextureToolで作成可能です。
作り方はgoogle先生に聞いてください。「DDSファイル」
行列スタック(ID3DXMatrixStackインターフェイス)を使用する。
関節などの階層構造を持つオブジェクトの描画には、行列スタックを使うと便利です。Direct3Dには行列スタックを扱うためのID3DXMatrixStackインターフェイスが用意されています。
typedef std::shared_ptr< ID3DXMatrixStack > MatrixStackPtr; class Graphics { /*略*/ public: /** * 行列スタックオブジェクトの生成 */ MatrixStackPtr CreateMatrixStack(); };
/** * 行列スタックオブジェクトの生成 * * @return 行列スタックオブジェクトを指すスマートポインタ */ MatrixStackPtr Graphics::CreateMatrixStack() { ID3DXMatrixStack* matrix_stack = nullptr; HRESULT hr = D3DXCreateMatrixStack( 0, &matrix_stack ); if( FAILED( hr ) )throw std::runtime_error( "D3DXCreateMatrixStack" ); return MatrixStackPtr( matrix_stack, com_deleter() ); }
一例として、マウス座標で回転するオブジェクト(はちゅねミク)のまわりを自転しながら公転するオブジェクト(ネギ)を描画してみます。
class Game { /*略*/ private: Graphics graphics; ///< 描画機能 SpritePtr sprite; ///< MatrixStackPtr matrix_stack; ///< TexturePtr texture; TexturePtr texture2; float rotate; };
Game::Game( const WindowWeakPtr& window ) : window( window ) , graphics( window ) , sprite( graphics.CreateSprite() ) , matrix_stack( graphics.CreateMatrixStack() ) , texture( graphics.CreateTexture( "image\\bg.bmp" ) ) //画像ファイル名を指定 , texture2( graphics.CreateTexture( "image\\miku.dds" ) ) //画像ファイル名を指定 , rotate( 0.0f ) { /*略*/ }
rotate += D3DXToRadian( 1.0f ); //ミクの描画 // マウス位置に描画 matrix_stack->LoadIdentity(); { RECT rect = { 0, 0, 128, 128 }; D3DXVECTOR3 center( 64.0f, 64.0f, 0.0f ); matrix_stack->TranslateLocal( mouse_pos.x, mouse_pos.y, 0.0f ); matrix_stack->RotateYawPitchRollLocal( 0.0f, 0.0f, rotate ); //自転角 matrix_stack->ScaleLocal( 1.0f, 1.0f, 1.0f ); //ネギの描画 // ミクを中心に自転しながら公転する matrix_stack->Push(); { RECT rect = { 128, 0, 256, 128 }; D3DXVECTOR3 center( 64.0f, 64.0f, 0.0f ); matrix_stack->RotateYawPitchRollLocal( 0.0f, 0.0f, rotate ); //公転角 matrix_stack->TranslateLocal( 100.0f, 0.0f, 0.0f ); //公転半径 matrix_stack->RotateYawPitchRollLocal( 0.0f, 0.0f, rotate ); //自転角 matrix_stack->ScaleLocal( 1.0f, 1.0f, 1.0f ); sprite->SetTransform( matrix_stack->GetTop() ); sprite->Draw( texture2.get(), &rect, ¢er, nullptr, 0xffffffff ); } matrix_stack->Pop(); sprite->SetTransform( matrix_stack->GetTop() ); sprite->Draw( texture2.get(), &rect, ¢er, nullptr, 0xffffffff ); }
download
- framework07.zip
- VC++2010 ExpressEdition、DirectX SDK(February 2010)で動作確認
- 「はちゅねミク」の画像は、三次元CG@七葉(http://nanoha.kirara.st/3dcg/)からお借りしたモデルを使用しています。
今回はDirectXの機能を直接利用してスプライト描画を行いましたが、DirectXに依存したプログラムは出来るだけ書きたくないので、前回作成したRectクラス、Colorクラスの利用、また今後の3D描画のためのベクトルクラス、行列クラスの作成なども行っていきたいと思います。
ゲームフレームワーク的なものを作る。(5)〜Point、Size、Rect、Color〜
今回はグラフィックス系プログラムでよく使われる点、サイズ、矩形、色クラスを作ります。
とりあえず、基本的な機能のみ。
Pointクラス
スクリーン上の点を表す。
class Point { public: Point( int x = 0, int y = 0 ); public: int x, y; }; Point::Point( int x, int y ) : x( x ), y( y ) { }
Sizeクラス
スクリーン上の矩形の縦横サイズを表す。
class Size { public: Size( int width = 0, int height = 0 ); public: int width, height; }; Size::Size( int width, int height ) : width( width ), height( height ) { }
Rectクラス
スクリーン上の矩形を表す。
class Rect { public: /** * コンストラクタ。左上と右下の点を指定 */ Rect( int left = 0, int top = 0, int right = 0, int bottom = 0 ); /** * コンストラクタ。左上と右下の点を指定 */ Rect( const Point& left_top, const Point& right_bottom ); /** * コンストラクタ。左上の点とサイズを指定 */ Rect( const Point& left_top, const Size& size ); public: //矩形の幅を取得 int Width()const{ return right - left; } //矩形の高さを取得 int Height()const{ return bottom - top; } //矩形のSizeを取得 Size GetSize()const{ return Size( Width(), Height() ); } //矩形の左上の点と右下の点にoffsetを加算 void Offset( const Point& offset ); public: int left, top, right, bottom; ///< 左上と右下の点 }; Rect::Rect( int left, int top, int right, int bottom ) : left( left ), top( top ) , right( right ), bottom( bottom ) { } Rect::Rect( const Point& left_top, const Point& right_bottom ) : left( left_top.x ), top( left_top.y ) , right( right_bottom.x ), bottom( right_bottom.y ) { } Rect::Rect( const Point& left_top, const Size& size ) : left( left_top.x ), top( left_top.y ) , right( left_top.x + size.width ), bottom( left_top.y + size.height ) { } void Rect::Offset( const Point& offset ) { left += offset.x; top += offset.y; right += offset.x; bottom += offset.y; }
Colorクラス
0〜1の成分(r,g,b,a)の色を表す。
class Color { public: Color( float r = 0.0f, float g = 0.0f, float b = 0.0f, float a = 0.0f ); public: DWORD to_A8R8G8B8()const { DWORD a_ = static_cast< DWORD >( a * 255 ) << 24; DWORD r_ = static_cast< DWORD >( r * 255 ) << 16; DWORD g_ = static_cast< DWORD >( g * 255 ) << 8; DWORD b_ = static_cast< DWORD >( b * 255 ) << 0; return a_ | r_ | g_ | b_; } public: float r, g, b, a; }; Color::Color( float r, float g, float b, float a ) : r( r ), g( g ), b( b ), a( a ) { }
文字列化インターフェイス
ゲームのデバッグでは、リアルタイムに変化するパラメータを常に画面上に表示することが多いと思いますが、例えばRectのメンバを表示するためには、以下のように非常に面倒な記述が必要になります。
Rect rect( 1, 2, 3, 4 ); std::stringstream ss; ss << "rect:" << "( " <<rect.left << ", " << rect.top << ", " << rect.right << ", " << rect.bottom << " )" << std::endl; graphics.DrawString( Point( 0, 0 ), ss.str(), Color( 1.0f, 1.0f, 1.0f, 1.0f ) );
以下のように、ストリームに直接渡せるようになると便利ですよね。
Rect rect( 1, 2, 3, 4 ); std::stringstream ss; ss << "rect:" << rect << std::endl; //"rect:( 1, 2, 3, 4 )" graphics.DrawString( Point( 0, 0 ), ss.str(), Color( 1.0f, 1.0f, 1.0f, 1.0f ) );
ということで、文字列化インターフェイスの作成と、operator<<のオーバーロードを行いましょう。
class IStringizable { /** * 文字列への変換 */ virtual std::string to_string()const = 0; /** * ostreamへの挿入 */ friend inline std::ostream& operator<<( std::ostream& os, const IStringizable& s ) { os << s.to_string(); return os; } };
使い方
デバッグ時にストリームに対して出力したいクラスにIStringizableインターフェイスを実装します。
class Rect : public IStringizable { /*略*/ private: /** * 文字列への変換 */ std::string to_string()const; }; /** * 文字列への変換 */ std::string Rect::to_string()const { std::stringstream ss; ss << "( " << left << ", " << top << ", " << right << ", " << bottom << " )"; return ss.str(); }
Point point( 1, 2 ); Size size( 1, 2 ); Rect rect( 1, 2, 3, 4 ); Color color( 1.0f, 1.0f, 1.0f, 1.0f ); std::stringstream ss; ss << "point:" << point << std::endl << "size:" << size << std::endl << "rect:" << rect << std::endl << "color:" << color << std::endl ; graphics.DrawString( Point( 0, 0 ), ss.str(), Color( 1.0f, 1.0f, 1.0f, 1.0f ) );
ダウンロード
- framework06.zip
- VC++2010 ExpressEdition、DirectX SDK(February 2010)で動作確認
stringizableの発音がわかりません><
explicit(06/21追記)
コメントでご指摘頂いた通り、コンストラクタにexplicitを付けなければ、暗黙の変換によって意図しない変換が行われてしまいます。
graphics.DrawString( 1, //Point( 1, 0 )が渡される。 ss.str(), Color( 1.0f, 1.0f, 1.0f, 1.0f ) );
あるいは、
Point point( 1, 2 ); point = 1; //explicitがなければ、これもOK。意味がわからないですね。
非常にまずいですね。ということで、コンストラクタに引数が1つ、または2つ以上ある時で2つ目以降にデフォルト引数が設定されている場合、原則としてexplicitを付けましょう。たとえば文字列クラスのように、暗黙の変換がなければ不便な場合だけexplicitを外すとよいです。
class Point : public IStringizable { public: /** * コンストラクタ */ explicit Point( int x = 0, int y = 0 ); /*略*/ };
class Size : public IStringizable { public: /** * コンストラクタ */ explicit Size( int width = 0, int height = 0 ); /*略*/ };
class Rect : public IStringizable { public: /** * コンストラクタ。左上と右下の点を指定 */ explicit Rect( int left = 0, int top = 0, int right = 0, int bottom = 0 ); /*略*/ };
class Color : public IStringizable { public: /** * コンストラクタ */ explicit Color( float r = 0.0f, float g = 0.0f, float b = 0.0f, float a = 0.0f ); /*略*/ };
ゲームフレームワーク的なものを作る。(4)〜文字列描画〜
今回はID3DXFontを使った文字列描画を行います。
前回「ゲームフレームワーク的なものを作る。(3)〜Graphicsクラス〜 - while( c++ );」のGraphicsクラスに、
- ID3DXFontの生成関数
- 文字列描画関数
- std::shared_ptr< ID3DXFont >
を追加します。
/** * Graphicsクラス */ class Graphics { private: /** * 単純なフォントを生成 */ ID3DXFont* CreateSimpleFont( int size, const std::string& font_name ); public: /** * 文字列の描画 */ void DrawString( int x, int y, const std::string& str, D3DCOLOR color ); private: std::shared_ptr< ID3DXFont > font; ///< 文字列描画担当 };
関数の実装
/** * コンストラクタ * * @param window 既存ウィンドウのweak_ptr */ Graphics::Graphics( const WindowWeakPtr& window ) : window( window ) , direct3d( CreateDirect3D(), com_deleter() ) , device( CreateDirect3DDevice(), com_deleter() ) , font( CreateSimpleFont( 16, "MS ゴシック" ), com_deleter() ) { } /** * 単純なフォントを生成 * * @param size フォントサイズ(高さ) * @param font_name フォント名 */ ID3DXFont* Graphics::CreateSimpleFont( int size, const std::string& font_name ) { ID3DXFont* font = nullptr; HRESULT hr = D3DXCreateFont( device.get(), size, 0, 0, 0, FALSE, SHIFTJIS_CHARSET, OUT_DEFAULT_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH | FF_DONTCARE, font_name.c_str(), &font ); if( FAILED( hr ) )throw std::runtime_error( "D3DXCreateFont" ); return font; } /** * 文字列の描画 */ void Graphics::DrawString( int x, int y, const std::string& str, D3DCOLOR color ) { if( WindowPtr w = window.lock() ) { SIZE size = w->GetClientSize(); RECT rect = { x, y, size.cx, size.cy }; font->DrawText( nullptr, str.c_str(), -1, &rect, 0, color ); } }
使い方
std::stringstreamで描画する文字列を作成し、描画関数に渡します。
/** * ゲーム1フレームの処理を行う */ void Game::Run() { //シーンの実行 ; //バックバッファをクリアし、シーンのレンダリングを開始する //レンダリング終了後、画面を更新する graphics.Clear(); if( graphics.BeginScene() ) { //シーンの描画 ; //デバッグ情報 std::stringstream ss; ss << "hoge" << std::endl << "ほげほげ" << std::endl ; graphics.DrawString( 0, 0, ss.str(), D3DCOLOR_XRGB( 0xff, 0xcc, 0x99 ) ); graphics.EndScene(); } graphics.Update(); }
サンプル
- framework05.zip
- VC++2010ExpressEdition、DirectX SDK(February 2010)で動作確認
次回は、windowsのPOINT構造体、SIZE構造体、RECT構造体をクラス化したPointクラス、Sizeクラス、Rectクラス、0.0〜1.0の実数で色成分を扱うためのColorクラスを作る予定です。
ゲームフレームワーク的なものを作る。(2)〜Gameクラスに描画機能を追加〜
前回「ゲームフレームワーク的なものを作る。(1)〜Gameクラス〜 - while( c++ );」のGameクラスに、Direct3Dを使った描画機能を追加します。使用するDirectX SDKのバージョンはFebruary 2010です。初期化と解放、シーンのレンダリングについて必要最小限の機能だけ記述します。
まず、Direct3Dの機能を利用するために、d3d9.hのインクルードとd3d9.libのリンクを追加しておきます。
#include <d3d9.h> #pragma comment( lib, "d3d9.lib" )
COMオブジェクトの解放について
DirectXには、インターフェイスのポインタをコピーする際にAddRef()を、破棄する際にRelease()を呼び出すという仕様があります。
IXxx* a = インスタンスを取得; //参照カウンタ = 1 IXxx* b = a; //コピーした際に参照カウンタをインクリメント b->AddRef(); //参照カウンタ = 2 ... //bが不要になった際に参照カウンタをデクリメント b->Release(); //参照カウンタ = 1 //aが不要になった際に参照カウンタをデクリメント a->Release(); //参照カウンタ = 0 インスタンスが破棄される
このような面倒な部分をスマートポインタで自動化することができます。本来であればboost::intrusive_ptrを使う場面ですが、出来るだけboostを使わない方針なので今回はstd::shared_ptrで代用していきます。
com_deleter
COMインターフェイスをReleaseするための関数オブジェクトを作ります。
struct com_deleter { void operator()( IUnknown* p ) { p->Release(); } };
上記のカスタム削除子をstd::shared_ptrのコンストラクタに指定すると、std::shared_ptrの参照カウンタが0になった時に自動的にCOMオブジェクトが解放されます。
#include <memory> std::shared_ptr< IXxx > p( インスタンス, com_deleter() );
Direct3Dの初期化と解放
以上を踏まえて、まずはGameクラスにDirect3Dの初期化処理を追加します。
#include <memory> /** * Gameクラス */ class Game { /*略*/ private: /** * Direct3Dオブジェクトの生成 */ IDirect3D9* CreateDirect3D(); /** * Direct3Dデバイスオブジェクトの生成 */ IDirect3DDevice9* CreateDirect3DDevice(); /** * COMオブジェクト解放用関数オブジェクト */ struct com_deleter { void operator()( IUnknown* p ) { p->Release(); } }; private: std::shared_ptr< IDirect3D9 > direct3d; ///< Direct3Dオブジェクト std::shared_ptr< IDirect3DDevice9 > device; ///< 描画担当 };
以下、実装。
Direct3Dの初期化は、ドキュメントのチュートリアル通りで構いません。とりあえず、デバイス生成時の「ウィンドウモードの指定」と「ウィンドウハンドルの指定」以外はデフォルトの設定で問題無いかと。
Direct3Dの初期化に失敗した場合、例外を投げます。コンストラクタから例外を投げることになるのでメモリリークに注意が必要ですが、スマートポインタを利用しているので、初期化の途中で例外が発生しても、それまでに生成済みのオブジェクトは正しく解放されます。
- 参考「コンストラクタ 例外」
/** * コンストラクタ * * @param window 既存ウィンドウのweak_ptr */ Game::Game( const WindowWeakPtr& window ) : window( window ) , direct3d( CreateDirect3D(), com_deleter() ) , device( CreateDirect3DDevice(), com_deleter() ) { //その他のゲームの初期化処理 ; std::cout << "ゲームの初期化成功" << std::endl; } /** * Direct3Dオブジェクトの生成 */ IDirect3D9* Game::CreateDirect3D() { IDirect3D9* p = Direct3DCreate9( D3D_SDK_VERSION ); if( p == nullptr ) throw std::runtime_error( "Direct3DCreate9" ); return p; } /** * Direct3Dデバイスオブジェクトの生成 */ IDirect3DDevice9* Game::CreateDirect3DDevice() { if( WindowPtr w = window.lock() ) //lockの失敗はありえないはず。。。 { IDirect3DDevice9* p = nullptr; D3DPRESENT_PARAMETERS pp = { 0 }; pp.Windowed = TRUE; //ウィンドウモードの場合はTRUEを指定 pp.SwapEffect = D3DSWAPEFFECT_DISCARD; HRESULT hr = direct3d->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, w->GetHandle(), //ウィンドウハンドルを渡す D3DCREATE_HARDWARE_VERTEXPROCESSING, &pp, &p ); if( SUCCEEDED( hr ) )return p; } throw std::runtime_error( "CreateDevice" ); }
使い方
int main() { try { WindowPtr window = ウィンドウ生成; //ゲームの実体を生成、描画エンジンの初期化を行う。 Game game( window ); //ゲームループ while( ウィンドウが閉じられるまで ) { 1フレームの処理; windowsイベントを処理; } } catch( std::exception& e ) { std::cout << e.what() << std::endl; } return 0; }
シーンのレンダリング
シーンをレンダリング、および画面を更新するために
- バックバッファのクリア
- シーンの描画開始
- シーンの描画終了
- 画面の更新
を行います。
class Game { /*略*/ // ------------------------------------------------------------------------------- // 描画機能 // ------------------------------------------------------------------------------- private: /** * シーンのクリア */ void Clear(); /** * シーンのレンダリング開始 */ bool BeginScene(); /** * シーンのレンダリング終了 */ void EndScene(); /** * 画面の更新 */ void Update(); };
/** * シーンのクリア */ void Game::Clear() { const DWORD clear_flag = D3DCLEAR_TARGET; const D3DCOLOR clear_color = D3DCOLOR_XRGB( 0x22, 0x22, 0x22 ); device->Clear( 0, nullptr, clear_flag, clear_color, 1.0f, 0 ); } /** * シーンのレンダリング開始 */ bool Game::BeginScene() { return SUCCEEDED( device->BeginScene() ); } /** * シーンのレンダリング終了 */ void Game::EndScene() { device->EndScene(); } /** * 画面の更新 */ void Game::Update() { device->Present( nullptr, nullptr, nullptr, nullptr ); }
1フレームの処理
さらにGameクラスに1フレームの処理を行うメソッドを追加して、とりあえず完成。
class Game { /*略*/ public: /** * ゲーム1フレームの処理を行う */ void Run(); };
/** * ゲーム1フレームの処理を行う */ void Game::Run() { //シーンの実行 ; //バックバッファをクリアし、シーンのレンダリングを開始する //レンダリング終了後、画面を更新する Clear(); if( BeginScene() ) { //シーンの描画 ; EndScene(); } Update(); }
/** * エントリーポイント */ int main() { //メモリリークの検出を有効化 // アウトプットウィンドウにメモリリーク時の情報が出力される ::_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); try { //ゲーム用ウィンドウを生成 WindowPtr window = Application::CreateGameWindow( "hoge", 720, 480 ); //ゲームを初期化して、ゲームループを開始する Game game( window ); //ゲームループ // ウィンドウが閉じられるまで1フレームの処理を繰り返す while( !window->IsClosed() ) { //1フレームの処理 game.Run(); //メッセージキューにある全てのwindowsイベントを処理する Application::DoEvents(); } } catch( std::exception& e ) { std::cout << e.what() << std::endl; } return 0; }
サンプル
- framework03.zip
- VC++2010ExpressEdition
- DirectX SDK February 2010
Gameクラスの初期化・解放メソッドを排除して、コンストラクタ内で描画エンジンの初期化を行ってみました。今後、テクスチャの生成、描画メソッドなどGameクラスの肥大化が予想されます。ということで、次回はGameクラスから描画機能を分離して描画クラスを作ってみたいと思います。
ゲームフレームワーク的なものを作る。(3)〜Graphicsクラス〜
前回「ゲームフレームワーク的なものを作る。(2)〜Gameクラスに描画機能を追加〜 - while( c++ );」、Gameクラスへ描画機能を追加しました。
このまま画像などの描画機能も追加していくとGameクラスが肥大化するので、以下のように描画機能だけGraphicsクラス的なものに分離してみましょう。
Graphics.h
class Graphics { // ------------------------------------------------------------------------------- // 生成と破棄 // ------------------------------------------------------------------------------- public: /** * コンストラクタ */ Graphics( const WindowWeakPtr& window ); /** * デストラクタ */ ~Graphics(); // ------------------------------------------------------------------------------- // 基本機能 // ------------------------------------------------------------------------------- private: /** * Direct3Dオブジェクトの生成 */ IDirect3D9* CreateDirect3D(); /** * Direct3Dデバイスオブジェクトの生成 */ IDirect3DDevice9* CreateDirect3DDevice(); /** * COMオブジェクト解放用関数オブジェクト */ struct com_deleter { void operator()( IUnknown* p ) { p->Release(); } }; public: /** * シーンのクリア */ void Clear(); /** * シーンのレンダリング開始 */ bool BeginScene(); /** * シーンのレンダリング終了 */ void EndScene(); /** * 画面の更新 */ void Update(); private: WindowWeakPtr window; ///< Graphicsを関連付けたWindow std::shared_ptr< IDirect3D9 > direct3d; ///< Direct3Dオブジェクト std::shared_ptr< IDirect3DDevice9 > device; ///< 描画担当 };
Graphics.cpp
/** * コンストラクタ * * @param window 既存ウィンドウのweak_ptr */ Graphics::Graphics( const WindowWeakPtr& window ) : window( window ) , direct3d( CreateDirect3D(), com_deleter() ) , device( CreateDirect3DDevice(), com_deleter() ) { } /** * デストラクタ */ Graphics::~Graphics() { } /** * Direct3Dオブジェクトの生成 */ IDirect3D9* Graphics::CreateDirect3D() { IDirect3D9* p = Direct3DCreate9( D3D_SDK_VERSION ); if( p == nullptr ) throw std::runtime_error( "Direct3DCreate9" ); return p; } /** * Direct3Dデバイスオブジェクトの生成 */ IDirect3DDevice9* Graphics::CreateDirect3DDevice() { if( WindowPtr w = window.lock() ) //lockの失敗はありえないはず。。。 { IDirect3DDevice9* p = nullptr; D3DPRESENT_PARAMETERS pp = { 0 }; pp.Windowed = TRUE; //ウィンドウモードの場合はTRUEを指定 pp.SwapEffect = D3DSWAPEFFECT_DISCARD; HRESULT hr = direct3d->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, w->GetHandle(), //ウィンドウハンドルを渡す D3DCREATE_HARDWARE_VERTEXPROCESSING, &pp, &p ); if( SUCCEEDED( hr ) )return p; } throw std::runtime_error( "CreateDevice" ); } /** * シーンのクリア */ void Graphics::Clear() { const DWORD clear_flag = D3DCLEAR_TARGET; const D3DCOLOR clear_color = D3DCOLOR_XRGB( 0x22, 0x22, 0x22 ); device->Clear( 0, nullptr, clear_flag, clear_color, 1.0f, 0 ); } /** * シーンのレンダリング開始 */ bool Graphics::BeginScene() { return SUCCEEDED( device->BeginScene() ); } /** * シーンのレンダリング終了 */ void Graphics::EndScene() { device->EndScene(); } /** * 画面の更新 */ void Graphics::Update() { device->Present( nullptr, nullptr, nullptr, nullptr ); }
Game.h
class Game { // ------------------------------------------------------------------------------- // 生成と破棄 // ------------------------------------------------------------------------------- public: /** * コンストラクタ */ Game( const WindowWeakPtr& window ); /** * デストラクタ */ ~Game(); // ------------------------------------------------------------------------------- // 基本機能 // ------------------------------------------------------------------------------- public: /** * ゲーム1フレームの処理を行う */ void Run(); private: Graphics graphics; ///< 描画機能 };
Game.cpp
/** * コンストラクタ * * @param window 既存ウィンドウのweak_ptr */ Game::Game( const WindowWeakPtr& window ) : graphics( window ) { //その他のゲームの初期化処理 ; std::cout << "ゲームの初期化成功" << std::endl; } /** * デストラクタ */ Game::~Game() { //その他のゲームの解放処理 ; } /** * ゲーム1フレームの処理を行う */ void Game::Run() { //シーンの実行 ; //バックバッファをクリアし、シーンのレンダリングを開始する //レンダリング終了後、画面を更新する graphics.Clear(); if( graphics.BeginScene() ) { //シーンの描画 ; graphics.EndScene(); } graphics.Update(); }