Mehul Mohan Mehul Mohan - 6 months ago 24
Android Question

Why is the energy of this ball is increasing?

I've been trying from hours to setup gravity and relate it to time or what we call frame independent bounce ball. I did everything correct I guess, and I tried to implement the system where height of ball would decrease after every bounce. I did not even start that, and my code is creating something absurd I don't understand why. Here's my code:

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();
}


Here's a GIF what is happening:

enter image description here

UPDATE:

Here's my updated code which is clean, understandable and works perfect:

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();
}

Answer

Here ...

    if(goingDown) {
        //velocityY = Math.sqrt(100 + 2*gravity*(posY));
        velocityY = gravity*(currentFrame - runTime);
    } else {
        velocityY = downV - gravity*(currentFrame - runTime);
    }

... you update the velocity (speed, actually) assuming that the ball will not bounce during this frame.

Then here ...

    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;
    }

... you have not yet updated posY, so you are determining whether the ball hit the floor as a result of the previous update. If it did, you reverse the direction of motion, but keep the speed you already computed for this frame. As a result, each time the ball bounces, its initial upward speed is one frame's worth of acceleration greater than the speed it was traveling when it hit the floor.

You have a similar effect at the top of the ball's motion, but it's smaller because the speed is small there.

There are a couple of ways you might solve this problem. The simplest is probably to perform the bounce check after the position update instead of before.

Additional notes:

  • use the signs of your X and Y speeds instead of separate direction-of-motion flags (thus making the names velocityY etc. accurate). Your code will be simpler, and you'll need to handle only one change of vertical direction, not two, because the equations of motion will handle the other automatically.

  • you have a bit of a precision problem because you assume that the ball travels in the same direction for a whole frame. This may become noticeable if you allow the ball to reach high speeds: it will appear to penetrate the floor before bouncing back up.

  • this computation is suspicious: dt = dt/1000. Since dt seems to be computed from System.currentTimeMillis(), I am inclined to guess that it, too, has type long. In that case, you are performing an integer division and thereby losing precision.