Home > Archives > 2009-07
2009-07
猫ウィンドウクラスを作る(3)
- 2009-07-26 (日)
- 技術
●アニメーションの登録と選択
CCatWndでは,猫表示のために
- 猫アニメーションパターンをいくつか登録する
- 現在表示すべきアニメーションパターンを選択する
という手順を踏みます.アニメーション登録の方法は
- アニメーションフレーム画像を並べた画像(BMP)
- アニメーションフレーム画像のサイズ
- アニメーションフレーム数
- 各アニメーションフレーム毎の情報(どの絵を表示するか,次のフレームまでの時間)
- アニメーションID値(選択するときの指定に使う)
といったデータを用意して,登録用関数に渡すことにしました.
仕様として,BMPは「同一サイズのアニメーションフレーム画像を隙間なく羅列したもの」を用意することにします.
また,背景部分は白であることとします.
例えば,次の様な画像です.
黒猫の着地アニメーション用BMP.フレーム画像サイズ48*54の画像を4枚羅列.
この画像例では,フレーム画像を縦にならべていますが,横にならべても,複数行*複数列に並べてもいいように,各フレームでどの場所のフレーム画像を表示するか,という情報については(x,y)のインデクスで指定する形にしました.
//アニメーションパターン設定用データ //登録用メソッドには構造体を作って渡す形にした //各フレームのデータ struct SAnimFrameData { unsigned int m_XIndex; //X方向パタンインデクス unsigned int m_YIndex; //Y方向パタンインデクス unsigned int m_AnimTime_ms; //表示時間[ms] SAnimFrameData( unsigned int XIndex=0, unsigned int YIndex=0, unsigned int AnimTime=1 ) : m_XIndex(XIndex), m_YIndex(YIndex), m_AnimTime_ms(AnimTime) {} }; //アニメーションデータ struct SAnimData { unsigned int m_UnitWidth;//フレーム画像サイズ unsigned int m_UnitHeight; std::vector< SAnimFrameData > m_Frames; unsigned int m_AnimID; //アニメーションを表す固有の値 SAnimData() : m_UnitWidth(0), m_UnitHeight(0), m_AnimID(0) {} }; //アニメーションの登録関数 //ファイル名指定タイプと,リソースID指定タイプを用意. bool RegisterAnimation_BMPFILE( const char *ImageName, const SAnimData &rAnimData ); bool RegisterAnimation_RESOURCE( UINT BMP_ID, const SAnimData &rAnimData ); //アニメーションの選択 bool SetCurAnimFrame( unsigned int AnimID, unsigned int FrameIndex ); void GetCurAnimFrame( unsigned int *pAnimID, unsigned int *pFrameIndex ) const;登録関数内では,指定された画像の読込みに成功したら,アニメーション表示に必要となる情報を,アニメーションIDをキーとしたmapに蓄えています.
std::map< unsigned int, 必要なデータをまとめた型 * > m_AnimMap;//キーはアニメーションID値
アニメーション選択関数では,指定されたIDがm_AnimMapに登録されていれば,そのアニメーションを,指定されたフレームから開始するように設定を行います.
また,ウィンドウのサイズをアニメーションのフレーム画像サイズに合わせます.
このとき,フレーム画像のサイズが前に選択されていたアニメーションと違うと猫の位置がずれてしまったりするのですが,このことへの対処は選択する側に任せることにしています.
(面倒なら,全アニメーションのフレームサイズを統一すればいいか,とか.)
●アニメーションの表示
これは単純にタイマで時間毎にフレームを切り替えて表示するだけです.
登録情報に次のフレームまでの時間があるので,アニメーション選択が行われた時点で::SetTimer()でタイマをその時間に合わせ,あとはタイマイベント毎にフレームを次に進めてタイマ時間をセットして::InvalidateRect()で再描画要求を出します.
WM_PAINTに対する描画処理部分で現在のフレーム画像を描画します.
●猫じゃないとダメですよ
何やら隣近所から
「これなら絵が猫じゃなくても何ら問題がない→クラス名はCAnimWndとかにすべき」
とかいうまるで理解できない声が聞こえてきたりしています.何なのでしょうか?CCatWndは「猫ウィンドウ」クラスなのであり,猫でないものを表示するのは「クラス設計者の意図していない間違った使い方」であるので,アニメーション登録関数の注釈にはきちんと
//アニメーションの登録
//※ちゃんと「猫」の画像を指定すること.猫でない画像を指定した場合の動作は不定ですと明記して対策しておきます.
(不定なのだから,偶然正しく動作する,ということもあり得る)
- Comments (Close): 0
- Trackbacks (Close): 0
猫ウィンドウクラスを作る(2)
- 2009-07-26 (日)
- 技術
●クラスCCatWndの骨組み
まずは,「ウィンドウを出して,イベントを処理して,ウィンドウを閉じられる」という最低限の枠組みをコーディングします.
- ウィンドウプロシージャを用意する
- ウィンドウクラスの登録(RegisterClassEx)
- ウィンドウの生成と表示(CreateWindow,ShowWindow,UpdateWindow)
- ウィンドウの破棄(DestroyWindow)
- 登録したウィンドウクラスを捨てる処理が必要かも(UnregisterClass)
…と,順番通りにAPIが呼ばれるように.
各所でAPIにインスタンスハンドルを渡す必要がありますが,::GetModuleHandle( NULL );で取れるっぽいので,これを渡しています.
●ウィンドウクラスの登録
これをいつやるか? ということになりますが,とりあえず「最初にウィンドウを作ろうとしたとき」に行うことにしました.
(「最初に…」を判断するためにstaticメンバでフラグをひとつ用意して対処)
ウィンドウクラスの登録にはウィンドウプロシージャを指定する必要があるので,staticメンバ関数でウィンドウプロシージャを用意して,これを指定しておきます.
RegisterClassEx()が成功したら,上記5.のUnregisterClassをするための処理関数をatexit()で指定しておきます.
//CCatWndの宣言部抜粋 class CCatWnd { public: bool CreateWnd( ... );//ウィンドウ生成 bool DestroyWnd();//ウィンドウ破棄 private: HWND m_hWnd; //ウィンドウハンドルを覚えておく用 private: static bool ms_bRegisterClassFlag; //RegisterClassしたことを覚えておくフラグ //ウィンドウクラスの登録と削除,staticなウィンドウプロシージャ static bool RegisterClass(); static void UnRegisterClass(); static LRESULT CALLBACK WndProc__( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ); }
- CreateWnd()は最初にフラグms_bRegisterClassFlagを見て,必要ならRegisterClass()をコール,その後,::CreateWindow(…WS_POPUP…)でウィンドウの生成を行います.
- RegisterClass()はWndProc__をプロシージャとしたウィンドウクラスを::RegisterClass()APIで登録し,ms_bRegisterClassFlagを書き換え,最後にatexit( CCatWnd::UnRegisterClass )しておきます.
- UnRegisterClass()はms_bRegisterClassFlagを見て,必要なら単に::UnregisterClass()APIでウィンドウクラスの登録を解除するだけです.
●ウィンドウプロシージャでメンバ変数とか触りたい
ウィンドウプロシージャWndProc__()はstaticメンバ関数なので,このままではCCatWndインスタンスに手が届かない存在(?)です.
WndProc__()に渡されてくるウィンドウハンドルを頼りに,メッセージ処理を行うべきCCatWndインスタンスを判断する機構が必要です.
要するに,CCatWndに,非staticな(普通の)メンバ関数
//オブジェクト毎のプロシージャ
LRESULT WndProc( UINT message, WPARAM wParam, LPARAM lParam );
があったら,WndProc__()が適切なオブジェクトのWndProc()をコールするようにできればOKです.
例えば,
static std::map< HWND, CCatWnd * > ms_HWND2ObjMap;
なんてものを作って管理してもよさそうですが,どうやらSetProp(),GetProp()というAPIを使えばその手間が省けるようなので,こちらを使うことにしました.
- ウィンドウが生成できたら,SetProp()でウィンドウハンドルにオブジェクトへのポインタを登録しておく
- WndProc__()では,GetProp()でオブジェクトへのポインタを取得し,そこからWndProc()を呼ぶ.
ということをすればよさそうです.
1.をどこでやるか,ですが,::CreateWindow()内でWM_CREATEメッセージが発生してくる関係上,CreateWnd()内でウィンドウを作るあたりで
m_hWnd = ::CreateWindow( … );
::SetProp( m_hWndとthisを渡して登録 );
というような記述をしてしまうと,オブジェクト毎のウィンドウプロシージャでWM_CREATEに対する処理を行うことができなくなってしまいました.
対策として,(ちょっとかっこわるいですが)CreateWnd()内で
ms_pNowCreatingObj = this;//CreateWindow()の直前にstaticメンバに,thisを代入
CreateWindow( … );
として,これからウィンドウを作るオブジェクトを覚えておいて,WndProc__()を以下のようにしました.
//ウィンドウプロシージャ //ウィンドウハンドルに登録されているオブジェクトのプロシージャに処理を渡す LRESULT CALLBACK CCatWnd::WndProc__(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { CCatWnd* pObj = NULL; if( message == WM_CREATE ) {//WM_CREATEはCreateWindow()内で来るので,特別処理. pObj = ms_pNowCreatingObj; if( pObj ) { pObj->m_hWnd = hWnd; //※ここでウィンドウハンドルをメンバ変数に設定. ::SetProp( hWnd, "THIS", (HANDLE)( pObj ) ); //オブジェクトのポインタ登録 } } else { pObj = (CCatWnd*)( GetProp( hWnd, "THIS" ) ); } // if( pObj ) { return pObj->WndProc( message, wParam, lParam ); } else { return DefWindowProc( hWnd, message, wParam, lParam ); } }あとでms_pNowCreatingObjを排他処理で守るようにすれば安全…かな?
とにかくこれでCCatWndオブジェクトが自分で作ったウィンドウへのメッセージを処理できる枠組みができました.
あとは,コンストラクタで適切な初期化をし,WM_CREATE時あたりでレイヤードウィンドウの設定を行い,デストラクタやWM_DESTROY時の処理あたりに必要な後始末コードを追加したりしておきます.
- Comments (Close): 0
- Trackbacks (Close): 0
Home > Archives > 2009-07