操作系统:Windows8.1

显卡:Nivida GTX965M

开发工具:Unity2017.2.0f3


原文出处 : Quick Tip: Use Quadtrees to Detect Likely Collisions in 2D Space

许多游戏需要使用碰撞检测算法去判定两个对象是否发生碰撞,但是这些算法通常意味着昂贵操作,拖慢游戏的运行速度。在这篇文章中我们将会学习四叉树 quadtrees,并学习如果通过四叉树跳过那些物理空间距离比较远的对象,最终提高碰撞检测速度。

注:原文中使用Java实现,但是考虑目前多产品是基于Unity3D,故采用C#进行相关实现说明。

IntroDuction


碰撞检测 Collision detection 对于视频游戏来说是非常必要的。无论是2D游戏或是3D游戏中,正确的检测两个物体发生碰撞检测非常重要,否则会出现一切有趣的效果:

然而,碰撞检测是一种非常昂贵的操作。假设有100个对象需要进行碰撞检测时。每两个对象进行比较:100 x 100 = 10000 次,检测的次数实在太多,消耗大量CPU资源。

一种优化途径是减少非必要的碰撞检测的数量。比如屏幕两端的两个物体位于上下两侧,是不可能发生碰撞检测,因此不需要检测它们之间的碰撞。这正是四叉树发挥作用的地方。

What Is a Quadtree?


一个四叉树 quadtree 是一种将2D区域划分为更易于管理的数据结构。他是基于二叉树 binary tree 的扩展,采用四个节点代替两个节点。

在下面的图示中,每个图像是2D空间的一个可视化呈现,红色方块表示对象物体。同时为了更好的说明问题,子节点按照逆时针顺序标记。

一个四叉树开始于单节点(根节点)。此时的根节点还没有进行2D空间的分隔,故添加到四叉树的对象被添加到单节点里。

当更多的对象添加到四叉树时,他最终会进行分裂为四个子节点的形态。每个对象会会根据他们在2D空间中的位置划分到这些子节点中。任何不能完全适合子节点内部边界规则的对象将会被放置在父节点中。

随着对象数量的增加,每个子节点可以继续分裂。

如图所示,每个节点只能包含有限的对象。同时我们了解到,左上角节点中的对象不能与右下角节点中的对象发生碰撞,所以我们不需要在这些节点之间进行昂贵的碰撞检测算法。

Take a look at this JavaScript example 基于Javascript实现的四叉树案例。

Implementing a Quadtree


实现四叉树相对比较简单、容易。下面的代码采用C#编写,注原文基于Java。但是无论啥语言实现理念都是一致的,另外会在每个代码块之后进行注释说明。

我们从创建四叉树的核心类 Quadtree 开始。代码如下所示:

  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4.  
  5. public class QuadTree {
  6.  
  7. private int MAX_OBJECTS = ;
  8. private int MAX_LEVELS = ;
  9.  
  10. private int level;
  11. private List<SquareOne> objects;
  12. private Rect bounds;
  13. private QuadTree[] nodes;
  14.  
  15. public QuadTree (int pLevel, Rect pBounds)
  16. {
  17. level = pLevel;
  18. objects = new List<SquareOne>();
  19. bounds = pBounds;
  20. nodes = new QuadTree[];
  21. }

这个类的看上去是比较直观的, MAX_OBJECTS 定义了一个节点所能持有的最大对象数量,如果超过则进行分裂。 MAX_LEVELS 定义了子节点的最大深度。level 是当前节点深度 ( 0 代表最上层节点 ), bounds 代表2D空间的区域面积, 最后 nodes 代表四个子节点的集合。

在这个例子中,四叉树可以容纳的对象是基于矩形形状 Rectangles 的,但是没有任何限制进行自定义。

下面我们实现四叉树的核心五个函数,分别为: Clear Split GetIndex Insert Retrieve

  1. // Clear quadtree
  2. public void Clear()
  3. {
  4. objects.Clear();
  5.  
  6. for(int i = ; i < nodes.Length; i++)
  7. {
  8. if(nodes[i] != null)
  9. {
  10. nodes[i].Clear();
  11. nodes[i] = null;
  12. }
  13. }
  14. }

Clear 函数基于递归的思路清理四叉树每个节点及节点中的对象集合。

  1. // Split the node into 4 subnodes
  2. private void Split()
  3. {
  4. int subWidth = (int)(bounds.width / );
  5. int subHeight = (int)(bounds.height / );
  6. int x = (int)bounds.x;
  7. int y = (int)bounds.y;
  8.  
  9. nodes[] = new QuadTree(level + , new Rect(x + subWidth, y, subWidth, subHeight));
  10. nodes[] = new QuadTree(level + , new Rect(x, y, subWidth, subHeight));
  11. nodes[] = new QuadTree(level + , new Rect(x, y + subHeight, subWidth, subHeight));
  12. nodes[] = new QuadTree(level + , new Rect(x + subWidth, y + subHeight, subWidth, subHeight));
  13. }

Split 函数用于将当前节点分裂为四个子节点,并对四个节点进行边界裁剪的初始化操作。

  1. private List<int> GetIndexes(Rect pRect)
  2. {
  3.  
  4. List<int> indexes = new List<int>();
  5.  
  6. double verticalMidpoint = bounds.x + (bounds.width / );
  7. double horizontalMidpoint = bounds.y + (bounds.height / );
  8.  
  9. bool topQuadrant = pRect.y >= horizontalMidpoint;
  10. bool bottomQuadrant = (pRect.y - pRect.height) <= horizontalMidpoint;
  11. bool topAndBottomQuadrant = pRect.y + pRect.height + >= horizontalMidpoint && pRect.y + <= horizontalMidpoint;
  12.  
  13. if(topAndBottomQuadrant)
  14. {
  15. topQuadrant = false;
  16. bottomQuadrant = false;
  17. }
  18.  
  19. // Check if object is in left and right quad
  20. if(pRect.x + pRect.width + >= verticalMidpoint && pRect.x - <= verticalMidpoint)
  21. {
  22. if(topQuadrant)
  23. {
  24. indexes.Add();
  25. indexes.Add();
  26. }
  27. else if(bottomQuadrant)
  28. {
  29. indexes.Add();
  30. indexes.Add();
  31. }
  32. else if(topAndBottomQuadrant)
  33. {
  34. indexes.Add();
  35. indexes.Add();
  36. indexes.Add();
  37. indexes.Add();
  38. }
  39. }
  40.  
  41. // Check if object is in just right quad
  42. else if(pRect.x + >= verticalMidpoint)
  43. {
  44. if(topQuadrant)
  45. {
  46. indexes.Add();
  47. }
  48. else if(bottomQuadrant)
  49. {
  50. indexes.Add();
  51. }
  52. else if(topAndBottomQuadrant)
  53. {
  54. indexes.Add();
  55. indexes.Add();
  56. }
  57. }
  58. // Check if object is in just left quad
  59. else if(pRect.x - pRect.width <= verticalMidpoint)
  60. {
  61. if(topQuadrant)
  62. {
  63. indexes.Add();
  64. }
  65. else if(bottomQuadrant)
  66. {
  67. indexes.Add();
  68. }
  69. else if(topAndBottomQuadrant)
  70. {
  71. indexes.Add();
  72. indexes.Add();
  73. }
  74. }
  75. else
  76. {
  77. indexes.Add(-);
  78. }
  79.  
  80. return indexes;
  81. }

GetIndex 是四叉树内部的辅助函数。他决定了四叉树中一个对象属于哪个节点,最终将该对象划分到该节点中。

  1. public void Insert(SquareOne sprite)
  2. {
  3. SquareOne fSprite = sprite;
  4. Rect pRect = fSprite.GetTextureRectRelativeToContainer();
  5.  
  6. if(nodes[] != null)
  7. {
  8. List<int> indexes = GetIndexes(pRect);
  9. for(int ii = ; ii < indexes.Count; ii++)
  10. {
  11. int index = indexes[ii];
  12. if(index != -)
  13. {
  14. nodes[index].Insert(fSprite);
  15. return;
  16. }
  17. }
  18.  
  19. }
  20.  
  21. objects.Add(fSprite);
  22.  
  23. if(objects.Count > MAX_OBJECTS && level < MAX_LEVELS)
  24. {
  25. if(nodes[] == null)
  26. {
  27. Split();
  28. }
  29.  
  30. int i = ;
  31. while(i < objects.Count)
  32. {
  33. SquareOne sqaureOne = objects[i];
  34. Rect oRect = sqaureOne.GetTextureRectRelativeToContainer();
  35. List<int> indexes = GetIndexes(oRect);
  36. for(int ii = ; ii < indexes.Count; ii++)
  37. {
  38. int index = indexes[ii];
  39. if (index != -)
  40. {
  41. nodes[index].Insert(sqaureOne);
  42. objects.Remove(sqaureOne);
  43. }
  44. else
  45. {
  46. i++;
  47. }
  48. }
  49. }
  50. }
  51. }

Insert 是每个加入四叉树的对象要执行的函数。该方法首先确定节点是否有子节点,并尝试向子节点添加对象。如果没有子节点或者对象根据边界规则不适合任何子节点的插入操作,则将对象划分到父节点中。

一旦对象添加到某一个节点中,该节点需要进一步判断当前持有的对象数量 是否 超过最大对象持有对象数量,如果是则进行分化。分化节点会导致该节点插入的所有对象重新划分到子节点的操作,如果不满足边界规则,则将对象保留在父节点中。

  1. private List<SquareOne> Retrieve(List<SquareOne> fSpriteList, Rect pRect)
  2. {
  3. List<int> indexes = GetIndexes(pRect);
  4. for(int ii = ; ii < indexes.Count; ii++)
  5. {
  6. int index = indexes[ii];
  7. if(index != - && nodes[] != null)
  8. {
  9. nodes[index].Retrieve(fSpriteList, pRect);
  10. }
  11.  
  12. fSpriteList.AddRange(objects);
  13. }
  14.  
  15. return fSpriteList;
  16. }

最后一个 Retrieve 函数,他根据输入的对象返回所有可能发生碰撞的对象集合。该方法将有助于极爱年少碰撞检测对的数量。

Using This for 2D Collision Detection


现在我们已经实现了完整的四叉树,是时候使用他帮助我们减少碰撞检测的数量。

在典型的游戏场景中,我们需要根据传递的 Screen 屏幕边界尺寸来创建合适的四叉树对象。

  1. Quadtree quad = new Quadtree(, new Rect(,,,));

在游戏每一帧中,清理四叉树,然后使用 Insert 函数将所有的对象到添加到四叉树中。

当所有的对象添加完毕,你会遍历每个对象,并检索它可能碰撞的对象列表。然后使用碰撞检测算法检查列表中的每个对象与初始对象之间是否真的发生碰撞。

  1. List returnObjects = new List<SqureOne>();
  2. for (int i = ; i < allObjects.size(); i++) {
  3. returnObjects.Clear();
  4. quad.Retrieve(returnObjects, objects.get(i));
  5.  
  6. for (int x = ; x < returnObjects.size(); x++) {
  7. // Run collision detection algorithm between objects
  8. }
  9. }

注意:碰撞检测的算法已经超出了本文的讨论范围,这里有一个 文章 进行学习。

Conclusion


碰撞检测通常是一种比较昂贵的操作,可能会对游戏的性能造成挑战。四叉树是一种加速碰撞检测过程的途径,最终使得游戏运行更加流畅。

[译]2D空间中使用四叉树Quadtree进行碰撞检测优化的更多相关文章

  1. 2D空间中求一点是否在多边形内

    参考自这篇博文:http://www.cnblogs.com/dabiaoge/p/4491540.html 一开始没仔细看做法,浪费了不少时间.下面是最终实现的效果: 大致流程: 1.随便选取多边形 ...

  2. 2D空间中判断一点是否在三角形内

    要注意如果是XY坐标轴的2D空间,要取差乘分量z而不是y. 实现原理是,将三角形ABC三个边(AB,BC,CA)分别与比较点判断差乘,如果这3个差乘结果表示的方向一致,说明就在三角形内. 效果: 代码 ...

  3. 2D空间中求两圆的交点

    出处:https://stackoverflow.com/questions/19916880/sphere-sphere-intersection-c-3d-coordinates-of-colli ...

  4. 2D空间中求线段与圆的交点

    出处: https://answers.unity.com/questions/366802/get-intersection-of-a-line-and-a-circle.html 测试脚本(返回值 ...

  5. 地图四叉树一般用在GIS中,在游戏寻路中2D游戏中一般用2维数组就够了

    地图四叉树一般用在GIS中,在游戏寻路中2D游戏中一般用2维数组就够了 四叉树对于区域查询,效率比较高. 原理图

  6. 2D和3D空间中计算两点之间的距离

    自己在做游戏的忘记了Unity帮我们提供计算两点之间的距离,在百度搜索了下. 原来有一个公式自己就写了一个方法O(∩_∩)O~,到僵尸到达某一个点之后就向另一个奔跑过去 /// <summary ...

  7. 2D空间的OBB碰撞实现

    OBB全称Oriented bounding box,方向包围盒算法.其表现效果和Unity的BoxCollider并无二致.由于3D空间的OBB需要多考虑一些情况 这里仅关注2D空间下的OBB. 实 ...

  8. [译]async/await中使用阻塞式代码导致死锁 百万数据排序:优化的选择排序(堆排序)

    [译]async/await中使用阻塞式代码导致死锁 这篇博文主要是讲解在async/await中使用阻塞式代码导致死锁的问题,以及如何避免出现这种死锁.内容主要是从作者Stephen Cleary的 ...

  9. 译<容器网络中OVS-DPDK的性能>

    译<容器网络中OVS-DPDK的性能> 本文来自对Performance of OVS-DPDK in Container Networks的翻译. 概要--网络功能虚拟化(Network ...

随机推荐

  1. 源码编译运行android emulator

    source buile/envsetup.sh lunch sdk-eng make sdk -j2 编译完之后,sdk安装在了下面的目录里 ANDROIID_DIR/out/host/linux- ...

  2. 创芯Xilinx Microblaze 学习系列第一集

    创芯Xilinx Microblaze 学习系列第一集 Xilinx ISE Design Suite 13.2 The MicroBlaze™ embedded processor soft cor ...

  3. Beta阶段第2周/共2周 Scrum立会报告+燃尽图 13

    作业要求[https://edu.cnblogs.com/campus/nenu/2018fall/homework/2411] 版本控制:https://git.coding.net/liuyy08 ...

  4. react中findDOMNode

    在使用react过程中,大家有时会那么这里的findDomNode是做什么的呢? import { findDomNode } from 'react-dom'; 简单来说是用来得到实际Dom的,因为 ...

  5. HDU 3986

    http://acm.hdu.edu.cn/showproblem.php?pid=3986 从开始的最短路里依次删一条边,求新的最短路,求最长的最短路 删边操作要标记节点以及节点对应的边 #incl ...

  6. Spinner的用法

    1.先自定义一个数组 2.在new一个ArrayAdapter方法: ArrayAdapter<String> adapter = new ArrayAdapter<String&g ...

  7. 20155236 2016-2017-2 《Java程序设计》第六周学习总结

    20155236 2016-2017-2 <Java程序设计>第六周学习总结 教材学习内容总结 InputStream与OutputStream 从应用程序角度来看,如果要将数据从来源取出 ...

  8. (译)KVO的内部实现

    09年的一篇文章,比较深入地阐述了KVO的内部实现.   KVO是实现Cocoa Bindings的基础,它提供了一种方法,当某个属性改变时,相应的objects会被通知到.在其他语言中,这种观察者模 ...

  9. 《DSP using MATLAB》 示例 Example 9.12

    代码: %% ------------------------------------------------------------------------ %% Output Info about ...

  10. web程序2

    .