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

LibGDX and Box2D

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

LibGDX and Box2D

Avatar for Jussi Pohjolainen

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