《MFC游戏开发》笔记七 游戏特效的实现(一):背景滚动
来源:互联网 发布:js slice方法 编辑:程序博客网 时间:2024/06/04 18:51
本系列文章由七十一雾央编写,转载请注明出处。
http://blog.csdn.net/u011371356/article/details/9344721
作者:七十一雾央 新浪微博:http://weibo.com/1689160943/profile?rightmod=1&wvr=5&mod=personinfo
有同学提到无限大地图的问题并且举了无尽跑酷游戏的列子,雾央在这里简单的和大家说一下自己的看法:
雾央认为,像Temple Run这种游戏,它们的无尽地图的实现是依靠逻辑生成的,并不是使用的很大的地图文件。它的地图可以依靠几种有限的图元,然后经过拼接组合来生成。仔细观察就可以发现,游戏中的场景总是似曾相识的。
另外,游戏中如果直接使用太大的地图,也是不合适的。一方面是占内存大,另外如果使用3D加速,图片都会被加载到显存中,太大的地图会很烧显存。
至于无限地图,雾央觉得可以将大地图分成小地图,当玩家处在某块区域中时,先将周围的小地图预先加载进内存中。事实上,这些都有很复杂的理论,如果有兴趣,建议可以查查资料。
最后,非常欢迎大家和雾央一起讨论游戏相关的东西。
by雾央 2013.7.18
大家现在玩游戏的时候,如果游戏的地图只有一个屏幕那么大,那么肯定会很没有意思,毕竟一眼可以看见全貌,随便走几下就到头了,失去了一种探索的乐趣,而且频繁的切换地图也是会大大降低游戏的流畅体验,PS:雾央一直期待着会出现那种没有场景切换的游戏,无缝拼接的超大地图一定感觉很爽,呵呵。
如果游戏中的地图比较大,超过了一个屏幕的大小,那么就会涉及到背景移动的问题了。现在游戏中背景滚动一般有两种方式,一种是冒险岛那样的游戏,人物基本上是会显示在画面中央,背景随着人物的移动而移动,另一种是像魔兽争霸那样,背景是随着鼠标的移动而移动,而鼠标是可以到处移动的,也就是不管人物在何处,都是可以看到任何地方的背景,当然这些游戏是有战争迷雾的,仍然和人物位置关联了起来。
不管是上面哪种方式,实现的原理都是一样的,无非一个以人物位置,一个以鼠标位置作为参考。
在今天的教程里,雾央会以前一种作为示例进行讲解,即背景随着人物的移动而移动。雾央相信,如果大家搞懂了这一种的话,那么第二种也不在话下,大家可以自己尝试着去实现这种效果。
在人物移动的时候,背景需要随着人物的移动而移动,而人物的移动方向有水平移动和垂直移动,两种实现的原理过程是一模一样的,因此雾央只讲解一下水平方向的移动。
先放出来几张贴图,让大家看看效果,呵呵。
主角仍然是上一节中那个骑着白马的少年
往前走,欣赏沿途的风景
路上风光不错吧
继续向前走
嗯,要走到头了,旅途结束
那么,现在请大家思考一下,我们要怎么做才能实现背景滚动呢?
如果背景是重复的,那么我们完全可以在贴出背景图后,不断的把其中一边的部分图像贴到另一边去,形成背景移动的假象。
很遗憾的是,在游戏之中,每个地方基本都是独特的,也就是说每个地方的背景基本是唯一的,那么上述方法显然是行不通的,那么应该怎么做呢?
其实,实现的方法说穿了是非常简单的:准备好一张大地图,每次根据人物的位置显示出大地图其中的一部分即可。
下面,我们就开始进行编程工作了。
首先,雾央准备了一张大地图,因为我们的窗口是800x600的,所以这张地图是2666x600的,雾央只示例水平方向上的地图滚动。
然后呢,我们思考一下假设人物处在(x,y)这一点,我们应该显示背景的哪一部分。我们是希望人物处在地图的中央,那么是显示出大地图上(x-200,0)到(x+600,600)这一部分吗?
基本上是这样,但是大家要注意一些细节,人物是不能移出地图边界的,因此当人物在地图最左边和最右边的半个屏幕时候,地图是不需要滚动的,这个需要我们单独考虑。
雾央画了下面的图来帮助大家理解这三种情况:
1.当人物处在最左边半个屏幕时
2.当人物处在最右边半个屏幕时,与1同理
3.当人物处在中间时
因为地图的宽度超过了游戏窗口的大小,因此就不再是一个坐标可以解决问题了。我们需要使用两种坐标,屏幕坐标和实际坐标。
比如我们大地图背景宽度是2666,那么人物的位置x就可以是0~2666了,这个x就是人物的实际坐标,但是显示到屏幕上后就必须在0~800之间,就是屏幕坐标了,因此我们还需要进行实际坐标和屏幕坐标之间的转换。
转换的函数雾央写好了,是下面这样,大家结合上面的图应该很好理解。
- //获取人物在屏幕上的坐标
- int GetScreenX(int xHero,int mapWidth)
- {
- //如果人物不在最左边和最右边半个屏幕内时,那么人物就处在屏幕中间
- if(xHero<mapWidth-WINDOW_WIDTH/2 && xHero>WINDOW_WIDTH/2)
- return WINDOW_WIDTH/2;
- else if(xHero<=WINDOW_WIDTH/2) //在最左边半个屏幕时,人物在屏幕上的位置就是自己的x坐标了
- return xHero;
- else
- return WINDOW_WIDTH-(mapWidth-xHero); //在最右边半个屏幕
- }
这两种坐标大家在作逻辑判断的时候都使用的是实际坐标,只有在画到屏幕上的时候再进行转换,转换成屏幕坐标就可以了,这样可以避免自己搞混淆。
下面上源代码,大家结合着看,呵呵。由于雾央主要讲解的是地图背景滚动实现的原理,所以代码组织的形式比较随意,如果大家只有c语言的基础,那就先这样,如果有C++的基础,那么建议大家封装成类,这样看的更清楚,目前就可以封装成人物Hero类,和地图Map类,以后雾央视情况也会这样做的。
首先是头文件
- // 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; //背景图片
- int m_xMapStart; //x方向上地图的起始点
- int m_mapWidth; //背景地图的宽度
- 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:
- void GetMapStartX();
- 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
- //窗口大小
- #define WINDOW_WIDTH 800
- #define WINDOW_HEIGHT 600
- // 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("bigbg.png");
- //获取背景地图的宽度
- m_mapWidth=m_bg.GetWidth();
- //加载英雄图片
- MyHero.hero.Load("heroMove.png");
- TransparentPNG(&MyHero.hero);
- //初始化英雄状态
- MyHero.direct=UP;
- MyHero.frame=0;
- //设置英雄初始位置
- MyHero.x=80;
- MyHero.y=400;
- //设置地图初始从最左端开始显示
- m_xMapStart=0;
- return TRUE;
- }
- //计算地图左端x开始位置
- void CChildView::GetMapStartX()
- {
- //如果人物不在最左边和最右边半个屏幕内时,地图的起始坐标是需要根据人物位置计算的。
- if(MyHero.x<m_mapWidth-WINDOW_WIDTH/2 && MyHero.x>WINDOW_WIDTH/2)
- m_xMapStart=MyHero.x-WINDOW_WIDTH/2;
- }
- //获取人物在屏幕上的坐标
- int GetScreenX(int xHero,int mapWidth)
- {
- //如果人物不在最左边和最右边半个屏幕内时,那么人物就处在屏幕中间
- if(xHero<mapWidth-WINDOW_WIDTH/2 && xHero>WINDOW_WIDTH/2)
- return WINDOW_WIDTH/2;
- else if(xHero<=WINDOW_WIDTH/2) //在最左边半个屏幕时,人物在屏幕上的位置就是自己的x坐标了
- return xHero;
- else
- return WINDOW_WIDTH-(mapWidth-xHero); //在最右边半个屏幕
- }
- 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);
- //计算背景地图起始位置
- GetMapStartX();
- //————————————————————开始绘制——————————————————————
- //贴背景,现在贴图就是贴在缓冲DC:m_cache中了
- m_bg.Draw(m_cacheDC,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,m_xMapStart,0,WINDOW_WIDTH,WINDOW_HEIGHT);
- //贴英雄
- MyHero.hero.Draw(m_cacheDC,GetScreenX(MyHero.x,m_mapWidth),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;
- }
本节笔记源代码点这里下载,0积分哦
《MFC游戏开发》笔记七到这里就结束了,更多精彩请关注下一篇。如果您觉得文章对您有帮助的话,请留下您的评论,点个赞,能看到你们的留言是我最高兴的事情,因为这让我知道我正在帮助曾和我一样迷茫的少年,你们的支持就是我继续写下去的动力,愿我们一起学习,共同努力,复兴国产游戏。
对于文章的疏漏或错误,欢迎大家的指出。
- 《MFC游戏开发》笔记七 游戏特效的实现(一):背景滚动
- 《MFC游戏开发》笔记七 游戏特效的实现(一):背景滚动
- 《MFC游戏开发》笔记七 游戏特效的实现(一):背景滚动
- 《MFC游戏开发》笔记七 游戏特效的实现(一):背景滚动
- 《MFC游戏开发》笔记三 游戏贴图与透明特效的实现
- 《MFC游戏开发》笔记八 游戏特效的实现(二):粒子系统
- 《MFC游戏开发》笔记三 游戏贴图与透明特效的实现
- 《MFC游戏开发》笔记八 游戏特效的实现(二):粒子系统
- 《MFC游戏开发》笔记三 游戏贴图与透明特效的实现
- 《MFC游戏开发》笔记八 游戏特效的实现(二):粒子系统
- 《MFC游戏开发》笔记三 游戏贴图与透明特效的实现
- 《MFC游戏开发》笔记八 游戏特效的实现(二):粒子系统
- 基于MFC框架的C++游戏开发(三)游戏贴图与透明特效的实现
- 《MFC游戏开发》笔记一 系列简介
- 《MFC游戏开发》笔记一 系列简介
- 《MFC游戏开发》笔记一 系列简介
- 固定背景实现的背景滚动特效
- MFC游戏开发笔记
- MySQL查询子句(group by,limit,union,order by等)
- Asp.net-知识总结(7)
- uva10828(高斯消元)
- JAVA多线程实例详解
- 浅析PageRank算法
- 《MFC游戏开发》笔记七 游戏特效的实现(一):背景滚动
- nyoj42 一笔画问题 (欧拉回路)
- 架构设计:负载均衡层设计方案(7)——LVS + Keepalived + Nginx安装及配置
- linux下打开windows txt文件中文乱码解决方法
- Unreal4 IOS上使用第三方库和C++11 特性问题解决
- JRockit安装配置
- 【Python】一次性解决Eciplse中Python中文乱码
- hdu 3338 Kakuro Extension(最大流)
- hdu5323 给出左右边界,求出线段树最小的大小(暴力dfs搜索)