11

我编写了一个相当简单的 Java 应用程序,它允许您拖动鼠标,并根据您所做的鼠标拖动长度,它会朝那个方向射一个球,并在它移动时从墙壁上反弹。

这是一个快速的屏幕截图:
alt text http://img222.imageshack.us/img222/3179/ballbouncemf9.png

屏幕上的每个圆圈都是一个 Ball 对象。球的运动被分解为一个 x 和 y 向量;

public class Ball {
    public int xPos;
    public int yPos;
    public int xVector;
    public int yVector;

    public Ball(int xPos, int yPos, int xVector, int yVector) {
        this.xPos = xPos;
        this.yPos = yPos;
        this.xVector = xVector;
        this.yVector = yVector;
    }

    public void step()
    {
        posX += xVector;
        posY += yVector;

        checkCollisions();
    }

    public void checkCollisions()
    {
        // Check if we have collided with a wall
        // If we have, take the negative of the appropriate vector
        // Depending on which wall you hit
    }

    public void draw()
    {
        // draw our circle at it's position
    }
}

这很好用。所有的球都从一堵墙反弹到另一堵墙。

但是,我已经决定我希望能够包括重力的影响。我知道物体以 9.8m/s 的速度向地球加速,但我不直接知道这应该如何转化为代码。我意识到 yVector 会受到影响,但我对此的实验并没有达到我想要的效果。

理想情况下,我希望能够为这个程序添加一些重力效果,并让球在落到“地面”之前反弹几次。

我怎样才能创建这种弹跳有弹性的重力效果?我必须如何在每一步操纵球的速度矢量?当它撞到“地面”时必须做什么,以便我可以让它再次弹起,但比上次短一些?

任何帮助都可以为我指明正确的方向。


谢谢大家的评论!它已经很好用了!

在我的 step() 中,我向我的 yVector 添加了一个重力常数,就像人们建议的那样,这是我的 checkCollision():

public void checkCollision()
{
    if (posX - radius < 0)              // Left Wall?
    {
        posX = radius;              // Place ball against edge
        xVector = -(xVector * friction);
    }
    else if (posX + radius > rightBound) // Right Wall?
    {
        posX = rightBound - radius;     // Place ball against edge
        xVector = -(xVector * friction);
    }

    // Same for posY and yVector here.
}

但是,球将继续在地板上滑动/滚动。我认为这是因为我只是在每次反弹时仅取其向量的百分比(90%),而且它永远不会真正为零。我是否应该添加一个检查,如果 xVector 变为某个绝对值,我应该将其更改为零?

4

9 回答 9

15

你需要做的是不断地从你的 yVector 中减去一个小常数(代表你的 9.8 m/s)。当球向下时(yVector 已经是负数),这会使它跑得更快。当它上升时(yVector 为正),它会减慢速度。

这不会解释摩擦,所以事情应该永远反弹。

编辑1:考虑到摩擦,每当它反转(并且你反转符号)时,将绝对数字降低一点。就像它在 yVector=-500 处命中一样,当您反转符号时,将其设为 +480 而不是 +500。您可能应该对 xVector 做同样的事情来阻止它左右弹跳。

编辑2:另外,如果你想让它对“空气摩擦”做出反应,每次调整都会将两个向量减少一个非常小的量。

编辑3:关于永远在底部滚动的东西——根据你的数字有多高,它可能是两件事之一。要么你的数字很大,而且似乎需要很长时间才能完成,要么你正在四舍五入,你的向量总是 5 或其他东西。(5 的 90% 是 4.5,所以它可能会四舍五入到 5)。

我会打印出一个调试语句,看看向量编号是什么样的。如果它们到达 5 左右并停留在那里,那么你可以使用一个函数将你的分数截断为 4 而不是四舍五入回到 5。如果它继续下降并最终停止,那么你可能不得不提高你的摩擦系数.

如果你找不到一个简单的“舍入”函数,你可以使用 (0.9 * Vector) - 1,从现有方程中减去 1 应该做同样的事情。

于 2008-12-04T21:47:14.530 回答
13

当球在地面上滚动时,是的,检查速度是否低于某个最小值,如果是,则将其设置为零。如果您查看这种理想化运动背后的物理原理并与现实世界中发生的情况进行比较,您会发现无法使用单个方程来解释真实球停止运动的事实。

顺便说一句,您所做的称为数值积分的欧拉方法。它是这样的:

  • 从运动学方程开始:
    x(t) = x0 + vx*t + 0.5*ax t^2
    y(t) = y0 + vy
    t + 0.5*ay t^2
    vx(t) = vx0 + ax
    t
    vy(t) = vy0 + ay*t
    其中 x 和 y 是位置,vx 和 vy 是速度,ax 和 ay 是加速度,t 是时间。x0、y0、vx0 和 vy0 是初始值。这描述了在没有任何外力的情况下物体的运动。

  • 现在应用重力:
    ay = -9.8 m/s^2
    至此,没有必要做任何棘手的事情。我们可以随时使用这个方程求解每个球的位置。

  • 现在添加空气摩擦:由于它是一个球形球,我们可以假设它的摩擦系数为 c。对于如何模拟空气摩擦,通常有两种选择。它可以与速度或速度的平方成正比。让我们使用平方:
    ax = -c vx^2
    ay = -c
    vy^2 - 9.8
    因为加速度现在取决于速度,它不是恒定的,我们必须积分。这很糟糕,因为没有办法手动解决这个问题。我们必须在数字上进行整合。

  • 我们采用离散时间步长 dt。对于欧拉方法,我们只需将上述方程中所有出现的 t 替换为 dt,并使用前一个时间步的值代替初始值 x0、y0 等。所以现在我们的方程看起来像这样(在伪代码中) :

    // 保存以前的值
    xold = x;
    yold = y;
    vxold = vx;
    vyold = vy;

    // 更新加速度
    ax = -c vxold^2;
    ay = -c
    vyold^2 - 9.8;

    // 更新速度
    vx = vxold + ax dt;
    vy = vyold + ay
    dt;

    // 更新位置
    x = xold + vxold*dt + 0.5*ax dt^2;
    y = yold + vyold
    dt + 0.5*ay*dt^2;

这是一个近似值,所以它不会完全正确,但看起来还不错。问题是对于更大的时间步长,误差会增加,所以如果我们想要准确地模拟一个真实球的移动方式,我们必须使用非常小的 dt 值,这会导致计算机的准确性问题。为了解决这个问题,有更复杂的技术。但是如果你只是想同时看到看起来像重力和摩擦的行为,那么欧拉的方法是可以的。

于 2008-12-05T17:45:54.220 回答
1

每次切片时,您都必须通过向下加速球来施加重力的影响。正如比尔 K 建议的那样,这就像从“yVector”中减去一样简单。当球击中底部时,yVector = -yVector,所以现在它向上移动但仍在向下加速。如果你想让球最终停止弹跳,你需要使碰撞稍微缺乏弹性,基本上是通过在 y 向上移除一些速度,可能不是“yVector = -yVector”,而是让它成为“yVector = -0.9 * y向量”。

于 2008-12-04T21:54:00.553 回答
1
public void step()
{
    posX += xVector;
    posY += yVector;

    yVector += g //some constant representing 9.8

    checkCollisions();
}

在 checkCollisions() 中,当 yVector 在地面上反弹时,您应该将 yVector 反转并乘以 0 到 1 之间的数字。这应该会给你想要的效果

于 2008-12-04T21:54:57.683 回答
1

这是一种弹道运动。所以你在 x 轴上有一个线性运动,在 y 轴上有一个匀速加速运动。

基本思想是 y 轴将遵循以下等式:

y = y0 + v0 * t + (0.5)*a*t^2

或者,在 C 代码中,例如:

float speed = 10.0f, acceleration = -9.8f, y = [whatever position];

y += speed*t + 0.5f*acceleration*t^2;

我在这里使用 tiem 参数化。但是你可以使用 Torricelli:

v = sqrt(v0^2 + 2*acceleration*(y-y0));

而且,在这个模型上,您必须保持 v 和 y 的最后一个值。

最后,我使用第一个模型做了类似的事情,其中​​ dt(时间差)固定为 1/60 秒(60 FPS)。

好吧,这两个模型都给出了很好的真实结果,但是例如 sqrt() 是昂贵的。

于 2010-12-05T05:10:48.090 回答
0

你真的想模拟重力的作用——它所做的只是产生随时间变化的力来改变物体的速度。每次你迈出一步,你都会稍微改变你的球的速度,以便将它“拉”向小部件的底部。

为了解决无摩擦/弹跳球的解决问题,您需要使“地面”碰撞产生与严格反射不同的效果 - 它应该从球中移除一些能量,使其反弹回来撞击地面后的速度比其他情况下要小。

在这些类型的弹性可视化中,您通常想要做的另一件事是也给地面一些侧向摩擦力,这样当它一直撞击地面时,它最终会滚动停止。

于 2008-12-04T21:55:00.553 回答
0

我同意“Bill K”所说的话,并补充说,如果您希望他们“解决”,您将需要随着时间的推移减少 x 和 y 向量(施加阻力)。这一次必须是非常小的数量,因此您可能必须将向量从 int 更改为浮点类型,或者每隔几秒将它们减少 1。

于 2008-12-04T21:55:07.603 回答
0

您要做的是更改 xVector 和 yVector 的值以模拟重力和摩擦力。这真的很简单。(需要将所有变量更改为浮点数。当需要绘制时,只需围绕浮点数。)

在您的 step 函数中,更新球的位置后,您应该执行以下操作:

yVector *= 0.95;
xVector *= 0.95;
yVector -= 2.0;

这会稍微降低 X 和 Y 速度,让您的球最终停止移动,然后对 Y 值应用一个恒定的向下“加速度”,这将比“减速”更快地累积并导致球下落。

这是您真正想做的近似值。你真正想要的是保留一个代表你的球的加速度的向量。然后,您将每一步都将该向量与恒定的重力向量进行点积,以稍微改变球的加速度。但我认为我比你想得到的更复杂,除非你正在寻找更真实的物理模拟。

于 2008-12-04T21:55:34.233 回答
0

当它撞到“地面”时必须做什么,这样我才能让它再次弹起

如果你假设一个完美的碰撞(即所有的能量都是守恒的),你所要做的就是反转速度标量之一的符号,这取决于被撞到的墙。

例如,如果球击中右侧或左侧墙壁,则修改 x 标量分量并保持 y 标量分量相同:

 this.xVector = -this.xVector;

如果球撞击顶部或底部墙壁,则反转 y 标量分量并保持 x 标量分量相同:

 this.yVector = -this.yVector;

但比上次短一些?

在这种情况下,一些能量会在与墙壁的碰撞中损失掉,所以只需添加一个损失因子以在每次撞到墙壁时获取一些速度:

 double loss_factor = 0.99;
 this.xVector = -(loss_factor * this.xVector);
 this.yVector = -(loss_factor * this.yVector;
于 2008-12-04T22:37:05.820 回答