《MFC游戏开发》笔记五 定时器和简单动画

时间:2023-12-27 13:43:19

本系列文章由七十一雾央编写,转载请注明出处。

http://blog.csdn.net/u011371356/article/details/9332377

作者:七十一雾央 新浪微博:http://weibo.com/1689160943/profile?rightmod=1&wvr=5&mod=personinfo

上一节笔记中,我们讲解了键盘响应和鼠标响应,实现了对于玩家的操作,程序做出正确的响应。但是大家在玩游戏的过程中,应该会注意到,在大家没有操作的时候,程序的画面仍然不是静止的,比如NPC会四处走动,怪物仍然会跑过来攻击玩家等,也就是说,画面仍然在随着时间不断的更新。这一点在程序中对应的就是无时无刻不在更新着游戏信息和绘制画面,以便及时的反映出游戏的状态。

在WIN 32程序中,一般大家会把绘制程序放到消息循环之中,但是在MFC中由于对WIN 32高度的封装和消息印射机制,我们很难找到消息循环的位置,所以我们就需要采用别的办法。大家现在已经知道,我们绘图都是在OnPaint里进行的,那么我们不断的执行OnPaint函数不就行了吗?实现的方法就是今天要讲解的定时器了。

定时器(Timer)对象可以每隔一段时间发出一个时间消息,程序一旦接收到此消息之后,便可以决定接下来要做哪些事情。雾央先说一下定时器大概会有5毫秒左右的误差,精度不够,在实际游戏开发中,很少使用到,但是对于我们初学者来说,这个对于游戏性几乎没有任何影响,还很方便大家的开发,所以我们仍然使用了定时器。

下面来介绍如何建立与删除定时器。

1.建立定时器

Windows API 的SetTimer()函数可为窗口建立一个定时器,并每隔一段时间就发出WM_TIMER消息,此函数的定义是

UINT_PTRSetTimer(
UINT_PTRnIDEvent, //定时器代号
UINTuElapse, //时间间隔
TIMERPROClpTimerFunc //处理函数
);

SetTimer()函数的第1个参数是定时器的代号,这个代号在同一个窗口中必须是唯一的,且值不为0,第2个参数则是定时器发出WM_TIMER消息的时间间隔:第3个参则用于设定由系统调用处理WM_TIMER消息的相应函数,如果不用响应函数处理WM_TIMER消息,则此参数应设为NULL。

一句话概括,就是SetTimer函数会创建一个ID为第一个参数的定时器,它每隔第二个参数的时间就会执行一次第三个参数指向的函数。

如果不需要自己定义处理函数,第三个参数设置为NULL,我们可以使用默认的消息处理函数。

下面是设定一个每隔100毫秒发出WM_TIMER消息的定时器的程序代码。

SetTimer(1,100,NULL);

  

      2.删除定时器

定时器建立后,就会一直自动地按照定义设定的时间间隔发出WM_TIMER消息,如果要停用某个定时器,必须使用下面的这个函数:

 BOOL   KillTimer(int 定时器代号);

在MFC中,大家要使用定时器,需要先通过类向导添加“WM_TIMER”消息,添加的具体过程如果有不会的同学请阅读上一节笔记:鼠标响应和键盘响应。

在添加完定时器消息后,CChildView.cpp中会出现

void CChildView::OnTimer(UINT_PTR nIDEvent)

这个函数,这就是定时器消息处理函数了,它的参数nIDEvent就是表示执行OnTimer函数的定时器的ID了。


 雾央要强调一下关于创建定时器的位置,大家基本可以在任何地方创建,比如在OnPaint中等,但是千万不要在PreCreateWindow函数中创建定时器,否则大家就会发现程序一运行就会弹出来一个出错框了。

如果大家希望在窗口一创建的时候就创建定时器,比如驱动我们窗口绘制的定时器等,那么我们可以添加“WM_CREATE”消息,在这里面进行创建。

在示例程序中我们要实现的是按下T键人物自动向右移动,按下I键定值移动。大家如果自己运行一下程序,就会感觉这有几分动画的影子了。事实上,如果让人物移动的时候,变化一下图片,比如几张跑动的图片不断的切换,那么就是一个真正意义上的动画了。

另外,雾央有一个感到非常抱歉的事情要和大家说明一下,在之前的代码中,雾央漏掉了一句很重要的代码,在OnPaint函数中释放DC即ReleaseDC之前要加上ValidateRect(&m_client);这个函数的作用是使绘图区变得有效。在windows中,如果我们的窗口被遮挡了什么的,窗口那部分就变得无效,就会产生WM_PAINT消息,当绘制完毕后,必须要使窗口变得有效,否则系统将周而复始的产生WM_PAINT消息,使得CPU占用率非常高,而且还会出现很多莫名其妙的问题,比如使用MessageBox会导致程序失去响应等。



下面贴代码

头文件

// ChildView.h : CChildView 类的接口
// #pragma once // CChildView 窗口 class CChildView : public CWnd
{
// 构造
public:
CChildView(); // 特性
public:
CRect m_client; //保存客户区大小
CRect m_heroPos; //保存英雄的位置
CImage m_hero; //英雄
CImage m_bg; //背景图片
// 操作
public: // 重写
protected:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs); // 实现
public:
virtual ~CChildView(); // 生成的消息映射函数
protected:
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnTimer(UINT_PTR nIDEvent);
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
};

CPP文件

// ChildView.cpp : CChildView 类的实现
// #include "stdafx.h"
#include "GameMFC.h"
#include "ChildView.h" #ifdef _DEBUG
#define new DEBUG_NEW
#endif //定时器的名称用宏比较清楚
#define TIMER_PAINT 1
#define TIMER_HEROMOVE 2 // CChildView CChildView::CChildView()
{
} CChildView::~CChildView()
{
} BEGIN_MESSAGE_MAP(CChildView, CWnd)
ON_WM_PAINT()
ON_WM_KEYDOWN()
ON_WM_LBUTTONDOWN()
ON_WM_TIMER()
ON_WM_CREATE()
END_MESSAGE_MAP() //将png贴图透明
void TransparentPNG(CImage *png)
{
for(int i = 0; i <png->GetWidth(); i++)
{
for(int j = 0; j <png->GetHeight(); j++)
{
unsigned char* pucColor = reinterpret_cast<unsigned char *>(png->GetPixelAddress(i , j));
pucColor[0] = pucColor[0] * pucColor[3] / 255;
pucColor[1] = pucColor[1] * pucColor[3] / 255;
pucColor[2] = pucColor[2] * pucColor[3] / 255;
}
}
} // CChildView 消息处理程序 BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs)
{
if (!CWnd::PreCreateWindow(cs))
return FALSE; cs.dwExStyle |= WS_EX_CLIENTEDGE;
cs.style &= ~WS_BORDER;
cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,
::LoadCursor(NULL, IDC_ARROW), reinterpret_cast<HBRUSH>(COLOR_WINDOW+1), NULL); //加载背景
m_bg.Load("bg.png"); //加载英雄图片
m_hero.Load("hero.png");
TransparentPNG(&m_hero); //设置英雄初始位置
m_heroPos.left=100; //人物左边贴在100的位置
m_heroPos.right=100+60; //人物的右边等于左边加上人物的宽度
m_heroPos.top=400;
m_heroPos.bottom=400+60; return TRUE;
} void CChildView::OnPaint()
{
//获取窗口DC指针
CDC *cDC=this->GetDC();
//获取窗口大小
GetClientRect(&m_client);
//贴背景
m_bg.Draw(*cDC,m_client);
//贴英雄
m_hero.Draw(*cDC,m_heroPos); //在绘制完图后,使窗口区有效
ValidateRect(&m_client);
//释放DC
ReleaseDC(cDC);
} //按键响应函数
void CChildView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
//nChar表示按下的键值
switch(nChar)
{
case 'd': //游戏中按下的键当然应该不区分大小写了
case 'D':
m_heroPos.left+=10; //向右移动10个像素的单位
m_heroPos.right+=10; //左边和右边都要移动哦
break;
case 'a':
case 'A':
m_heroPos.left-=10;
m_heroPos.right-=10;
break;
case 'w':
case 'W':
m_heroPos.top-=10;
m_heroPos.bottom-=10;
break;
case 's':
case 'S':
m_heroPos.top+=10;
m_heroPos.bottom+=10;
break;
case 't':
case 'T': //创建定时器
SetTimer(TIMER_HEROMOVE,100,NULL);
break;
case 'i':
case 'I': //撤销定时器
KillTimer(TIMER_HEROMOVE);
}
} //鼠标左键单击响应函数
void CChildView::OnLButtonDown(UINT nFlags, CPoint point)
{
m_heroPos.left=point.x;
m_heroPos.right=m_heroPos.left+60;
m_heroPos.top=point.y;
m_heroPos.bottom=m_heroPos.top+60;
} //定时器响应函数
void CChildView::OnTimer(UINT_PTR nIDEvent)
{ switch(nIDEvent)
{
case TIMER_PAINT:OnPaint();break; //若是重绘定时器,就执行OnPaint函数
case TIMER_HEROMOVE: //控制人物移动的定时器
{
m_heroPos.left+=10;
m_heroPos.right+=10;
}
break;
}
} int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CWnd::OnCreate(lpCreateStruct) == -1)
return -1; // TODO: 在此添加您专用的创建代码 //创建一个10毫秒产生一次消息的定时器
SetTimer(TIMER_PAINT,10,NULL); return 0;
}

《MFC游戏开发》笔记五到这里就结束了,更多精彩请关注下一篇。如果您觉得文章对您有帮助的话,请留下您的评论,点个赞,能看到你们的留言是我最高兴的事情,因为这让我知道我正在帮助曾和我一样迷茫的少年,你们的支持就是我继续写下去的动力,愿我们一起学习,共同努力,复兴国产游戏。

对于文章的疏漏或错误,欢迎大家的指出。