Windows编程之互动与动画
第五節: 考慮屏幕左側一臺坦克,向水平方向發射一枚炮彈,穿越屏幕。
很自然地,這場景中有坦克和炮彈兩個對象,各自有各自坐標,坦克坐標是固定的,而炮彈坐標是變化的。因此有兩個結構體類型:Tank和Bullet
在Win_Learn工作區中構建新工程WinStep2,同樣選擇Win32Application,仍然選擇典型的Windows程序。
建好工程以后,在WinStep2.cpp文件開始的地方創建兩個結構體類型,以及全局變量。
程序片段13 數據結構和全局變量
以上代碼中,包含了將坦克和子彈繪出的函數的聲明。
程序片段14 繪出坦克和子彈(版本1)
在繪圖的消息中添加對坦克和子彈的繪圖調用:
程序片段15 在繪圖消息調用對象的繪圖
仔細錄入程序并運行——嗯?看到子彈的軌跡了,這個不太正常。而且子彈速度太快了,根本沒有看到子彈穿越過程。所以第一程序需要一個“擦除上次子彈”的代碼,第二程序在連續畫子彈時需要有個延遲時間。
代碼修訂如下:
程序片段16 擦除子彈軌跡殘影
程序片段17 加入了擦除軌跡和延時的顯示代碼
注意程序若要正確編譯,還需要聲明繪制坦克和繪制子彈的地方加入擦除子彈函數的聲明。
運行程序——可以看到子彈正確向右發射了,就是似乎慢了點,這個可以通過調整子彈速度和休眠時間來實現。不過有個嚴重問題——在繪圖過程中,整個程序像死機一樣。回憶拓展一曾經提到過,任何消息處理的過程必須是快速的,否則程序將停止響應。在上面的例子里,將整個子彈移動過程都放到了繪制消息,若是子彈飛行1分鐘,那么這1分鐘內程序什么事都做不了。因此需要通過某種方式,將這一分鐘的繪圖打散為分解為很多時間碎片,每個時間碎片中繪制子彈,然后下一時間碎片中先擦除子彈再在新的位置中畫出子彈。這樣就需要用到Windows提供的定時器消息。定時器允許最短每50毫秒間隔觸發向WndProc發出WM_TIMER消息,在Win7中這個時間間隔被減少到10毫秒,這樣可以提供更佳的時間精確度。此外,子彈的射出最好還是由用戶控制,比如按下空格鍵發射子彈。這樣程序需要響應WM_KEY,如果是空格鍵則發出一枚子彈。程序調整如下:(因為代碼較長,可以直接貼到VC++)
// WinStep2.cpp : Defines the entry point for the application. #include "stdafx.h" #include "resource.h" #include <math.h> #define MAX_LOADSTRING 100struct Tank {int x,y,v;COLORREF MarkColor;char Mark[10];float faceangle; }; struct Bullet {int x,y;int v;float direction;BOOL exist; };void DrawTank(HDC hdc,struct Tank *pt); void DrawBullet(HDC hdc,struct Bullet *pb); void EraseBullet(HDC hdc,struct Bullet *pb); RECT rtWindow; //整個窗口大小,一開始就設置好,省得每次畫圖都查一遍。 HPEN TankFrame,TankCannon; HBRUSH TankBody; HFONT TankMark; HPEN BulletFrame; HBRUSH BulletBody; struct Tank Tank1; struct Bullet b[10];// Global Variables: HINSTANCE hInst; // current instance HWND hWnd; //主窗口 TCHAR szTitle[MAX_LOADSTRING]; TCHAR szWindowClass[MAX_LOADSTRING]; // Foward declarations of functions included in this code module: ATOM MyRegisterClass(HINSTANCE hInstance); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow) {int i=0;MSG msg;HACCEL hAccelTable;LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);LoadString(hInstance, IDC_WINSTEP2, szWindowClass, MAX_LOADSTRING);MyRegisterClass(hInstance);hInst = hInstance;hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_WINSTEP2);GetClientRect(hWnd,&rtWindow);TankFrame=(HPEN)GetStockObject(NULL_PEN);TankCannon=CreatePen(PS_SOLID,5,RGB(0,0,0));TankBody=CreateSolidBrush(RGB(0,255,0));TankMark=CreateFont(18,0,0,0,FW_BOLD,TRUE,FALSE,FALSE,DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH,"新宋體");BulletFrame=(HPEN)GetStockObject(NULL_PEN);BulletBody=(HBRUSH)GetStockObject(BLACK_BRUSH);Tank1.x=rtWindow.left+20;Tank1.y=(rtWindow.bottom+rtWindow.top)/2;Tank1.faceangle=0;Tank1.v=3;strcpy(Tank1.Mark,"008");Tank1.MarkColor=RGB(227,221,73);for(i=0;i<10;i++)b[i].exist=FALSE;ShowWindow(hWnd, nCmdShow);UpdateWindow(hWnd);SetTimer(hWnd,1,50,NULL); //設置每50毫秒觸發一次WM_TIMER.while (GetMessage(&msg, NULL, 0, 0)) {if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {TranslateMessage(&msg);DispatchMessage(&msg);}}return msg.wParam; } ATOM MyRegisterClass(HINSTANCE hInstance) {WNDCLASSEX wcex;wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW;wcex.lpfnWndProc = (WNDPROC)WndProc;wcex.cbClsExtra = 0;wcex.cbWndExtra = 0;wcex.hInstance = hInstance;wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_WINSTEP2);wcex.hCursor = LoadCursor(NULL, IDC_ARROW);wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);wcex.lpszMenuName = (LPCSTR)IDC_WINSTEP2;wcex.lpszClassName = szWindowClass;wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);return RegisterClassEx(&wcex); }LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {PAINTSTRUCT ps;HDC hdc;int i;switch (message) {case WM_PAINT:hdc = BeginPaint(hWnd, &ps);DrawTank(hdc,&Tank1);for(i=0;i<10;i++)if(b[i].exist)DrawBullet(hdc,&b[i]);EndPaint(hWnd, &ps);break;case WM_KEYDOWN: //當用戶按下鍵if(wParam==' ')//如果是空格鍵{for(i=0;i<10;i++) //尋找一個可用的子彈if(b[i].exist==FALSE){b[i].exist=TRUE;b[i].direction=Tank1.faceangle;b[i].v=5;b[i].x=Tank1.x+30*cos(b[i].direction);b[i].y=Tank1.y+30*sin(b[i].direction);break;}if(i>=10) //屏幕子彈過多Beep(500,50); //發出滴滴聲警告}return 0;case WM_TIMER: //定時器hdc=GetDC(hWnd);for(i=0;i<10;i++)if(b[i].exist){EraseBullet(hdc,&b[i]);b[i].x+=b[i].v*cos(b[i].direction);b[i].y+=b[i].v*sin(b[i].direction);if(b[i].x<rtWindow.left || b[i].x>rtWindow.right || b[i].y<rtWindow.top || b[i].y>rtWindow.bottom)b[i].exist=FALSE;elseDrawBullet(hdc,&b[i]);}ReleaseDC(hWnd,hdc);return 0;case WM_DESTROY:PostQuitMessage(0);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}return 0; } void DrawTank(HDC hdc,struct Tank* pt) {RECT TankArea;POINT CannonEnd;HGDIOBJ OldPen=SelectObject(hdc,TankFrame);HGDIOBJ OldBrush=SelectObject(hdc,TankBody);HGDIOBJ OldFont=SelectObject(hdc,TankMark);COLORREF OldFontColor=SetTextColor(hdc,pt->MarkColor);TankArea.left=pt->x-18,TankArea.right=pt->x+18;TankArea.top=pt->y-18,TankArea.bottom=pt->y+18;RoundRect(hdc,pt->x-20,pt->y-20,pt->x+20,pt->y+20,4,4); DrawText(hdc,pt->Mark,-1,&TankArea,DT_CENTER | DT_SINGLELINE | DT_VCENTER);SelectObject(hdc,TankCannon);MoveToEx(hdc,pt->x,pt->y,NULL);CannonEnd.x=pt->x+30*cos(pt->faceangle);CannonEnd.y=pt->y+30*sin(pt->faceangle);LineTo(hdc,CannonEnd.x,CannonEnd.y);SetTextColor(hdc,OldFontColor);SelectObject(hdc,OldFont);SelectObject(hdc,OldBrush);SelectObject(hdc,OldPen); }void DrawBullet(HDC hdc,struct Bullet* pb) {HGDIOBJ OldBrush=SelectObject(hdc,BulletBody);HGDIOBJ OldPen=SelectObject(hdc,BulletFrame);Ellipse(hdc,pb->x-2,pb->y-2,pb->x+2,pb->y+2);SelectObject(hdc,OldBrush);SelectObject(hdc,OldPen); }void EraseBullet(HDC hdc,struct Bullet* pb) {HGDIOBJ oldBrush=SelectObject(hdc,GetStockObject(WHITE_BRUSH));HGDIOBJ oldPen=SelectObject(hdc,GetStockObject(WHITE_PEN));Rectangle(hdc,pb->x-2,pb->y-2,pb->x+2,pb->y+2);SelectObject(hdc,oldPen);SelectObject(hdc,oldBrush); }程序片段18 坦克發射子彈代碼
以上程序人機控制較少。還可通過鼠標鍵來移動坦克的炮口,使用光標鍵來控制坦克自身的移動。為實現這個功能,需要消息函數中響應鼠標移動事件和響應新的按鍵事件;另外使用空格鍵發射子彈不如使用鼠標左鍵發射子彈來的合理,所以前述程序的WndProc過程可以修改如下:
程序片段19 修改一:增加EraseTank聲明
程序片段20 修改二:增加按鍵響應。這里給出上箭頭,其他按鍵自行補上
程序片段21 修改三:增加鼠標移動消息響應
程序片段22 修改四:增加左鍵按下響應
程序片段23 修改五:增加擦除坦克代碼
訂正:
更正為:
思考:若是在子彈移動過程中碰到物體,比如敵方坦克,或者打到墻上,如何處理?這類問題叫做碰撞檢測。需要對系統中每個對象保留exist(是否有效)狀態,或者life(生命值)。舉例而言,若考慮一個經典的坦克大戰游戲,包括:磚墻,敵方坦克,己方坦克,草叢,雙方子彈,則需要擴充前文的struct結構體,新增加Brick數組,Grass數組,Bullet數組。然后在WM_TIME中依次對這些可變對象做一對一檢測。另外坦克是不能穿越磚墻的,所以,在WM_KEYDOWN中也需要對坦克移動作出限制。只需要簡單地堆砌代碼即可將程序變得更生動有趣。
總結
以上是生活随笔為你收集整理的Windows编程之互动与动画的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 女人潇洒是什么意思
- 下一篇: Windows编程之网络之邮件槽通讯