您的位置:首页 > 其它

GDI初窥--一个简单的打球游戏

2010-10-17 20:20 190 查看
开篇前言:

关注游戏编程很久了,自己从未还写过真正意义上的游戏,即使是DEMO也没有。书中的例子倒是照着敲过几个,但由于本人学习效率比较低,归纳能力差,加之惰性很重,从未写过心得。这算是第一篇“技术性”的“代码描摹”后的学习心得,以后希望能坚持写下去,很多前辈都教导我们:程序员要经常做经验总结。由于是第一篇,技术含量相当低,见笑了。
正文:
前几天把书上的GDI看完了,于是昨晚把书上的一个简单例子敲了一遍——一个简单的打球游戏,今天花了一个上午时间把代码进行了整理。作者可能是为了让读者更好的理解游戏逻辑和GDI的基础原理,很多地方结构不是很好,我按自己的编码习惯进行了整理,并修改了几个细节。
这个游戏由两个文件:simpletennis.h和simpletennis.cpp。前者存放需要引用的函数库、全局变量、函数声明。后者主要包括了游戏的逻辑和执行。(这些是废话,为下文理清思路而已)。
下面是整个代码的结构,前四步包含了一个windows窗口程序基本流程:
1、
int WINAPI
WinMain(HINSTANCE,hInstance,
HINSTANCE hPrevInstace,
LPSTR lpCmLine,
int nCmdShow)
该函数相当于main()函数,是程序的入口。
其中,WINAPI也可以用PASCAL代替(作者源代码中使用了后者)。他俩的区别是参数的传递约定不同:前者函数参数从右向左,后者反之。前者是WIN32的默认约定原则;后者是WIN16的约定,它的代码量会较小。在WIN32下两者可以通用,个人觉得使用WINAPI较好。
2、
设计窗口类,注册窗口类、实例化窗,更新,显示(基本流程,不多赘述)
3、
消息循环:
while(alive)
{
if(PeekMessage(&msg,NULL,0,0,PM_REMOVE))
{
if(msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
PostMessage(hWnd,WM_PAINT,0,0);
Game_Main(hWnd);
}
其中alive为true代表是玩家存活状态,一旦玩家失败,即游戏结束,消息循环则停止进行。GetMessage()用于高效的从消息队列读取消息,但消息队列为空时,会休眠。而PeekMessage()不会休眠,会返回0。为了满足游戏实时变化的要求,使用后者。
GameMain()中包含了所有游戏的逻辑和地图绘制。
4、
在消息循环写完后,需要一个处理消息的函数——窗口过程(函数)。
LRESULT CALLBACK WindowProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
LRESULT CALLBACK 可用long far PASCAL代替,与上面提及的PASCAL同理,可以通用,但本人觉得前者较好。
该函数内主要是一个switch语句。每个case小句代表对于一种消息的处理。大部分情况下必须的消息处理有:WM_CREATE,WM_DESTROY,WM_CLOSE。
WM_PAINT是常用的消息处理:对当前窗口进行重画。在作者的源程序中并未使用该消息。作者将绘画函数放置在GAME_MAIN()(事实上作者并未使用该函数,直接将代码放在while循环中),利用while循环实现重画。而这样的做法也导致了一些问题,比如,在玩家失败后会弹出一个OK消息框,如果移动该消息框,由于已经退出了while循环,游戏窗口不能进行重绘,凡是被消息框覆盖部分不能还原。而使用WM_PAINT则正好弥补了该缺陷。
附上WM_PAINT的结构:
hdc = BeginPaint(hWnd,&ps);
DrawBkground(hWnd);
DrawAround(hWnd);
DrawField(hWnd);
DrawNet(hWnd);
DrawNPC(hWnd);
DrawBall(hWnd);
DrawShader(hWnd);
DrawMan(hWnd);
EndPaint(hWnd,&ps);
其中的hdc ,ps两个变量在过程函数中已经定义。BeginPaint和EndPaint为基本结构。
5、
GDI绘图:如果把游戏和人作比较,游戏的逻辑就好比是人的思想,GDI绘图就是躯体。
以该代码为例,绘图分为几个步骤:先是最底层背景(DrawBkground()),然后是场地(DrawBkground()、DrawAround()),球网,NPC,玩家,球和球的影子。因为这是2D,不能很清晰的看到球的高度,有了影子以后,就可以利用球与影子的距离判断球与地面的高度。还有一点:玩家(或者说球拍)和球的绘图顺序不能搞错,否则会出现球穿透球拍的情况。
以DrawField()为例:

void
DrawField(HWND hWnd)
{
HDC hdc;
hdc = GetDC(hWnd);
SelectObject(hdc,black_pen);
SelectObject(hdc,field_brush);
Polygon(hdc,FIELDPOINT,4);

MoveToEx(hdc,400,150,NULL);
LineTo(hdc,400,550);
MoveToEx(hdc,130,300,NULL);
LineTo(hdc,665,300);
MoveToEx(hdc,24,551,NULL);
LineTo(hdc,775,551);

SelectObject(hdc,black_pen);
SelectObject(hdc,field_brush);
ReleaseDC(hWnd,hdc); }

GetDC()是程序不在处理WM_PAINT时,获取设备句柄的方法,SelectObject()用来拾取对象,我们在头文件中定义好了不同颜色和不同特征的画笔和画刷,现在你想用那个工具作画,就用该函数去取,用完后放回释放空间——DeleteObject(),该代码中这一函数放在了程序的末尾。注意函数中前后两次进行了取画笔的操作,我对此不理解,有个朋友说是“对画笔进行还原”。
MoveToEx()和LineTo()两个函数在这个代码中用来划界线(羽毛球和网球的场地都有横竖多条界线)。前者是定义直线(严格说是线段)的起点,后者是决定末点,并进行画直线操作。

6、
最后,Game_Main()的结构,主要就是游戏的逻辑,不在话下。
从目前看过的几个游戏来说,在windows游戏中,主要就是三块内容:创建windows窗口,GDI绘制游戏世界,游戏逻辑。这一个归纳是否正确,还有待之后不断的学习探索。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: