【问题标题】:Bounce velocity calculation, collision with ground弹跳速度计算,与地面碰撞
【发布时间】:2013-07-04 13:13:40
【问题描述】:

在画布上,我有一个球在重力作用下落到地上

vy += gravity
ball.y += vy

我想让球反弹,所以我实现了一个近似的解决方案来检测球是否已经越过地面边界,如果是的话,将它的位置重置到地面,并将它的速度翻转为负数方向。

if (ball.y + ball.radius > bottom) {
ball.y = bottom - ball.radius;
vy *= -1;

但是,我希望这是 100% 准确的。要做到这一点,ball.y 需要稍微高于地面,它的速度将略低于 -vy,因为重力已经开始减慢它。如何计算这些更准确的值?

地面以下的距离为ball.y + ball.radius - bottom。所以必须有一个公式可以将其转换为地面以上的距离,我认为是ball.y = (2 * bottom) - ball.y - 2*ball.radius;

这就是我卡住的地方。到目前为止我是对的吗?我现在需要计算新的速度。我确信这是可能的,但我就是无法理解它。有人可以帮忙吗?

【问题讨论】:

    标签: canvas collision-detection physics game-physics gravity


    【解决方案1】:

    这是一个演示重力和反弹物理的完整演示:

    Bouncing Ball (jsfiddle)


    代码是自我解释的,但进行模拟的主要部分是包含以下代码的更新函数:

    // calculate new position
    ball.x += ball.vx;
    ball.y += ball.vy;
    
    // bounce Y (don't bounce on top)
    if (ball.y >= bottom - ball.radius) {
        ball.y = bottom - ball.radius; // (!) GROUND LIMIT
        ball.vy = -(ball.vy * ball.elasticity);
    }
    
    // bounce X
    if ((ball.x >= right - ball.radius) || (ball.x <= left + ball.radius)) {
    
        ball.x = (ball.x < (left + ball.radius) ? (left + ball.radius) : (right - ball.radius));
        ball.vx = -(ball.vx * ball.elasticity);
    }
    
    // compute gravity
    ball.vy += gravity;
    
    // compute frictions
    ball.vx *= airDrag;
    ball.vy *= airDrag;
    if (ball.y >= (bottom - ball.radius)) {
        ball.vx *= groundFriction;
    }
    

    上面的代码添加了三个不同的物理变量来创建更真实的模拟:

    • ball.elasticity:恢复系数;
    • airDrag:风阻系数;
    • groundFriction:在地面上移动时施加的摩擦力。

    以上所有变量均由 0 到 1 的值表示。最接近 1 的值表示弹性更大、空气阻力更小、摩擦更小。最接近 0 的值意味着弹性更小、空气阻力更大和摩擦更大。


    高级物理模拟 - 如果您受到鼓励并且对物理和数学有基本的掌握:

    如果您想要更真实的模拟,您可以使用drag equation 动态更新airDrag

    另一个改进是使用基于数值积分的更精确算法来计算速度。这是一个很好的例子,比较了游戏开发中最常用的集成方法:http://codeflow.org/entries/2010/aug/28/integration-by-example-euler-vs-verlet-vs-runge-kutta/#

    如果您需要更复杂的模拟,也可以使用 box2d-web 等物理引擎。

    【讨论】:

      【解决方案2】:

      有几个因素会影响坠落物体撞击地面

      您可以通过添加“恢复”因子来调整反弹期间的能量损失。

      从本质上讲,restitution 代表了球的弹力,范围为 0-1。

      Restitution==0 表示球根本不反弹——它像保龄球一样停在地上。

      Restitution==1 表示球在反弹过程中不会失去任何速度。

      要实现恢复原状,只需将速度乘以恢复原状:

      vy *= restitutionFactor;
      

      如果您的球以一定角度下落,您还可以考虑实施“摩擦”,这是在反弹过程中对方向速度的调整。

      摩擦表示当你的粗球在地板上“摩擦”时方向速度的损失。

      摩擦由从 0 到 1 的值表示,并像恢复原状一样实现:

      vx *= frictionFactor;
      

      分步说明

      假设在第 1 帧,球没有反弹并且在地面上方。

      假设在第 2 帧,球已经反弹并重新回到空中。

      您需要进行这些计算才能获得第 2 帧的球位置。

      (1) 保存小球的初始位置和初始速度(我们稍后会用到):

      startingY = ball.y;
      startingVelocity = vy;
      

      (2) 小球使用部分 Frame-time 落地:

      ball.y = bottom;
      

      (3) 球落地并反转速度:

      vy* = -1;
      

      (4) 新的上行速度通过恢复和摩擦调整:

      vy *= restitution;
      vx *= friction;     // if moving at an angle
      

      (5) 球使用了该帧时间的一部分向下移动,因此球获得的向上速度小于整个帧时间:

      downtime = ( bottom - startingY ) / startingVelocity;
      uptime = (1 - downtime);
      

      (6) 球以新的速度向上反弹一帧的适当部分:

      ball.y += vy * uptime;
      

      您还可以引入另一个因素——球“糊状”。

      Smush 是指球触地时底部暂时变平。

      在 smush 期间,速度暂停。所以 smush 是一个延迟时间。

      Smush 随你的球的弹性而变化...更多的弹性 == 更多的 smush 延迟。

      您可以通过减少球可以用来向上移动的“正常运行时间”来调整 smush。

      重温第 5 步:

      uptime = (1 - downtime - smushtime);
      

      这些是移动球的标准调整...享受吧!

      【讨论】:

        【解决方案3】:

        您已经使用了加速度:重力。

        为什么不使用另一种加速:反弹。

        您唯一需要的是使“反弹”指数化,因此它不能穿透地面,因为您仅在球“足够接近地面”而不低于或准确位置之后应用该加速度。

        重力:无处不在。 弹跳:只在离地一定距离后才存在(因为球是有半径的,在“弹跳”下会有一定的缓冲距离让球加速)

        然后让这种加速发挥作用,而不是摆弄许多 if 语句和特殊计算。事实上,由于泡利不相容原理 + 电磁,它实际上是一种加速。

        vy += gravity
        if(closeEnough)
        {
            bounce=bConstant/pow(distance,3.0f)
            vy-=bounce      //this bounce will be calculated as F=bConstant/pow(distance,3.0f)
                            // bConstant is a small number that you try and choose
                            //That power does not have to be 3. You can try different values.
        }
        ball.y += vy
        

        指数排斥力使球越接近地面越难接近,然后您会看到那里的自然弹跳运动。

        你不可能 %100 准确,即使在现实世界中你也不能。将您的时间步长减少到尽可能低的值。不要直接将加速度添加到速度。在您的示例中,您的时间步长为 1(直接将加速度添加到速度)。

        让我们有更小的时间步长(例如 0.1f)以获得更稳定和更准确的运动:

        timeStep = 0.1f;
        vy += gravity * timeStep;
        if(closeEnough)
        {
            bounce=bConstant/pow(distance,3.0f)
            vy-=bounce * timeStep     
                    //this bounce will be calculated as F=bConstant/pow(distance,3.0f)
                    // bConstant is a small number that you try and choose
                   //That power does not have to be 3. You can try different values.
        }
        ball.y += vy * timeStep
        

        还可以使用时间步长根据游戏的 FPS 使用正确的加速度和位移。 (时间步长 = 1/ FPS)

        位移的积分主要有三种。 Euler 积分、Verlet 积分和 Runge-Kutta 积分。您使用的是非常简单的欧拉版本。最快但不稳定/不准确。

        当您使用数千个相互弹跳的球时,您可以用谷歌搜索 Verlet 和 Runge-Kutta。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-10-25
          • 1970-01-01
          • 1970-01-01
          • 2021-11-07
          • 1970-01-01
          相关资源
          最近更新 更多