Mehul Mohan - 6 months ago 24

Android Question

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:

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.