Upgrade to Pro — share decks privately, control downloads, hide ads and more …

LibGDX and Box2D

LibGDX and Box2D

Jussi Pohjolainen

February 15, 2016
Tweet

More Decks by Jussi Pohjolainen

Other Decks in Technology

Transcript

  1. Box2D • Free open source 2D physics simulator engine written

    in C++ • Has been used for example in Angry Birds, Tiny Wings • Has been ported to Java, Flash, C#, JavaScript • IDEs / frameworks that use Box2D – libGDX, Unity, iOS SpriteKit, GameMaker Studio ...
  2. Concepts • World – Collection of bodies, fixtures, joints that

    interact • Body – Game Objects that contains fixtures – Dynamic (spaceship), Static (ground) and Kinematic – Fixture • Body has Fixture! • Density: Mass per square meter: bowling ball dense, balloon not so • Friction: When sliding along something, amount of opposite force • Restitution: How bouncy object is (0 = does not bounce, 1 = very bouncy) • Shape – Fixture has Shape – Polygon or Circle, can be mixed • Joint – Holds bodies together. Several joints available.
  3. World Body Fixture Shape // Create array full of bodies

    Array<Body> bodies = new Array<Body>(); // Take all bodies from world and put them into array world.getBodies(bodies); // Get shape of first body, it's first fixture and it's shape Shape s = bodies.get(0).getFixtureList().get(0).getShape();
  4. Units • Box2D uses floating points • Tuned for meter

    – kilogram – second (KMS) • Optimized so that shapes can move between 0.1 – 10 meters • It's tempting to use pixel as units -> leads to poor simulation and weird behavior – Keep moving objects between 0.1 – 10 meters! • DO NOT USE PIXELS! DO NOT!
  5. Units in libGDX • Window/real resolution <-> World Units <->

    Box2D units (meters) • How to handle this? Just use meters in World Units, so you don't have to make any conversions – float width = 640.0f / 100.0f – float height = 400.0f / 100.0f – camera = new OrthographicCamera(); – camera.setToOrtho(false, width, height);
  6. Creating a World public class MyBox2D extends ApplicationAdapter { private

    SpriteBatch batch; private World world; @Override public void create() { // Create world and set it's gravity! // true => allow body sleeping. Bodies // are excluded from the simulation until something happens // to 'wake' them again. world = new World(new Vector2(0, -9.8f), true); // Next steps: create a body to the world ... batch = new SpriteBatch(); } @Override public void render() { ... } }
  7. Body Types • Dynamic Body – Player and other actors

    on screen • Static Body – Walls, floors and so on • Kinematic Body – "Moving platform in platform game" – It's a static body that can also move and rotate. When dynamic body collides, kinematic body "wins"
  8. Bodies • Bodies are objects in physics space – Does

    not hold texture! So it's not visible! • Hold properties like – mass, velocity, location, angle • Define size and shape of the body using a fixture – Shape can be circle or polygon or a mixture of these
  9. Creating a Body • To create body, use – Body

    playerBody = world.createBody(getDefinitionOfBody()); • Define the body: – Static, kinematic, dynamic? – Position?
  10. Creating Definition of a Body private BodyDef getDefinitionOfBody() { //

    Body Definition BodyDef myBodyDef = new BodyDef(); // It's a body that moves myBodyDef.type = BodyDef.BodyType.DynamicBody; // Initial position is centered up // This position is the CENTER of the shape! myBodyDef.position.set(WORLD_WIDTH / 2, WORLD_HEIGHT / 2); return myBodyDef; }
  11. Creating a Fixture of a Body • To create body,

    use – Body playerBody = world.createBody(getDefinitionOfBody()); – playerBody.createFixture(getFixtureDefinition()); • Define the fixture: – Density? (mass) – Restitution? (bouncy) – Friction? (slippery) – Shape? (circle or polygon, or combination?)
  12. Creating Fixture of a Body private FixtureDef getFixtureDefinition() { FixtureDef

    playerFixtureDef = new FixtureDef(); // Mass per square meter (kg^m2) playerFixtureDef.density = 1; // How bouncy object? Very bouncy [0,1] playerFixtureDef.restitution = 1.0f; // How slipper object? [0,1] playerFixtureDef.friction = 0.5f; // Create circle shape. CircleShape circleshape = new CircleShape(); circleshape.setRadius(0.5f); // Add the shape to the fixture playerFixtureDef.shape = circleshape; return playerFixtureDef; }
  13. About Rendering • Box2D holds the physics, it's not about

    rendering – For every render call, update (step) the world • To render, map some texture to the position of the playerBody! • For debugging, use debugRenderer – debugRenderer.render(world, camera.combined);
  14. Example About Rendering public class MyBox2DTemp extends ApplicationAdapter { private

    SpriteBatch batch; public static final boolean DEBUG_PHYSICS = true; public static final float WORLD_WIDTH = 8.0f; public static final float WORLD_HEIGHT = 4.8f; private OrthographicCamera camera; private Box2DDebugRenderer debugRenderer; private World world; @Override public void create() { camera = new OrthographicCamera(); camera.setToOrtho(false, WORLD_WIDTH, WORLD_HEIGHT); debugRenderer = new Box2DDebugRenderer(); world = new World(new Vector2(0, -9.8f), true); createBody(); batch = new SpriteBatch(); } private void createBody() { ... } private void clearScreen(float r, float g, float b) { Gdx.gl.glClearColor(r, g, b, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); } @Override public void render() { batch.setProjectionMatrix(camera.combined); clearScreen(0,0,0); if(DEBUG_PHYSICS) { debugRenderer.render(world, camera.combined); } batch.begin(); // Do some drawing batch.end(); world.step(1/60f, 6, 2); } }
  15. Update World: Stepping • To update world, we need to

    tell the world to step // Advance simulation 1/60 of a sec. This should // correspond to frame rate in your game float timeStep = 1/60.0f; // How strongly to correct velocity when colliding // More higher, more correct simulation but cost of performance int velocityIterations = 8; // How strongly to correct position when colliding // More higher, more correct simulation but cost of performance int positionIterations = 3; world.step(timeStep, velocityIterations, positionIterations); • Do this at the end of the render() call
  16. Variable timestep • If you have following – world.step(1/60f, 8,

    3); • Simulation can be ruined if machine is not able to run at 60fps • You could calculate the game speed and add it to stepping – world.step(Gdx.graphics.getDeltaTime(), 6, 2); • However: – "We also don't like the time step to change much. A variable time step produces variable results, which makes it difficult to debug. So don't tie the time step to your frame rate" • Use fixed time step. In game loop, call the step several times with fixed value!
  17. batch.begin(); doHeavyStuff(); batch.end(); doPhysicsStep(Gdx.graphics.getDeltaTime()); } public void doHeavyStuff() { try

    { Thread.sleep(50); } catch (Exception e) { e.printStackTrace(); } } private double accumulator = 0; private float TIME_STEP = 1 / 60f; private void doPhysicsStep(float deltaTime) { float frameTime = deltaTime; // If it took ages (over 4 fps, then use 4 fps) // Avoid of "spiral of death" if(deltaTime > 1 / 4f) { frameTime = 1 / 4f; } accumulator += frameTime; while (accumulator >= TIME_STEP) { // It's fixed time step! world.step(TIME_STEP, 8, 3); accumulator -= TIME_STEP; } } }
  18. "Ground" public void createGround() { Body groundBody = world.createBody(getGroundBodyDef()); //

    Add shape to fixture, 0.0f is density. // Using method createFixture(Shape, density) no need // to create FixtureDef object as on createPlayer! groundBody.createFixture(getGroundShape(), 0.0f); } private BodyDef getGroundBodyDef() { // Body Definition BodyDef myBodyDef = new BodyDef(); // This body won't move myBodyDef.type = BodyDef.BodyType.StaticBody; // Initial position is centered up // This position is the CENTER of the shape! myBodyDef.position.set(WORLD_WIDTH / 2, 0.25f); return myBodyDef; } private PolygonShape getGroundShape() { // Create shape PolygonShape groundBox = new PolygonShape(); // Real width and height is 2 X this! groundBox.setAsBox( WORLD_WIDTH / 2 , 0.25f ); return groundBox; }
  19. Draw texture to body pos! public void render() { batch.setProjectionMatrix(camera.combined);

    clearScreen(0, 0, 0); if(DEBUG_PHYSICS) { debugRenderer.render(world, camera.combined); } // Don't calculate this in here. float radius = ((CircleShape) playerBody.getFixtureList().get(0).getShape()).getRadius(); batch.begin(); batch.draw(texture, playerBody.getPosition().x - radius, playerBody.getPosition().y - radius, radius*2, radius*2); batch.end(); doPhysicsStep(Gdx.graphics.getDeltaTime()); }
  20. Draw texture to body pos! public void render() { batch.setProjectionMatrix(camera.combined);

    clearScreen(0, 0, 0); if(DEBUG_PHYSICS) { debugRenderer.render(world, camera.combined); } batch.begin(); batch.draw(tennisBallTexture, tennisBallBody.getPosition().x - tennisBallRadius, tennisBallBody.getPosition().y - tennisBallRadius, tennisBallRadius, // originX tennisBallRadius, // originY tennisBallRadius * 2, // width tennisBallRadius * 2, // height 1.0f, // scaleX 1.0f, // scaleY tennisBallBody.getTransform().getRotation() * MathUtils.radiansToDegrees, 0, // Start drawing from x = 0 0, // Start drawing from y = 0 tennisBallTexture.getWidth(), // End drawing x tennisBallTexture.getHeight(), // End drawing y false, // flipX false); // flipY batch.end(); doPhysicsStep(Gdx.graphics.getDeltaTime()); }
  21. Create Body and Add User Data // Create one body

    to the world tennisBallBody = createBody(); tennisBallTexture = new Texture("tennis.png"); tennisBallBody.setUserData(tennisBallTexture);
  22. Create Body and Add User Data @Override public void render()

    { ... // populate the array with bodies: Array<Body> bodies = new Array<Body>(); world.getBodies(bodies); batch.begin(); for (Body body : bodies) { if(body.getUserData() != null && body.getUserData() == tennisBallTexture) { // It's possible to fetch user data: Texture t = (Texture) body.getUserData(); batch.draw(t, tennisBallBody.getPosition().x - tennisBallRadius, tennisBallBody.getPosition().y - tennisBallRadius, tennisBallRadius, // originX tennisBallRadius, // originY tennisBallRadius * 2, // width tennisBallRadius * 2, // height 1.0f, // scaleX 1.0f, // scaleY tennisBallBody.getTransform().getRotation() * MathUtils.radiansToDegrees, 0, // Start drawing from x = 0 0, // Start drawing from y = 0 t.getWidth(), // End drawing x t.getHeight(), // End drawing y false, // flipX false); // flipY } } ; doPhysicsStep(Gdx.graphics.getDeltaTime()); }
  23. Forces and Impulses • To move things around, you'll need

    to apply forces or impulses to a body • Force – Gradually over time change velocity of a body – Also angular force available, torque (twisting strength) • Impulse – Change velocity immediately • Of course you can just change the position of a body
  24. Examples // gradually accelerate right body.applyForceToCenter(new Vector2(2f,0f), true); // Jump

    up body.applyLinearImpulse(new Vector2(0f, 20f), body.getWorldCenter(), true);
  25. Example private void checkUserInput() { if(Gdx.input.isKeyPressed(Input.Keys.LEFT)) { tennisBallBody.applyForceToCenter(new Vector2(-5f, 0),

    true); } else if(Gdx.input.isKeyPressed(Input.Keys.RIGHT)) { tennisBallBody.applyForceToCenter(new Vector2(5f, 0), true); } else if(Gdx.input.isKeyPressed(Input.Keys.UP)) { tennisBallBody.applyLinearImpulse(new Vector2(0, 0.5f), tennisBallBody.getWorldCenter(), true); } else if(Gdx.input.isKeyJustPressed(Input.Keys.SPACE)) { fireBullet(); } }
  26. Collision world.setContactListener(new ContactListener() { @Override public void beginContact(Contact contact) {

    Body body1 = contact.getFixtureA().getBody(); Body body2 = contact.getFixtureB().getBody(); } @Override public void endContact(Contact contact) { } @Override public void preSolve(Contact contact, Manifold oldManifold) { } @Override public void postSolve(Contact contact, ContactImpulse impulse) { } });