It seems that Box2D coordinate origin is the center of the screen, and LibGDX coordinate origin is the top left side of the screen. This causes problems when I try to draw a sprite on a Body.
This is how I create a body and draw a sprite:
EDIT: I POSTED THE WHOLE CLASS BELOW, ORIGINALLY I JUST PASTED THE CONSTRUCTOR.
width is an array which has 3 elements, these are x1, y and x2 for creating the shape. I’m using y as y1 and y2.
Drawing is simple enough, just sprite.draw(spriteBatch);
As you can see, I’m keeping a log of the body position and sprite position. Both the bodies and sprites are rendered using the same camera. My scaling is correct, my bodies and sprites are in the same size.
The weird thing is that, body position and sprite position are same, but they are not the same in the screen. There is a GROWING gap between sprite and body.
For example, if I render a body and a sprite on width[]{1, 5, 7}, it is fine. The body and the sprite are in the same position. But if I render on width[]{1,100,7}, the gap is about 10 cm.
What do I do to fix this?
EDIT: I POSTED THIS ON BAD LOGIC GAMES FORUM AS WELL. THE SUGGESTION WAS THAT I PASTE MY WHOLE CODE HERE. SO THAT’S WHAT I’M GOING TO DO:
This is my GameScreen class, which can be considered as a world that renders and updates everything:
public class GameScreen implements Screen {
// 2x32 image is 1x1 meter
public final float WTB = 1 / 32f;
private OrthographicCamera camera;
private Box2DDebugRenderer renderer;
private World world;
// Bottom. I'm putting a static body at the bottom of the screen so the player doesn't fall
private ChainShape borderShape;
private FixtureDef borderFixture;
private BodyDef borderBody;
private Body ground;
//These two will be used for my randomization process, which I haven't done yet.
private Vector2 bottomLeft, bottomRight;
private SpriteBatch spriteBatch;
Player player; //This is just a dynamic body with a sprite attached to it.
//After I realized the problem, I needed more than one platforms, so I made an array for them.
private Array<Platform> testPlatforms = new Array<Platform>();
@Override
public void render(float delta) {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
renderer.render(world, camera.combined);
spriteBatch.begin();
player.render(); //Player works perfectly.
// Whether I iterate or render them seperately, the result is the same
Iterator<Platform> iterate = testPlatforms.iterator();
while (iterate.hasNext()) {
Platform p = iterate.next();
p.render();
}
spriteBatch.end();
//Normally the rest is in another method called update(), but I just combined them here.
world.step(1 / 60f, 1,1);
// Follow the player - I removed this, it didn't change anything.
camera.unproject(new Vector3(player.body.getPosition().x, player.body.getPosition().y, 0));
camera.position.y = player.body.getPosition().y;
// Limit the movement
if (player.body.getPosition().x > bottomRight.x) {
player.body.setLinearVelocity(player.body.getLinearVelocity().x* (-0.75f), player.body.getLinearVelocity().y);
player.body.setTransform(player.body.getPosition().sub(2 * WTB, 0),player.body.getAngle());
} else if (player.body.getPosition().x < bottomLeft.x) {
player.body.setLinearVelocity(player.body.getLinearVelocity().x * (-0.75f), player.body.getLinearVelocity().y);
player.body.setTransform(player.body.getPosition().add(2 * WTB, 0),player.body.getAngle());
}
camera.update(true);
player.update();
spriteBatch.setProjectionMatrix(camera.combined);
}
@Override
public void resize(int width, int height) {
//It still shows in meters.
camera.viewportWidth = width * WTB;
camera.viewportHeight = height * WTB;
camera.update(true);
}
@Override
public void show() {
// Camera shows in meters now, not in pixels.
camera = new OrthographicCamera(Gdx.graphics.getWidth() * WTB,
Gdx.graphics.getHeight() * WTB);
renderer = new Box2DDebugRenderer();
world = new World(new Vector2(0, 0), true);
spriteBatch = new SpriteBatch();
spriteBatch.setProjectionMatrix(camera.combined);
player = new Player(spriteBatch, world, WTB);
bottomLeft = new Vector2(-Gdx.graphics.getWidth() / 2 * WTB, -Gdx.graphics.getHeight() / 2 * WTB + player.body.getPosition().y);
bottomRight = new Vector2(Gdx.graphics.getWidth() / 2 * WTB, -Gdx.graphics.getHeight() / 2 * WTB + player.body.getPosition().y);
borderShape = new ChainShape();
borderShape.createChain(new Vector2[] { bottomLeft, bottomRight });
borderFixture = new FixtureDef();
borderFixture.density = 5;
borderFixture.friction = .1f;
borderFixture.restitution = .9f;
borderFixture.shape = borderShape;
borderBody = new BodyDef();
borderBody.type = BodyDef.BodyType.StaticBody;
ground = world.createBody(borderBody);
ground.createFixture(borderFixture);
borderShape.dispose();
//To keep here as clean as possible, I made another method for controls.
controls();
//TESTING
platforms = new Array<Platform>();
testPlatforms.add(new Platform(spriteBatch, world, WTB, -2, 9, 19));
testPlatforms.add(new Platform(spriteBatch, world, WTB, 0, 4, 6));
testPlatforms.add(new Platform(spriteBatch, world, WTB, 0, 4, 12));
testPlatforms.add(new Platform(spriteBatch, world, WTB, 0, 3, 32));
testPlatforms.add(new Platform(spriteBatch, world, WTB, 2, 7, 29));
testPlatforms.add(new Platform(spriteBatch, world, WTB, 4, 10, 50));
testPlatforms.add(new Platform(spriteBatch, world, WTB, -10, 3, 80));
testPlatforms.add(new Platform(spriteBatch, world, WTB, -1, 3, 110));
}
public void controls() {
Gdx.input.setInputProcessor(new InputProcessor() {
//touchUp, touchDragged, touchDown, scrolled, mouseMoved, keyUp, keyTyped are empty.
@Override
public boolean keyDown(int keycode) {
switch (keycode) {
case Keys.W:
player.body.applyForceToCenter(0, 1000, true);
break;
case Keys.A:
player.body.applyForceToCenter(-500, 0, true);
break;
case Keys.S:
player.body.applyForceToCenter(0, -500, true);
break;
case Keys.D:
player.body.applyForceToCenter(500, 0, true);
break;
default:
break;
}
return true;
}
});
}
//Hide, pause, resume and dispose are empty methods for now.
}
And this is my Platform class, which I use to create platforms which the player should avoid (Originally the player would jump on that, but since this is a test project now, I am not sure what to do with these.
public class Platform {
public final float WTB;
// Drawing
public Sprite sprite;
public Texture texture;
public TextureRegion region;
public SpriteBatch spriteBatch;
// Box2D
public Body body;
public FixtureDef fixtureDef;
public BodyDef bodyDef;
public World world;
public float x1,x2,y;
public Platform(SpriteBatch spriteBatch, World world, float WTB,
float x1, float x2, float y) {
this.WTB = WTB;
this.spriteBatch = spriteBatch;
this.world = world;
this.x1 = x1;
this.x2 = x2;
this.y = y;
texture = new Texture("data/bridgeLogs.png");
texture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
texture.setWrap(TextureWrap.Repeat, TextureWrap.Repeat);
region = new TextureRegion(texture,0f,0f,(x2 - x1)/texture.getWidth()/WTB,1f);
sprite = new Sprite(region);
sprite.setSize(region.getRegionWidth(), region.getRegionHeight());
sprite.setScale(WTB);
PolygonShape shape = new PolygonShape();
shape.setAsBox((x2 - x1)/2, region.getRegionHeight()*WTB/2, new Vector2((x1+x2)/2,y), 0);
fixtureDef = new FixtureDef();
fixtureDef.density = 1;
fixtureDef.friction = .2f;
fixtureDef.restitution = .4f;
fixtureDef.shape = shape;
bodyDef = new BodyDef();
bodyDef.type = BodyDef.BodyType.StaticBody;
bodyDef.position.set(x1, y);
body = world.createBody(bodyDef);
body.createFixture(fixtureDef);
body.setFixedRotation(true);
}
}
public void render() {
// TRIED this in many different ways, like subtracting the half of the sprite height and width, still got no luck
sprite.setOrigin(body.getPosition().x, body.getPosition().y);
sprite.setPosition(body.getPosition().x, body.getPosition().y);
sprite.setRotation(body.getAngle() * MathUtils.radDeg);
sprite.draw(spriteBatch);
}
If you are willing to run this, you don’t need my player class, you can just make the camera move in y axis.