使用WINDOWS的GDI开发游戏(3)

 第三篇

无惊无险,来到第三篇了。前面两篇已经有了一个框架了,而且也有了地图编辑器,可以输出地图文件了。
这篇就可以开始真正游戏部分的制作了。
我们看到形形色色的游戏,其实其框架都万变不离其宗。都是一个大的循环,然后在循环里面进行每帧画面根据游戏逻辑进行更新,
然后输出画面,周而复始,循环不止,直到遇到退出命令。但我们现在使用GDI在Windows桌面下编程,大体也相近,但稍微有点不同。
我们的大循环使用的是跟一般桌面程序一样的消息循环,在前面建立的框架里已经建立了,如下:
// 主消息循环:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
这个循环会不断检测到消息,然后我们可以根据消息进行游戏逻辑的处理,再通过绘图消息(WM_PAINT)输出画面。
但这个循环只在消息产生时才能够运动起来,否则就一直处于等待状态。我们要让其周期运动起来,就像一般的游戏循环那样。
如何做呢?通过Windows自带的定时器。
相关函数是:
UINT_PTR SetTimer(
    __in_opt HWND hWnd,
    __in UINT_PTR nIDEvent,
    __in UINT uElapse,
    __in_opt TIMERPROC lpTimerFunc);
BOOL KillTimer(
    __in_opt HWND hWnd,
    __in UINT_PTR uIDEvent);
另外一个是一般游戏所使用的使用PeekMessage,因为这个函数不等待,有就处理,没就立刻返回,但这里为了简便还是使用Timer。
这个定时器虽然精度只到10毫秒左右,不过对付小游戏还是够用了。
我们每隔33毫秒触发一次Timer事件。为什么是33毫秒?因为一般认为动画只要达到每秒30帧,肉眼看起来就比较连贯了。
33X30=960 差不多1秒。
一般安装定时器在WM_CREATE消息中,即窗口创建时,并在窗口销毁时进行删除。
为了把注意力放到游戏中,我们建立一个独立的类,来处理游戏相关逻辑。框架只负责处理消息传递。
建立
class CGame
并给予一个进入游戏处理帧的函数,以及一个画面绘制的接口,如果需要更新画面就发送更新消息。
bool EnterFrame( DWORD dwTime );
void Draw( HDC hDC );
然后定义一个全局游戏总控对象
CGame g_Game;
在LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)函数中添加两个消息的处理
case WM_CREATE:
   SetTimer( hWnd, 1, 33, NULL );
   break;
case WM_TIMER:
   {
       DWORD dwTime = GetTickCount();
       if( g_Game.EnterFrame( dwTime ) )
       {
           InvalidateRect( hWnd, NULL, FALSE );
       }
   }
画面更新消息修改如下:
case WM_PAINT:
   {
   hdc = BeginPaint(hWnd, &ps);
    
   CMemDC memDC( hdc );
      
   HBITMAP hBmp = ::CreateCompatibleBitmap( hdc, C_MAP_W*C_TILE_W, C_MAP_H*C_TILE_H );
   memDC.SelectObject( hBmp );
       g_Game.Draw( memDC.GetDC() );
        
       BitBlt( hdc, 0, 0, C_MAP_W*C_TILE_W, C_MAP_H*C_TILE_H, memDC.GetDC(), 0, 0, SRCCOPY );
        
       EndPaint(hWnd, &ps);
}
break;
这样我们就可以把框架隔开了,而把注意力放到CGame上面的处理上了。
接着就可以进入真正游戏逻辑部分编写了。
我们先不考虑坦克游戏的实作部分,先给一个一般游戏都有的游戏LOGO,我们制作一个比较像样的片头来,提高大家的兴趣。
一般游戏都会在片头黑色背景显示出公司LOGO,然后是一声清脆的音乐,看起来很有商业味道。我们就来实现那种效果吧。
不过游戏部分暂时不讲,留到后面再来处理声音。先处理画面。
我们要在游戏大循环下,进入片头处理逻辑。所以在EnterFrame里加入进入片头的分支。
我们可以在CGame里定义一个变量m_nGameState来进行游戏画面切换的标示。
然后使用
switch( m_nGameState )
{
    case 开场动画:
    break;
    case 游戏主程序:
    break;
}
来进入不同部分,这样就可以在各个独立部分考虑各自的逻辑了。
一、开场动画设计
我们可以用绘图软件绘制Cool一点的Logo,然后渐显显示出来。
在这里我们使用网上的LOGO,大家可以设计好看点的LOGO
显示完动画,只需要设置游戏状态为进入菜单状态就可以了。
m_nGameState = GS_MENU;

二、菜单设计
菜单设计需要处理用户输入。所以需要一个接收键盘消息的接口。
bool ProcessKeyDown( WPARAM wKey );
然后从窗口那里把输入传递过来
case WM_KEYDOWN:
    g_Game.ProcessKeyDown( wParam );
    break;
    
通过在菜单逻辑里判断输入的是什么键进行菜单选择处理。
如对上箭头选择上一项
case VK_UP:
    m_nMenuSelected--;
    
    if( m_nMenuSelected<0 )
        m_nMenuSelected = 0;
        
    return true;
    
通过m_nMenuSelected这个变量标示出当前选择的是第几项菜单。
当回车时就进入菜单处理。
case VK_RETURN:
case VK_SPACE:
    return OnSelectMenu( m_nMenuSelected );
    break;

三、游戏结束
由于GameOver的处理与菜单处理很相似,所以我们跳过游戏主程序,直接编写游戏结束的处理代码。游戏结束的菜单处理部分可以使用游戏菜单的逻辑
只要在选择菜单的处理上进行相应处理就可以了。大家可以参考游戏源代码来查看。

这样我们就可以得到一个可以运行的游戏框架了。下一篇我们再来介绍游戏主逻辑的设计。
我们目前没有游戏逻辑处理,所以我们让游戏一开始就进入游戏失败逻辑。
case GS_GAMELOOP:
    m_nGameState = GS_GAMEOVER;
    break;
下一篇再介绍具体的处理过程。
现在我们已经有一个像模像样的游戏框架了。
本章代码 下载
  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

b2b160

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值