JavaScript:碰撞检测

56

JavaScript中的碰撞检测是如何工作的?

我不能使用jQuery或gameQuery - 已经在使用prototype - 因此,我正在寻找非常简单的东西。我不是要求完整的解决方案,只是指点正确的方向。

假设有:

<div id="ball"></div>
and
<div id="someobject0"></div>

现在球正在移动(任意方向)。"Someobject"(0-X)已经预定义,有20-60个随机放置如下:

#someobject {position: absolute; top: RNDpx; left: RNDpx;}

我可以创建一个具有"someobject(X)"位置的数组,并在"球"移动时测试碰撞...... 像这样:

for(var c=0; c<objposArray.length; c++){
........ and code to check ball's current position vs all objects one by one....
}

但我猜这可能是一个“新手”解决方案,而且看起来相当慢。 有更好的方法吗?

10个回答

85

这里是一个非常简单的边界矩形函数。 它期望ab都是具有xywidthheight属性的对象:

function isCollide(a, b) {
    return !(
        ((a.y + a.height) < (b.y)) ||
        (a.y > (b.y + b.height)) ||
        ((a.x + a.width) < b.x) ||
        (a.x > (b.x + b.width))
    );
}

如果您想看这个函数的实际应用,请查看这里的codepen,感谢@MixerOID提供的帮助。


有人能够澄清我在这里做错了什么吗?无论'collision'如何它总是返回true。http://jsbin.com/akeSeDI/2/edit - KryptoniteDove
2
@KryptoniteDove:不幸的是,在这个例程中,你不能简单地输入一个DOM元素,你需要进行某种转换(例如使用jQuery)。此外,你的div应该使用position: absolute而不是relative。这是一个可以工作的版本的分支:http://jsbin.com/akeSeDI/3/edit - Husky
6
我为这个美妙的功能创建了可视化GUI :) http://codepen.io/mixal_bl4/pen/qZYWOm 你可以将它添加到你的回答中 :) - mixalbl4
这需要使用jQuery,因此不是一个有效的答案。https://dev59.com/HnE95IYBdhLWcg3wI6ft#35974082 实际上是正确的,并依赖于这个函数。 - eljefedelrodeodeljefe
@eljefedelrodeodeljefe 我的代码不需要 jQuery。它只需要具有给定属性的对象,你可以使用 jQuery 获取这些对象,但也可以使用你的函数中描述的方法获取。 - Husky
@Husky 抱歉,但是对于一个要求 JS 解决方案并且看起来非常像 jQuery 的问题,这些都是很多的预设条件。这很令人困惑,因为你没有检查属性是否存在,但仍然返回了(离散的)布尔值,我仍然认为这是不正确的。 - eljefedelrodeodeljefe

37

一个没有使用jQuery的答案,使用HTML元素作为参数:

这是一种更好的方法,它检查元素在视口中显示的实际位置,即使它们是绝对、相对位置或通过变换进行了操作:

function isCollide(a, b) {
    var aRect = a.getBoundingClientRect();
    var bRect = b.getBoundingClientRect();

    return !(
        ((aRect.top + aRect.height) < (bRect.top)) ||
        (aRect.top > (bRect.top + bRect.height)) ||
        ((aRect.left + aRect.width) < bRect.left) ||
        (aRect.left > (bRect.left + bRect.width))
    );
}

4
实际上,这应该是上述问题的唯一答案。而排名第一的答案需要使用jQuery。 - eljefedelrodeodeljefe

33

首先要做的是实际函数,用于检测球和物体之间是否发生碰撞。

为了提高性能,可以实现一些粗略的碰撞检测技术,例如边界矩形,如果需要更准确的检测,则可以使用更精确的技术,以便您的函数运行速度更快,但使用完全相同的循环。

另一个有助于提高性能的选项是对您拥有的对象进行一些预处理。例如,您可以将整个区域分成类似于通用表格的单元格,并存储包含在特定单元格中的适当对象。因此,要检测碰撞,您正在检测球占据的单元格,获取这些单元格中的对象,并使用碰撞检测函数。

为了进一步加快速度,您可以实现2d树四叉树R树


23

你可以尝试使用jquery-collision插件。全面披露:这是我自己编写并发布的,因为我没有找到合适的解决方案。

它可以让你实现以下功能:

var hit_list = $("#ball").collision("#someobject0");

这将返回与“#ball”重叠的所有“#someobject0”。


7
楼主说他们不能使用jQuery,但还是很好的,对我自己的解决方案有帮助。 - Irwin
4
@eruciform说SourceForge几乎无法使用。我相信很多人会喜欢它如果它在Github上。 - Alexis
1
@PeterAjtai - 我可能会在今年夏天将它们全部移动到Github上。已经有一段时间没有更新了,所以没有动力去打破已经运作良好的东西。不过,我很快就会添加新内容! - eruciform
@Scoop - 我可能会在今年夏天将它们全部移动到Github上。已经有一段时间没有更新了,所以没有动力去打破已经运作良好的东西。不过,我很快就会添加新内容! - eruciform
@eruciform 是哪个夏天? :D - Александр Фишер
显示剩余4条评论

12

Mozilla有一篇很好的文章介绍了这个问题,下面是代码。

2D碰撞检测

矩形碰撞

if (rect1.x < rect2.x + rect2.width &&
   rect1.x + rect1.width > rect2.x &&
   rect1.y < rect2.y + rect2.height &&
   rect1.height + rect1.y > rect2.y) {
    // Collision detected!
}

圆形碰撞

if (distance < circle1.radius + circle2.radius) {
    // Collision detected!
}

10

bcm的回答目前没有得到投票,但实际上它是一个非常好、未受到足够重视的答案。它使用了古老的毕达哥拉斯定理来检测物体是否比它们的组合边界圆更接近。简单的碰撞检测通常使用矩形碰撞检测,如果你的精灵往往是矩形的,那么这是可以的。但如果它们是圆形(或者不那么矩形),比如球、小行星或任何其它极角通常为透明的形状,你可能会发现这个高效的例程是最准确的。

但为了更清晰,下面是代码的更完整版本:

function doCollide(x1, y1, w1, x2, y2, w2) {
    var xd = x1 - x2;
    var yd = y1 - y2;
    var wt = w2 + w1;
    return (xd * xd + yd * yd <= wt * wt);
}

传入的参数是两个不同精灵对象的x、y和宽度值。


8
我遇到了一个轻量级的解决方案——
function E() { // Check collision
    S = X - x;
    D = Y - y;
    F = w + W;
    return (S * S + D * D <= F * F)
}

big和small变量是两个对象的属性(x坐标、y坐标和宽度w)。

来自这里


2
就我所看到的,这个函数假定对象是正方形(或者甚至是圆形?)。而且,它看起来比上面Husky的代码不那么易读,如果还不够的话,它似乎也要慢一些(参见:http://jsperf.com/simple-collision-detection)。 - Roccivic

3
//Off the cuff, Prototype style. 
//Note, this is not optimal; there should be some basic partitioning and caching going on. 
(function () { 
    var elements = []; 
    Element.register = function (element) { 
        for (var i=0; i<elements.length; i++) { 
            if (elements[i]==element) break; 
        } 
        elements.push(element); 
        if (arguments.length>1)  
            for (var i=0; i<arguments.length; i++)  
                Element.register(arguments[i]); 
    }; 
    Element.collide = function () { 
        for (var outer=0; outer < elements.length; outer++) { 
            var e1 = Object.extend( 
                $(elements[outer]).positionedOffset(), 
                $(elements[outer]).getDimensions() 
            ); 
            for (var inner=outer; inner<elements.length; innter++) { 
                var e2 = Object.extend( 
                    $(elements[inner]).positionedOffset(), 
                    $(elements[inner]).getDimensions() 
                ); 
                if (     
                    (e1.left+e1.width)>=e2.left && e1.left<=(e2.left+e2.width) && 
                    (e1.top+e1.height)>=e2.top && e1.top<=(e2.top+e2.height) 
                ) { 
                    $(elements[inner]).fire(':collision', {element: $(elements[outer])}); 
                    $(elements[outer]).fire(':collision', {element: $(elements[inner])}); 
                } 
            } 
        } 
    }; 
})(); 

//Usage: 
Element.register(myElementA); 
Element.register(myElementB); 
$(myElementA).observe(':collision', function (ev) { 
    console.log('Damn, '+ev.memo.element+', that hurt!'); 
}); 
//detect collisions every 100ms 
setInterval(Element.collide, 100);

3
这是一种简单但效率低下的方式,当你不需要太复杂的东西或没有许多对象时,这种方法是相当合理的。
否则,有许多不同的算法,但大多数都很复杂,难以实现。
例如,您可以使用“分而治之”(divide et impera)方法,按照它们之间的距离层次化聚类对象,并为每个聚类分配一个包含聚类中所有项目的边界框。然后,您可以检查哪些聚类发生碰撞,并避免检查属于未发生碰撞/重叠的聚类的对象对。
否则,您可以想出一个通用的空间划分算法,以类似的方式拆分对象以避免无用的检查。这些类型的算法将碰撞检测分为两个阶段:一个“粗略”的阶段,在该阶段中,您可以看到可能发生碰撞的对象,以及一个“精细”的阶段,在该阶段中您可以有效地检查单个对象。例如,您可以使用QuadTree(维基百科)来找出一种简单的解决方案...

请看维基百科页面。它可以给你一些提示。


2

hittest.js;可以检测两个透明的PNG图片(像素)是否发生碰撞。

演示和下载链接

HTML代码

<img id="png-object-1" src="images/object1.png" />
<img id="png-object-2" src="images/object2.png" />

初始化函数

var pngObject1Element = document.getElementById( "png-object-1" );
var pngObject2Element = document.getElementById( "png-object-2" );

var object1HitTest = new HitTest( pngObject1Element );

基础用法

if( object1HitTest.toObject( pngObject2Element ) ) {
    // Collision detected
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接