【问题标题】:html5 canvas elastic collision squareshtml5画布弹性碰撞方块
【发布时间】:2014-02-15 00:39:03
【问题描述】:

我重新问这个问题,因为我没有在上一个问题中明确说明我想要什么。

有谁知道如何使用矩形在 Canvas 中进行弹性碰撞或处理碰撞?或者可以指出我正确的方向吗?

我创建了一个有多个正方形的画布,并且希望每个正方形在它们接触时偏转。

这是一个快速的小提琴,我把它放在一起展示给黑色缓冲画布http://jsfiddle.net/claireC/Y7MFq/10/

第 39 行是我开始碰撞检测的地方,第 59 行是我尝试执行它的地方。我将有超过 3 个方块四处移动,并希望它们在/当它们相互接触时偏转

var canvas = document.getElementById("canvas"),
    context = canvas.getContext("2d");
context.fillStyle = "#FFA500";
    context.fillRect(0, 0, canvas.width, canvas.height);

var renderToCanvas = function (width, height, renderFunction) {
    var buffer = document.createElement('canvas');
    buffer.width = width;
    buffer.height = height;
    renderFunction(buffer.getContext('2d'));
    return buffer;
};

var drawing = renderToCanvas(100, 100, function (ctx) {
ctx.fillStyle = "#000";
    ctx.fillRect(0, 0, canvas.width, canvas.height);


});

var drawing2 = renderToCanvas(100, 100, function (ctx) {
ctx.fillStyle = "blue";
    ctx.fillRect(0, 0, canvas.width, canvas.height);


});

var x = 0, 
 y = 0,
x2 = 200,
y2 = 10,
vx = .80,
vy = .80,
vx2 = .80,
vy2 = .80;



    function collides(rectA, rectB) {
      return !(rectA.x + rectA.width < rectB.x2 ||
       rectB.x2 + rectB.width < rectA.x ||
       rectA.y + rectA.height < rectB.y2 ||
       rectB.y2 + rectB.height < rectA.y);
      }; 

    function executeFrame() {
        x+=vx;
        y+=vy;
        x2+=vx2;
        y2+=vy2;

        if( x < 0 || x > 579) vx = -vx; 
        if( y < 0 || y > 265) vy = -vy;

        if( x2 < 0 || x2 > 579) vx2 = - vx2; 
        if( y2 < 0 || y2 > 233) vy2 = - vy2;

        if(collides(drawing, drawing2)){
            //move in different direction
        };

        context.fillStyle = "#FFA500"; 
        context.fillRect(0, 0, canvas.width, canvas.height);
        context.drawImage(drawing, x, y);
        context.drawImage(drawing2, x2, y2);


        requestAnimationFrame(executeFrame);
    }

    //start animation
    executeFrame();

【问题讨论】:

    标签: javascript html canvas html5-canvas collision


    【解决方案1】:

    矩形碰撞检测

    进行矩形碰撞检测可能比看起来更复杂。

    这不仅仅是要弄清楚两个矩形是否相交或重叠,我们还需要知道它们碰撞的角度以及它们移动的方向以便正确地偏转它们,理想情况下将“速度”传递给彼此(质量/energy) 等等。

    我在这里介绍的这个方法将执行以下步骤:

    • 首先进行简单的相交检测,以确定它们是否完全发生碰撞。
    • 如果相交:计算两个矩形之间的角度
    • 将设置的主要矩形划分为圆形的四个区域,其中区域 1 位于右侧,区域 2 位于底部,依此类推。
    • 根据区域,检查矩形向哪个方向移动,如果根据检测到的区域朝向另一个矩形偏转它。

    Online demo

    Version with higher speed here

    检测交点并计算角度

    检测交点和角度的代码如下,这里r1r2是具有xywh属性的对象。

    function collides(r1, r2) {
    
        /// classic intersection test
        var hit = !(r1.x + r1.w < r2.x ||
                   r2.x + r2.w < r1.x ||
                   r1.y + r1.h < r2.y ||
                   r2.y + r2.h < r1.y);
    
        /// if intersects, get angle between the two rects to determine hit zone
        if (hit) {
            /// calc angle
            var dx = r2.x - r1.x;
            var dy = r2.y - r1.y;
    
            /// for simplicity convert radians to degree
            var angle = Math.atan2(dy, dx) * 180 / Math.PI;
            if (angle < 0) angle += 360;
    
            return angle;
            
        } else
            return null;
    }
    

    这个函数将返回一个 anglenull,然后我们用它来确定循环中的偏转(即:在我们的例子中,角度用于确定命中区域)。这是必要的,以便它们在正确的方向反弹。

    为什么要打区?

    仅通过简单的相交测试和偏转,您就有可能像右图那样使方框偏转,这对于 2D 场景是不正确的。您希望方框继续沿左侧没有影响的方向前进。

    确定碰撞区域和方向

    这是我们如何确定要反转哪个速度矢量(提示:如果您想要更物理正确的偏转,您可以让矩形“吸收”其他一些速度,但我不会在这里覆盖):

    var angle = collides({x: x, y: y, w: 100, h: 100},    /// rect 1
                         {x: x2, y: y2, w: 100, h: 100}); /// rect 2
    
    /// did we have an intersection?
    if (angle !== null) {
    
        /// if we're not already in a hit situation, create one
        if (!hit) {
            hit = true;
    
            /// zone 1 - right
            if ((angle >= 0 && angle < 45) || (angle > 315 && angle < 360)) {
                /// if moving in + direction deflect rect 1 in x direction etc.
                if (vx > 0) vx = -vx;
                if (vx2 < 0) vx2 = -vx2;
    
            } else if (angle >= 45 && angle < 135) { /// zone 2 - bottom
                if (vy > 0) vy = -vy;
                if (vy2 < 0) vy2 = -vy2;
    
            } else if (angle >= 135 && angle < 225) { /// zone 3 - left
                if (vx < 0) vx = -vx;
                if (vx2 > 0) vx2 = -vx2;
    
            } else { /// zone 4 - top
                if (vy < 0) vy = -vy;
                if (vy2 > 0) vy2 = -vy2;
            }
        }
    } else
        hit = false;  /// reset hit when this hit is done (angle = null)
    

    差不多就是这样。

    使用hit 标志,以便当我们受到打击时,我们将“情况”标记为受到打击的情况,这样我们就不会出现内部偏转(例如,高速时可能发生)。只要我们在 hit 设置为 true 后得到一个角度,我们仍然处于相同的击中情况(无论如何理论上)。当我们收到 null 时,我们会重置并准备好迎接新的命中情况。

    另外值得一提的是,这里的主要矩形(我们检查的一侧)是第一个(在本例中为黑色)。

    两个以上的矩形

    如果您想添加两个以上的矩形,那么当涉及到矩形本身时,我会建议与此处使用的方法不同的方法。我建议创建一个rectangle object,它在位置、大小、颜色方面是独立的,并且还嵌入了更新速度、方向和绘画的方法。矩形对象可以由执行清除和调用对象更新方法的宿主对象维护。

    要检测碰撞,您可以使用这些对象对数组进行迭代,以找出哪个矩形与当前正在测试的对象发生碰撞。在这里重要的是你“标记”(使用标志)一个已经过测试的矩形,因为在碰撞中总是至少有两个,如果你测试 A 然后 B 你最终会在不使用的情况下反转速度变化的效果跳过每帧碰撞“伙伴”对象测试的标志。

    总结

    注意:这里没有涉及特殊情况,例如精确角上的碰撞,或者一个矩形被困在一个边缘和另一个矩形之间(您可以使用上面提到的命中标志边缘测试)。

    我没有优化任何代码,但尽量保持简单,使其更易于理解。

    希望这会有所帮助!

    【讨论】:

    • 感谢您的详细解释。我不知道完成此操作所需的速度和命中检查之外的代码。这让我大开眼界。你能推荐一些能帮助我更好地理解角度的读物吗?另外,自从我开始学习 Javascript(一般代码)以来已经有一个月了。这是我现在应该知道或自己想出来的吗?
    • @user2856111 如果您是一个月前开始的,那绝不是!可能需要很长时间(取决于您的关注点)才能在此详细级别上跨越您的路径。在这方面我很抱歉;如果您希望我更详细地解释一些代码,请告诉我。预计花费 6 个月 +/- 熟悉该语言。然后需要几年时间才能克服所有的陷阱:^)我无法推荐阅读,因为我自己通常不阅读任何内容。我想这样做超过 3 年使我能够自己解决(大部分)这些事情。嗯,玩转 arc() 方法..
    • @user2856111 .. 这可能是在角度挑战自己的好人选。您可以在弧度(它使用的)和度数之间进行转换。它将逐字说明角度的使用,角度相对于画布的位置等等(即 0 度 = 右,90 度等)。
    • 使用 arc 方法练习听起来是个好主意,我将投入使用。随着一些玩耍,我应该更了解它。再次感谢 :) 我喜欢获取超出我知识范围的信息,所以这对我学习很有帮助。
    • @user2856111 :如果你想了解更多关于在 JavaScript 中进行弹性碰撞的信息,我发现了一个很好的教程:adambrookesprojects.co.uk/project/…
    【解决方案2】:

    答案其实很简单:当它们碰撞时交换每个块的速度。而已!同样对于您的碰撞测试,将 RectA.x 更改为 x,因为它们是给定的正常变量:

        function collides(rectA, rectB) {
          return !(x + rectA.width < x2 ||
           x2 + rectB.width < x ||
           y + rectA.height < y2 ||
           y2 + rectB.height < y);
          }; 
    

    以及交换速度:

            if(collides(drawing, drawing2)){
                var t = vx; var t2 = vy;
                vx = vx2; vy = vy2;
                vx2 = t; vy2 = t2;
            };
    

    在这些小改动之后,我们有了有效的弹性碰撞:http://jsfiddle.net/Y7MFq/11/

    【讨论】:

    • 如果您在您发布的小提琴中看到第二次碰撞(jsfiddle.net/Y7MFq/11,参考edit),您可以看到它们没有正确偏转角度。除此之外,这是一种很好且简单的检测方法。
    • 这是因为该公式是基于一维的解决方案,如果将它们视为跨一维线,则碰撞是正确的。能量吸收也适用(假设质量相同)。我知道它们不会在 2D 平面上进行角度偏转,但我想要一个非常简单的解决方案。
    • 尽管用户要求弹性碰撞,但我会给出与您的答案类似的内容。所以我假设一个基本的一维弹性碰撞比一个适当偏转的不是弹性碰撞的二维碰撞更合适。
    • 你可能是对的,我个人认为是二维的,因为小提琴。让我们看看 OP 是否可以对此有所了解。
    • 谢谢。我已经接近以同样的方式求解一维解,但我希望获得二维结果。
    猜你喜欢
    • 2021-04-12
    • 1970-01-01
    • 2013-07-18
    • 1970-01-01
    • 1970-01-01
    • 2014-08-30
    • 1970-01-01
    • 2017-11-07
    • 2013-04-19
    相关资源
    最近更新 更多