Win32小游戏--贪吃蛇
近日里学习了关于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 编程小菜鸟自我反省,望大佬能多提意见和建议,谢谢!!!