EternalWindows
再描画 / 交差判定

交差判定は、ゲームでは当たり判定などといわれます。 キャラクター等の位置をRECT型で表現していれば、 IntersectRectで簡単に当たり判定を実現できます。

BOOL IntersectRect(
  LPRECT lprcDst,
  CONST RECT *lprcSrc1,
  CONST RECT *lprcSrc2
);

lprcDstは、2つの長方形が交差したときの範囲を受け取ります。 単に交差したかを知りたい場合でも、この変数は指定しなければなりません。 lprcSrc1とlprcSrc2は、判定に使うRECT構造体のアドレスを指定します。 交差していた場合、戻り値はTRUEとなり、そうでない場合はFALSEとなります。

今回のプログラムは長方形を2つ描画し、その片方を移動できるようにします。 そして、互いが交差しているかどうかをIntersectRectで判定します。

#include <windows.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static RECT rcMove = {0};
	static RECT rcBlock = {0};

	switch (uMsg) {

	case WM_CREATE:
		SetRect(&rcMove, 20, 30, 60, 70);
		SetRect(&rcBlock, 120, 130, 180, 190);
		return 0;

	case WM_KEYDOWN: {
		int  dx, dy;
		RECT rctmp;

		dx = dy = 0;

		switch (wParam) {
		case VK_LEFT:   dx = -5; break;
		case VK_UP:     dy = -5; break;
		case VK_RIGHT:  dx = +5; break;
		case VK_DOWN:   dy = +5; break;
		default: return 0;
		}

		OffsetRect(&rcMove, dx, dy);
		
		if (IntersectRect(&rctmp, &rcMove, &rcBlock))
			OffsetRect(&rcMove, -dx, -dy);
		else
			InvalidateRect(hwnd, NULL, TRUE);

		return 0;
	}

	case WM_PAINT: {
		HDC         hdc;
		PAINTSTRUCT ps;

		hdc = BeginPaint(hwnd, &ps);

		Rectangle(hdc, rcMove.left, rcMove.top, rcMove.right, rcMove.bottom);
		Rectangle(hdc, rcBlock.left, rcBlock.top, rcBlock.right, rcBlock.bottom);

		EndPaint(hwnd, &ps);

		return 0;
	}

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

WM_KEYDOWNは、キーボードの何らかのキーが押されたときに送られるメッセージです。 このメッセージのwParamは押されたキーの種類を表しており、 たとえば、VK_LEFTは←が押されたことを示します。 方向キー以外のキーが押された場合は何もしません。 移動増減値を求めたら、OffsetRectでRECT構造体のメンバを調整します。

OffsetRect(&rcMove, dx, dy);

rcMoveはウインドウの左上に位置する長方形で、これが移動の対象です。 たとえば、dxが5だった場合、leftとrightが共に5増えることになります。 移動した場合、rcBlockと交差していないかを判定します。 rcBlockは移動せず、交差判定用の長方形です。

if (IntersectRect(&rctmp, &rcMove, &rcBlock))
	OffsetRect(&rcMove, -dx, -dy);

第2引数と第3引数は逆にしても構いません。 戻り値がTRUEのとき、つまり交差したときに再びOffsetRectを呼び出しているのは、 交差することを許可しない設計にしているからです。 そのため、移動値をマイナスとすることにより元の場所に戻すわけです。 ユーザーから見れば、障害物に触れたため移動できなかったという風に思えます。 交差していない場合の処理は次のようになっています。

InvalidateRect(hwnd, NULL, TRUE);

OffsetRectでrcMoveを変更したといっても、それにより実際に長方形が 移動したのを知覚できるわけではありません。 ウインドウには依然として移動前の長方形が描画されているわけですから、 InvalidateRectで再描画を促し、移動後の長方形を描画しなくてはなりません。 長方形を移動する場合、前の長方形が描画されたままではいけませんので、 InvalidateRectの第3引数をTRUEにすることにより、 前の長方形をウインドウの背景色で塗りつぶすのです。


戻る