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

开篇前言:

      关注游戏编程很久了,自己从未还写过真正意义上的游戏,即使是DEMO也没有。书中的例子倒是照着敲过几个,但由于本人学习效率比较低,归纳能力差,加之惰性很重,从未写过心得。这算是第一篇“技术性”的“代码描摹”后的学习心得,以后希望能坚持写下去,很多前辈都教导我们:程序员要经常做经验总结。由于是第一篇,技术含量相当低,见笑了。

正文:

      前几天把书上的GDI看完了,于是昨晚把书上的一个简单例子敲了一遍——一个简单的打球游戏,今天花了一个上午时间把代码进行了整理。作者可能是为了让读者更好的理解游戏逻辑和GDI的基础原理,很多地方结构不是很好,我按自己的编码习惯进行了整理,并修改了几个细节。

      这个游戏由两个文件:simpletennis.hsimpletennis.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);

       }

      其中alivetrue代表是玩家存活状态,一旦玩家失败,即游戏结束,消息循环则停止进行。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_CREATEWM_DESTROYWM_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两个变量在过程函数中已经定义。BeginPaintEndPaint为基本结构。

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绘制游戏世界,游戏逻辑。这一个归纳是否正确,还有待之后不断的学习探索。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值