【VC++游戏开发#十】2D篇 —— 人工智能(一):滚动地图 用鼠标控制人物的走动
本文由BlueCoder编写 转载请说明出处:
http://blog.csdn.net/crocodile__/article/details/18039107
我的邮箱:bluecoder@yeah.net 欢迎大家和我交流编程心得
我的微博:BlueCoder_黎小华 欢迎光临^_^
和大家交流一下哈:
我最近,一直在思考一个问题:接下来的游戏效果的模拟,我到底是继续用MFC,还是转向Win32SDK呢?MFC能提供一个现成的框架,还有丰富的类库,很方便,但是效率:不算差也不算高吧,毕竟MFC封装了很多我们用不到的功能;Win32SDK,就不用多说,就是效率好,更接近底层,但是着实没有MFC那么方便。作为一个追求效率的、但又喜欢"偷点儿懒"的我,经过这么一想,就萌出了一个idea:可不可以用C++、Win32SDK效仿MFC框架来架构一个简易的框架呢,使这个框架能结合Win32SDK使用MFC类库,并能用C++面向对象的编程思想来写接下来的游戏效果。这样,就是取二者之长,更加适合windows游戏编程。听起来好像还不错哈,有个好消息是:这个简易的框架,我已经基本上架构好了,正在测试阶段,相信过两天就能发布了——当然,绝对的开源^_^
BlueCoder
2014/01/15注
Hello,大家好^_^漫长的期末考试总算结束,终于有时间继续做自己喜欢的事儿了——今天,继续我们的游戏梦想……最近,有款端游很受欢迎哈,腾讯推出的一款3D竞技端游——英雄联盟(LOL),想必很多朋友应该玩过吧。不过说实话,我没玩过,但看过室友玩过(因为我不喜欢玩游戏,这似乎听起来挺别扭的——一个喜欢研究游戏开发的,却不喜欢玩儿^_^)F话不多说了,在这款(或类似的)端游中,我们经常会看见一个场景:用鼠标控制自己的角色走动
就像上图所示一样,当你用鼠标点击屏幕任意位置时,这个角色就会自动按照你指定的方向走动,并且能滚动地图对,我今天就来和大家谈谈我对这个效果的看法以及实现思路下面,进入今天的话题
一、效果演示还是先来看看效果截图,一睹为快:可以看见,人物总是朝着鼠标点击的位置走动,并且地图也随着智能的滚动,至少初步上,是实现了我们预期的效果
二、准备工作1、准备一张大地图,以及人物在8个方向上走动的分解图2、一首动听的背景音乐3、类视图三、实现细节主要的实现细节有两大点:如何让人物根据你鼠标左键点击的位置来判断方向并智能地走动,以及如何智能地滚动背景——下面,我将详细地介绍1、如何智能地滚动地图背景(这里为了简单起见,只实现了水平方向上的滚动)1>我的思路是根据人物当前所在的位置来判断(一段伪代码):
if(人物的x坐标处在窗口中央) {if(人物向右走 && 地图未达到右边界)向左滚动地图;if(人物向左走 && 地图未达到左边界)向右滚动地图; } else {此时只需要移动人物; }
2>再来看一张,BlueCoder为大家绘制的示意图,帮助理解
3>最后看看实现的代码://如果人物x坐标在中央 if(m_role.IsCenter()) {int mx = m_role.GetXMove();//移动背景if(mx > 0 && !m_scene.IsRightBorder() ||mx < 0 && !m_scene.IsLeftBorder()){moveRoleX = false;m_scene.MoveScene(-mx);} }//移动人物 m_role.MoveRole(moveRoleX); m_role.NextFrame();
2、如何根据鼠标点击的位置来判断人物移动的方向以及移动速度分量
1>移动速度分量的计算
结合实际实现代码来理解:
/**函数功能:计算人物移动速度(mx、my)*参数:dest——鼠标左键点击的坐标,目标位置 */ void CRole::SetMXY(CPoint dest) {CPoint org;//原点int w = m_img.GetWidth() / MAXFRAME;//人物宽度int h = m_img.GetHeight() / MAXFRAME;//人物高度//设置原点为人物下边中心位置org.SetPoint(m_role.x + w / 2,m_role.y + h);/*目标点与原点在垂直于x轴直线上(这时dest.x == org.x,不能用atan求出angle)*/if(dest.x == org.x){m_role.mx = 0;m_role.my = dest.y > org.y ? SPEED : -SPEED;}else{//求助夹角angledouble angle = atan(1.0 * (dest.y - org.y ) / (dest.x - org.x));//根据夹角将速度分解为x轴和y轴上的移动速度m_role.mx = (int)fabs((cos(angle) * SPEED));m_role.my = (int)fabs((sin(angle) * SPEED));//反向if(dest.x < org.x)m_role.mx = -m_role.mx;//反向if(dest.y < org.y)m_role.my = -m_role.my;} }
2>方向的判断,由于窗口默认的坐标系与实际的笛卡尔坐标系不同,因此需要先将窗口中求得夹角angle转换为笛卡尔坐标系中对应的弧度角
实现代码如下:
//求得移动方向的角度angle(以原点(0, 0)为参照点) double angle = atan(1.0 * m_role.my / m_role.mx);//将求得的angle转换为笛卡尔坐标系中对应的弧度角 if(angle > 0) {angle = 2*PI - angle;if(m_role.mx < 0){angle -= PI;} } else if(angle == 0) {if(m_role.mx < 0){angle = PI;} } else {angle = -angle;if(m_role.my > 0){angle += PI;} }
3>转换后的弧度角在[0, 2π),因此除以(2π / 8) ,再取整,就能得到[0, 15]之间的正整数dtn,根据dtn的值便能确定人物移动的方向(具体如下)
实现代码如下:
/*将angle分为16等份,除以PI/8,再取整dtn就是[0, 15]范围中的正整数 */ int dtn = (int)(angle / (PI / 8));//求得方向 switch(dtn) { case 15: case 0://东m_role.dtn = EAST;break;case 1: case 2://东北m_role.dtn = NORTHEAST;break;case 3: case 4://北m_role.dtn = NORTH;break;case 5: case 6://西北m_role.dtn = NORTHWEST;break;case 7: case 8://西m_role.dtn = WEST;break;case 9: case 10://西南m_role.dtn = SOUTHWEST;break;case 11: case 12://南m_role.dtn = SOUTH;break;case 13: case 14://东南m_role.dtn = SOUTHEAST;break; }
也许,你可能这会儿看的有点儿晕(当然,如果能明白,那就最好不过了),没关系
主要是,我觉得很久没写博客了,想做一个较为完整的Demo——按照实际,本节内容应该分为两次讲解,不过,我相信大家能渐渐地明白——我也是这样,慢慢儿琢磨琢磨,总会明白的^
四、代码剖析老规矩,这次呢,BlueCoder封装了两个类:CScene(地图场景类)和CRole(人物角色类)(代码注有详尽注释,就直接贴出来好了)1、CScene(负责地图的管理、背景音乐的播放)1>Scene.h头文件的定义#include<atlimage.h> #include <Mmsystem.h> #pragma comment(lib, "Winmm.lib")class CScene { private: private:CImage m_img;//背景地图png关联的CImage对象CRect m_rWnd;//view窗口的客户区int m_ix;//当前背景横坐标(只考虑地图的横向移动)int LEFTBORDER;//左边界int RIGHTBORDER;//右边界public:bool InitScene();//初始化void PaintScene(CDC&);//绘制场景void SetWndRect(CRect);//设置View窗口的客户区大小bool IsLeftBorder();//是否到达左边界bool IsRightBorder();//是否到达右边界void MoveScene(int);//移动背景void ReleaseScene();//释放内存资源public:CScene(void);~CScene(void); };
2>具体的成员函数实现//初始化场景 bool CScene::InitScene() {m_img.Load(L"res\\0.png");if(m_img.IsNull()){return false;}m_ix = 0;LEFTBORDER = 0;RIGHTBORDER = 0;mciSendString(L"open res\\bgm.wma alias bgm", NULL, 0, NULL);mciSendString(L"play bgm repeat", NULL, 0, NULL);return true; }//设置窗口Rect void CScene::SetWndRect(CRect rect) {m_rWnd = rect;RIGHTBORDER = m_rWnd.Width() - m_img.GetWidth(); }//移动背景 void CScene::MoveScene(int mx) {m_ix += mx;if(m_ix > LEFTBORDER)m_ix = LEFTBORDER;if(m_ix < RIGHTBORDER){m_ix = RIGHTBORDER;} }//判断背景是否到达左边界 bool CScene::IsLeftBorder() {return m_ix == LEFTBORDER; }//判断背景是否到达右边界 bool CScene::IsRightBorder() {return m_ix == RIGHTBORDER; }//绘制场景 void CScene::PaintScene(CDC &cdc) {cdc.SetStretchBltMode(COLORONCOLOR);m_img.StretchBlt(cdc, m_ix, 0, m_img.GetWidth(), m_rWnd.Height(), SRCCOPY); }//释放内存资源 void CScene::ReleaseScene() {mciSendString(L"close bgm", NULL, 0, NULL);m_img.Destroy(); }
2、CRole(人物角色的控制)1>Role.h头文件定义#include<atlimage.h> #include<math.h>//定义一个宏PI(π)——用于计算方向 #define PI 3.141592653//人物移动方向的枚举 enum DIRECTION{SOUTH, WEST, EAST, NORTH,SOUTHWEST, SOUTHEAST, NORTHWEST, NORTHEAST};typedef struct r {int x;int y;//x、y坐标int mx;int my;//x、y坐标上移动的速度int curFrame;//当前帧DIRECTION dtn;//移动方向 }ROLE;class CRole { private:static const int MAXFRAME = 8;//共8帧static const int SPEED = 6;//移动速度, 匀速:6 private:CImage m_img;//人物png关联的CImage对象ROLE m_role;//角色结构体变量CRect m_moveRange;//人物移动范围//是否切换当前帧:为了背景滚动与人物走动效果匹配bool m_change;public:bool InitRole();//初始化bool IsCenter();//判断人物是否到达中央(x坐标)int GetXMove();//获取当前人物在x轴上的移动速度void SetRange(CRect);//设置移动范围void SetMXY(CPoint);//计算mx、myvoid SetDirection();//计算方向void MoveRole(bool);//移动人物void NextFrame();//切换到下一帧void PaintRole(CDC &);//绘制角色void ReleaseRole();//释放内存资源 public:CRole(void);~CRole(void); };
2>具体的成员函数实现( 实现细节 中贴出来的代码就不重复出现在这里了)//初始化角色 bool CRole::InitRole() {m_img.Load(L"res\\role.png");if(m_img.IsNull()){return false;}m_role.curFrame = 0;m_role.dtn = EAST;//初始化方向:东(右)m_role.x = 10;m_role.y = 350;m_role.mx = 0;m_role.my = 0;m_change = true;//一开始允许切换当前帧return true; }//是否到达客户区中央(x坐标) bool CRole::IsCenter() {int width = m_img.GetWidth() / MAXFRAME;int x = m_role.x + width / 2;int mx = (m_role.mx < 0 ? -1 : 1) * m_role.mx;return x >= m_moveRange.Width() / 2 &&x < m_moveRange.Width() / 2 + mx; }//获取当前人物在x轴上的移动速度 int CRole::GetXMove() {return m_role.mx; }/**函数功能:设置移动范围*参数:wnd——View窗口的客户区 */ void CRole::SetRange(CRect wnd) {m_moveRange.SetRect(wnd.left, wnd.Height()/2 + 100,wnd.right, wnd.bottom); }/**函数功能:移动人物*参数:isXMove——是否移动x坐标 */ void CRole::MoveRole(bool isXMove) {//如果人物在可移动范围中就可以移动if(m_role.x + m_role.mx>=0 &&m_role.x + m_role.mx<= m_moveRange.Width()-m_img.GetWidth()/8 &&m_role.y + m_role.my + m_img.GetHeight()/8>= m_moveRange.top &&m_role.y + m_role.my<= m_moveRange.bottom-m_img.GetHeight()/8){if(isXMove)m_role.x += m_role.mx;m_role.y += m_role.my;} }//切换下一帧 void CRole::NextFrame() {m_change = !m_change;if(m_change){m_role.curFrame++;if(m_role.curFrame == MAXFRAME){m_role.curFrame = 0;}} }//绘制角色 void CRole::PaintRole(CDC &cdc) {int width = m_img.GetWidth() / MAXFRAME;int height = m_img.GetHeight() / MAXFRAME;m_img.TransparentBlt(cdc, m_role.x, m_role.y, width, height,width * m_role.curFrame, height * m_role.dtn, width, height,RGB(255, 255, 255)); }//释放内存资源 void CRole::ReleaseRole() {m_img.Destroy(); }
3、View窗口类中来管理整个程序的运行逻辑1>头文件重要的定义部分#define ID_PAINT 100//计时器ID// 特性 private:CScene m_scene;//场景类对象CRole m_role;//角色类对象CRect m_rect;//窗口客户区大小
2>重要的函数实现部分//-----------------WM_PAINT消息_绘图------------------ void CChildView::OnPaint() {CPaintDC dc(this); // 用于绘制的设备上下文//--------------双缓冲贴图--------------------CDC bufferDC;CBitmap bufferBmp;bufferDC.CreateCompatibleDC(NULL);bufferBmp.CreateCompatibleBitmap(&dc,m_rect.Width(), m_rect.Height());bufferDC.SelectObject(bufferBmp);m_scene.PaintScene(bufferDC);m_role.PaintRole(bufferDC);dc.BitBlt(0, 0, m_rect.Width(), m_rect.Height(),&bufferDC, 0, 0, SRCCOPY);bufferBmp.DeleteObject();bufferDC.DeleteDC(); }//-----------------WM_CREATE消息_初始化------------------ int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct) {if (CWnd::OnCreate(lpCreateStruct) == -1)return -1;if(!m_scene.InitScene() ||!m_role.InitRole()){AfxMessageBox(L"png加载失败");exit(0);}SetTimer(ID_PAINT, 49, NULL);return 0; }//-------------WM_SIZE消息_获取窗口客户区大小-------------- void CChildView::OnSize(UINT nType, int cx, int cy) {CWnd::OnSize(nType, cx, cy);GetClientRect(&m_rect);m_scene.SetWndRect(m_rect);m_role.SetRange(m_rect); }//------WM_LBUTTONDOWN消息_用鼠标左键控制任务的走动------- void CChildView::OnLButtonDown(UINT nFlags, CPoint point) {SetCursor((HCURSOR)LoadImage(NULL, L"res\\hand.cur", IMAGE_CURSOR, 0, 0, LR_LOADFROMFILE));//修改为自定义光标m_role.SetMXY(point);m_role.SetDirection();CWnd::OnLButtonDown(nFlags, point); }//--------------WM_TIMER消息_移动人物和背景--------------- void CChildView::OnTimer(UINT_PTR nIDEvent) {bool moveRoleX = true;//是否移动人物的x坐标//如果人物x坐标在中央if(m_role.IsCenter()){int mx = m_role.GetXMove();//移动背景if(mx > 0 && !m_scene.IsRightBorder() ||mx < 0 && !m_scene.IsLeftBorder()){moveRoleX = false;m_scene.MoveScene(-mx);}}//移动人物m_role.MoveRole(moveRoleX);m_role.NextFrame();InvalidateRect(NULL, false);CWnd::OnTimer(nIDEvent); }//-----------------WM_DESTROY消息_释放内存资源------------------ void CChildView::OnDestroy() {CWnd::OnDestroy();KillTimer(ID_PAINT);m_scene.ReleaseScene();m_role.ReleaseRole(); }
五、零积分资源下载点击下载源代码工程
呵呵,其实说来惭愧,目前为止,我还没有正式地看过一本有关游戏开发的书籍——都只是在csdn上逛逛博客,当灵感来的时候,就写一写代码,然后发布博文——其实,也主要是上学期太忙,大三的专业课很多,所以导致这个现象……因此呢,我决定好好地看几本书了,等到学业已成之时,再来和大家分享自己的所学所得^_^
最后,还是送上一句话(与大家共勉):
给自己的想法和梦想一次破茧而出的机会,亲手创造属于你自己的幸福
加油,EveryBody!!!
【VC++游戏开发#十】2D篇 —— 人工智能(一):滚动地图 用鼠标控制人物的走动相关推荐
- 【ANDROID游戏开发十六】ANDROID GESTURE之【触摸屏手势识别】操作!利用触摸屏手势实现一个简单切换图片的功能!...
本站文章均为 李华明Himi 原创,转载务必在明显处注明: 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/android-game/337.html - ...
- 【Android游戏开发十四】深入Animation,在SurfaceView中照样使用Android—Tween Animation!
李华明Himi 原创,转载务必在明显处注明: 转载自 [黑米GameDev街区] 原文链接: http://www.himigame.com/android-game/331.html 很多童鞋说 ...
- 游戏开发心得——书籍篇——《游戏引擎框架》-导论
游戏开发心得--书籍篇--<游戏引擎框架>-导论 FOR THE SIGMA FOR THE GTINDER FOR THE ROBOMASTER 简介: 学习<游戏引擎框架> ...
- APP游戏开发十诫!第一个雏型就要搞定的事
APP游戏开发十诫!第一个雏型就要搞定的事 半路:在启动游戏 App 项目时,多数开发者都能条列出上百项的重要功能,似乎每个项目都非得完成不可,但到底哪些才是项目早期至关紧要,必须在第一个游戏雏型完成 ...
- 【Android游戏开发十二】(保存游戏数据 [上文])详解SharedPreference 与 FIleInputStream/FileOutputStream将数据存储到SD卡中!
李华明Himi 原创,转载务必在明显处注明: 转载自 [黑米GameDev街区] 原文链接: http://www.himigame.com/android-game/327.html 很多童鞋说 ...
- HTML5游戏开发(十)
HTML5游戏开发(十) 一.动画 1.动画循环 在Canvas中这病动画效果很简单:只需要在播放动画时持续更新并绘制就行了.这种持续更新与重绘叫就动画循环. (1)通过requestAnimat ...
- Android游戏开发十日通(7)- 开发一个双人游戏
提要 游戏需要分享才能获得快乐,想想你以前玩过的那些游戏,那些会是真正地存在你婶婶的脑海里?是独自一人躲在被窝里酣战PSP,还是和哥们在网吧一起开黑?是一个人单刷迅龙三连,还是和朋友联机怒刷黄黑龙? ...
- 微信小游戏开发教程-2D游戏原理讲解
微信小游戏开发教程-2D游戏原理讲解 原理 为了更加形象的描述,这里先上一张图: 背景 a. 首先,我们看到背景好像是一张无限长的图片在向下移动.实际则不然,这是一张顶部和底部刚好重叠的图片.这是一种 ...
- CutJS – 用于 HTML5 游戏开发的 2D 渲染引擎
CutJS 是轻量级的,快速的,基于 Canvas 开发的 HTML5 2D 渲染引擎,可以用于游戏开发.它是开源的,跨平台的,与现代的浏览器和移动设备兼容.CutJS 提供了一个类似 DOM 树的 ...
- Spine 游戏开发的 2D
Spine 是一款针对游戏开发的 2D 骨骼动画编辑工具. Spine 旨在提供更高效和简洁 的工作流程,以创建游戏所需的动画 在 Spine 中通过将图片绑定到骨骼上,然后再控制骨骼实现动画. 2D ...
最新文章
- 【jQuery 区别】.click()和$(document).on(click,指定的元素,function(){});的区别
- 转帖:3D音频之双耳效应
- c语言五子棋开题报告,基于VC的五子棋游戏的设计与实现(附答辩记录)
- F4+2 团队项目软件设计方案
- 目睹鸿蒙开创四大至高位面,吞噬星空 绝非鸿蒙系列,完结前最后的分析【申精】...
- pl/sql developer 自带汉化选项
- mysql redis教程_MySQL redis学习与应用
- 蓝桥杯 BASIC-13 基础练习 数列排序
- 西门子plm_企业IT系统集成之PLM、ERP、MES/MOM...
- 凤凰号首次传回未加工的火星照片,人类加紧施展征服火星计划
- for循环性能优化的几种思路
- 韩立刚计算机网络——第四章:网络层
- 用python给表格加边框_python如何设置表格边框
- 聚焦 Kusama Parachain Lease Offering(PLO),Karura 先行网抢先参与
- java 搭建ota服务器_通过文件服务器实现OTA升级的方法技术
- 大数据--论文读后感
- 驾培行业应对新形势“自学直考”新格局冲击剖析
- NOIP 2011 聪明的质检员
- HarmonyOS应用开发系列课(进阶篇)综合考试参考答案
- 统计1~N之间所有立方数的个数并输出这个数目(Java编写)