【问题标题】:Making Collision Detection more Efficient让碰撞检测更高效
【发布时间】:2015-09-15 21:45:19
【问题描述】:

我正在开发一个 HTML5 画布游戏,其中地图是随机生成的 10 像素 x 10 像素的图块,玩家可以在这些图块上进行挖掘和构建。瓦片存储在对象数组中,一张小地图包含大约 23000 个瓦片。我的碰撞检测功能在每次运行时检查所有非空气瓷砖的玩家位置(使用requestAnimationFrame()),它运行良好,但我觉得它是 CPU 密集型的。碰撞函数如下(代码来源于在线教程):

function colCheck(shapeA, shapeB) {
    var vX = (shapeA.x + (shapeA.width / 2)) - (shapeB.x + (shapeB.width / 2)),
    vY = (shapeA.y + (shapeA.height / 2)) - (shapeB.y + (shapeB.height / 2)),
    hWidths = (shapeA.width / 2) + (shapeB.width / 2),
    hHeights = (shapeA.height / 2) + (shapeB.height / 2),
    colDir = null;

    // if the x and y vector are less than the half width or half height, they we must be inside the object, causing a collision
    if (Math.abs(vX) < hWidths && Math.abs(vY) < hHeights) {         
        // figures out on which side we are colliding (top, bottom, left, or right)         
        var oX = hWidths - Math.abs(vX),             
            oY = hHeights - Math.abs(vY);         
        if (oX >= oY) {
            if (vY > 0) {
                colDir = "t";
                shapeA.y += oY;
            } else {
                colDir = "b";
                shapeA.y -= oY;
            }
        } else {
            if (vX > 0) {
                colDir = "l";
                shapeA.x += oX;
            } else {
                colDir = "r";
                shapeA.x -= oX;
            }
        }
    }
    return colDir;
};

然后在我的更新函数中,我以播放器和图块作为参数运行此函数:

for (var i = 0; i < tiles.length; i++) {
    //the tiles tag attribute determines rendering colour and how the player can interact with it ie. dirt, rock, etc. 
    //anything except "none" is solid and therefore needs collision
    if (tiles[i].tag !== "none") {
        dir = colCheck(player, tiles[i]);
        if (dir === "l"){
            player.velX = 0;
            player.jumping = false;
        } else if  (dir === "r") {
            player.velX = 0;
            player.jumping = false;
        } else if (dir === "b") {
            player.grounded = true;
            player.jumping = false;
        } else if (dir === "t") {
            player.velY *= -0.3;
        }
    }
};

所以我想知道的是,如果我只使用Math.abs(tiles[i].x - player.x) &lt; 100y 之类的条件检查距玩家一定距离内的瓷砖,这是否会使代码更高效,因为它将检查碰撞针对较少的图块,或者检查额外参数的效率是否较低?

如果不进行测试就很难说,我该如何确定我的代码运行情况如何?

【问题讨论】:

  • 假设您有一个数组,其中的图块按 x 排序。您的角色宽度为 50,位置为 170。这意味着您只想检查 170 到 220 之间的图块。这可以通过 O(2 log2 n) 操作获得,这意味着对于 10000 个图块,您可以获得索引在 2*14 次操作中 x 位置在 170 和 220 之间的图块 = 28。因此,您只检查可能相交的图块。 y 也可以这样做
  • 您可以做的一件事实际上类似于流行的游戏Minecraft。这基本上是有一组瓷砖属于整个“块”。然后你可以得到每个块的距离,并从中确定是否需要对里面的项目进行碰撞检测。因此,远处的物品不会有碰撞检测,而附近的物品可以。但这取决于瓷砖系统的设置方式。当然距离计算有开销,但是(取决于它的设置方式)会节省碰撞检测性能。
  • @juvian 通过只检查与附近瓷砖的碰撞,就像你说的那样检查碰撞所花费的时间减少了 40-60%,并在较大的地图上显着提高了性能。
  • @spencer 尽管性能有所提高,但使用约 180000 个图块仍然很慢,所以我可能会尝试采纳您的建议并将地图分成三份(表面、中部和深层)以获得更大的地图,然后检查图块在球员进入的三分之一内,我一定会调查的!
  • @TuckerFlynn 不确定您如何检查附近的瓷砖,但如果您使用答案的方法,您仍在迭代 180000 瓷砖并多次执行 math.abs,这可能很慢。使用我的方法,您只会遍历可能区域的图块,这取决于您的图块大小和玩家大小,但应该少得多。需要更多细节来举例说明

标签: javascript html5-canvas collision-detection tile


【解决方案1】:

但我觉得它是 CPU 密集型的

CPU 旨在非常快速地完成很多事情。有数学可以确定您的算法的效率,并且您当前的实现似乎是 O(n)。如果您将瓦片的数量减少到一个常数,您将获得 O(1),这会更好,但对于您的应用程序可能并不明显。要实现 O(1),您必须保留 X 个最近的图块的索引,并在最近的图块更改时增量更新索引。 IE。如果玩家向右移动,您将修改索引,以便删除最左侧的瓷砖列,并在右侧获得新的瓷砖列。检查冲突时,您只需遍历索引中固定数量的图块,而不是整个图块集。

...是否应该使代码更高效,因为它将检查与更少图块的碰撞,还是检查额外参数的效率较低?

回答这个问题的最佳方法是使用分析器,但我希望它会提高性能,尤其是在较大的地图上。这将是一个 O(n) 解决方案,因为您仍然迭代整个瓦片集,并且您可以想象当瓦片集接近无穷大时,性能将再次开始下降。您提出的解决方案可能是我上面建议的 O(1) 解决方案之间的一个很好的折衷方案。

您不想做的事情是过早地优化代码。最好在实际遇到性能问题时进行优化,并且应该系统地进行优化,以便获得最大的收益。换句话说,即使您确实遇到了性能问题,碰撞检测也可能不是问题的根源。

如何确定我的代码运行情况如何?

优化代码的最佳方法是 attach a profiler 并测量代码的哪些部分是 CPU 最密集的部分。当您发现代码的哪一部分太慢时,要么自己找出解决方案,要么前往https://codereview.stackexchange.com/ 并询问有关如何改进表现不佳的代码部分并包含您的分析器的非常具体的问题信息和相关的代码部分。

【讨论】:

  • 很好地解释了如何确定效率,而且探查器是我不知道的一个很好的资源。我将使用它作为答案发布我的结果,以防其他人感兴趣!
【解决方案2】:

针对 Samuel 建议我使用分析器的回答:

地图由数组中的约 23 000 个图块组成: 原始碰撞代码运行了 48% 的时间。通过将if (tiles[i].tag !== "none") 更改为以下值,检查冲突所花费的时间减少到 5%。

if (tiles[i].tag !== "none" 
    && Math.abs(tiles[i].x - player.x) < 200 
    && Math.abs(tiles[i].y - player.y) < 200)

使用由约 180 000 个图块组成的地图: 最初的碰撞代码在 60-65% 的时间内运行,游戏的性能非常低,无法播放。使用更新后的代码,碰撞功能只运行了 0.5% 的时间,但性能仍然很低,所以我认为即使检查碰撞的瓷砖较少,但检查它们相对于玩家的位置的瓷砖太多了游戏运行缓慢。

【讨论】:

  • 我会说,如果您将碰撞检测降低到 0.5%,那么您现在的缓慢可能来自其他原因
猜你喜欢
  • 2012-09-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-05
  • 2019-04-30
相关资源
最近更新 更多