Windows游戏编程 - 简单的弹球窗口

放一个菜鸟级的程序出来:

做一个简单的弹球窗口,碰到窗口边缘就会弹回。

该程序的球不会粘边缘,可以任意改变窗口,可以最小化都不会出问题。

效果图如下:

Windows游戏编程 - 简单的弹球窗口_第1张图片

算法:

1 取得窗口大小,以作为判断是否到边缘了

2 判断球的位置是否到了边缘,如果到了边缘,速度方向就为反方向


1 先初始化窗口之后就进入下面的主循环。

下面是它的主循环代码,游戏就是在这样的循环中不断更新,直到退出的时候,销毁windows。

while(!gameEnd)
	{
		while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) 
		{
			if( msg.message == WM_QUIT ) 
			{
				// Stop loop if it's a quit message
				gameEnd = true;
			} 
			else 
			{
				TranslateMessage( &msg );
				DispatchMessage( &msg );
			}
		}
		
		timer.approxiStableFPS();
		InvalidateRect(hWnd, NULL, TRUE);
		UpdateWindow(hWnd);
	}

2 首先定义一个球的数据结构

struct Ball
{
	int x; int y;

	int xVelocity;	int yVelocity;

	Ball(){}
};

3 在callback 函数中初始化参数,这里包括画笔,球和创建back buffer的初始化变量

LRESULT CALLBACK WindowProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	static HPEN MagentaPen  = CreatePen(PS_SOLID, 2, RGB(255, 0, 255));
	static HPEN OldPen   = NULL;

	static HBRUSH YellowBrush = CreateSolidBrush(RGB(255, 255, 0));
	static HBRUSH OldBrush = NULL;

	static int cxWidth, cyHeight; 

	static Ball* balls = new Ball[NUM_BALLS];

	static HDC	hdcBackBuffer;
	static HBITMAP	hBitmap;
	static HBITMAP	hOldBitmap;

初始化之后就是判断windows消息msg,

4 在首次创建Windows的时候,产生一个WM_CREATE消息,开始画图:

switch (msg)
	{
	case WM_CREATE:
		{
			RECT rect;

			GetClientRect(hwnd, &rect);

			cxWidth = rect.right;
			cyHeight = rect.bottom;

			srand((unsigned) time(NULL));

			for (int i=0; i<NUM_BALLS; ++i)
			{
				balls[i].x = uti::intRangeRand(0, cxWidth);
				balls[i].y = uti::intRangeRand(0, cyHeight);
				balls[i].xVelocity = uti::intRangeRand(1, MAX_VELOCITY);
				balls[i].yVelocity = uti::intRangeRand(1, MAX_VELOCITY);
			}

			hdcBackBuffer = CreateCompatibleDC(NULL);

			HDC hdc = GetDC(hwnd);
			hBitmap = CreateCompatibleBitmap(hdc,cxWidth,cyHeight);

			hOldBitmap = (HBITMAP)SelectObject(hdcBackBuffer, hBitmap);

			ReleaseDC(hwnd, hdc);
		}
		break;

5 在消息WM_PAINT中更新:

系统有需要重新画窗口或窗口的一部分的时候,就会产生一个WM_PAINT消息。比如最大化,最小化,改变窗口大小,窗口被遮挡的时候。
主循环中不断地调用:

		InvalidateRect(hWnd, NULL, TRUE);
		UpdateWindow(hWnd);



这两个函数,InvalidateRect确定要更新的区域,NULL代表默认整个窗口, UpdateWindow产生WM_PAINT消息,使得下面的代码不断更新:

case WM_PAINT:
		{
			PAINTSTRUCT ps;
			BeginPaint (hwnd, &ps);

			BitBlt(hdcBackBuffer, 0,	0, cxWidth, cyHeight, NULL, NULL, NULL,WHITENESS);

			OldPen = (HPEN)SelectObject(hdcBackBuffer, MagentaPen);
			OldBrush = (HBRUSH)SelectObject(hdcBackBuffer, YellowBrush);

			for (int i=0; i<NUM_BALLS; ++i)
			{
				if (balls[i].x >= cxWidth) 
				{
					balls[i].x = cxWidth - 1;
					balls[i].xVelocity *= -1;
				}
				if (balls[i].x < 0)
				{
					balls[i].x = 1;
					balls[i].xVelocity *= -1;
				}

				if (balls[i].y >= cyHeight)
				{
					balls[i].y = cyHeight -1;
					balls[i].yVelocity *= -1;
				}
				if (balls[i].y < 0)
				{
					balls[i].y = 1;
					balls[i].yVelocity *= -1;
				}


				balls[i].x += balls[i].xVelocity;
				balls[i].y += balls[i].yVelocity;

				Ellipse(hdcBackBuffer,
					balls[i].x - RADIUS,
					balls[i].y - RADIUS,
					balls[i].x + RADIUS,
					balls[i].y + RADIUS);
			}

			SelectObject(hdcBackBuffer, OldPen);
			SelectObject(hdcBackBuffer, OldBrush);

			BitBlt(ps.hdc, 0, 0, cxWidth, cyHeight, hdcBackBuffer, 0, 0, SRCCOPY); 

			RECT rect;
			GetClientRect(hwnd, &rect);
			char ch[] = "Bill Su's Bouncing Balls Demo";
			int chSize = strlen(ch);
			TextOut(ps.hdc, rect.left, rect.top, ch, chSize); 

			EndPaint (hwnd, &ps);
		}
		break;

这段代码有点复杂,因为我完善了一个偏移功能:

当球与窗口边缘触碰的时候,球就自动向窗口内偏移1, 这样可以防止球“粘”在窗口边缘,不断碰撞。

使用GDI就需要这样处理,使用Direct2D就不需要了,原理解析:http://blog.csdn.net/kenden23/article/details/17402485

6 当窗口大小变化的时候更新球的位置:

case WM_SIZE:
		{
			double oldcx = (double)cxWidth;
			double oldcy = (double)cyHeight;
			cxWidth = LOWORD(lParam);
			cyHeight = HIWORD(lParam);

			double rateX = double(cxWidth) / oldcx;
			double rateY = double(cyHeight) / oldcy;
			for (int i=0; i<NUM_BALLS; ++i)
			{
				balls[i].x *= rateX;
				balls[i].y *= rateY;
			}			

			SelectObject(hdcBackBuffer, hOldBitmap);
			DeleteObject(hBitmap); 

			HDC hdc = GetDC(hwnd);

			hBitmap = CreateCompatibleBitmap(hdc, cxWidth, cyHeight);

			ReleaseDC(hwnd, hdc);

			SelectObject(hdcBackBuffer, hBitmap);
		}

球的位置与窗口的大小成比例的。

不过注意一个问题,就是当窗口最小化的时候,球的位置会变为(0,0) 。

如果前面没有偏移功能,那么这个时候就会把球粘在右上角了。

最后再看看效果图:


从真正的游戏程序角度来看,本程序为简单程序,有两个缺陷:

1 球的位置更新并不是按照时间来更新的

2 帧率并不是固定的

但是简单程序嘛,先这样吧。

我写的完整代码,有兴趣的可以参考下,可以很容易修改球的数量,速度和大小。

方便菜鸟了,包含了完成的所有vs文件,可以直接打开运行了。免积分下载:

http://download.csdn.net/detail/kenden23/6712077

声明:这个是GDI写的,直接的底层语言是C++,跟c#一点边也挨不上,居然有人评论我的资源是C#写的,不知道是被气死还是笑死,O(∩_∩)O~

这个是使用GDI写的,微软要使用Direct2D代替GDI,虽然Direct2D速度和效果比起GDI+都上了一个档次,但是给我感觉是Direct2D也不好用。估计微软的Direct2D要胎死腹中了,还是直接使用Direct3D吧。


你可能感兴趣的:(Windows游戏编程,简单的弹球窗口)