放一个菜鸟级的程序出来:
做一个简单的弹球窗口,碰到窗口边缘就会弹回。
该程序的球不会粘边缘,可以任意改变窗口,可以最小化都不会出问题。
效果图如下:
算法:
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有关,因为如果是使用DirectX做的话,应该就不会出现这样的问题的。
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写的,现在在学Direct2D,过段时间都用Direct2D实现这些东西了。
作者:kenden23 发表于2013-12-14 7:58:32 原文链接
阅读:93 评论:0 查看评论