第49章 簡易アニメーション その1


今回は、簡単なアニメーションの方法について解説します。 本格的なものではなく、あくまでも簡易的方法です。

左のような簡単なアニメーションを作ってみます。 よく見ると、赤い円が左から右に動いているだけですが、 背景の黒の対角線は動いている円から透けて見えています。

いろいろな方法が考えられますが、今回はメタファイルという 方法で実現してみました。メタファイルとは、グラフィックスなどを 描画する手順をそのまま記録して、後で再生(?)できるという おもしろい手法です。いろいろ欠点もありますが、取り扱いは簡単です。

まず、全体のプログラムの筋書きを考えてみます。 まず、いろいろな位置にある円を描画します。これをメタファイルに記録します。 今回は5つほど作ってあります。

次に、どうやって次々とメタファイルの再生をするかというと、 これはもうわかりますね。SetTimer関数を使えばよいということがわかります。 WM_TIMERメッセージが来たら、再生するメタファイルの番号を次々と 変えます。この番号はグローバル変数に保存します。そして、クライアント領域を 更新します。すると、WM_PAINTメッセージが来ますから、このところで グローバル変数の番号によってメタファイルを再生します。

これで、次々とメタファイルが再生されてアニメーションもどきになります。

では、具体的にメタファイルの作り方を見てみましょう。

メタファイルの作り方 1.CreateMetaFile関数でデバイスコンテキストハンドルhdcを取得 2.hdcを使って描画する 3.CloseMetaFile(hdc)でメタファイルハンドルを取得する 4.PlayMetFile関数で再生する 5.DeleteMetaFile関数でメタファイルハンドルを破棄する

と、こんな感じになります。

HDC CreateMetaFile( LPCTSTR lpszFile // ファイルネームへのポインタ );

このとき、lpszFileをNULLにすると、メモリ上にメタファイルが作られます。 この関数が成功するとメタファイルに対するデバイスコンテキストハンドルを 取得することができます。失敗したときはNULLです。 さて、このとき取得したデバイスコンテキストハンドルを使ってたいていの グラフィックスを描画することができますが、すべてではありません。 ヘルプで確認してみてください。

HMETAFILE CloseMetaFile( HDC hdc // メタファイルデバイスコンテキストハンドル );

この関数で、メタファイルをクローズします。このとき得られる メタファイルハンドルは後で再生するときに必要になります。

BOOL PlayMetaFile( HDC hdc, // デバイスコンテキストハンドル HMETAFILE hmf // メタファイルハンドル );

メモリ上のメタファイルはこれで再生できます。 メタファイルハンドルhmfはCloseMetaFile関数の戻り値です。 この関数が成功したときはTRUE、失敗したときはFALSEを返します。

BOOL DeleteMetaFile( HMETAFILE hmf // handle to Windows-format metafile );

これで不要になったメタファイルハンドルを破棄します。 さて、以上の一連の関数は16ビット版32ビット版で共通に使えます。 32ビット版では、CreateEnhMetaFile, CloseEnhMetaFile, PlayEnhMetaFile, DeleteEnhMetaFile関数があります。ヘルプで使い方を調べてみてください。

では、さっそくサンプルのプログラムを見てみましょう。

// meta01.cpp #include <windows.h> #define IDO 50 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); BOOL InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); void MakeMyMeta(void); HMETAFILE MakeMyEllipse(int, int); char szClassName[] = "meta01"; //ウィンドウクラス HMETAFILE hmf[5]; int play_no; int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; if (!hPrevInst) { if (!InitApp(hCurInst)) return FALSE; } if (!InitInstance(hCurInst, nCmdShow)) { return FALSE; } while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }

このへんはいつもとほとんど同じです。

//ウィンドウ・クラスの登録 BOOL InitApp(HINSTANCE hInst) { WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; //インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; return (RegisterClass(&wc)); }

これも、いつもと同じです。

//ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかるメタファイル", //タイトルバーにこの名前が表示されます WS_OVERLAPPED | WS_SYSMENU, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 258,//幅 75, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }

ウィンドウスタイルをいつものWS_OVERLAPPEDWINDOWから少し変えてみました。 ウィンドウの大きさを固定しました。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id, i; HDC hdc; PAINTSTRUCT ps; RECT rc; switch (msg) { case WM_CREATE: MakeMyMeta(); SetTimer(hWnd, 1, 200, NULL); break; case WM_TIMER: play_no++; if (play_no > 5) play_no = 1; InvalidateRect(hWnd, NULL, TRUE); break; case WM_PAINT: //背景の描画 GetClientRect(hWnd, &rc); hdc = BeginPaint(hWnd, &ps); MoveToEx(hdc, rc.left, rc.bottom / 2, NULL); LineTo(hdc, rc.right, rc.bottom / 2); MoveToEx(hdc, rc.left, rc.top, NULL); LineTo(hdc, rc.right, rc.bottom); MoveToEx(hdc, rc.left, rc.bottom, NULL); LineTo(hdc, rc.right, rc.top); //メタファイルの再生 PlayMetaFile(hdc, hmf[play_no - 1]); EndPaint(hWnd, &ps); break; case WM_CLOSE: id = MessageBox(hWnd, (LPCSTR)"終了してもよいですか", (LPCSTR)"終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: //メタファイルハンドルの解放 for (i = 0; i <= 4; i++) { DeleteMetaFile(hmf[i]); } KillTimer(hWnd, 1); PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0L; }

親ウィンドウが作られたらすぐに、メタファイルの作成と SetTimer関数を呼びます。

WM_TIMERメッセージが来たら再生するメタファイルの番号を 1つ増やして、クライアント領域を描き直します。

WM_PAINTメッセージのところでは、背景の対角線を描画して メタファイルを再生します。

アプリケーションの終了直前でメタファイルハンドルを解放します。

void MakeMyMeta(void) { static int x1 = 0, x2 = 50; int i; for (i = 0; i <= 4; i++) { hmf[i] = MakeMyEllipse(x1, x2); x1 += IDO; x2 += IDO; } return; } HMETAFILE MakeMyEllipse(int x1, int x2) // メタファイルを作る { HDC hdc_meta; HBRUSH hBrush; HPEN hPen; hdc_meta = CreateMetaFile(NULL); hBrush = GetStockObject(NULL_BRUSH); hPen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0)); SelectObject(hdc_meta, hPen); SelectObject(hdc_meta, hBrush); Ellipse(hdc_meta, x1, 0, x2, 50); DeleteObject(hPen); return (CloseMetaFile(hdc_meta)); }

実際にメタファイルを作成する関数です。 背景が透けて見えるようにNULL_BRUSHを使っていることに 注意してください。GetStockObjectで取得したhBrushは DeletObjectをしなくてもOKです。DeleteObjectしても 害はありません。

これで、簡単なアニメーションの出来上がりです。 いろいろ不満があります。1つは、WM_TIMERメッセージが くる度にクライアント領域をすべて描き直しています。 これは少し不経済です。前章でやったリージョンを使って 無効領域を指定する方法が考えられます。 描き直すのは、前回の円の描画と今回の円の描画領域を あわせたものです。このリージョンを使って InvalidateRgn関数を呼べばよいのではないでしょうか。 少しプログラムが複雑になりますが、挑戦してみてください。


[SDK Index] [総合Index] [Previous Chapter] [Next Chapter]

Update Jun/23/1997 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。