0
votes

I'm trying to get rid of having to scale all the coordinates on my sprites when using Box2D and LibGDX.

Here are the settings for my viewport and physics world:

// Used to create an Extend Viewport
public static final int MIN_WIDTH = 480;
public static final int MIN_HEIGHT = 800;
// Used to scale all sprite's coordinates
public static final float PIXELS_TO_METERS = 100f;
// Used with physics world.
public static final float GRAVITY = -9.8f;
public static final float IMPULSE = 0.15f;
world.setGravity(new Vector2(0f, GRAVITY));

When I apply a linear impulse to my character (when the user taps the screen) everything works fine:

body.setLinearVelocity(0f, 0f);
body.applyLinearImpulse(0, IMPULSE, body.getPosition().x, body.getPosition().y, true);

The body has a density of 0f, but changing this to 1f or even 100f doesn't seem to have any real effect.

This means that I have to scale all the sprite's locations in the draw method by PIXELS_TO_METERS. I figured (perhaps incorrectly) that I could simply scale GRAVITY and IMPULSE by PIXELS_TO_METERS and have it work exactly the same. This doesn't seem to be the case. Gravity seems really small, and applying the impulse barely has any effect at all.

// Used to scale all sprite's coordinates
public static final float PIXELS_TO_METERS = 1f;
// Used with physics world.
public static final float GRAVITY = -9.8f * 100;
public static final float IMPULSE = 0.15f * 100;

So:

1) why doesn't simply scaling up all the values make it work the same?

2) Is there a better way to do this?

2

2 Answers

1
votes

It looks like you're over complicating your design by using some imaginary pixel units (i doubt it are actual pixels you're referring to). I'd advice you to use meaningful units instead, for example meters, and stick to it. Thus, use meters for all coordinates (including your virtual viewport). So, practically modify you code to look like this:

// Used to create an Extend Viewport
public static final float MIN_WIDTH = 4.8f; // at least 4.8 meters in width is visible
public static final float MIN_HEIGHT = 8.0f; // at least 8 meter in height is visible

This completely removes the need to scale meter to your imaginary pixel units. The actual scaling from your units (virtual viewport size) to the screen (values between -1 and +1) is done by the camera. You should not have to think about scaling units in your design.

Make sure to remove your PIXELS_TO_METERS constant (don't set it to 1f, it is only complicating your code at no gain) and make sure you're not using imaginary pixels at all in your code. The latter includes all sprites that you create without explicitly specifying its size in meters.

It is still possible to "scale" your units (in your game logic) compared to SI units, because of valid reasons. For example, when creating a space game, you might find yourself using very large numbers when using meters. Typically you'd want to keep the values around 1f to avoid floating point errors. In such case it can be useful to use e.g. dekameters (x10), hectometers (x100) or kilometers (x1000) instead. If you do this, make sure to be consistent. It might help to add the units in comments so you don't forget to scale properly (e.g. GRAVITY = -0.0098f; // kilometer per second per second).

0
votes

I have implemented as this:

// in declaration
float PIXELS_TO_METERS = 32; // in my case: 1m = 32 pixels
Matrix4 projection = new Matrix4();

// in creation
viewport = new FitViewport(
        Application.width
        , Application.height
        , Application.camera);
viewport.apply();

// set vieport dimensions
camera.setToOrtho(false, gameWidth, gameHeight);

// in render
projection.set(batch.getProjectionMatrix());
projection.scl(PIXELS_TO_METERS);
box2dDebugRenderer.render(world, projection);

// in player body creation
BodyDef bodyDef = new BodyDef();
bodyDef.type = BodyDef.BodyType.DynamicBody;
bodyDef.position.x = getPosition().x / PIXELS_TO_METERS;
bodyDef.position.y = getPosition().y / PIXELS_TO_METERS;

CircleShape shape = new CircleShape();
shape.setRadius((getBounds().width * 0.5f) / PIXELS_TO_METERS);

// in player update
setPosition(
    body.getPosition().x * PIXELS_TO_METERS - playerWidth,
    body.getPosition().y * PIXELS_TO_METERS - playerHeight);

So to set pixels to meters in box2d methods you have to divide pixel-positions by PIXELS_TO_METERS and to set meters to pixels in player position you have to multiply box2d values by PIXELS_TO_METERS.

Set your PIXELS_TO_METERS correctly to how much pixels in your screens match to 1 meter.

Good luck.