四叉树碰撞优化版,速度飞一样

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

四叉树碰撞优化版,速度飞一样
原来对四叉树有点恐惧,完全不知道这东西怎么回事,觉得很高级,只好花时间研究了一下,最终效果如下:下面是对四叉树的基本介绍。

懂四叉树的同学可以直接跳过前面看优化部分。

四叉树很简单,就是把一块2d的区域,等分成4份,如下图: 我们把4块区域从右上象限开始编号,逆时针。

[查看图片]
接着每份继续等分4份。

[查看图片]
一直到我们不需要再继续等分为止。

每次插入对象,对象都在其中一个格子里。

进行碰撞检测的时候,对象只会和他同在一个格子里的以及周围的一些对象进行检测,所以提高了效率。

四叉树的原理大致就是这样,和网格法差不多,简单说就是分格子,检测。

//---------------------------------------------分割线
-----------------------------------------------------------//
以下部分会对四叉树的实现以及优化进行说明。

下面我们对四叉树进行实现:
主要代码复制代码MAX_OBJECTS是每个节点能容纳的最多对象超过则分割4个节点, 我们并不是事先就分好格子,而是在插入对象的时候才进行划分。

MAX_LEVELS是四叉树的最大层数超过则不再划分从根节点开始最多6 层。

level:当前层数
objects: 当前节点内的待检测的对象。

bounds 当前节点所表示的2d区域的范围
nodes: 4个子节点队列。

构造函数:复制代码分割函数:复制代码如你所见,每次划分节点,都会把父节点的区域对半分。

辅助方法getIndex:复制代码这个方法是
返回你所要碰撞检测的矩形在哪块区域(当前节点)。

比如当前区域是Rectange(0, 0, 600, 600) 待检测矩形是Rectangel(0, 0, 30, 30) 那么他就在左上象限index = 1 如果是Rectange(400, 400, 30, 30) 那么他就在右下象限index = 3 请注意,当对象刚好在区域的中心线上的时候,也会返回-1。

下面是插入方法insert
最关键的方法:复制代码每次插入一个对象我们都先
看看当前节点有没有子节点如果有我们就插入子节点。

一直检测到他没有子节点为止我们就把对象插入到这个节点,如果这个节点的对象数量> 10个并且当前节点的层数< MAX_LEVELS 我们就把节点继续划分4个子节点。

然后把当前对象循环删除并插入子节点。

如果对象在中心线上,getIndex会返回-1,所以这些对象会被插入到父节
点上面。

最后一个方法,他输入一个你要检测的对象,返回你需要检测的对象列表:复制代码就是从根节点开始一
直递归找到对象所在的节点然后把节点里的对象全push进去再把它的父节点的对象push进去一直到根节点为止。

进行碰撞检测的代码:复制代码四叉树的代码就到这里为止了。

这只是一个开始而已。

//-----------------------------------分割线---------------------------------------//
下面是四叉树的优化部分。

折腾半天,四叉树终于写好了。

运行,一看1000对象居然只有10几20fps。

哥怒了!看人家写的1000对象也有30fps,咋我写的效率就这低呢?
优化折腾了更半天,硬是把帧数从10几fps 活活优化到50fps。

我都不得不表扬一下我自己了!!
下面让我一条一条优化说明。

1.屏幕上移动的对象是Sprite ,我们可以把他们换成Shape来提高效率,换成shape以后大约提高了1 fps。

2.张明光大师曾经说过,对于动态四叉树,由于你每次都
要重新遍历节点(意思应该是你每个检测对象都要重新插入到四叉树里因为每次对象移动他所处的节点都可能发生变
化看上面onEnterFrame 每次都重新insert), 它比普通的遍历对象还要慢。

实际上也没有那么慢。

那么我们要如何不让对象每次都要重新插入呢,看下面:每次对象所在的节点位置发生了变化就会从红色变为绿色。

注意到对象所处的节点并不是一直变化,只是在接触到分割线的一瞬间以及进入到另一个象限的一瞬间,对象所处的节点会发生变化。

由getIndex 和insert方法你可以知道,当对象完全处于4个象限中会返回0 1 2 3, 而当对象刚好移动到分割线上时它不属于任何一个象限,所以会被insert到父节点上。

所以我们并不需要每次都重新插入对象只在对象所处的
节点位置发生变化的时候,重新插入。

引入一个新类:复制代码新加两个属性lastIndex 和nowIndex来记录对象移动前后所在的节点。

parent为了方便删除对象。

观察后我们发现,在retrive方法里每次都有递归计算对象的index 所以我们可以在这里保存一下对象的index。

retrive方法修改如下:复制代码因为getIndex每次都返回0-3 用2进制表示就是00 01 10 11 所以我们可以把每次的结果左移两位<< 这样可以得到如11 01 00 之类的结果达到保存对象节点位置的办法。

加了这部分代码后,程序效率并没有如我想的那样fps变成double,treble
fps大约增加了5-10 帧有20几帧的样子。

这。

看人家每次都是动态插入删除也有30fps,我这需要才插入居然还没有人家高。

吐了一口老血继续优化。

3.研究后发现,在retrive方法里复制代码push也是一个很花时间的东西,1000对象每个对象返回的待检测对象大约几个~上百个之间,假如平均30个那么我们每帧就要push 3万次。

而且由于retrive方法是个递归方法,更花时间。

研究后决定每次直接push整个objects数组检测碰撞时用
二维数组检测,并且利用rect的parent属性去掉递归方法。

把retrive整理一下,计算index的部分我们放到另外
一个方法calIndex中。

所以retrive方法最后被优化成这个样子:复制代码计算时间立马变为原来的1/10.4.给Rect加一个新属性Hitted复制代码每次碰撞检测如果A碰到B 那么B的hitted属性就设为true,下次检测B的时候就跳过B. 加入1000对象基本所有的对象都有碰撞,等于只检测了一半物体,效率立马提
高100%。

5.到了这里已经绞尽脑汁了
还有什么优化呢?
思考n久发现,retrive方法里会从对象所在节点开始一
直遍历到根节点把节点上的对象全部push进待检测列表里。

但是,我们很容易发现每个检测对象最多和自己所在节点,父节点,父节点的父节点有关系,最多3层。

比如1000对象,最左下角的格子就不可能和根节点的对象有交集。

下面是在优化以前的retrive方法:点击任意矩形返回它可
能会碰撞检测的列表。

你发现即使你选择了最左下角的矩形,根节点也就是屏幕最中间的那两条线矩形最多的那两条,上面的矩形也会返回。

下面是retrive方法优化以后。

发现没有,返回的节点数量立马少了一大半。

它只会返回2-3层节点。

对于处于屏幕左下象限的对象,如果对象在矩形区域内的左下角区域,他会返回当前小矩形内的对象,以及他的父节点内的对象。

2层。

否则他会返回3层对象,具体可以测试看看。

(这个其实不是跟左下象限有关,而是当前对象的父节点的所在象限和他的父节点的父节点的所在象限相同则返回两层,否者返回三层所以最左上, 右上, 左下, 右下的对象都只返回2层节点)
(这个返回2-3层的优化必须是你的检测对象大小至少不大于最小的格子大小否则会不准确比如一个对象就占了
n个格子。

)6.优化到这里,脑细胞已经死完了,效率也可以了,但是还有什么可以优化么?
是的,的确还有。

虽然优化过的retrive方法返回的对象已经少了很多,但是并不是所有返回的对象都需要进行碰撞检测。

比如对象处于左下角的矩形,他就是在左象限以及下象限(这个很好理解吧),而所在矩形的父节点上的对象(十字型的那些对象),就只用检测处于左象限以及下象限的对象,也就是省略了上半部和右半部。

这里代码表示有点绕,就不亮出来了。

简单说每个Rect新增上下左右四象限的Boolean数据表示对象是否处于四部分象限内,左下就是左= true 下= true
每个四叉树节点也新增上下左右四象限数据如0号节点他是右上象限,那么他的右= true 上= true
我们就是利用这些新增的数据来判断是否需要对返回的对象进行碰撞检测。

7.到这里效率真的已经不错了,还有什么优化么?
的确还有。

这里我也思考了n就才发现,帧数又提高了几帧。

在上面的优化之后,我们只在对象改变节点位置的时候才重新插入,每次插入insert的时候我们都有用
getIndex(rect) 来计算对象所处的节点位置,insert是一个递归方法,他从根节点开始一直计算到叶子节点,
getIndex算然计算量不大但是我们可以省略这一步么?
是的。

retrive方法每次都有计算对象的index,而且保存在新增属性nowIndex里面,所以我们插入的时候可以根据nowIndex计算得出。

给Rect新增一个level属性
每次计算index的时候复制代码来保存对象所在的深度.
接着新增方法newInsert,把方法上半部修改成这样:复制代码注意是& 而不是&&8.最后说说想到的其他优化
把四叉树的对象列表Array改为Vector
试验后发现帧数反而下降了1,2帧。

研究后发现Flash11里Array的效率提高了, Flash10里Array的Push Splice等操作都比Vector慢(至少是差不多差一点的样子), 但是Flash11里Push操作居然比Vector 还快一倍, 其他的方法又差不多,Splice则比Vector慢一倍。

所以最后不推荐把Array改成Vector优化到这里,终于大功告成,真想不出还有什么优化了。

运行一看,1000对象有47~50fps 真不错了。

再放到浏览器中运行一看,居然60fps 无压力, 即使增加到1500对象也几乎是60fps。

才发现原来浏览器的
FlashPlayer效率比独立播放器高,OH Yeah!写到这
里真心已经精疲力尽肝脑涂地脑细胞死光光了,如果你觉得此文对你有帮助,请多多评论,多多发言,顶。

代码下载:QuadTest.rar
(4.65 KB)
(下载次数: 478, 2013-9-3 15:37:59 上传)
下载积分: 银子-1 两最后说下这个四叉树的bug 第5,6,7条优化会导致碰撞不准确所以我把5,6,7条优化注释掉了代码重新上传了帧数因此下降了10fps
至于为什么会导致这种结果过阵子再研究现在在折腾其
他东西。

5,6,7条就先不删掉了至少提供了几种思路。

相关文档
最新文档