admin 管理员组

文章数量: 887021


2024年3月1日发(作者:numpy库文档)

一 游戏编程基础

1-概论

1.游戏的组成

游戏由剧情、图形图像、声音、文本等资源组成。

2.游戏设计与制作

设计与制作过程大致分为策划,美工,音效,程序,测试五部分。

策划:负责设计游戏的剧情、类别、玩法等,是游戏最重要的部分,直接决定了游戏的成功与否。

美工:负责绘制游戏中所需图形图像资源。

音效:负责制作游戏中所需的声音资源。

程序:负责将多媒体资源按照策划规定的方式组合起来,制作成最终产品-游戏。

测试:负责测试程序的稳定性、游戏的难度等。

我之前看过一本书,书中有这么一个比喻:如果拿游戏与人来类比的话,策划就是心脏,程序是骨骼,美工是皮肤,音效是衣服。

游戏编程,就是游戏设计与制作的程序部分。在我的整个笔记中,所探讨的核心内容,就是游戏的程序实现。

2-游戏程序组成

1.组成

主要由逻辑更新和画面渲染两部分组成,也可以说游戏程序就只干这两件事情。

逻辑更新:接收玩家的输入,更新敌人、玩家、世界等数据。

画面渲染:将游戏内容以图像的方式呈现出来。

2.程序流程

初始化数据-更新-渲染-释放资源。

3-Windows程序设计基础

我所使用到的技术都是基于windows操作系统的,在2D游戏编程方面,使用GDI(图像开发接口)来处理图形图像,虽然GDI的执行效率较低,但是相对于其他的开发包来说,它比较容易学习和理解,在我们学习阶段使用它没问题。当我们对游戏编程思想有了更深的了解的时候,可以使用其他开发包来处理图形图像,如DirectX 3D,OpenGL等。

1.程序入口WinMain

一个简单的windows程序。

[cpp] view plaincopy

1. #include

2. int WINAPI WinMain(HINSTANCE hInstance,

3. HINSTANCE hPrevInstance,

4. LPSTR lpCmdLine,

5. int nShowCmd )

6. {

7. MessageBox(NULL,

8. L"这是一个简单的windows应用程序!",

9. L"这是标题",

10. MB_OKCANCEL|MB_ICONINFORMATION);

11. return 0;

12. }

WinMain和C语言的main函数类似,都是程序的入口函数,由系统调用。

[cpp] view plaincopy

1. int WINAPI WinMain(

2. HINSTANCE hInstance, // handle to current instance

3. HINSTANCE hPrevInstance, // handle to previous instance

4. LPSTR lpCmdLine, // command line

5. int nCmdShow // show state

6. );

详细参数可以参看MSDN或去百科上看,写程序时,保持这个结构不变即可。最重要的参数是hInstance,为应用程序实例句柄,标识了当前应用程序的资源地址。在游戏编程中,经常会用到它,所以,我们经常会将该值保存起来,方便后面使用。

2.创建windows应用程序的流程

主函数(WinMain)->注册窗口类(RegisterClassEx)->创建窗口(CreateWindowEx) ->消息循环(MainLoop),处理窗口过程(WinProc)。其中,窗口过程在消息循环中被反复调用。以下是算法伪代码:

[cpp] view plaincopy

1. WinMain()

2. {

3. RegisterClassEx()

4. CreateWindowEx()

5. MainLoop()

6. }

7. MainLoop()

8. {

9. while(true)

10. {

11. WinProc()

12. }

13. }

3.注册窗口类RegisterClassEx

窗口类,即窗口的类型,它并不是指C++中的类(class)。告诉操作系统即将创建什么样的窗口。

[cpp] view plaincopy

1. ATOM RegisterClassEx(

2. CONST WNDCLASSEX *lpwcx // class data

3. );

4. typedef struct _WNDCLASSEX { //窗口类数据结构

5. UINT cbSize; //本结构大小

6. UINT style; //窗口类的样式

7. WNDPROC lpfnWndProc; //窗口过程函数指针

8. int cbClsExtra; //附加参数

9. int cbWndExtra; //附加参数

10. HINSTANCE hInstance; //应用程序实例句柄

11. HICON hIcon; //窗口图标

12. HCURSOR hCursor; //窗口光标

13. HBRUSH hbrBackground; //背景画刷

14. LPCTSTR lpszMenuName; //菜单名称

15. LPCTSTR lpszClassName; //窗口类名称

16. HICON hIconSm; //窗口小图标

17. } WNDCLASSEX, *PWNDCLASSEX;

4.创建窗口CreateWindowEx

[cpp] view plaincopy

1. HWND CreateWindowEx(

2. DWORD dwExStyle, // extended window style扩展窗口样式

3. LPCTSTR lpClassName, // registered class name已注册的窗口类名称

4. LPCTSTR lpWindowName,// window name窗口标题

5. DWORD dwStyle, // window style窗口样式

6. int x, // horizontal position of window坐标x

7. int y, // vertical position of window坐标y

8. int nWidth, // window width窗口宽度

9. int nHeight, // window height高度

10. HWND hWndParent, // handle to parent or owner window父窗口

11. HMENU hMenu, // menu handle or child identifier菜单句柄

12. HINSTANCE hInstance, // handle to application instance实例句柄

13. LPVOID lpParam // window-creation data附加参数

14. );

返回值是一个窗口句柄,句柄好比就是一个地址,标识了这个窗口的资源位置。

5.消息循环MainLoop

Windows程序都是基于消息机制的,所有的通信都是经过消息传递实现。

1) 消息MSG:

[cpp] view plaincopy

1. typedef struct tagMSG {

2. HWND hwnd; //窗口句柄

3. UINT message; //消息

4. WPARAM wParam; //参数

5. LPARAM lParam; //附加参数

6. DWORD time; //消息产生时间

7. POINT pt; //消息产生时的鼠标坐标

8. } MSG, *PMSG;

2) 获取消息,GetMessage与PeekMessage:

[cpp] view plaincopy

1. BOOL GetMessage(

2. LPMSG lpMsg, // message information

3. HWND hWnd, // handle to window

4. UINT wMsgFilterMin, // first message

5. UINT wMsgFilterMax // last message

6. );

7. BOOL PeekMessage(

8. LPMSG lpMsg, // message information

9. HWND hWnd, // handle to window

10. UINT wMsgFilterMin, // first message

11. UINT wMsgFilterMax, // last message

12. UINT wRemoveMsg // removal options

13. );

两者都是从消息队列中取消息,不同的是当消息队列为空时,两者的处理方式不一样,前者是等待,后者是继续执行。由于我们的游戏需要不断的更新和重绘,而不能等待,所以我们要选择后者。

3)翻译消息TranslateMessage

将消息翻译成可处理的格式。

[cpp] view plaincopy

1. BOOL TranslateMessage(

2. CONST MSG *lpMsg // message information

3. );

4)转发消息DispatchMessage

将消息转发给窗口过程。

[cpp] view plaincopy

1. LRESULT DispatchMessage(

2. CONST MSG *lpmsg // message information

3. );

6.窗口过程WinProc

用户处理消息的函数,该函数在消息循环中被系统函数所调用。该函数的结构必须与下面这个函数类型相同!名称可以不同。

[cpp] view plaincopy

1. LRESULT CALLBACK WindowProc(

2. HWND hwnd, // handle to window

3. UINT uMsg, // message identifier

4. WPARAM wParam, // first message parameter

5. LPARAM lParam // second message parameter

6. );

7.例:创建窗口

[cpp] view plaincopy

1. #include

2. //窗口过程

3. LRESULT CALLBACK WndProc(

4. HWND hwnd, // handle to window

5. UINT uMsg, // message identifier

6. WPARAM wParam, // first message parameter

7. LPARAM lParam // second message parameter

8. )

9. {

10. switch(uMsg)

11. {

12. case WM_DESTROY://窗口销毁消息。按下窗口的叉时会产生。

13. PostQuitMessage(0);//发送退出程序消息WM_QUIT。

14. break;

15. case WM_LBUTTONDOWN:

16. MessageBox(hwnd,L"正处理鼠标左键单击消息",L"这是标题",MB_OK);

17. break;

18. default:

19. return DefWindowProc(hwnd,uMsg,wParam,lParam);//调用默认窗口过程

20. }

21. return 0;

22. }

23. //主函数

24. int WINAPI WinMain(

25. HINSTANCE hInstance, // handle to current instance

26. HINSTANCE hPrevInstance, // handle to previous instance

27. LPSTR lpCmdLine, // command line

28. int nCmdShow // show state

29. )

30. {

31. //MessageBox(NULL,L"这是消息框",L"这是标题",MB_OKCANCEL);

32. WNDCLASSEX wcx;//窗口类

33. memset(&wcx,0,sizeof(WNDCLASSEX));

34. = sizeof(WNDCLASSEX);//窗口类大小

35. = CS_CLASSDC;//窗口类风格

36. kground = (HBRUSH)GetStockObject(WHITE_BRUSH);//获得系统画刷(白色)

37. r = LoadCursor(NULL,IDC_HAND);//加载系统光标

38. m = = LoadIcon(NULL,IDI_APPLICATION);//加载系统图标

39. nce = hInstance;//应用程序实例句柄

40. /*字符串前面‘L’的意思是,该字符串为Unicode编码格式,

41. 不是默认的ASCII格式。如果要改成ASII格式,可以修改项目属性。*/

42. assName = L"WndClass";//窗口类名称

43. dProc = (WNDPROC)WndProc;//窗口过程

44. //注册窗口类

45. RegisterClassEx( &wcx );

46. //窗口过程

47. HWND hWnd = CreateWindowEx(0,L"WndClass",L"这是窗口标题",

48. WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,

49. 0,0,640,480,NULL,NULL,hInstance,NULL);

50. //显示窗口

51. ShowWindow(hWnd,SW_SHOWNORMAL);

52. //更新窗口,即发送重绘消息

53. UpdateWindow(hWnd);

54. MSG message;//消息结构

55. while(true)

56. {

57. //从消息队列中取消息

58. if(PeekMessage(&message,NULL,0,0,PM_REMOVE))

59. {

60. if (e == WM_QUIT)//跳出循环,退出程序。

61. {

62. break;

63. }

64. //翻译消息

65. TranslateMessage(&message);

66. //发送给窗口过程

67. DispatchMessage(&message);

68. }

69. Sleep(1);//暂停ms,即放弃CPU时间片ms,避免浪费CPU。

70. }

71. return 0;

72. }

8.使用VS2008-VC9创建Win32应用程序

菜单:文件->新建->项目,在弹出的对话框中选Win32项目,截图如下:

输入名称,选择好路径后,点确定,然后再点下一步,来到如下步骤,

点选windows应用程序,勾选空项目,然后完成。这样一个空的Win32应用程序框架就建好了,接着给框架添加C++源文件(cpp文件)。写代码,调试,运行,OK。

如果使用的是VC6.0,操作方法跟这个也很类似,但是VC6中的默认编码格式是ASCII,所以字符串前面不需要加„L‟。

注意,不管是VS还是VC6,都要建立win32应用程序项目,不要创建控制台应用程序!否则无法通过链接。

修改编码:在解决方案上点右键->属性,在弹出来的对话框中,将字符集改为“使用多字节字符集”,之后程序的编码将会变成ASCII格式。如果是初学者的话,建议将字符集改为“使用多字节字符集”。

9.小节

创建窗口的过程比较麻烦,但是大部分代码是固定不变的。所以,我们可以将不变的部分封装起来,今后使用到的时候,直接拿过来使用,而不必再写一遍,可以达到一劳永逸的效果!

WindowsAPI比较多,对于这些API,我们只用知道它的用法就可以了,具体的参数我们可以去MSDN(微软软件开发文档)查,所以建议大家一定要装个MSDN不管哪个版本,都必须要有一个。

刚开始学习的时候,新概念会有很多,如果不能理解的话,可以先忽略它,把它当一个黑盒子使用,用的时间长了,就可以理解了。

二 游戏编程起步

1.一个简单的游戏-贪吃蛇

1.贪吃蛇游戏剖析

1)游戏的目标。在不被撞死的前提下,吃掉奖子增加自己的长度,来完成升级。

2)游戏中的物体。蛇,墙壁,奖子。

3)动作。蛇移动,蛇吃奖子,蛇增加长度。

2.数据结构与算法分析

1)数据结构。简单起见,所有物体都用方块拼接。

则蛇可以使用一个一维数组描述,数组的每个单元描述了蛇块的状态,如方向;可使用一个二维数组来描述地面情况,不可通过的地方为墙壁设置为1,可通过的地方设置为0;奖子,就是一个特殊的方块。

2)算法。

注意观察蛇的特点。将蛇肢解为方块,则会发现每个方块的移动都依赖于它前面方块上一次的移动状态(第一个方块由玩家控制)。

如图,1是蛇头,4是尾,蓝色箭头是原来移动方向,红色箭头是玩家控制的方向(按了下键)。移动方向从1~4。(a)->(b) 下右右右,(b)->(c)下下右右。

根据这个规律,我们可以总结出一个基本算法:用数组来存贮蛇块,每个蛇块结点包含的信息有当前的移动方向,那么更新蛇的状态时,从尾部到头部进行处理。

[cpp] view plaincopy

1. for i=n-1 to 1

2. {

3. dir(i) = dir(i-1)

4. pos(i) += dir(i)

5. }

6. if dirKeyDown

7. {

8. dir(0) = k

9. }

10. pos(0) += dir(0)

这样,最复杂的部分就解决了。当然这不是最好的算法,如果我们再仔细观察下,就会发现这样的规律,蛇每次移动的时候,都只是头和尾发生了变化,那么,每次更新蛇的时候,我们只需要将尾部的蛇块移到头部相应的位置,不就更简单吗?答案是肯定的。这就是算法的魅力!只要我们勤于动脑筋,总会发现一些更好的解决办法。

[cpp] view plaincopy

1. pos(n-1) = pos(0)+dir

2. insert(n-1) before pos(0)

算法的复杂度立即从O(n)变到了O(1)!而且我们还会发现,我们只需要记录一个方向就可以了,则空间复杂度也因此降低了。

3)地图。描述了地面信息。

我们的贪吃蛇游戏地图信息很简单,总共有3类物体会站到地面上:墙壁,蛇,奖子。在每次更新的时候,我们将3类物体的信息按类别填充到地图中。如,墙壁的位置填1,奖子的位置填2,蛇的位置填3(每个蛇块都填),没有东西的地方填0。然后,将这个填满0,1,2,3的二维数组,交给渲染系统。

到此,我们的幕后操作就算基本完成,剩下的就是些细节,等到编码的时候在详细处理。

3.渲染地图数据描述

渲染贪吃蛇游戏其实也很简单,把地图中1的部分涂成蓝色,2的部分涂成红色,3的部分涂成绿色,这将会是一个什么样的效果呢?看下图:

怎么样,有贪吃蛇游戏的感觉吗?再看看下图:

简直是完美!

游戏编程笔记-起步(二)

2.在窗口上绘图

看了我上面的分析,我想,很多朋友都迫不及待的想学这部分知识,原因是上面的知识实在太简单了,只要稍懂点算法的同胞们都可以看懂。关键是大家苦于英雄无勇武之地啊!学了n年的算法,做了n到算法题,却写不出一个如此简单的小游戏。

那好,让我们开始我们真正的游戏编程之旅吧!

1.概述

在Windows上绘图方式,跟美术大师绘图差不多。美术绘画,首先要具备以下工具:画板,画布,画笔,画刷。同样,Windows上也有相关的概念。绘图设备DeviceContext(DC),位图Bitmap,画笔Pen,画刷brush。他们一一对应。

2.画板

在Windows中被称作设备上下文(Device Context,DC),我习惯称之为绘图设备。但是Windows的“画板”与美术大师手中的“画板”不一样,Windows中的“画板”实质上是一个工具的集合体,将画布、画笔、画刷、绘画方式全部综合管理起来,然后,所有的绘画操作都将在这上面进行。

1)系统绘图设备。系统有默认的绘图,我们可以直接使用它进行一系列的绘图操作。

获取系统绘图设备:

[cpp] view plaincopy

1. HDC GetDC(

2. HWND hWnd // 窗口句柄。即指定获得那个窗口的绘图设备。

3. );

如:

[cpp] view plaincopy

1. HDC hDC = GetDC(g_hWnd);

2. Rectangle(hDC,0,0,100,100);//绘制矩形

3. ....

使用完毕后要归还给窗口:

[cpp] view plaincopy

1. int ReleaseDC(

2. HWND hWnd, // handle to window

3. HDC hDC // handle to DC

4. );

2)创建画板。通常我们不直接使用系统画板,因为系统画板直接和显示器关联,在上面一边画,就会一边显示到窗口,由于在绘制的时候,中间有时间差(我们可能经过若干次绘制,才绘制完一个地图),而游戏程序需要反复的擦除、重绘,所以常常会造成画面剧烈闪烁。为了解决这个问题,我们会创建一个辅助画板,在幕后绘制完毕后,一次性的将它显示到屏幕上,因为显示的时间非常快,所以就看不到闪烁。

创建辅助绘图设备:

[cpp] view plaincopy

1. HDC CreateCompatibleDC(

2. HDC hdc // 指向一个已经存在的DC,如果该值传入0,则系统会创建一个存贮DC。

3. );

存贮DC(辅助绘图设备),在创建完毕后,它的大小只有一个像素,我们没办法直接往他上面直接绘图,我们还需要往它上面贴一张画布。

使用完毕后记得要释放资源:

[cpp] view plaincopy

1. BOOL DeleteDC(

2. HDC hdc // handle to DC

3. );

例:

[cpp] view plaincopy

1. HDC memDC = CreateCompatibleDC(0); //创建辅助绘图设备

2. SelectObject(memDC,hBitmap); //将画布贴到绘图设备上

3. Rectangle(memDC,x1,y1,x2,y2); //绘制矩形

4. HDC hDC = GetDC(g_hWnd); //获得系统绘图设备

5. copy memDC to hDC //复制到系统设备上显示

6. ReleaseDC(g_hWnd,hDC); //归还系统绘图设备

7. DeleteDC(memDC); //释放辅助绘图设备

3.画布

画布其实就是位图。位图的作用非常强大,在此我只介绍它的普通用法——充当画布。其他作用,后面再介绍。

创建掩码位图(画布):

[cpp] view plaincopy

1. HBITMAP CreateCompatibleBitmap(

2. HDC hdc, //指向绘图设备,最好是系统的绘图设备

3. int nWidth, // 位图宽度

4. int nHeight // 位图高度

5. );

释放资源:

[cpp] view plaincopy

1. BOOL DeleteObject(

2. HGDIOBJ hObject // handle to graphic object

3. );

4.画笔

画笔就不用解释了,Windows的画笔和美术大师的差不多。

创建画笔:

[cpp] view plaincopy

1. HPEN CreatePen(

2. int fnPenStyle, // 画笔风格。直线:PS_SOLID,点线:PS_DOT

3. int nWidth, // 画笔宽度。决定了画出来的线条粗细

4. COLORREF crColor // 画笔颜色

5. );

释放资源:

[cpp] view plaincopy

1. BOOL DeleteObject(

2. HGDIOBJ hObject // handle to graphic object

3. );

颜色:可由红绿蓝三种颜色按程度混合起来构成。

如COLORREF cr = RGB(255,0,255),此时cr是粉红色(红色+蓝色)。COLORREF实际上是无符号长整型的别名,RGB将红绿蓝三个数值整合成一个无符号长整型数。

[cpp] view plaincopy

1. COLORREF RGB(

2. BYTE byRed, //红色分量0~255,值越大表面程度越深。

3. BYTE byGreen, // 绿色分量0~255

4. BYTE byBlue // 蓝色分量0~255

5. );

5.画刷

[cpp] view plaincopy

1. 创建画刷:

2. HBRUSH CreateSolidBrush(

3. COLORREF crColor // brush color value

4. );

5. 释放资源:

6. BOOL DeleteObject(

7. HGDIOBJ hObject // handle to graphic object

8. );

6.绘画

画布、画笔、画刷创建好之后,要是分别使用API,将它们选入绘图设备。当然,绘图设备,有自己的默认画刷和画笔。

[cpp] view plaincopy

1. HGDIOBJ SelectObject(

2. HDC hdc, // 直线绘图设备

3. HGDIOBJ hgdiobj // 指向GDI对象。就是画刷、画笔、位图等。

4. );

返回值是旧有的相关内容。

一些常用绘图API:

1)绘制矩形。

[cpp] view plaincopy

1. BOOL Rectangle(

2. HDC hdc, // 绘图设备

3. int nLeftRect, // 矩形区域左上角x坐标

4. int nTopRect, // 矩形区域左上角y坐标

5. int nRightRect, //矩形区域右下角x坐标

6. int nBottomRect // 矩形区域右下角y坐标

7. );

2)填充区域。用画刷来填充区域,经常用来擦除整个窗口。

[cpp] view plaincopy

1. int FillRect(

2. HDC hDC, // 绘图设备

3. CONST RECT *lprc, // 矩形区域

4. HBRUSH hbr // 填充画刷

5. );

[cpp] view plaincopy

1. 区域结构:

2. typedef struct _RECT {

3. LONG left; //矩形左边(左上角x坐标)

4. LONG top; //矩形顶边(左上角y坐标)

5. LONG right; //矩形右边(右下角x坐标)

6. LONG bottom; //矩形底边(右下角y坐标)

7. } RECT, *PRECT;

[cpp] view plaincopy

1. 获得窗口客户区区域:

2. BOOL GetClientRect(

3. HWND hWnd, // handle to window

4. LPRECT lpRect // client coordinates

5. );

3)绘图设备之间的拷贝。

[cpp] view plaincopy

1. BOOL BitBlt(

2. HDC hdcDest,//目标绘图设备

3. int nXDest, // 目标区域左上角x坐标

4. int nYDest, //目标区域左上角y坐标

5. int nWidth, // 目标区域宽度

6. int nHeight,// 目标区域高度

7. HDC hdcSrc, // 源绘图设备

8. int nXSrc, // 源区域左上角x坐标

9. int nYSrc, // 源区域左上角y坐标

10. DWORD dwRop // 操作码。常用SRCCOPY(复制)。

11. );

可以简单的理解这个API:将源绘图设备拷贝给目标绘图设备。从幕后画布拷贝给前景画布。从幕后绘图设备拷贝给显示器。拷贝的区域,就是上面指定的。

如:BitBlt(hDC,0,0,g_nWidth,g_nHeight,memDC,0,0,SRCCOPY);

4)其他常用API

文字处理

创建字体:CreateFont,CreatePointFont,SelectObject(.,font)。

显示文本:Textout,DrawText

设置文本颜色:SetTextColor ,GetTextColor

设置背景:SetBkColor,SetBkMode(设置背景模式,TRANSPARENT为透明)

绘制

点:SetPixel,SetPixelV,GetPixel。

线:MoveToEx-LinTo。

圆:Ellipse

多边形:Polygon

弧:Arc

位图处理

从文件加载位图/图标/光标:LoadImage

从资源加载位图:LoadBitmap

获得位图信息:GetObject

绘图设备间的拷贝复制

直接模式:BitBlt

拉伸拷贝:StretchBlt

透明处理:TranparentBlt,需要加 库。

半透明处理(Alpha混合):AlpaBlend

其他

获得系统属性:GetSystemMetrics,一些很重要的信息。

获得鼠标屏幕坐标:GetCursorPos,需使用坐标转换转换为窗口坐标

坐标转换:ClientToScreen,ScreenToClient

获得键盘键状态:GetAsyncKeyState

获得整个键盘信息:GetKeyboardState

消息框:MessageBox

7.样例:绘制地图网格

我将上第一章的,创建窗口代码用C语言格式稍微封装了下。核心代码如下:

[cpp] view plaincopy

1. #include "app.h"

2.

3. int g_nWidth = 600;//窗口宽度

4. int g_nHeight = 480;//窗口高度

5.

6. void init()//游戏初始化

7. {

8. }

9.

10. void update()//逻辑更新

11. {

12. }

13.

14. void render()//画面渲染

15. {

16. HDC hDC = GetDC(getHWnd()); //获得系统绘图设备

17.

18. HDC memDC = CreateCompatibleDC(0); //创建辅助绘图设备

19.

20. HBITMAP bmpBack = CreateCompatibleBitmap(hDC,g_nWidth,g_nHeight);//创建掩码位图(画布)

21. SelectObject(memDC,bmpBack); //将画布贴到绘图设备上

22.

23. HPEN penBack = CreatePen(PS_SOLID,1,RGB(255,0,255));//创建画笔

24. SelectObject(memDC,penBack); //将画笔选到绘图设备上

25.

26. HBRUSH brushBack = CreateSolidBrush(RGB(255,255,255));//创建画刷

27. SelectObject(memDC,brushBack); //将画刷选到绘图设备上

28.

29. //擦除背景

30. RECT rcClient;//区域结构

31. GetClientRect(getHWnd(),&rcClient);//获得客户区域

32. HBRUSH brushTemp = (HBRUSH)GetStockObject(WHITE_BRUSH);//获得库存物体,白色画刷。

33. FillRect(memDC,&rcClient,brushTemp);//填充客户区域。

34. //////////////////////////////////////////////////////////////////////////

35. HBRUSH brushObj = CreateSolidBrush(RGB(0,255,0));//创建物体画刷

36. //绘制维网格,矩形画法。

37. int dw = 30;

38. int rows = g_nHeight/dw;

39. int cols = g_nWidth/dw;

40. for (int r=0; r

41. {

42. for (int c=0; c

43. {

44. if (r == c)

45. {

46. SelectObject(memDC,brushObj);

47. }

48. else

49. {

50. SelectObject(memDC,brushBack);

51. }

52. Rectangle(memDC,c*dw,r*dw,(c+1)*dw,(r+1)*dw);

53. }

54. }

55.

56. DeleteObject(brushObj);

57. //////////////////////////////////////////////////////////////////////////

58. BitBlt(hDC,0,0,g_nWidth,g_nHeight,memDC,0,0,SRCCOPY);//复制到系统设备上显示

59. DeleteObject(penBack); //释放画笔资源

60. DeleteObject(brushBack);//释放画刷资源

61. DeleteObject(bmpBack); //释放位图资源

62. DeleteDC(memDC); //释放辅助绘图设备

63. ReleaseDC(getHWnd(),hDC); //归还系统绘图设备

64. Sleep(1);

65. }

66.

67. void clear()//资源释放

68. {

69. }

70.

71.

72. //主函数

73. int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE , LPSTR , int )

74. {

75. if(!initApp(hInstance,L"贪吃蛇游戏",g_nWidth,g_nHeight))

76. {

77. return 0;

78. }

79.

80. //初始化游戏

81. init();

82.

83. //游戏循环

84. mainLoop();

85.

86. //释放资源

87. clear();

88.

89. return 0;

90. }

3.贪吃蛇游戏实现

代码下载:/detail/you_lan_hai/3738025

核心代码实现如下,算法描述见 “起步(一)”:

[cpp] view plaincopy

1. #include "app.h"

2. #include

3. #include

4.

5. int g_map[100][100];//2维地图

6.

7. int g_nWidth = 610; //窗口宽度

8. int g_nHeight = 514;//窗口高度

9.

10. int g_rows = 0; //地图行数

11. int g_cols = 0; //地图列数

12. int g_nSize = 30; //地图方格尺寸

13.

14. struct Point//二维点

15. {

16. int r;//行

17. int c;//列

18. };

19.

20. Point g_snake[1000]; //蛇

21. int g_nLength = 0; //蛇的长度

22. int g_nSpeed;

23. bool g_bLive = true; //是否存活

24. int g_nSnakeDir = 0; //蛇的当前移动方向。0-左,1-右,2-上,3-下

25.

26. const Point g_direction[4] = {{0,-1},{0,1},{-1,0},{1,0}};//四个方向运动增量。

27.

28. Point g_prizePos; //奖子坐标

29.

30. bool g_bPause = true; //是否暂停

31.

32. HBRUSH g_brushs[4]; //背景画刷

33.

34.

35. void setSnakeDir(int dir)//设置蛇的运动方向。

36. {

37. if ((dir==0 && g_nSnakeDir==1)

38. || (dir==1 && g_nSnakeDir==0)

39. || (dir==2 && g_nSnakeDir==3)

40. || (dir==3 && g_nSnakeDir==2))//反方向移动则令他暂停

41. {

42. g_bPause = true;

43. return;//直接返回,什么也不做。

44. }

45. else

46. {

47. g_bPause = false;

48. g_nSnakeDir = dir;

49. }

50. }

51.

52. void resetLevel()//重置关卡

53. {

54. /*初始化地图。将四边初始为障碍,即不可通过。

55. * 0-可通过,1-障碍,2-奖子,3-蛇

56. */

57. memset(g_map,0,sizeof(g_map));

58. for (int r=0; r

59. {

60. g_map[r][0] = 1;

61. g_map[r][g_cols-1] = 1;

62. }

63. for (int c=0; c

64. {

65. g_map[0][c] = 1;

66. g_map[g_rows-1][c] = 1;

67. }

68.

69. g_nSpeed = 500;

70.

71. g_bPause = true;//暂停

72. g_nSnakeDir = 1;//向右。

73. g_bLive = true;

74. g_nLength = 3;//蛇初始有3个结点

75. g_snake[0].r = 1;//蛇头

76. g_snake[0].c = 3;

77. g_snake[1].r = 1;

78. g_snake[1].c = 2;

79. g_snake[2].r = 1;

80. g_snake[2].c = 1;

81. //把初始状态写入地图

82. for (int i=0; i

83. {

84. g_map[g_snake[i].r][g_snake[i].c] = 3;

85. }

86.

87. //初始化奖子坐标

88. g_prizePos.r = 1;

89. g_prizePos.c = g_cols-2;

90. }

91.

92. void init()//游戏初始化

93. {

94. g_brushs[0] = CreateSolidBrush(RGB(255,255,255));//白色画刷

95. g_brushs[1] = CreateSolidBrush(RGB(0,0,255));//绘制障碍画刷

96. g_brushs[2] = CreateSolidBrush(RGB(255,0,0));//绘制奖子画刷

97. g_brushs[3] = CreateSolidBrush(RGB(0,255,0));//绘制蛇画刷

98.

99. srand(GetTickCount());

100.

101. //矫正窗口高度、宽度数据

102. RECT rc;

103. GetClientRect(getHWnd(),&rc);

104. g_nWidth = - ;

105. g_nHeight = - ;

106.

107. //计算行列值

108. g_rows = g_nHeight / g_nSize;

109. g_cols = g_nWidth / g_nSize;

110.

111. resetLevel();

112. }

113.

114. void update()//逻辑更新

115. {

116. //时钟控制

117. static int oldTime = 0;

118. int curTime = GetTickCount();

119. if (curTime - oldTime < g_nSpeed)

120. {

121. return ;

122. }

123. oldTime = curTime;

124. //////////////////////////////////////////////////////////////////////////

125. static int lastOcurTime = 0;//奖子上次出现的时间

126.

127. if(curTime - lastOcurTime > 10000)//每隔10s更新一次位置。

128. {

129. //lastOcurTime不为-1表示,没有被吃掉,则将原来位置的奖子从地图擦掉。

130. if (lastOcurTime != -1)

131. {

132. g_map[g_prizePos.r][g_prizePos.c] = 0;

133. }

134. lastOcurTime = curTime;

135.

136. //随机产生奖子位置。

137. bool flag = true;

138. while(flag)

139. {

140. g_prizePos.r = rand()%(g_rows-2)+1;

141. g_prizePos.c = rand()%(g_cols-2)+1;

142. if (g_map[g_prizePos.r][g_prizePos.c] == 0)//符合条件

143. {

144. flag = false;

145. g_map[g_prizePos.r][g_prizePos.c] = 2;

146. }

147. }

148. }

149.

150. if (!g_bLive || g_bPause)//死亡或暂停

151. {

152. return ;

153. }

154.

155. //先判断是否可走,即是否撞死。

156. Point newHead;

157. newHead.r = g_snake[0].r + g_direction[g_nSnakeDir].r;

158. newHead.c = g_snake[0].c + g_direction[g_nSnakeDir].c;

159. if (g_map[newHead.r][newHead.c]==1 || g_map[newHead.r][newHead.c]==3)

160. {

161. g_bLive = false;

162. if(IDYES == MessageBox(getHWnd(),L"你撞死啦!是否重新来过?",

163. L"撞死提示",MB_YESNO))

164. {

165. resetLevel();

166. }

167. else

168. {

169. PostQuitMessage(0);

170. }

171. return ;

172. }

173. else if (g_map[newHead.r][newHead.c] == 2)//遇到奖子

174. {

175. ++g_nLength ;

176. lastOcurTime = -1;

177. g_nSpeed -= 10;

178. }

179. else

180. {

181. //撤销尾部在地图上遗留数据

182. g_map[g_snake[g_nLength-1].r][g_snake[g_nLength-1].c] = 0;

183. }

184.

185. memmove(g_snake+1,g_snake,sizeof(Point)*(g_nLength-1));//移动蛇的中间数据

186. g_snake[0].r = newHead.r;

187. g_snake[0].c = newHead.c;

188.

189. //添加蛇头数据

190. g_map[g_snake[0].r][g_snake[0].c] = 3;

191. //////////////////////////////////////////////////////////////////////////

192. }

193.

194. void render()//画面渲染

195. {

196. HDC hDC = GetDC(getHWnd()); //获得系统绘图设备

197.

198. HDC memDC = CreateCompatibleDC(0); //创建辅助绘图设备

199.

200. HBITMAP bmpBack = CreateCompatibleBitmap(hDC,g_nWidth,g_nHeight);//创建掩码位图(画布)

201. SelectObject(memDC,bmpBack); //将画布贴到绘图设备上

202.

203. HPEN penBack = CreatePen(PS_SOLID,1,RGB(255,0,255));//创建画笔

204. SelectObject(memDC,penBack); //将画笔选到绘图设备上

205.

206. //擦除背景

207. RECT rcClient;//区域结构

208. GetClientRect(getHWnd(),&rcClient);//获得客户区域

209. HBRUSH brushTemp = (HBRUSH)GetStockObject(WHITE_BRUSH);//获得库存物体,白色画刷。

210. FillRect(memDC,&rcClient,brushTemp);//填充客户区域。

211. //////////////////////////////////////////////////////////////////////////

212.

213. //绘制2维网格,矩形画法。

214. for (int r=0; r

215. {

216. for (int c=0; c

217. {

218. SelectObject(memDC,g_brushs[g_map[r][c]]);

219. Rectangle(memDC,c*g_nSize,r*g_nSize,(c+1)*g_nSize,(r+1)*g_nSize);

220. }

221. }

222.

223. //////////////////////////////////////////////////////////////////////////

224. BitBlt(hDC,0,0,g_nWidth,g_nHeight,memDC,0,0,SRCCOPY);//复制到系统设备上显示

225. DeleteObject(penBack); //释放画笔资源

226. DeleteObject(bmpBack); //释放位图资源

227. DeleteDC(memDC); //释放辅助绘图设备

228. ReleaseDC(getHWnd(),hDC); //归还系统绘图设备

229.

230. Sleep(10);

231. }

232.

233. void clear()//资源释放

234. {

235. //释放画刷资源

236. for (int i=0; i<4; ++i)

237. {

238. if(g_brushs != NULL)

239. {

240. DeleteObject(g_brushs[i]);

241. g_brushs[i] = NULL;

242. }

243. }

244. }

245.

246. /*窗口过程。如果没有处理消息请返回0,否则返回1。*/

247. LRESULT wndProc(HWND hwnd,UINT uMsg, WPARAM wParam,LPARAM lParam )

248. {

249. switch(uMsg)

250. {

251. case WM_KEYDOWN:

252. {

253. switch(wParam)

254. {

255. case VK_LEFT: setSnakeDir(0); break; //向左

256. case VK_RIGHT: setSnakeDir(1); break; //向右

257. case VK_UP : setSnakeDir(2); break; //向上

258. case VK_DOWN: setSnakeDir(3); break; //向下

259. case VK_ESCAPE: DestroyWindow(hwnd);break;

260. }

261. }

262. break;

263. default:

264. return 0;

265. }

266. return 1;

267. }

268.

269. //主函数

270. int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE , LPSTR , int )

271. {

272. if(!initApp(hInstance,L"贪吃蛇游戏",g_nWidth,g_nHeight))

273. {

274. return 0;

275. }

276.

277. //初始化游戏

278. init();

279.

280. //游戏循环

281. mainLoop();

282.

283. //释放资源

284. clear();

285.

286. return 0;

287. }


本文标签: 游戏 绘图 设备 消息 系统