I want to maintain the speed of a free-falling Box2D body using LibGDX. I'd like the vertical velocity increase with the level. I've applied a linear impulse and velocity actually increases like this:
fruitBody.applyLinearImpulse(0, -800, fruitBody.getLocalCenter().x, fruitBody.getLocalCenter().y, true);
I think this is a bad approach, because the speed has increased only a little and I applied as much as -800 units of impulse.
Below is my render function of game play screen class:
public void render(float delta) {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
// camera.update();
// batch.setProjectionMatrix(camera.combined);
batch.begin();
backgroundSprite.draw(batch);
grassSprite.draw(batch);
// bucketSprite.draw(batch);
Iterator bodies = world.getBodies();
while(bodies.hasNext()){
Body body = bodies.next();
if(body.getUserData() != null){
String spriteName = (String) body.getUserData();
Sprite sprite = (Sprite) userDataMap.get(spriteName);
if(spriteName.equals("bucket"))
sprite.setPosition(body.getPosition().x + 345, body.getPosition().y + 230);
else
sprite.setPosition(body.getPosition().x + 375, body.getPosition().y + 225);
sprite.draw(batch);
}
}
batch.end();
fruitBody.applyLinearImpulse(0, -800, fruitBody.getLocalCenter().x, fruitBody.getLocalCenter().y, true);
// debugRenderer.render(world, camera.combined);
world.step(1/60f, 8, 3);
// world.clearForces();
}
My world is:
world = new World(new Vector2(0, -10), true);
Orthographic camera:
camera = new OrthographicCamera(800, 480);
My fruitBody fixture def is:
bodyDef.type = BodyType.DynamicBody;
bodyDef.position.set(0, 100);
// shape
CircleShape ballShape = new CircleShape();
ballShape.setRadius(15f);
//fixture
fixtureDef.friction = .1f;
fixtureDef.restitution = .7f;
fixtureDef.shape = ballShape;
fixtureDef.density = .2f;
fruitBody = world.createBody(bodyDef);
// fruitBody.setUserData(fruitSprite);
fruitBody.setUserData("fruit");
fruitBody.createFixture(fixtureDef);
I just want to increase the fruit's speed (free falling body) at each level by a given value.
Answer
Disclaimer: I have not used libgdx or Java before, this answer borrows syntax from the question and official documentation, and the code is untested
To make the bodies 'fall' under the influence of gravity in box2d, you must first pass a non-zero gravity vector to the b2World
when constructing the world
World world = new World(new Vector2(0, -10), true);
This means that every time World::step
is called the gravitational force will be applied to every DynamicBody
and the velocity will be updated accordingly.
This is all well and good if all you want is constant acceleration, without any opposing forces. To add a little realism we would like to add a drag force to simulate air resistance. I know there are a lot of resources out there explaining how air drag works, and the wikipedia article is the obvious starting point.
In a nutshell, the magnitude of the drag force is most frequently modelled like this:
Whenever you encounter a new formula like this, I find the best way to get an understanding of it is to see what happens when specific variables are set to zero, become negative, or take on large values.
In this case I just want to consider what happens to the drag force when the velocity, v, changes and all other parameters are set to positive, real values.
- If v -> 0 then Fd will also tend to zero.
- If v >> 0 then Fd will take on a large positive value.
- If v << 0 then Fd will take on a large positive value.
Notice Fd will never be negative. This is an important feature.
The direction of the drag force is frequently assumed to be opposite to the velocity vector. This is a simple assumption that is easy to implement and unconditionally stable in code.
So if our fruit is falling (accelerating) under the influence of gravity it's gaining speed. But while that's happening the drag force must be increasing because the velocity magnitude is increasing. The drag force will continue to increase until terminal velocity is reached.
Terminal velocity is the point at which the drag force is equal in magnitude and opposite in direction to the gravity force. If the forces are equal, the acceleration of the fruit must be zero, and the velocity of the fruit which satisfies this condition is the terminal velocity.
You can even determine what the terminal velocity must be at the outset if you set Fd = Fg and solve for v.
Implementation
This is great in theory, but how can we implement this in code?
box2d does not provide any built-in features to implement drag, so we have to do it ourselves.
To do this we need to get all the bodies and cycle through them at each time step, calling Body::applyForceToCenter
on each one. I'm going to borrow the code from your rendering function as an example.
Iterator bodies = world.getBodies();
//This constant H is the lump constant of 1/2*rho*Cd*A from the wikipedia formula
//Play with this parameter to get the results you want
float H = 0.5;
while(bodies.hasNext()){
Body body = bodies.next();
Vector2 v = body.getLinearVelocity();
//Get the square of the velocity by computing the square of the distance from the origin
float vSqrd = v.dst2(Vector2());
//Calculate the magnitude of the drag force
float fMag = H*vSqrd;
//Calculate the drag force vector to apply
//We do this by taking the norm of the velocity and negating it to get the direction.
//That vector is multiplied by the magnitude to get the drag force we want to apply
Vector2 fd = -v.norm()*fMag;
//Finally we communicate this to box2d by calling applyForceToCenter
body.applyForceToCenter(fd);
}
That should be it. I don't have anything setup right now to run that code, but that is the is the general idea. I judged from your language that you were having more trouble understanding the physics than the code, which is why I expounded on that first.
As a side note applying linear impulses at every time step can produce some funky results because it applies the same impulse regardless of the timestep size (which can vary) whereas applying a force takes into account the timestep size.
No comments:
Post a Comment