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

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

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

在前几节的笔记里,大家肯定会为一个问题感到心烦:画面怎么老是一闪一闪的啊,太难受了。确实是的,如果玩这样的游戏简直就是一种折磨。但是大家玩游戏的时候,从来没有遇到过这种情况吧?那么游戏开发者是怎么解决这个问题的呢?雾央在这一节笔记里给大家讲解一种简单通用的方法——图像双缓冲。

一、闪烁原因

为了解决问题,我们得首先搞清楚闪烁的原因是什么,然后才能对症下药。能够导致游戏画面闪烁的原因非常多,但是对于我们做游戏开发的同学来说,最主要的就是一种:贴图贴的太频繁。

如果大家是一个细心人的话,那么应该可以发现,在笔记三讲解贴图的时候,当我们贴出背景图的时候,是根本不会闪烁的,但是当我们贴出人物后,闪烁就出来了,而当我们移动人物的时候,闪烁的画面简直惨不忍睹啊。

想弄清楚真正的原因就得要理解GDI绘图的原理:GDI绘图的时候是先绘制到显存里面,然后显存每隔一段时间就需要把里面的内容输出到屏幕上,这个时间就是刷新周期。在绘图的时候,系统会先用一种背景色擦除掉原来的图像,然后再绘制新的图像。如果这几次绘制不在同一个刷新周期中,那么我们看到的就是先看到背景色,再看到内容出来,就会有闪烁的感觉,而绘制的次数越多,看到这种现象的可能性就越大,就闪烁的越厉害。

二、图像双缓冲技术

大家清楚了闪烁的原因后,再结合我们只贴出背景的时候并没有闪烁的事实,那么或许大家就可以想到一种解决方法了:我们事先将要画的所有东西画在一张图片上,然后将这张图直接贴出来,不就解决了吗?

如果你想到这里,那么恭喜你,你已经想到了图像双缓冲技术。其实看起来很高端的这个名词其实非常简单。我们之前画图的时候都是直接画在窗口DC上,在之前我们可以自己先创建一个内存DC,然后把画图都画在内存DC中,最后再一次性的将内存DC输出到窗口DC中,就可以解决画面闪烁的问题了。

下面我们讲述写代码的方法

 1.定义变量

首先在CChildView.h中定义两个变量

CDC m_cacheDC;   //缓冲DC
CBitmap m_cacheCBitmap;//缓冲位图

2.创建缓冲DC

然后呢,在CChildView.cpp中OnPaint中创建缓冲DC

//创建缓冲DC
m_cacheDC.CreateCompatibleDC(NULL);
m_cacheCBitmap.CreateCompatibleBitmap(cDC,m_client.Width(),m_client.Height());
m_cacheDC.SelectObject(&m_cacheCBitmap);

3.在缓冲DC上绘图

后面贴图都贴在缓冲DC上就可以了,如

m_bg.Draw(m_cacheDC,m_client);

      4.缓冲DC输出到窗口DC

最后一次性的将缓冲DC中的内容输出到窗口DC中去,函数都是之前笔记二介绍过的,不熟悉的同学请阅读笔记二。

cDC->BitBlt(0,0,m_client.Width(),m_client.Height(),&m_cacheDC,0,0,SRCCOPY);

此时OnPaint函数中的内容就如同下面这样

void CChildView::OnPaint()
{
//获取窗口DC指针
CDC *cDC=this->GetDC();
//获取窗口大小
GetClientRect(&m_client);
//创建缓冲DC
m_cacheDC.CreateCompatibleDC(NULL);
m_cacheCBitmap.CreateCompatibleBitmap(cDC,m_client.Width(),m_client.Height());
m_cacheDC.SelectObject(&m_cacheCBitmap); //————————————————————开始绘制——————————————————————
//贴背景,现在贴图就是贴在缓冲DC:m_cache中了
m_bg.Draw(m_cacheDC,m_client);
//贴英雄
MyHero.hero.Draw(m_cacheDC,MyHero.x,MyHero.y,80,80,MyHero.frame*80,MyHero.direct*80,80,80);
//最后将缓冲DC内容输出到窗口DC中
cDC->BitBlt(0,0,m_client.Width(),m_client.Height(),&m_cacheDC,0,0,SRCCOPY); //————————————————————绘制结束————————————————————— //在绘制完图后,使窗口区有效
ValidateRect(&m_client);
//释放缓冲DC
m_cacheDC.DeleteDC();
//释放对象
m_cacheCBitmap.DeleteObject();
//释放窗口DC
ReleaseDC(cDC);
}

三、实现一个真正意义上的动画demo

雾央在这里实现的是一个骑着白马的少年在场景中闲逛的demo,按下WASD人物会向四个方向移动,移动的过程中动态更换图片。图片使用的是一张大图,然后每次去取其中一小块显示出来,当然大家也可以使用一张张分开好的图。具体的请看代码。

先来几张截图看看效果,呵呵。

头文件

// ChildView.h : CChildView 类的接口
// #pragma once // CChildView 窗口 class CChildView : public CWnd
{
// 构造
public:
CChildView(); // 特性
public:
struct shero
{
CImage hero; //保存英雄的图像
int x; //保存英雄的位置
int y;
int direct; //英雄的方向
int frame; //运动到第几张图片
}MyHero; CRect m_client; //保存客户区大小
CImage m_bg; //背景图片 CDC m_cacheDC; //缓冲DC
CBitmap m_cacheCBitmap;//缓冲位图
// 操作
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
//四个方向
#define DOWN 0
#define LEFT 1
#define RIGHT 2
#define UP 3 // 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");
//加载英雄图片
MyHero.hero.Load("heroMove.png");
TransparentPNG(&MyHero.hero);
//初始化英雄状态
MyHero.direct=UP;
MyHero.frame=0;
//设置英雄初始位置
MyHero.x=100;
MyHero.y=400; return TRUE;
} void CChildView::OnPaint()
{
//获取窗口DC指针
CDC *cDC=this->GetDC();
//获取窗口大小
GetClientRect(&m_client);
//创建缓冲DC
m_cacheDC.CreateCompatibleDC(NULL);
m_cacheCBitmap.CreateCompatibleBitmap(cDC,m_client.Width(),m_client.Height());
m_cacheDC.SelectObject(&m_cacheCBitmap); //————————————————————开始绘制——————————————————————
//贴背景,现在贴图就是贴在缓冲DC:m_cache中了
m_bg.Draw(m_cacheDC,m_client);
//贴英雄
MyHero.hero.Draw(m_cacheDC,MyHero.x,MyHero.y,80,80,MyHero.frame*80,MyHero.direct*80,80,80);
//最后将缓冲DC内容输出到窗口DC中
cDC->BitBlt(0,0,m_client.Width(),m_client.Height(),&m_cacheDC,0,0,SRCCOPY); //————————————————————绘制结束————————————————————— //在绘制完图后,使窗口区有效
ValidateRect(&m_client);
//释放缓冲DC
m_cacheDC.DeleteDC();
//释放对象
m_cacheCBitmap.DeleteObject();
//释放窗口DC
ReleaseDC(cDC);
} //按键响应函数
void CChildView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
//nChar表示按下的键值
switch(nChar)
{
case 'd': //游戏中按下的键当然应该不区分大小写了
case 'D':
MyHero.direct=RIGHT;
MyHero.x+=5;
break;
case 'a':
case 'A':
MyHero.direct=LEFT;
MyHero.x-=5;
break;
case 'w':
case 'W':
MyHero.direct=UP;
MyHero.y-=5;
break;
case 's':
case 'S':
MyHero.direct=DOWN;
MyHero.y+=5;
break;
}
} //鼠标左键单击响应函数
void CChildView::OnLButtonDown(UINT nFlags, CPoint point)
{
char bufPos[50];
sprintf(bufPos,"你单击了点X:%d,Y:%d",point.x,point.y);
AfxMessageBox(bufPos);
} //定时器响应函数
void CChildView::OnTimer(UINT_PTR nIDEvent)
{ switch(nIDEvent)
{
case TIMER_PAINT:OnPaint();break; //若是重绘定时器,就执行OnPaint函数
case TIMER_HEROMOVE: //控制人物移动的定时器
{
MyHero.frame++; //每次到了间隔时间就将图片换为下一帧
if(MyHero.frame==4) //到最后了再重头开始
MyHero.frame=0;
}
break;
}
} int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CWnd::OnCreate(lpCreateStruct) == -1)
return -1; // TODO: 在此添加您专用的创建代码 //创建一个10毫秒产生一次消息的定时器
SetTimer(TIMER_PAINT,10,NULL);
//创建人物行走动画定时器
SetTimer(TIMER_HEROMOVE,100,NULL);
return 0;
}

       对于代码中一些地方,可能有一些同学有疑惑,雾央在这里作一下解释。

       首先是人物的移动动画,雾央使用了下面这张图作为素材

          

       这张图片是320x320,也就是说每个人物大小是80x80,第一行是人物向下移动,第二行是向左,第三行是向右,第四行是向下。

       我们在人物结构体中使用了一个变量direct记录人物的方向,并且宏定义了四个方向分别为0,1,2,3

       比如当前人物移动方向是右,即RIGHT,也就是2,那么我们就应该截取这张图片的第三行画出来,第三行y的起始坐标就是80*2,也就是80*direct

       我们还使用了一个变量frame记录当前方向上的帧数,即在x方向上的起始坐标,比如当前应该显示第二张图片,frame为1,那么就是80*1,即80*frame

       在Draw函数中最后四个参数的含义分别是源图片的x起始坐标,y起始坐标,宽度,高度,即画出源图片的从x,y开始的宽为width,高为height的部分。

现在大家应该清楚了这个过程吧?

         

       下面雾央再举一个例子来帮大家充分的理解这个过程。

       当前玩家按下了D键,人物要向右边行走,那么此时direct=RIGHT,RIGHT被我们宏定义为2

       开始frame=0;那么我们就画出原来图片中80*frame,80*direct开始高为80,宽为80的图片,即下图

             
     

         在行走过程中frame++;接下来画的就是下面这个

          

         依次类推,当frame=4的时候,即画到最后一个的时候再从头开始,frame=0



        然后有同学提到在一直按着键的时候刚开始会感觉会停一下,雾央在这里说一下自己的想法。

        关于停一下,这个是由于第一次按下键后,人物移动很小,后来一直按着键,可以认为是以非常高的频率不断按键,人物移动的非常快,这之间一个对比就感觉停了一下,如果你把人物每次按键移动的距离增大很多,比如到50,就会觉得这种感觉小了很多。事实上,这样按键后人物突然增加一段位移是非常不科学的,用在游戏中也是非常不合适的,在后面的讲解中我会讲解一种新的人物移动方式,会很流畅,敬请关注。

       

         如果大家还有疑问,欢迎留言



      由于这一节笔记我觉得对于新手来说可能内容较多,所以我把源代码上传了,大家可以下载回去自己试试,本着分享的精神,当然是0积分下载了。

《MFC游戏开发》笔记六 源代码下载

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

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

《MFC游戏开发》笔记六 图像双缓冲技术:实现一个流畅的动画的更多相关文章

  1. 双缓冲技术(Double Buffering)(1、简介和源代码部分)

    这一节实在是有些长,翻译完后统计了一下,快到2w字了.考虑到阅读的方便和网络的速度,打算把这节分为5个部分,第一部分为双缓冲技术的一个 简介和所有的代码,如果能够看懂代码,不用看译文也就可以了.第二部 ...

  2. 《MFC游戏开发》笔记十 游戏中的碰撞检测进阶:地图类型&障碍物判定

    本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9394465 作者:七十一雾央 新浪微博:http:// ...

  3. 《MFC游戏开发》笔记九 游戏中的碰撞判定初步&怪物运动简单AI

    本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9374935 作者:七十一雾央 新浪微博:http:// ...

  4. 《MFC游戏开发》笔记八 游戏特效的实现(二):粒子系统

    本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9360993 作者:七十一雾央 新浪微博:http:// ...

  5. 《MFC游戏开发》笔记七 游戏特效的实现(一):背景滚动

    本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9344721 作者:七十一雾央 新浪微博:http:// ...

  6. 《MFC游戏开发》笔记三 游戏贴图与透明特效的实现

    本系列文章由七十一雾央编写,转载请注明出处. 313239 作者:七十一雾央 新浪微博:http://weibo.com/1689160943/profile?rightmod=1&wvr=5 ...

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

    本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9332377 作者:七十一雾央 新浪微博:http:// ...

  8. 《MFC游戏开发》笔记四 键盘响应和鼠标响应:让人物动起来

    本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9327377 作者:七十一雾央 新浪微博:http:// ...

  9. 《MFC游戏开发》笔记二 建立工程、调整窗口

    本系列文章由七十一雾央编写,转载请注明出处.  http://blog.csdn.net/u011371356/article/details/9300383 作者:七十一雾央 新浪微博:http:/ ...

随机推荐

  1. SSH下载的方法2

    ------------------------------下作下载方法二---------------------------------------------------String msg=n ...

  2. vc6.0 通过ADO(udl)连接sql 2008

    转载声明:本文转自http://blog.sina.com.cn/s/blog_7328b9dd0100pkbw.html茗之的博客   首先声明:本文是针对vc与sql菜鸟的,高人请让路. 本文讲解 ...

  3. 采用Asp.Net的Forms身份验证时,非持久Cookie的过期时间会自动扩展

    问题描述 之前没有使用Forms身份验证时,如果在登陆过程中把HttpOnly的Cookie过期时间设为半个小时,总会收到很多用户的抱怨,说登陆一会就过期了. 所以总是会把Cookie过期时间设的长一 ...

  4. 功能强大支持64位操作系统的转Flash软件(doc转swf):Print2Flash

    Print2Flash是一个虚拟打印机类的文档转换软件,因此只要是可打印的文档,都可以轻松转换为Flash文件,即SWF动画,特别是用于转换PDF.Word.Excel.PowerPoint等文档为S ...

  5. xx.exe 中的 0x7c92e4df 处最可能的异常: 0xC0000008: An invalid handle was specified

    今天遇到个超级奇怪的问题,昨天还好端端的程序,今天用VS打开后,在关闭主窗口的时候居然弹出错误提示:xx.exe 中的 0x7c92e4df 处最可能的异常: "0xC0000008: An ...

  6. vim复制多行<转>

    比如我要复制从第1行到第5行的数据,复制到第9行 光标移到第5行任意位置,输入ma光标移到第1行任意位置,输入y'a(这一定要打这个“'”单引号,否则就进入“INSERT”状态了光标移到需要复制的行, ...

  7. 会吓人的概念证明病毒: Chameleon

    近期有这么一条新闻指出,有一对家长发现,黑客入侵了他们为10个月女儿所准备的婴儿监视器(baby monitor).该黑客除了远程操控该监视器的录像角度,还大声对着小孩喊叫.婴儿的爸爸冲进女儿房间后, ...

  8. c#winform使用WebBrowser 大全[超长文转载]

    1.主要用途:使用户可以在窗体中导航网页. 2.注意:WebBrowser 控件会占用大量资源.使用完该控件后一定要调用 Dispose 方法,以便确保及时释放所有资源.必须在附加事件的同一线程上调用 ...

  9. 用JSP实现的商城购物车模块

    这两天,在学习JSP,正好找个小模块来练练手: 下面就是实现购物车模块的页面效果截图: 图1. 产品显示页面 通过此页面进行产品选择,增加到购物车 图2 .购物车页面 图3 . 商品数量设置 好了,先 ...

  10. pushState onpopstate

    转载自:http://www.cnblogs.com/gaoxue/p/3885796.html 参考MDN: https://developer.mozilla.org/zh-CN/docs/DOM ...