为什么这个球的能量越来越大?
Why is the energy of this ball is increasing?
我已经尝试了几个小时来设置重力并将其与时间或我们所说的独立于框架的弹跳球联系起来。我猜我做的一切都是正确的,并且我尝试实施每次弹跳后球的高度都会降低的系统。我什至没有开始,我的代码正在创造一些荒谬的东西,我不明白为什么。这是我的代码:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
currentFrame = System.currentTimeMillis();
dt = currentFrame - lastFrame;
dt = dt/1000;
lastFrame = currentFrame;
myFreakinRect.set(0,0, canvas.getWidth(), canvas.getHeight());
freakinRed.setColor(Color.RED);
freakinRed.setStyle(Paint.Style.FILL);
canvas.drawRect(myFreakinRect, freakinRed);
//
// o yuea
if(goingDown) {
//velocityY = Math.sqrt(100 + 2*gravity*(posY));
velocityY = gravity*(currentFrame - runTime);
} else {
velocityY = downV - gravity*(currentFrame - runTime);
}
if(posX > w - ballRadius*2) {
goingRight = false;
}
if(posX < 0) {
goingRight = true;
}
if(posY > h - ballRadius*2) {
//initY = initY - 0.25F;
//if(initY < 0) initY = 0;
Log.i("xxx", String.valueOf(initY));
runTime = currentFrame;
downV = velocityY;
goingDown = false;
}
if(velocityY <= 0) {
goingDown = true;
runTime = currentFrame;
}
if(goingDown) posY += velocityY*dt;
else posY -= velocityY*dt;
if(goingRight) posX += velocityX*dt;
else posX -= velocityX*dt;
canvas.drawText(String.valueOf(posX)+" "+String.valueOf(posY), 10, 10, new Paint());
canvas.drawBitmap(rBall, (float)posX, (float)posY, myFreakingFaintPaint);
invalidate();
}
这是正在发生的事情的 GIF:
更新:
这是我更新后的代码,它干净、易于理解且运行完美:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
currentFrame = System.currentTimeMillis();
dt = currentFrame - lastFrame;
dt = dt/1000;
lastFrame = currentFrame;
velocityY = downV + gravity*(currentFrame - runTime);
posY += velocityY*dt;
posX += velocityX*dt;
if(posX > w - ballRadius*2 || posX < 0) {
velocityX = -velocityX;
}
if(posY >= h - ballRadius*2) {
posY = h - ballRadius*2 - 2;
runTime = currentFrame;
downV = -0.8*velocityY;
}
canvas.drawBitmap(rBall, (float)posX, (float)posY, null);
invalidate();
}
总的来说:
拆分为模型和视图。在这种情况下,渲染仍然 运行 没问题,因为计算非常轻量级,但你不应该 运行 在渲染例程中编写与绘制某些东西没有直接关系的代码。
下一点:
如果您模拟物理,请尽可能接近现实。事后你总是可以优化,但首先要确保你的代码确实在做它应该做的事情。我目前正在玩一些弹丸运动,所以我对代码应该做什么有了一个基本的了解。到目前为止,我一直在尝试理解您的代码 10 分钟。中间结果:我很困惑,不太明白。
我的建议:
从更清晰的代码开始,并尽量遵守物理规则。这段代码没有尽可能地优化,但它是可读的、可理解的并且模拟得足够接近现实生活。这使得调试变得更加简单:
final double GRAVITY = -9.81;
final double BALL_ELASTICITY = 0.95;
double vx, vy;
double x, y;
//dt is delta-time in seconds!!!
void simulateBall(double dt){
//calculate when the ball will touch the floor the next time
double next_floor_touch = (-vy + Math.sqrt(vy * vy - 2 * GRAVITY * y)) / GRAVITY;
double after_floor_touch = dt - next_floor_touch;
boolean touches_floor = (next_floor_touch <= dt);
//calculate new y
if(touches_floor){
//predict the speed the ball will have, after it bounces from the floor
double vy_at_floor = vy + GRAVITY * next_floor_touch;
double vy_from_floor = vy_at_floor * (-1) * BALL_ELASTICITY;
//predict y starting from the floor at t = next_floor_touch until dt
y = 0 + vy_from_floor * after_floor_touch + 0.5 * GRAVITY * after_floor_touch * after_floor_touch;
}else{
//uniform acceleration
y = y + vy * dt + 0.5 * GRAVITY * dt * dt;
}
//calculate vy
if(touches_floor){
//see above
double vy_after_floor = (vy + GRAVITY * next_floor_touch) * (-1) * BALL_ELASTICITY;
vy = vy_after_floor + GRAVITY * after_floor_touch;
}else{
vy = vy + GRAVITY * dt;
}
... //that's already the hardest part
}
这使用quadratic equation to predict when the ball will hit the floor and uniform acceleration从给定的位置、速度和加速度计算位置。除非我在计算中犯了任何错误(此代码 未 测试),这应该是物理上精确的。 BALL_ELASTICITY
表示球落地后还剩多少速度。这在物理上并不精确 - 可能是 IDK - ,但应该用于此目的。
这里...
if(goingDown) {
//velocityY = Math.sqrt(100 + 2*gravity*(posY));
velocityY = gravity*(currentFrame - runTime);
} else {
velocityY = downV - gravity*(currentFrame - runTime);
}
...您更新速度(实际上是速度),假设球在这一帧期间不会反弹。
那么这里...
if(posY > h - ballRadius*2) {
//initY = initY - 0.25F;
//if(initY < 0) initY = 0;
Log.i("xxx", String.valueOf(initY));
runTime = currentFrame;
downV = velocityY;
goingDown = false;
}
...您尚未更新 posY
,因此您正在确定球是否因 先前 更新而落地。如果是,则反转运动方向,但保持已为该帧计算的速度。因此,每次球弹跳时,它的初始向上速度都比它落地时的行进速度大一帧的加速度。
你在球运动的顶部有类似的效果,但它更小,因为那里的速度很小。
有几种方法可以解决这个问题。最简单的可能是在位置更新之后而不是之前执行反弹检查。
补充说明:
使用 X 和 Y 速度的符号而不是单独的运动方向标志(因此名称为 velocityY
etc。准确的)。您的代码会更简单,您只需要处理一个垂直方向的变化,而不是两个,因为运动方程会自动处理另一个。
你的精度有点问题,因为你假设球在整个帧中都沿同一方向运动。如果您让球达到高速,这可能会变得很明显:它看起来会在弹回之前穿透地板。
这个计算是可疑的:dt = dt/1000
。由于 dt
似乎是从 System.currentTimeMillis()
计算得出的,我倾向于猜测它也具有类型 long
。在那种情况下,您正在执行整数除法,从而失去精度。
我已经尝试了几个小时来设置重力并将其与时间或我们所说的独立于框架的弹跳球联系起来。我猜我做的一切都是正确的,并且我尝试实施每次弹跳后球的高度都会降低的系统。我什至没有开始,我的代码正在创造一些荒谬的东西,我不明白为什么。这是我的代码:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
currentFrame = System.currentTimeMillis();
dt = currentFrame - lastFrame;
dt = dt/1000;
lastFrame = currentFrame;
myFreakinRect.set(0,0, canvas.getWidth(), canvas.getHeight());
freakinRed.setColor(Color.RED);
freakinRed.setStyle(Paint.Style.FILL);
canvas.drawRect(myFreakinRect, freakinRed);
//
// o yuea
if(goingDown) {
//velocityY = Math.sqrt(100 + 2*gravity*(posY));
velocityY = gravity*(currentFrame - runTime);
} else {
velocityY = downV - gravity*(currentFrame - runTime);
}
if(posX > w - ballRadius*2) {
goingRight = false;
}
if(posX < 0) {
goingRight = true;
}
if(posY > h - ballRadius*2) {
//initY = initY - 0.25F;
//if(initY < 0) initY = 0;
Log.i("xxx", String.valueOf(initY));
runTime = currentFrame;
downV = velocityY;
goingDown = false;
}
if(velocityY <= 0) {
goingDown = true;
runTime = currentFrame;
}
if(goingDown) posY += velocityY*dt;
else posY -= velocityY*dt;
if(goingRight) posX += velocityX*dt;
else posX -= velocityX*dt;
canvas.drawText(String.valueOf(posX)+" "+String.valueOf(posY), 10, 10, new Paint());
canvas.drawBitmap(rBall, (float)posX, (float)posY, myFreakingFaintPaint);
invalidate();
}
这是正在发生的事情的 GIF:
更新:
这是我更新后的代码,它干净、易于理解且运行完美:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
currentFrame = System.currentTimeMillis();
dt = currentFrame - lastFrame;
dt = dt/1000;
lastFrame = currentFrame;
velocityY = downV + gravity*(currentFrame - runTime);
posY += velocityY*dt;
posX += velocityX*dt;
if(posX > w - ballRadius*2 || posX < 0) {
velocityX = -velocityX;
}
if(posY >= h - ballRadius*2) {
posY = h - ballRadius*2 - 2;
runTime = currentFrame;
downV = -0.8*velocityY;
}
canvas.drawBitmap(rBall, (float)posX, (float)posY, null);
invalidate();
}
总的来说:
拆分为模型和视图。在这种情况下,渲染仍然 运行 没问题,因为计算非常轻量级,但你不应该 运行 在渲染例程中编写与绘制某些东西没有直接关系的代码。
下一点:
如果您模拟物理,请尽可能接近现实。事后你总是可以优化,但首先要确保你的代码确实在做它应该做的事情。我目前正在玩一些弹丸运动,所以我对代码应该做什么有了一个基本的了解。到目前为止,我一直在尝试理解您的代码 10 分钟。中间结果:我很困惑,不太明白。
我的建议:
从更清晰的代码开始,并尽量遵守物理规则。这段代码没有尽可能地优化,但它是可读的、可理解的并且模拟得足够接近现实生活。这使得调试变得更加简单:
final double GRAVITY = -9.81;
final double BALL_ELASTICITY = 0.95;
double vx, vy;
double x, y;
//dt is delta-time in seconds!!!
void simulateBall(double dt){
//calculate when the ball will touch the floor the next time
double next_floor_touch = (-vy + Math.sqrt(vy * vy - 2 * GRAVITY * y)) / GRAVITY;
double after_floor_touch = dt - next_floor_touch;
boolean touches_floor = (next_floor_touch <= dt);
//calculate new y
if(touches_floor){
//predict the speed the ball will have, after it bounces from the floor
double vy_at_floor = vy + GRAVITY * next_floor_touch;
double vy_from_floor = vy_at_floor * (-1) * BALL_ELASTICITY;
//predict y starting from the floor at t = next_floor_touch until dt
y = 0 + vy_from_floor * after_floor_touch + 0.5 * GRAVITY * after_floor_touch * after_floor_touch;
}else{
//uniform acceleration
y = y + vy * dt + 0.5 * GRAVITY * dt * dt;
}
//calculate vy
if(touches_floor){
//see above
double vy_after_floor = (vy + GRAVITY * next_floor_touch) * (-1) * BALL_ELASTICITY;
vy = vy_after_floor + GRAVITY * after_floor_touch;
}else{
vy = vy + GRAVITY * dt;
}
... //that's already the hardest part
}
这使用quadratic equation to predict when the ball will hit the floor and uniform acceleration从给定的位置、速度和加速度计算位置。除非我在计算中犯了任何错误(此代码 未 测试),这应该是物理上精确的。 BALL_ELASTICITY
表示球落地后还剩多少速度。这在物理上并不精确 - 可能是 IDK - ,但应该用于此目的。
这里...
if(goingDown) { //velocityY = Math.sqrt(100 + 2*gravity*(posY)); velocityY = gravity*(currentFrame - runTime); } else { velocityY = downV - gravity*(currentFrame - runTime); }
...您更新速度(实际上是速度),假设球在这一帧期间不会反弹。
那么这里...
if(posY > h - ballRadius*2) { //initY = initY - 0.25F; //if(initY < 0) initY = 0; Log.i("xxx", String.valueOf(initY)); runTime = currentFrame; downV = velocityY; goingDown = false; }
...您尚未更新 posY
,因此您正在确定球是否因 先前 更新而落地。如果是,则反转运动方向,但保持已为该帧计算的速度。因此,每次球弹跳时,它的初始向上速度都比它落地时的行进速度大一帧的加速度。
你在球运动的顶部有类似的效果,但它更小,因为那里的速度很小。
有几种方法可以解决这个问题。最简单的可能是在位置更新之后而不是之前执行反弹检查。
补充说明:
使用 X 和 Y 速度的符号而不是单独的运动方向标志(因此名称为
velocityY
etc。准确的)。您的代码会更简单,您只需要处理一个垂直方向的变化,而不是两个,因为运动方程会自动处理另一个。你的精度有点问题,因为你假设球在整个帧中都沿同一方向运动。如果您让球达到高速,这可能会变得很明显:它看起来会在弹回之前穿透地板。
这个计算是可疑的:
dt = dt/1000
。由于dt
似乎是从System.currentTimeMillis()
计算得出的,我倾向于猜测它也具有类型long
。在那种情况下,您正在执行整数除法,从而失去精度。