其他分享
首页 > 其他分享> > Win32小游戏--贪吃蛇

Win32小游戏--贪吃蛇

作者:互联网

近日里学习了关于win32编程的相关知识,利用这些知识制作了一款贪吃蛇小游戏,具体细节还是分模块来叙述

前期准备:在网上找到一些贪吃蛇的游戏素材图片,以及具体的逻辑框图

在正式写功能之前,先把一系列环境配置好,配置环境总体来说分为以下几步:

  1. 图片转化为bmp格式( Bitmap )二进制流
  2. 将图片加载到内存中,在加载内存中也分为三步

 

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));

现在就正式进入编写小游戏的阶段

先来分析一下贪吃蛇游戏的功能

  1. 显示背景
  2. 显示蛇
  3. 显示苹果
  4. 移动
  5. 吃苹果
  6. 生成新苹果
  7. 蛇长个
  8. 撞墙死亡
  9. 自咬死亡

逻辑上还是很清晰很简单的,接下来就是按功能实现

第一个,显示背景,首先一打开游戏就会有背景,那么只有重绘能实现,想要画图的话,必须获取窗口环境句柄(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 编程小菜鸟自我反省,望大佬能多提意见和建议,谢谢!!!

 

 

标签:compatible,30,Win32,pHead,hdc,Hbitmap,贪吃蛇,小游戏,pMark
来源: https://www.cnblogs.com/xgmzhna/p/10874640.html