近日里學習了關於win32編程的相關知識,利用這些知識制作了一款貪吃蛇小游戲,具體細節還是分模塊來敘述
前期准備:在網上找到一些貪吃蛇的游戲素材圖片,以及具體的邏輯框圖
在正式寫功能之前,先把一系列環境配置好,配置環境總體來說分為以下幾步:
- 圖片轉化為bmp格式( Bitmap )二進制流
- 將圖片加載到內存中,在加載內存中也分為三步
- 導入資源
- 將.rc文件代碼中的絕對路徑修改為相對路徑(可不改,如果打包發給別人的話,不一定能保證對方存儲文件的路徑和你一致,我這里是將素材存儲到 .c 文件的上一級當中)
- 在.c文件中利用LoadBitmap() 函數加載位圖進內存,其中參數的意義就不贅述了,直接通過vs自帶幫助文檔進行查看,並且自定義一個 位圖句柄 去接着函數返回的地址,引用宏的時候別忘了引入頭文件
1 Hbitmap_BackGroup = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_BITMAP1)); 2 Hbitmap_Apple = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_BITMAP2)); 3 Hbitmap_SnackHead = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_BITMAP3)); 4 Hbitmap_SnackHead_Up = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_BITMAP5)); 5 Hbitmap_SnackHead_Down = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_BITMAP7)); 6 Hbitmap_SnackHead_Left = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_BITMAP6)); 7 Hbitmap_SnackHead_Right = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_BITMAP4));
現在就正式進入編寫小游戲的階段
先來分析一下貪吃蛇游戲的功能
- 顯示背景
- 顯示蛇
- 顯示蘋果
- 移動
- 吃蘋果
- 生成新蘋果
- 蛇長個
- 撞牆死亡
- 自咬死亡
邏輯上還是很清晰很簡單的,接下來就是按功能實現
第一個,顯示背景,首先一打開游戲就會有背景,那么只有重繪能實現,想要畫圖的話,必須獲取窗口環境句柄(HDC),用完再去釋放, 但是在win32中如何將我的背景位圖,貼到HDC當中呢,查詢后了解,在win32中貼圖是以像素點為單位,一個像素點一個像素點的去傳輸到HDC當中,那么就得創建一個兼容性的HDC,再為兼容性DC選擇我的背景位圖,再分像素點進行傳輸
1 HDC hdc_compatible; 2 hdc_compatible = CreateCompatibleDC(hdc); //創建兼容性DC 3 SelectObject(hdc_compatible,Hbitmap_BackGroup); //為兼容性DC選擇背景位圖 4 BitBlt(hdc,0,0,600,600,hdc_compatible,0,0,SRCCOPY); //按像素點進行傳輸 5 DeleteDC(hdc_compatible); //刪除兼容性DC 6 return;
第二個,顯示蛇,首先以蛇的圖片大小構建一個虛擬的坐標網(也可以不建,但像素點太小定位太麻煩),以我這個為例背景圖是600*600的大小,蛇頭,蛇身,蘋果大小都是30*30大小,那么就是長寬都為0~19的坐標網,蛇的長個很容易想到可以用鏈表的添加進行實現,那么就把蛇身做成一個雙向鏈表(為什么雙向,在移動的時候就明白了),在添加鏈表后,再進行先蛇頭后蛇身的貼圖,上下左右用枚舉類型
1 enum FX {UP,DOWN,LEFT,RIGHT};2 enum FX fx = RIGHT;
1 void AddNode(int x,int y) 2 { 3 Snack *pTemp = (Snack *)malloc(sizeof(Snack)); 4 pTemp->x = x; 5 pTemp->y = y; 6 pTemp->pLast = NULL; 7 pTemp->pNext = NULL; 8 9 if(pHead == NULL) 10 { 11 pHead = pTemp; 12 } 13 else 14 { 15 pEnd->pNext = pTemp; 16 pTemp->pLast = pEnd; 17 } 18 pEnd = pTemp; 19 } 20 void ShowSnack(HDC hdc) 21 { 22 Snack *pMark = pHead->pNext; 23 HDC hdc_compatible; 24 hdc_compatible = CreateCompatibleDC(hdc); 25 switch (fx) 26 { 27 case UP: 28 SelectObject(hdc_compatible,Hbitmap_SnackHead_Up); 29 break; 30 case DOWN: 31 SelectObject(hdc_compatible,Hbitmap_SnackHead_Down); 32 break; 33 case LEFT: 34 SelectObject(hdc_compatible,Hbitmap_SnackHead_Left); 35 break; 36 case RIGHT: 37 SelectObject(hdc_compatible,Hbitmap_SnackHead_Right); 38 break; 39 default: 40 break; 41 } 42 BitBlt(hdc,pHead->x*30,pHead->y*30,30,30,hdc_compatible,0,0,SRCCOPY); 43 44 while(pMark) 45 { 46 SelectObject(hdc_compatible,Hbitmap_SnackHead); 47 BitBlt(hdc,pMark->x*30,pMark->y*30,30,30,hdc_compatible,0,0,SRCCOPY); 48 pMark = pMark->pNext; 49 } 50 DeleteDC(hdc_compatible); 51 }
第三個,顯示蘋果,直接貼圖即可
1 void ShowApple(HDC hdc) 2 { 3 HDC hdc_compatible; 4 hdc_compatible = CreateCompatibleDC(hdc); 5 SelectObject(hdc_compatible,Hbitmap_Apple); 6 BitBlt(hdc,apple.x*30,apple.y*30,30,30,hdc_compatible,0,0,SRCCOPY); 7 DeleteDC(hdc_compatible); 8 }
第四個,移動,win32中有個定時器的功能,每隔一段時間就向窗口發送定時器消息,那么蛇的坐標只要挨個代替前一個貼圖就會實現移動的效果,但是問題就在於是從蛇頭向蛇尾去遍歷坐標變化,還是從蛇尾向蛇頭變化,在幾次嘗試后發現還是蛇尾向蛇頭,因為如果蛇頭向蛇尾的話,蛇頭坐標都改變了,而下一個蛇身就找不到蛇頭的地址,就會出現蛇頭蛇尾分家的情況,這也就是為什么做成雙向鏈表的原因
1 void Move() 2 { 3 Snack *pMark = pEnd; 4 while(pMark != pHead) 5 { 6 pMark->x = pMark->pLast->x; 7 pMark->y = pMark->pLast->y; 8 pMark = pMark->pLast; 9 } 10 switch (fx) 11 { 12 case UP: 13 pHead->y--; 14 break; 15 case DOWN: 16 pHead->y++; 17 break; 18 case LEFT: 19 pHead->x--; 20 break; 21 case RIGHT: 22 pHead->x++; 23 break; 24 } 25 }
第五個,吃蘋果,也就是當蛇頭坐標和蘋果坐標重合的時候代表吃到蘋果了,一個判斷就搞定了
1 BOOL IfEatApple() 2 { 3 if(pHead->x == apple.x && pHead->y == apple.y) 4 return TRUE; 5 return FALSE; 6 }
第六個,生成新蘋果,利用隨機數給蘋果生成一個新的坐標,但是后期給室友測試的時候發現一個問題,這個蘋果坐標的隨機數在蛇長長之后會隨到蛇身上,這個是需要改進的地方,所以就得每隨機一次就得判斷是否和蛇的所有坐標重合,沒有的話,才把隨機的x,y拿出來去貼圖
void NewApple() { Snack *pMark = pHead; int x; int y; do { x = rand() % 18 + 1; y = rand() % 18 + 1; pMark = pHead; while(pMark) { if(pMark->x == x && pMark->y == y) break; pMark = pMark->pNext; } }while(pMark); apple.x = x; apple.y = y; }
第七個,蛇長個,添加鏈表即可,但是貼圖坐標只要給到窗口以外,這樣不影響游戲體驗
1 AddNode(-10,-10);
第八個,撞牆死亡,給蛇頭的坐標規定一個界限即可
1 BOOL IfBumpWall() 2 { 3 if(pHead->x == 0 || pHead->x == 19 || pHead->y == 0 || pHead->y == 19) 4 return TRUE; 5 return FALSE; 6 }
第九個,自咬死亡,判斷蛇頭坐標是否等於自身坐標即可
1 BOOL IfEatSelf() 2 { 3 Snack *pMark = pHead->pNext; 4 while(pMark) 5 { 6 if(pMark->x == pHead->x && pMark->y == pHead->y) 7 return TRUE; 8 pMark = pMark->pNext; 9 } 10 return FALSE; 11 }
至此,功能函數就完成了,接下來就是在回調函數里進行邏輯連接的過程了,在后期給多個朋友進行試玩測試的時候,也發現了不少的bug在此也一並寫出
1.鍵盤上下左右的鍵碼VK_加上對應的大寫英文
2.由於貼圖是連續的,上一次貼圖無法銷毀,那么就得用一層背景圖,一層蛇圖,一層蘋果圖的方式,實現游戲的實際效果
3.當蛇向右運行的時候,快速按下向上+向右的按鍵,會顯示游戲結束,針對這個問題,是這樣分析的,在一個定時器周期內,出現了兩次按鍵反饋,也就會變成向左,那么蛇就出現自咬的情況,處理的辦法就是人為的規定在一個定時器周期內只允許出現一次鍵盤消息,設置一個標記,沒進定時器的時候為TURE,進過定時器為FALSE,此時鍵盤內的第二次快速按下的消息就無效了
4.暴力玩法,當屏幕內蛇幾乎占滿時,窗口會出現閃爍的問題,在進行查閱后,發現是因為重繪是有一個刷新周期的,我的所有貼圖沒在一個周期內貼完,屏幕就會出現閃爍的現象,要想處理這個bug,可以用雙緩沖技術,也就是為當前HDC創建一個兼容性HDC,再為這個兼容性HDC,再創建一個兼容性HDC,兩個兼容性HDC之間進行多次貼圖操作,那么在一個重繪周期內,一次就把第一次的兼容性DC給貼上就可以了,滿足一個刷新周期內圖貼完的要求,具體實現的話,在以后深入學習c++的過程中繼續完善。
完整代碼如下(圖片素材在文件里,有興趣的可以自行下載):
1.貪吃蛇.rc
1 // Microsoft Visual C++ generated resource script. 2 // 3 #include "resource.h" 4 5 #define APSTUDIO_READONLY_SYMBOLS 6 ///////////////////////////////////////////////////////////////////////////// 7 // 8 // Generated from the TEXTINCLUDE 2 resource. 9 // 10 #include "afxres.h" 11 12 ///////////////////////////////////////////////////////////////////////////// 13 #undef APSTUDIO_READONLY_SYMBOLS 14 15 ///////////////////////////////////////////////////////////////////////////// 16 // 中文(簡體,中國) resources 17 18 #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS) 19 LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED 20 21 #ifdef APSTUDIO_INVOKED 22 ///////////////////////////////////////////////////////////////////////////// 23 // 24 // TEXTINCLUDE 25 // 26 27 1 TEXTINCLUDE 28 BEGIN 29 "resource.h\0" 30 END 31 32 2 TEXTINCLUDE 33 BEGIN 34 "#include ""afxres.h""\r\n" 35 "\0" 36 END 37 38 3 TEXTINCLUDE 39 BEGIN 40 "\r\n" 41 "\0" 42 END 43 44 #endif // APSTUDIO_INVOKED 45 46 47 ///////////////////////////////////////////////////////////////////////////// 48 // 49 // Bitmap 50 // 51 52 IDB_BITMAP1 BITMAP "..\\she\\背景.bmp" //得用相對路徑 53 IDB_BITMAP2 BITMAP "..\\she\\蘋果.bmp" 54 IDB_BITMAP3 BITMAP "..\\she\\蛇身.bmp" 55 IDB_BITMAP4 BITMAP "..\\she\\蛇頭0.bmp" 56 IDB_BITMAP5 BITMAP "..\\she\\蛇頭1.bmp" 57 IDB_BITMAP6 BITMAP "..\\she\\蛇頭2.bmp" 58 IDB_BITMAP7 BITMAP "..\\she\\蛇頭3.bmp" 59 #endif // 中文(簡體,中國) resources 60 ///////////////////////////////////////////////////////////////////////////// 61 62 63 64 #ifndef APSTUDIO_INVOKED 65 ///////////////////////////////////////////////////////////////////////////// 66 // 67 // Generated from the TEXTINCLUDE 3 resource. 68 // 69 70 71 ///////////////////////////////////////////////////////////////////////////// 72 #endif // not APSTUDIO_INVOKED
2.resource.h
1 //{{NO_DEPENDENCIES}} 2 // Microsoft Visual C++ 生成的包含文件。 3 // 供 貪吃蛇.rc 使用 4 // 5 #define IDB_BITMAP1 101 6 #define IDB_BITMAP2 102 7 #define IDB_BITMAP3 103 8 #define IDB_BITMAP4 104 9 #define IDB_BITMAP5 105 10 #define IDB_BITMAP6 106 11 #define IDB_BITMAP7 107 12 13 // Next default values for new objects 14 // 15 #ifdef APSTUDIO_INVOKED 16 #ifndef APSTUDIO_READONLY_SYMBOLS 17 #define _APS_NEXT_RESOURCE_VALUE 108 18 #define _APS_NEXT_COMMAND_VALUE 40001 19 #define _APS_NEXT_CONTROL_VALUE 1001 20 #define _APS_NEXT_SYMED_VALUE 101 21 #endif 22 #endif
3.Snack.c
1 #include <Windows.h> 2 #include <time.h> 3 #include "resource.h" 4 5 typedef struct NODE 6 { 7 int x; 8 int y; 9 struct NODE *pLast; 10 struct NODE *pNext; 11 }Snack,Apple; 12 13 Snack *pHead; 14 Snack *pEnd; 15 Apple apple = {5,6,NULL,NULL}; 16 enum FX {UP,DOWN,LEFT,RIGHT}; 17 enum FX fx = RIGHT; 18 BOOL g_flag = TRUE; //設置標記,避免在一個定時器周期內有兩次動作 19 20 HBITMAP Hbitmap_BackGroup; 21 HBITMAP Hbitmap_Apple; 22 HBITMAP Hbitmap_SnackHead; 23 HBITMAP Hbitmap_SnackHead_Up; 24 HBITMAP Hbitmap_SnackHead_Down; 25 HBITMAP Hbitmap_SnackHead_Left; 26 HBITMAP Hbitmap_SnackHead_Right; 27 28 void ShowBackGround(HDC hdc); //顯示背景 29 void AddNode(int x,int y); //添加蛇身 30 void ShowSnack(HDC hdc); //顯示蛇 31 void Move(); //移動蛇 32 void ShowApple(HDC hdc); //顯示蘋果 33 BOOL IfEatApple(); //判斷是否吃到蘋果 34 void NewApple(); //隨機出現新蘋果 35 BOOL IfBumpWall(); //判斷是否撞牆 36 BOOL IfEatSelf(); //判斷是否自咬 37 38 LRESULT CALLBACK MyWNDPROC(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam); 39 40 int CALLBACK WinMain( 41 HINSTANCE hInstance, 42 HINSTANCE hPrevInstance, 43 LPSTR lpCmdLine, 44 int nCmdShow 45 ) 46 { 47 HWND hwnd; 48 MSG msg; 49 //1.窗口設計 50 WNDCLASSEX ex; 51 ex.style = (UINT)NULL; 52 ex.cbSize = sizeof(ex); 53 ex.cbClsExtra = 0; 54 ex.cbWndExtra = 0; 55 ex.hInstance = hInstance; 56 ex.hIcon = NULL; 57 ex.hCursor = NULL; 58 ex.hbrBackground = CreateSolidBrush(RGB(0,255,0)); 59 ex.hIconSm = NULL; 60 ex.lpfnWndProc = &MyWNDPROC; 61 ex.lpszMenuName = NULL; 62 ex.lpszClassName = "aa"; 63 64 Hbitmap_BackGroup = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_BITMAP1)); 65 Hbitmap_Apple = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_BITMAP2)); 66 Hbitmap_SnackHead = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_BITMAP3)); 67 Hbitmap_SnackHead_Up = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_BITMAP5)); 68 Hbitmap_SnackHead_Down = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_BITMAP7)); 69 Hbitmap_SnackHead_Left = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_BITMAP6)); 70 Hbitmap_SnackHead_Right = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_BITMAP4)); 71 72 AddNode(5,5); 73 AddNode(4,5); 74 AddNode(3,5); 75 76 //2.注冊 77 RegisterClassEx(&ex); 78 //3.創建 79 hwnd = CreateWindow(ex.lpszClassName,"貪吃蛇",WS_OVERLAPPEDWINDOW,50,50,615,638,NULL,NULL,hInstance,NULL); 80 //4.顯示 81 ShowWindow(hwnd,SW_SHOW); 82 83 SetTimer(hwnd,1,120,NULL); 84 85 srand((unsigned int)time(0)); 86 87 //消息循環 88 while(GetMessage(&msg,NULL,0,0)) 89 { 90 TranslateMessage(&msg); 91 DispatchMessage(&msg); 92 } 93 94 return 0; 95 } 96 97 LRESULT CALLBACK MyWNDPROC(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam) 98 { 99 HDC hdc; 100 PAINTSTRUCT paintstruct; 101 switch(message) 102 { 103 case WM_CLOSE: 104 PostQuitMessage(0); 105 break; 106 case WM_PAINT: 107 hdc = BeginPaint(hWnd,&paintstruct); 108 ShowBackGround(hdc); 109 ShowSnack(hdc); 110 ShowApple(hdc); 111 112 EndPaint(hWnd,&paintstruct); 113 break; 114 case WM_TIMER: 115 g_flag = TRUE; 116 Move(); 117 if(IfBumpWall() || IfEatSelf()) 118 { 119 KillTimer(hWnd,1); 120 MessageBox(hWnd,"GAME OVER","提示",MB_OK); 121 } 122 if(IfEatApple()) //判斷是否吃到蘋果 123 { 124 NewApple(); //隨機一個新蘋果 125 AddNode(-10,-10); //長個 126 } 127 hdc = GetDC(hWnd); 128 ShowBackGround(hdc); //層層覆蓋 129 ShowSnack(hdc); 130 ShowApple(hdc); 131 ReleaseDC(hWnd,hdc); 132 break; 133 case WM_KEYDOWN: 134 if(g_flag == TRUE) 135 { 136 switch(wParam) 137 { 138 case VK_UP: 139 if(fx != DOWN) 140 { 141 fx = UP; 142 } 143 break; 144 case VK_DOWN: 145 if(fx != UP) 146 { 147 fx = DOWN; 148 } 149 break; 150 case VK_LEFT: 151 if(fx != RIGHT) 152 { 153 fx = LEFT; 154 } 155 break; 156 case VK_RIGHT: 157 if(fx != LEFT) 158 { 159 fx = RIGHT; 160 } 161 break; 162 } 163 } 164 g_flag = FALSE; 165 hdc = GetDC(hWnd); 166 ShowBackGround(hdc); 167 ShowSnack(hdc); 168 ShowApple(hdc); 169 ReleaseDC(hWnd,hdc); 170 break; 171 } 172 173 return DefWindowProc(hWnd,message,wParam,lParam); 174 } 175 void ShowBackGround(HDC hdc) 176 { 177 HDC hdc_compatible; 178 hdc_compatible = CreateCompatibleDC(hdc); //創建兼容性DC 179 SelectObject(hdc_compatible,Hbitmap_BackGroup); //為兼容性DC選擇背景位圖 180 BitBlt(hdc,0,0,600,600,hdc_compatible,0,0,SRCCOPY); //按像素點進行傳輸 181 DeleteDC(hdc_compatible); //刪除兼容性DC 182 return; 183 } 184 void AddNode(int x,int y) 185 { 186 Snack *pTemp = (Snack *)malloc(sizeof(Snack)); 187 pTemp->x = x; 188 pTemp->y = y; 189 pTemp->pLast = NULL; 190 pTemp->pNext = NULL; 191 192 if(pHead == NULL) 193 { 194 pHead = pTemp; 195 } 196 else 197 { 198 pEnd->pNext = pTemp; 199 pTemp->pLast = pEnd; 200 } 201 pEnd = pTemp; 202 } 203 void ShowSnack(HDC hdc) 204 { 205 Snack *pMark = pHead->pNext; 206 HDC hdc_compatible; 207 hdc_compatible = CreateCompatibleDC(hdc); 208 switch (fx) 209 { 210 case UP: 211 SelectObject(hdc_compatible,Hbitmap_SnackHead_Up); 212 break; 213 case DOWN: 214 SelectObject(hdc_compatible,Hbitmap_SnackHead_Down); 215 break; 216 case LEFT: 217 SelectObject(hdc_compatible,Hbitmap_SnackHead_Left); 218 break; 219 case RIGHT: 220 SelectObject(hdc_compatible,Hbitmap_SnackHead_Right); 221 break; 222 default: 223 break; 224 } 225 BitBlt(hdc,pHead->x*30,pHead->y*30,30,30,hdc_compatible,0,0,SRCCOPY); 226 227 while(pMark) 228 { 229 SelectObject(hdc_compatible,Hbitmap_SnackHead); 230 BitBlt(hdc,pMark->x*30,pMark->y*30,30,30,hdc_compatible,0,0,SRCCOPY); 231 pMark = pMark->pNext; 232 } 233 DeleteDC(hdc_compatible); 234 } 235 void Move() 236 { 237 Snack *pMark = pEnd; 238 while(pMark != pHead) 239 { 240 pMark->x = pMark->pLast->x; 241 pMark->y = pMark->pLast->y; 242 pMark = pMark->pLast; 243 } 244 switch (fx) 245 { 246 case UP: 247 pHead->y--; 248 break; 249 case DOWN: 250 pHead->y++; 251 break; 252 case LEFT: 253 pHead->x--; 254 break; 255 case RIGHT: 256 pHead->x++; 257 break; 258 } 259 } 260 void ShowApple(HDC hdc) 261 { 262 HDC hdc_compatible; 263 hdc_compatible = CreateCompatibleDC(hdc); 264 SelectObject(hdc_compatible,Hbitmap_Apple); 265 BitBlt(hdc,apple.x*30,apple.y*30,30,30,hdc_compatible,0,0,SRCCOPY); 266 DeleteDC(hdc_compatible); 267 } 268 BOOL IfEatApple() 269 { 270 if(pHead->x == apple.x && pHead->y == apple.y) 271 return TRUE; 272 return FALSE; 273 } 274 void NewApple() 275 { 276 Snack *pMark = pHead; 277 int x; 278 int y; 279 do 280 { 281 x = rand() % 18 + 1; 282 y = rand() % 18 + 1; 283 pMark = pHead; 284 while(pMark) 285 { 286 if(pMark->x == x && pMark->y == y) 287 break; 288 pMark = pMark->pNext; 289 } 290 }while(pMark); 291 apple.x = x; 292 apple.y = y; 293 } 294 BOOL IfBumpWall() 295 { 296 if(pHead->x == 0 || pHead->x == 19 || pHead->y == 0 || pHead->y == 19) 297 return TRUE; 298 return FALSE; 299 } 300 BOOL IfEatSelf() 301 { 302 Snack *pMark = pHead->pNext; 303 while(pMark) 304 { 305 if(pMark->x == pHead->x && pMark->y == pHead->y) 306 return TRUE; 307 pMark = pMark->pNext; 308 } 309 return FALSE; 310 }
2019-05-16 11:32:24 編程小菜鳥自我反省,望大佬能多提意見和建議,謝謝!!!