环境:vs2017+windows10+mfc
动画实现原理:
游戏中动画实现的方式主要有两种,
1,直接播放视频文件
2,连续显示稍有差比的图片序列来产生动画效果,称为程序动画,是用代码来控制动画
逐帧动画实现原理:
使用定时器创建游戏刷新循环,一般不使用多个定时器,多个定时器可能会出现某一时刻需要刷新多个动画,会造成混乱
需要用到得到类:
CMyApp(继承自CWinApp)和CMyWnd(继承自CFrameWnd)这在上一篇博客已经说到了
其余的是需要定时器:
Timer:
CWnd(CFrameWnd的父类)有创建和销毁Timer的方法
CWnd 定时器的两个方法:
使用 SetTimer()方法来启动定时器
先搜索
在CWnd的members页面ctrl+f搜索SeetTimer
看看这两个方法的用法:
SetTimer
UINT_PTR SetTimer(
UINT_PTR nIDEvent,//无符号整数,取非0的值,作为定时器的唯一标识
UINT nElapse,//毫秒,定时器执行的周期
void (CALLBACK* lpfnTimer//回调函数,暂时不管,一般传递NULL即可
)(HWND,
UINT,
UINT_PTR,
DWORD
)
);
KillTimer:
BOOL KillTimer(
UINT_PTR nIDEvent //对应SetTimer那里指定的id
);
涉及到贴图,在上一篇博客已经说明,这里就默认你会了·
开始代码编写:
#include<afxwin.h>
class MyWnd :public CFrameWnd {
private :
CDC * m_Dc;
CBitmap * m_Bitmap;//小偷
CBitmap * m_BackG;//背景
int m_nFrameWndNo;//定时器计数,用于判断贴哪一张小偷位图
int x;//改变绘制小偷的x方向位置,让其移动
public :
MyWnd() {
Create(NULL, "WINDOW");
MoveWindow(0, 0, 800, 600);//指定窗口位置和大小
CClientDC dc(this);
m_Dc = new CDC;
m_Dc->CreateCompatibleDC(&dc);
//背景
m_BackG = new CBitmap;
m_BackG->m_hObject = LoadImage(NULL, "BackGround960x640.bmp", IMAGE_BITMAP, 0, 0,LR_LOADFROMFILE);
//小偷
m_Bitmap = new CBitmap;
m_Bitmap->m_hObject = LoadImage(NULL, "crimer2.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
m_nFrameWndNo = 0;
SetTimer(1,100,NULL);
x = 400;
}
DECLARE_MESSAGE_MAP()
afx_msg void OnPaint();
afx_msg void OnTimer(UINT_PTR nIDEvent);
};
class MyApp :public CWinApp {
BOOL InitInstance();
};
BOOL MyApp::InitInstance() {
//创建窗口
MyWnd * pf = new MyWnd;
//pf->Create(NULL, "贴图测试window");
pf->ShowWindow(m_nCmdShow);
pf->UpdateWindow();
this->m_pMainWnd = pf;
return TRUE;
}
MyApp TheApp; BEGIN_MESSAGE_MAP(MyWnd, CFrameWnd)
ON_WM_PAINT()
ON_WM_TIMER()
END_MESSAGE_MAP()
void MyWnd::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: 在此处添加消息处理程序代码
//如何贴换掉上次的画面呢?
//简单高效的处理,再贴一次背景,上一次的就被掩盖了
//贴背景
m_Dc->SelectObject(m_BackG);
dc.BitBlt(0, 0, 800, 600, m_Dc, 0, 0, SRCCOPY);
//选取小偷
m_Dc->SelectObject(m_Bitmap);
switch (m_nFrameWndNo) {
case 1:
//贴小偷动作1
dc.BitBlt(x, 300, 63, 154, m_Dc, 63, 0, SRCAND);
dc.BitBlt(x, 300, 63, 154, m_Dc, 0, 0, SRCPAINT);
break;
case 0:
//贴小偷动作2
dc.BitBlt(x, 300, 63, 154, m_Dc, 63, 154, SRCAND);
dc.BitBlt(x, 300, 63, 154, m_Dc, 0, 154, SRCPAINT);
break;
default:
break;
}
// 处理小偷x方向移动
if (x <= 0) {
x = 400;
}
else {
x -= 10;
}
m_nFrameWndNo = ((0 == m_nFrameWndNo) ? 1 : 0);
}
void MyWnd::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
//让窗口重绘
Invalidate();
CFrameWnd::OnTimer(nIDEvent);
}
代码中的两个方法:
MoveWindow:给变窗口的位置和大小,具体文档说明可见参考msdn文档
Invalidate():这是CWnd的一个方法:
传递参数,默认值是TRUE,
The client area is marked for painting when the next WM_PAINT message occurs
当下一个绘制窗口消息出现之前,客户区域标记为 绘制,
void Invalidate(
BOOL bErase = TRUE
);
思路总结:
1,CMyWnd的构造方法中,初始化成员变量,主要是加载图片到内存
2,CMyApp的InitInstance方法中实例化一个CMyWnd对象,将其交给CMyApp的一个成员指针m_pMainWnd
3,处理绘图逻辑,在OnPaint方法中处理贴图逻辑
另一种方法:
绘图逻辑可以不使用OnPaint函数,迁移到OnTimer方法中
需要注意的是:在InitInstance方法之外的地方需要使用CClientDc来创建dc
void MyWnd::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CClientDC dc(this);//在InitInstance方法之外的地方需要使用CClientDc来创建dc
m_Dc->SelectObject(m_BackG);
dc.BitBlt(0, 0, 800, 600, m_Dc, 0, 0, SRCCOPY);
m_Dc->SelectObject(m_Bitmap);
switch (m_nFrameWndNo) {
case 1:
dc.BitBlt(x, 300, 63,154, m_Dc,63,0, SRCAND);
dc.BitBlt(x, 300, 63, 154, m_Dc, 0, 0, SRCPAINT);
break;
case 0:
dc.BitBlt(x, 300, 63, 154, m_Dc, 63, 154, SRCAND);
dc.BitBlt(x, 300, 63, 154, m_Dc, 0,154, SRCPAINT);
break;
default:
break;
}
if (x <= 0) {
x = 400;
}
else {
x -= 10;
}
m_nFrameWndNo = ((0 == m_nFrameWndNo) ? 1 : 0);
CFrameWnd::OnTimer(nIDEvent);
}
效果:
给出几张截图吧:
至此,一个踏着小碎步的小偷就跑起来了!