1

I'm trying to play around with Box2D in libgdx but unfortunately I can't seem to understand the way it works.

Here are a few examples that drive me crazy:

.1. It is known that Box2D works with meters. Everybody knows that. Then, why I getting results by Pixels? For instance, if I define some body definition and I set the position to 0,1, It draws the related fixture/sprite at the bottom-left corner of the screen! I thought the 0,0 point in Box2D is at the center of the screen.

.2. Another problem that I have been struggling to understand and solve is the values of the Shapes, Joints and other stuff. Let's start with the shapes: I defined a Polygon shape like this:

shape.setAsBox(1, 2);

Now the shape was supposed to be 2 meters wide and 4 meters height, but it's not the case. What I got is a super small shape.

And lastly, the values of the joints. I defined a body with a polygon shape and a ground body. Now I "pinned" those two to the center of the ground body using the Revolute Joint, with the goal to create some sort of a catapult that rotates nicely within a certain range.

Now I defined also a Mouse Joint so I could drag the catapult(the polygon shape) back and forth nicely, but it seems that I need to set the maxForce of the joint to an enormous value so I can actually see movement/rotation of the catapult! I don't understand. All this matter should be operated by small values, with regard to the values I had to set up.

Here is my very basic code that contains all of the above. Please tell me what I'm doing wrong, I'm freaking out here:

GameScreen.java

package com.david.box2dpractice;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer;
import com.badlogic.gdx.physics.box2d.ChainShape;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.physics.box2d.joints.RevoluteJointDef;
import com.badlogic.gdx.utils.Array;

public class GameScreen implements Screen{

    private Box2DDebugRenderer debugRenderer;
    private Texture texture;
    private Sprite sprite;
    private Sprite tempSprite;
    private SpriteBatch batch;
    private Body arm , ground;
    private World world;
    public OrthographicCamera camera;
    private RevoluteJointDef jointDef;
    private Array<Body> tempBodies;

    public GameScreen() {
        debugRenderer = new Box2DDebugRenderer();
        batch = new SpriteBatch();
        texture = new Texture(Gdx.files.internal("catapult_arm.png"));
        camera = new OrthographicCamera();
        camera.setToOrtho(false, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
        tempBodies = new Array<Body>();
    }
    @Override
    public void render(float delta) {
        // TODO Auto-generated method stub
        Gdx.gl.glClearColor(0, 0, 0, 0);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        batch.setProjectionMatrix(camera.combined);
        world.getBodies(tempBodies);
        batch.begin();
        for(Body body : tempBodies) {
            if(body.getUserData() != null && body.getUserData() instanceof Sprite) {
                tempSprite = (Sprite) body.getUserData();
                tempSprite.setPosition(body.getPosition().x-tempSprite.getWidth()/2, body.getPosition().y-tempSprite.getHeight()/2);
                tempSprite.setRotation((float) Math.toDegrees(body.getAngle()));
                tempSprite.draw(batch);
            }
        }
        batch.end();
        debugRenderer.render(world, camera.combined);
        world.step(1/60f, 6, 2);
    }

    @Override
    public void resize(int width, int height) {
        // TODO Auto-generated method stub
        Gdx.app.log("System", "resize() was invoked");
    }

    @Override
    public void show() {
        // TODO Auto-generated method stub
        Gdx.app.log("System", "show() was invoked");
        world = new World(new Vector2(0,0), true);
        sprite = new Sprite(texture);
        BodyDef bodyDef = new BodyDef();
        bodyDef.position.set(Gdx.graphics.getWidth()/2,Gdx.graphics.getHeight()/2+sprite.getHeight()/2);
        bodyDef.type = BodyType.DynamicBody;

        // The shape
        PolygonShape shape = new PolygonShape();
        shape.setAsBox(11, 91);

        // The fixture
        FixtureDef fixtureDef = new FixtureDef();
        fixtureDef.shape = shape;
        fixtureDef.density = .10f;
        arm = world.createBody(bodyDef);
        arm.createFixture(fixtureDef);
        sprite.setOrigin(sprite.getWidth()/2, sprite.getHeight()/2);
        arm.setUserData(sprite);
        shape.dispose();

        bodyDef = new BodyDef();
        bodyDef.position.set(Gdx.graphics.getWidth()/2,Gdx.graphics.getHeight()/2);
        bodyDef.type = BodyType.StaticBody;

        ChainShape shape2 = new ChainShape();
        shape2.createChain(new Vector2[] {new Vector2(-20*Pixels_To_Meters,0),new Vector2(20*Pixels_To_Meters,0)});

        // The fixture
        fixtureDef.shape = shape2;
        fixtureDef.restitution = .65f;
        fixtureDef.friction = .75f;
        ground = world.createBody(bodyDef);
        ground.createFixture(fixtureDef);
        shape2.dispose();
        // joint
        jointDef = new RevoluteJointDef();
        jointDef.bodyA = arm;
        jointDef.bodyB = ground;
        jointDef.localAnchorB.set(ground.getLocalCenter());
        jointDef.localAnchorA.set(arm.getLocalCenter().x,arm.getLocalCenter().y-sprite.getHeight()/2);
        jointDef.enableLimit = true;
        jointDef.enableMotor = true;
        jointDef.motorSpeed = 15;
        jointDef.lowerAngle = (float) -Math.toRadians(75);
        jointDef.upperAngle = (float) -Math.toRadians(9);
        jointDef.maxMotorTorque = 4800;
        world.createJoint(jointDef);
        Gdx.input.setInputProcessor(new InputHandler(arm,ground,world,camera));
    }

    @Override
    public void hide() {
        // TODO Auto-generated method stub
        Gdx.app.log("System", "hide() was invoked");
        dispose();
    }

    @Override
    public void pause() {
        // TODO Auto-generated method stub
        Gdx.app.log("System", "pause() was invoked");
    }

    @Override
    public void resume() {
        // TODO Auto-generated method stub
        Gdx.app.log("System", "resume() was invoked");
    }

    @Override
    public void dispose() {
        // TODO Auto-generated method stub
        Gdx.app.log("System", "dispose() was invoked");
        texture.dispose();
        batch.dispose();
        world.dispose();
    }
}

InputHandler.java

package com.david.box2dpractice;

import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.QueryCallback;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.physics.box2d.joints.MouseJoint;
import com.badlogic.gdx.physics.box2d.joints.MouseJointDef;

public class InputHandler implements InputProcessor{

    Body ground;
    MouseJoint mouseJoint;
    MouseJointDef mouseJointDef;
    World world;
    Vector2 target,initialPos;
    Vector3 temp;
    QueryCallback query;
    OrthographicCamera camera;
    boolean firstTime = true;
    public InputHandler(Body arm, Body ground, final World world, OrthographicCamera camera) {
        this.camera = camera;
        this.ground = ground;
        this.world =  world;
        mouseJointDef = new MouseJointDef();
        target = new Vector2();
        temp = new Vector3();
        mouseJointDef.bodyA = ground;
        mouseJointDef.collideConnected = true;
        mouseJointDef.maxForce = 9000;
        query = new QueryCallback() {

            @Override
            public boolean reportFixture(Fixture fixture) {
                // TODO Auto-generated method stub
                if(!fixture.testPoint(temp.x, temp.y))
                    return true;
                if(firstTime) {
                    initialPos = new Vector2(fixture.getBody().getPosition().x,fixture.getBody().getPosition().y);
                    firstTime = false;
                }
                mouseJointDef.bodyB = fixture.getBody();
                mouseJointDef.target.set(temp.x,temp.y);
                mouseJoint = (MouseJoint) world.createJoint(mouseJointDef);
                return false;
            }
        };
    }
    @Override
    public boolean keyDown(int keycode) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean keyUp(int keycode) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean keyTyped(char character) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean touchDown(int screenX, int screenY, int pointer, int button) {
        // TODO Auto-generated method stub
        camera.unproject(temp.set(screenX, screenY, 0));
        world.QueryAABB(query, temp.x, temp.y, temp.x, temp.y);
        return true;
    }

    @Override
    public boolean touchUp(int screenX, int screenY, int pointer, int button) {
        // TODO Auto-generated method stub
        if(mouseJoint == null) 
            return false;
        mouseJoint.setTarget(initialPos);
        world.destroyJoint(mouseJoint);
        mouseJoint = null;
        firstTime = true;
        return true;
    }

    @Override
    public boolean touchDragged(int screenX, int screenY, int pointer) {
        // TODO Auto-generated method stub
        if(mouseJoint == null)
            return false;
        camera.unproject(temp.set(screenX, screenY, 0));
        mouseJoint.setTarget(target.set(temp.x, temp.y));
        return true;
    }

    @Override
    public boolean mouseMoved(int screenX, int screenY) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean scrolled(int amount) {
        // TODO Auto-generated method stub
        return false;
    }

}

If you could help me out here I would really really appreaciate that. Thanks!!

David Lasry
  • 1,407
  • 4
  • 26
  • 43

1 Answers1

7

Box2D is only the Physic-Engine, the logical part. It does nothing with the view, so it is your job to convert the meters to pixels.
In Libgdx this could be done by using a Camera.
You are allready using a Camera, but you give it the "wrong" Viewport-Size.
You tell the Camera to be as big as the game (Gdx.graphics.getWidth, Gdx.graphics.getHeight), instead you should think about how many meters you want to show on your Screen.
If you want to show 80 meters in width and 45 meters i height (16/9), then you need to setup the Camera like this:

 camera = new OrthographicCamera();
 camera.setToOrtho(false, 80, 45);

So if your Game has a resolution of 1600*900 px, every meter would be convertet to 20px (camera does this for you), if you are using a resolution of 800*450, every meter would be converted to 10px.

Also box2Ds P(0/0) is not in the middle of the Screen, it is nowhere on the screen. It is on the P(0/0) of the box2D world, it is your job to draw it in the middle or the bottom or whererever you want.
Again, this is done by Camera. the Cameras P(0/0) by default is in the middle of the Screen, but you can move the camera arround, so it could be everywhere.

Now it should be clear, that the shape you created is not "super-small", but you just did not "zoom in". A 3m long car seems verry small if you watch it from a few 100m of distance. If you instead stand 1m away from it, you are almost unable to see the whole car at one time, as it is bigger then your "viewport".
I am not sure about the joints/forces, but it could be, that your problem is solved, if you "zoom in" by using a camera. But i also can be wrong, as i never used box2D...

Some tutorials:

  1. IForce2D - Box2D, it is a tutorial fpr C++. but by reading the explanations you should understand it and be able to implement it in java/libgdx.
  2. Using Box2D in Libgdx Game, this tutorial showes you how to create a game with box2D and libgdx. It really helps by understanding how to connect box2D with Libgdx.
Robert P
  • 9,398
  • 10
  • 58
  • 100
  • Very informative comment. You gave me some intersting insights. – David Lasry Sep 29 '14 at 11:34
  • @DavidLasry i am thinking about using box2d since a pretty long time and therefore i read a lot of tutorials. However, i never actually tried it :P I edit my answer and give you the links to some great tutorials! – Robert P Sep 29 '14 at 11:58