近日裡學習了關于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 程式設計小菜鳥自我檢討,望大佬能多提意見和建議,謝謝!!!