俯视视角游戏碰撞检测:限定移动类型和不限定移动类型

http://www.webjx.com/  2010-03-21 07:15:53  来源:闪吧BBS 作者:Cl@rke将军

Webjx核心提示:俯视视角游戏常见碰撞检测方法全解(4) 360度不限定移动类型.

(3)4或8方向限定移动类型

如果你已经看到这里,我们已经对要制作的东西做了两次分类
第一次将整个“游戏”分类,取了“俯视”的一部分
第二次将“俯视”分类,取了4或8方向限定移动类型(下文简称“限定移动”)
这样的“化整为零”是结构化面向过程程序设计和面向对象程序设计的正确思路,我们将继续这个思路,直到问题简化到能用具体办法解决为止。
后文会用类似下面粗体字的明确书签标志当前的划分层次,避免逻辑混乱
每个类型本人会举大量经典游戏作为例子,方便对号入座
限定移动可以分为完全限定移动和不完全限定移动。
前者的每个元素(玩家、敌人、地图元素等)必须与地图的分格一一对应
后者只是限制了玩家的操作是4方向或8方向的,但是未必对应地图的分格

3.1  游戏->俯视->限定移动->完全限定移动
特点:某游戏主要使用键盘操作,玩家只能向上下左右移动,障碍物是矩形障碍,大小和坐标非常统一,对齐到网格,等同于玩家大小的障碍。
举例:传统rpg、传统slg、吃豆精灵、推箱子、包括部分方块类。此类游戏的首选碰撞方式是“数组地图”,数组地图是一个二维数组:


var map:Array = [

[1,1,1,1,1,1,1,1,1,1],

[1,0,0,0,0,0,1,0,0,1],

[1,0,0,0,1,1,1,0,0,1],

[1,0,0,0,1,0,0,0,0,1],

[1,0,0,0,1,1,1,1,0,1],

[1,0,0,0,0,0,0,0,0,1],

[1,1,1,1,1,1,1,1,1,1]];

这是一张简易的数组地图。我们可以用“0”来表示“空地”,用“1”来表示“石头”
玩家有自己在数组地图上的坐标。
玩家(player)的现坐标为(3,2),注意,数组是从0开始计数的,而不是1
如果操作玩家向左移动,那么检测(3-1,2)是否为0,该位置是0,返回false,玩家可以移动
如果该位置操作玩家向右移动,检测到(3+1,2)是1,返回true,发生“碰撞”,禁止玩家的移动
玩家和敌人在地图上随时写入数组,如玩家用2标识,敌人用3标识,
某单位移动时将数组地图对应该单位坐标清零,并在该单位的新坐标处把0修改为该单位标识
此类游戏存在投射性武器不写入数组
武器检测碰撞时,根据武器的坐标计算出该武器属于数组地图的哪一格
例如真实地图的每格宽度为20
炮弹真实坐标(212.5,113),以bullet.x和bullet.y表示:

var bulletMapX:uint = Math.floor(bullet.x/20);

var bulletMapY:uint = Math.floor(bullet.y/20);

//其中的Math.floor可换成int()或者其他取整方法,对as2同

得到bulletMapX = 20,bulletMapY = 10,即其在数组地图上坐标。
只检测数组地图上的(20,10)位置标识,即可判断炮弹应继续飞行还是爆炸,如果爆炸杀伤的目标是谁。
SLG游戏可能需要自动寻路。数组地图公认A*算法为先进高效的寻路方法,该算法可于百度搜索到,不再赘述。

3.2  游戏->俯视->限定移动->不完全限定移动
特点:某游戏主要使用键盘操作,玩家只能向上下左右移动,障碍物是矩形障碍,大小和坐标可能不统一,没有小于玩家大小的障碍。
举例:赤色要塞、魂斗罗2的第二关等ACT游戏,大部分手柄操作的ARPG如黄金太阳(GBA)、赛达尔传说(NDS前的版本)。
此类游戏可以使用点描述方法检测碰撞,用顶点作为碰撞点,其点碰描述整个物体碰撞的方法。
未命名-1.jpg
图中红色点即为顶点,其坐标为(player.x±20,player.y±20)
在“地图没有小于玩家的物体”条件下,“玩家碰撞物体”等价于“不少于1个顶点碰撞了物体”
使用DisplayObject/hitTestPoint前,需将所有地图上固定的物体放入一个容器内,而不要用for循环检测与每个物体的碰撞。
运动物体虽然可以放入这个容器,但是运动物体往往是玩家自身或者需要运动碰撞检测的敌人自身,放入容器会导致和自身的碰撞检测,恒为true,没有意义。
解决办法是将碰撞点向外侧移动。略微放大碰撞区域,这样就不会与自身碰撞。
未命名-2.jpg
如碰撞区域必须小于等于运动物体实际图形大小,那么必须单独在运动物体之间检测碰撞(运动物体数量少使用for循环,如果运动物体过多需较高的解决方法,参加4楼使用BitmapData/hitTest的办法),不能使用这个方法。
如地图上的固定元素有小于玩家宽高大小,但是不小于玩家宽高大小一半的大小,请适当增加碰撞点数量
未命名-3.jpg

(4):360度不限定移动类型

特点:可能使用键盘或鼠标操作,玩家可以向任意方向移动,障碍物形状未必固定,大小和坐标未必统一,所有元素自由度非常高。
举例:赛车类游戏,大部分战争题材纵版游戏
此类游戏可以使用位图碰撞、点描述和函数描述的办法检测碰撞。

4.1  游戏->俯视->不限定移动->位图碰撞
使用BitmapData/hitTest
具体办法是将玩家用BitmapData.draw方法转成位图数据(注意,这个方法不会考虑玩家的缩放)
地图可能非常大(比如2000x1500)这个转换成位图效率非常低下。
正确的做法是根据玩家坐标,只描绘玩家周围的一小部分位图。
描绘区域高度有 玩家高度+玩家速度x2 即可
此方法效率很高,能立刻检测玩家是否碰撞了地图障碍,缺点也比较明显,就是不知道玩家的哪一点碰撞了地图障碍。一个赛车游戏如果用这个的话,只能做成“碰到墙立刻爆炸”或“碰到墙立刻减速为0”再或“碰到墙立刻速度变成相反的”等很绝对的判定。
BitmapData/hitTest的检测比较万能。
前面提到的所有碰撞都可以用它来检测,考虑缓存大小和效率问题,如果运动物体很多的话不能完全依赖。

4.2  游戏->俯视->不限定移动->点描述
比起4方向移动的点描述,360度点描述相对需要计算,必须大量使用三角函数
(三角函数是常规解决方案。个别高手使用矩阵或者向量乘法表示角度,尤其是向量乘法表示角度能减少计算量,提高速度)
s1.jpg

设上图剪辑为mc。
mc的箭头指向为mc注册时的y轴方向,现在mc右旋转了rotation度
设剪辑注册时的宽度(即短边长度)为og_width;
设剪辑注册时的高度(即长边长度)为og_height;
如果不使用极坐标,碰撞点相对mc的坐标可以通过三角函数算出
本人推荐熟练的程序员自行制作极坐标类,在360度移动的俯视视角游戏中作用很大


var dtAngle:Number = Math.atan(og_width/og_height)//单位夹角

var length:Number = Math.sqrt//对角线长度的一半(og_width*og_width+og_height*og_height)/2

var point1: Point = new Point();

point1.x = length*Math.cos((90-(mc.rotation-dtAngle))*Math.PI/180);

point1.y = -length*Math.sin((90-(mc.rotation-dtAngle))*Math.PI/180);

//因为flash坐标轴y轴与数学y轴上下相反,本人习惯保留纵坐标的符号,而不是将其带入三角函数。可根据个人代码风格修改。

var point2: Point = new Point();

point2.x = length*Math.cos((90-(mc.rotation+dtAngle))*Math.PI/180);

point2.y = -length*Math.sin((90-(mc.rotation+dtAngle))*Math.PI/180);

var point3: Point = new Point();

point3.x = length*Math.cos((270-(mc.rotation-dtAngle))*Math.PI/180);

point3.y = -length*Math.sin((270-(mc.rotation-dtAngle))*Math.PI/180);

var point4: Point = new Point();

point4.x = length*Math.cos((270-(mc.rotation+dtAngle))*Math.PI/180);

point4.y = -length*Math.sin((270-(mc.rotation+dtAngle))*Math.PI/180);

hitTestPoint检测的是一个全局坐标和某剪辑的碰撞
使用mc的localToGlobal方法获得点的全局坐标
之后即可检测其与碰撞区是否碰撞。
此方法优点在于能较清楚地知道剪辑究竟哪里与外界碰撞。
例如,一台汽车与房子碰撞,我们检测到了一辆前进的车碰撞点是车的左前,那么可以在这个点播放一个“碰撞火花动画”并根据这个碰撞作出车应该被动向右转向和减速的判断。
上例给了车4个碰撞点。碰撞点越多,对这次碰撞的描述越清晰。
如果同时有两个以上的点发生碰撞,取其中点作为这次碰撞的实际发生点。
例如此方块向箭头方向运动,红点和绿点同时检测到碰撞,可以认为是此方块前方正中发生了碰撞,并作出方块直接速度反向并在前方正中播放碰撞火花动画的判定。

4.3  游戏->俯视->不限定移动->函数描述
特点:障碍物形状未必相同,但是规则的几何图形,或者能近似考虑成几何图形,注册点严谨在几何中心,总数量较少。或项目出现难以用点描述的情况。
举例:台球为代表的球类平面游戏,停车场等要求非常精准碰撞的游戏
停车场虽然用BitmapData/hitTest也可以制作,但是找不到碰撞点,不如后面方法。
此类碰撞不使用flash自带的碰撞接口,使用的是高中数学知识。

首先定义几个基础类:
Line
//线段类,属性有a,b,c,P1,P2,其中P1,P2为端点,线段上的坐标为(x,y)的点满足a*x+b*y+c==0
Box
//矩形,属性有P1,P2,P3,P4即四个顶点,get方法能得到任意两点间的线段L12,L23,L34,L41: Line
Ball
//圆形,属性有x,y,r,即圆心横、纵坐标,半径
最长应用的数学公式如下:
点之间距离公式(勾股):Math.sqrt((P1.x-P2.x)*(P1.x-P2.x)+(P1.y-P2.y)*(P1.y-P2.y))
点到直线距离公式:Math.abs(a*x+b*y+c)/Math.sqrt(a*a+b*b);
线段联立方程://求解过程略
a1*x+b1*y+c1 == 0;
a2*x+b2*y+c2 == 0;
过后补出AS3.0类

我们可以通过检测方程联立解是否在线段上判断两条线段碰撞。
可以用圆心距离是否小于半径和判断两个圆形碰撞。
可以用圆心到直线距离是否小于圆半径判断球和直线的碰撞
可以综合分析四条边分别有无碰撞,以及是否出现一个矩形包含另一个,来判定两个矩形的碰撞
上述检测方法都能将具体碰撞的位置求出

试举一个用函数碰撞优化的例子:
此图中,玩家的炮台发射了一道长度1000的直激光。敌人有3个,要检测是否命中敌人。
新建 BMP 图像.JPG
hitTestObject首先被排除掉了,理由略。

接着排除的是BitmapData.hitTest。因为BitmapData的效率极大取决于位图大小,这道激光斜着发射的话将会被缓存1000/1.414 x 1000/1.414的很大的位图。

hitTestPoint可以作碰撞检测。在激光上定比分点,根据敌人大小。如敌人大小是20宽度,那么每20个像素长度取一个点检测碰撞,共取50个点。每个点检测3次碰撞,循环共150次。

如果此图情况用点到直线距离小于圆半径作为判断条件,将大大地减少计算量。只需要3个敌人各自使用这个公式检测一次就够了。

待续。。。

更多