《MFC游戏开发》笔记九 游戏中的碰撞判定初步&怪物运动简单AI
本系列文章由七十一雾央编写,转载请注明出处。
http://blog.csdn.net/u011371356/article/details/9374935
作者:七十一雾央 新浪微博:http://weibo.com/1689160943/profile?rightmod=1&wvr=5&mod=personinfo
在上一节笔记中,雾央预告说打算这一节讲流畅动画的改进以及重新封装代码的,但是看起来似乎没有多少同学感兴趣,所以雾央决定把他们往后挪一挪,先来讲解点有意思的东西。
在游戏之中,碰撞判定是必不可少的一块,在许多地方都需要用到。比如说在即时战斗游戏中,当人物碰到怪物的时候,人物是会扣血的;在回合制中,碰到怪物是要进入战斗场景的;游戏之中也还有一些荆棘之类的东西,还有非鼠标控制拾取物体等等,这些都涉及到了碰撞判定。
游戏之中,还有一块很重要的部分:障碍物判定,即玩家在运动的时候,需要判定哪些地方可以通过,哪些地方可以到达,这部分内容比较多,雾央将在下一节继续讲解,感兴趣的同学请保持关注。
在这一节笔记中,雾央要实现的demo是怪物追逐人物,当两者碰撞的时候给出提示信息。
一、简单怪物运动AI
相比于应用软件,游戏软件中包含的知识太多了,每一块拿出来都可以写一本厚厚的书,比如怪物AI这一部分。AI:Artificial Intelligence,即人工智能,人工智能研究如何用计算机去模拟、延伸和扩展人的智能;如何设计和制造更聪明的计算机以及智能水平更高的智能计算机等等。游戏人工智能博大精深,雾央也只是了解很少的一部分,本教程也只是面向游戏初学者,所以那些高深的基因算法、神经网络、蚂蚁算法等有待大家以后自己去探索。
本文将要实现的怪物运动AI是:怪物主动追逐人物。
首先,怪物追逐人物,在游戏中表现的效果就是两者距离不断靠近。归结到算法就是
if(Monster.x<Hero.x)
Monster.x++;
else Monster.x--;
if(Monster.y<Hero.y)
Monster.y++;
else Monster.y--;
当然大家还可以让怪物更智能,比如让怪物依据当前的血量和玩家的血量来决定是不是继续追击,在Dota AI地图中,敌方英雄AI在半血的时候一般就会回家补给状态去了,但是如果玩家的血量比电脑还要少,AI一般会选择奋不顾身的追杀。再比如大家可以给怪物加上活动范围,在玩家进入怪物的活动区间时才选择追逐玩家,当玩家与怪物之间的距离达到一定值时,放弃追击,这个都不不难实现的,只是在上面代码的基础上增加更多判断的代码而已。在后面雾央打算讲解一个完整的游戏demo,在里面将会继续完善怪物AI。
下面给大家看一下实现效果
一开始,玩家出现在地图左下角,怪物出现在地图右上角。
怪物向人物靠近
躲开怪物,呵呵
另外说一句,背景地图是古剑奇谭一的安陆县,这是雾央很喜欢玩的RPG游戏,画面和剧情真心很赞。一个月后即8.18日古剑奇谭二就要出了,古剑二由回合制改为即时战斗了,很是期待,呵呵。
二、碰撞判定
关于碰撞检测,雾央又得说这也是游戏中很复杂的一块了。越复杂的算法得到的精度当然也就越高,带来的就是运行速度的下降。在一般的游戏中,为了速度上的考虑,都会采用近似的算法。
精确的算法中比较简单的一种就是通过像素颜色进行,在下一节中雾央将会进行详解。这里先介绍一下近似算法。
近似算法一般都是根据物体的形状来进行,包括外接圆,外接矩形等判定方法。有些物体不太规则,可能还会对物体进行分段,每一段是一个小矩形,采用组合起来进行判定的方法。
对于我们来说,外接矩形方法就是一种非常简单,精度又不太差的方法,因为我们的人物形状很接近矩形。因此我们的人物与怪物碰撞检测就转化为了判断两个矩形碰撞的问题。
下面是两个矩形刚好碰撞的情况:
仔细观察,我们便可以得出碰撞时满足的条件:
假定黑色矩形固定,那么碰撞时,蓝色矩形的中心在红色方框内部。
那么,我们只需要时刻检查蓝色矩形的中心位置是否在红色方框内部即可。具体代码请见最后,雾央已经写好了注释。
在发生碰撞的时候在窗口左上角输出“发生碰撞”,否则输出“没有碰撞”,输出文字的函数是TextOut,大家看着代码应该可以直接明白参数的函数,即xy坐标和输出字符串。
运行效果如下:
另外,雾央加入了背景音乐,使用的是媒体控制接口。大家要使用的话,记得添加头文件和导入库
#include "mmsystem.h"
#pragma comment(lib,"winmm.lib")//导入声音头文件库
至于打开,播放,暂停,关闭音乐等都可以通过mciSendString来实现,详细请参考代码或者百度,有不懂的也可以留言问雾央,这个函数甚至可以用来播放视频,很是强大,呵呵。当然大家也可以使用PlaySound这个API来播放音乐。
三、源代码欣赏
头文件
// ChildView.h : CChildView 类的接口
// #pragma once #define SNOW_NUMBER 100 //雪花例子的数量
// CChildView 窗口 class CChildView : public CWnd
{
// 构造
public:
CChildView(); // 特性
public:
//人物结构体
struct charcter
{
CImage character; //保存人物的图像
int x; //保存人物的位置
int y;
int direct; //人物的方向
int frame; //运动到第几张图片
int width; //图片的宽度和高度,用于碰撞判定
int height;
int Xcenter;
int Ycenter;
}MyHero,Monster; 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:
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文件
//-----------------------------------【程序说明】----------------------------------------------
// 【MFC游戏开发】笔记九 碰撞检测和运动型AI 配套源代码
// VS2010环境
// 更多内容请访问雾央CSDN博客 http://blog.csdn.net/u011371356/article/category/1497651
// 雾央的新浪微博: @七十一雾央
//------------------------------------------------------------------------------------------------ // ChildView.cpp : CChildView 类的实现
// #include "stdafx.h"
#include "GameMFC.h"
#include "ChildView.h" #include "mmsystem.h"
#pragma comment(lib,"winmm.lib")//导入声音头文件库 #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()
{
mciSendString("stop bgMusic ",NULL,0,NULL);
} 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.character.Load("heroMove.png");
TransparentPNG(&MyHero.character); MyHero.width=80;
MyHero.height=80;
//初始化英雄状态
MyHero.direct=UP;
MyHero.frame=0;
//设置英雄初始位置
MyHero.x=80;
MyHero.y=400; //加载怪物
Monster.character.Load("monster.png");
TransparentPNG(&Monster.character);
Monster.width=96;
Monster.height=96;
Monster.direct=LEFT;
Monster.frame=0;
Monster.x=700;
Monster.y=100; //打开音乐文件
mciSendString("open background.mp3 alias bgMusic ", NULL, 0, NULL);
mciSendString("play bgMusic repeat", NULL, 0, NULL); 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,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,0,0,WINDOW_WIDTH,WINDOW_HEIGHT);
//贴英雄
MyHero.character.Draw(m_cacheDC,MyHero.x,MyHero.y,80,80,
MyHero.frame*80,MyHero.direct*80,80,80);
//贴怪物
Monster.character.Draw(m_cacheDC,Monster.x,Monster.y,96,96,
Monster.frame*96,Monster.direct*96,96,96);
//怪物状态更新
//水平方向上靠近
if(Monster.x<MyHero.x)
{
Monster.x++;
Monster.direct=RIGHT;
}
else if(Monster.x>MyHero.x)
{
Monster.x--;
Monster.direct=LEFT;
}
//竖直方向上靠近
if(Monster.y<MyHero.y)
Monster.y++;
else
Monster.y--; //判断是否碰撞
MyHero.Xcenter=MyHero.x+MyHero.width/2;
MyHero.Ycenter=MyHero.y+MyHero.height/2;
Monster.Xcenter=Monster.x+Monster.width/2;
Monster.Ycenter=Monster.y+Monster.height/2;
//设置文字背景透明
m_cacheDC.SetBkMode(TRANSPARENT);
//设置文字为红色
m_cacheDC.SetTextColor(RGB(255,0,0)); //假定我们将英雄作为图中的黑色矩形
if(Monster.Xcenter< MyHero.Xcenter+(MyHero.width/2+Monster.width/2) &&
Monster.Xcenter> MyHero.Xcenter-(MyHero.width/2+Monster.width/2) &&
Monster.Ycenter< MyHero.Ycenter+(MyHero.height/2+Monster.height/2) &&
Monster.Ycenter> MyHero.Ycenter-(MyHero.height/2+Monster.height/2) )
m_cacheDC.TextOut(0,0,"发生碰撞"); //在窗口左上角显示碰撞信息
else
m_cacheDC.TextOut(0,0,"没有碰撞"); //最后将缓冲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;
Monster.frame++;
if(Monster.frame==4) //以后雾央会对代码进行封装,就不会像这里这么重复了。
Monster.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;
}
《MFC游戏开发》笔记九到这里就结束了,更多精彩请关注下一篇。如果您觉得文章对您有帮助的话,请留下您的评论,点个赞,能看到你们的留言是我最高兴的事情,因为这让我知道我正在帮助曾和我一样迷茫的少年,你们的支持就是我继续写下去的动力,愿我们一起学习,共同努力,复兴国产游戏。
对于文章的疏漏或错误,欢迎大家的指出。
《MFC游戏开发》笔记九 游戏中的碰撞判定初步&怪物运动简单AI的更多相关文章
- [Unity游戏开发]向量在游戏开发中的应用(二)
本文已同步发表在CSDN:http://blog.csdn.net/wenxin2011/article/details/50972976 在上一篇博客中讲了利用向量方向的性质来解决问题.这篇博客将继 ...
- Unity 2D游戏开发教程之游戏中精灵的跳跃状态
Unity 2D游戏开发教程之游戏中精灵的跳跃状态 精灵的跳跃状态 为了让游戏中的精灵有更大的活动范围,上一节为游戏场景添加了多个地面,于是精灵可以从高的地面移动到低的地面处,如图2-14所示.但是却 ...
- 【转载】【游戏开发】在Lua中实现面向对象特性——模拟类、继承、多态
[游戏开发]在Lua中实现面向对象特性——模拟类.继承.多态 阅读目录 一.简介 二.前提知识 三.Lua中实现类.继承.多态 四.总结 回到顶部 一.简介 Lua是一门非常强大.非常灵活的脚本语 ...
- Unity 2D游戏开发教程之游戏精灵的开火状态
Unity 2D游戏开发教程之游戏精灵的开火状态 精灵的开火状态 “开火”就是发射子弹的意思,在战争类型的电影或者电视剧中,主角们就爱这么说!本节打算为精灵添加发射子弹的能力.因为本游戏在后面会引入敌 ...
- [Unity3D]Unity3D游戏开发Lua随着游戏的债券(在)
---------------------------------------------------------------------------------------------------- ...
- Unity 4.2.0 官方最新破解版(Unity3D 最新破解版,3D游戏开发工具和游戏引擎套件)
Unity是一款跨平台的游戏开发工具,从一开始就被设计成易于使用的产品.作为一个完全集成的专业级应用,Unity还包含了价值数百万美元的功能强大的游戏引擎.Unity作为一个游戏开发工具,它的设计主旨 ...
- [Unity游戏开发]向量在游戏开发中的应用(三)
本文已同步发表在CSDN:http://blog.csdn.net/wenxin2011/article/details/51088236 在上一篇博客中讲了利用向量点乘在游戏开发中应用的几种情景.本 ...
- [Unity游戏开发]向量在游戏开发中的应用(一)
本文已同步发表在CSDN:http://blog.csdn.net/wenxin2011/article/details/50810102 向量在游戏开发中是非常实用的,我们在学校学完向量的知识后,只 ...
- 【读书笔记《Android游戏编程之从零开始》】20.游戏开发基础(游戏数据存储)
对于数据的存储,Android 提供了4种保存方式. (1)SharedPreference 此方法适用于简单数据的保持,文如其名,属于配置性质的保存,不适合比较大的情况,默认存放在手机内存里 (2) ...
随机推荐
- HDU 2437 Jerboas (剪枝搜索)
题意:给定一幅图,图上有两种点T,P.......一只跳鼠在一个T点作为起始点,它想通过图上的路到达某个P点,P点满足如下要求: (1).到达P点的途中路径权值为k的倍数 (2).尽量让路径权值取最小 ...
- springMVC使用注解方式进行页面跳转
<!--控制层-->package cn.org.spartacus.spring; import org.springframework.beans.factory.annotation ...
- 【M5】对定制的“类型转换函数”保持警觉
1.隐式类型转换有两种情况:单个形参构造方法和隐式类型转换操作符.注意:隐式类型转换不是把A类型的对象a,转化为B类型的对象b,而是使用a对象构造出一个b对象,a对象并没有变化. 2.单个形参构造方法 ...
- Android Studio系列教程一--下载与安装
背景 相信大家对Android Studio已经不陌生了,Android Studio是Google于2013 I/O大会针对Android开发推出的新的开发工具,目前很多开源项目都已经在采用,Goo ...
- .NET连接SAP系统专题:C#调用RFC代码(三)
本文就说明在C#中如何编写代码来调用SAP中的RFC函数获取数据. 首先需要引用两个NCO3.0的DLL DLL下载地址:http://files.cnblogs.com/mengxin523/SAP ...
- Codeforces Round #338 (Div. 2) D. Multipliers 数论
D. Multipliers 题目连接: http://codeforces.com/contest/615/problem/D Description Ayrat has number n, rep ...
- Codeforces Round #311 (Div. 2) E. Ann and Half-Palindrome 字典树/半回文串
E. Ann and Half-Palindrome Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/contes ...
- vs 设置生成的实体为复数
- Linux的内存回收和交换
Linux的内存回收和交换 版权声明: 本文章内容在非商业使用前提下可无需授权任意转载.发布. 转载.发布请务必注明作者和其微博.微信公众号地址,以便读者询问问题和甄误反馈,共同进步. 微博ID:or ...
- Linux下杀僵尸进程办法
1) 检查当前僵尸进程信息 # ps -ef | grep defunct | grep -v grep | wc -l 175 # top | head -2 top - 15:05:54 up 9 ...