1
\$\begingroup\$

It's my first time posting here!

I'm working on a top-down shooter game in LibGDX and Box2D (so gravity is set to (0, 0)). At the moment, to move my character, I'm using the setTransform() method. I understand it can be a bit buggy, but in past experiences the method worked fine and collisions were detected and handled.

Here's an image of my character (DynamicBody) colliding with a static obstacle. Even using setTransform(), it should try to push the character outside of the box's bounds? Bodies do not seem to be colliding.

Here's my Player code. The Vector2 set as linearVelocity is not being used.

public class Player extends BodyHolder implements Disposable {

    private float speed = 2.3f;

    private Sprite sprite;
    private Vector3 position;
    private Vector2 linearVelocity;
    private Vector3 mousePos;

    private static final Vector2 SIZE = new Vector2(93, 84);

    private int playerPhase = 0, prevPlayerPhase = 0;

    private int ID;
    private boolean isMain = false;

    public Player(final Vector2 position, final WorldManager world) {
        super(world, position, new Vector2(SIZE.x / 2, SIZE.y / 2), BodyDef.BodyType.DynamicBody, 0.8f, false, -1);
    }

    public Player(int id, Vector2 pos, WorldManager world, boolean main) {
        super(world, pos, new Vector2(SIZE.x / 2, SIZE.y / 2), BodyDef.BodyType.DynamicBody, 0.8f, false, id);

        this.ID = id;
        this.position = new Vector3(pos.x, pos.y, 0);
        this.mousePos = new Vector3();
        this.isMain = main;

        linearVelocity = new Vector2();

        getBody().setFixedRotation(true);

        sprite = new Sprite(new Texture(Gdx.files.internal("player/player_nogun.png")));
        playerPhase = 1;
    }

    public void update(SpriteBatch batch, ModifiedShapeRenderer renderer, CameraController cam) {

        sprite.setCenter(position.x, position.y);
        getBody().setTransform(position.x, position.y, 0);
//        getBody().setLinearVelocity(linearVelocity);
        if(isMain()) this.handleInput(cam);

        if(prevPlayerPhase != playerPhase) {
            prevPlayerPhase = playerPhase;
            if(playerPhase == 1) {
                sprite = new Sprite(new Texture(Gdx.files.internal("player/player_nogun.png")));
                sprite.setOrigin(sprite.getOriginX() + 5, sprite.getOriginY());
            } else if(playerPhase == 2) {
                sprite = new Sprite(new Texture(Gdx.files.internal("player/player.png")));
            }
        }

        for(Gun gun : GunSpawner.getGuns()) {
            if(gun.obtainObject().getBoundingRectangle().overlaps(this.sprite.getBoundingRectangle())) {
                gun.hoverOver(this);
            }
        }

        batch.setProjectionMatrix(cam.getCamera().combined);
        batch.begin();
        sprite.draw(batch);
        batch.end();
    }

    public void handleInput(CameraController cam) {
        mousePos.set(Gdx.input.getX(), Gdx.input.getY(), 0);
        cam.getCamera().unproject(mousePos);
        sprite.setRotation((float) (MathUtils.radiansToDegrees * Math.atan2((position.y - mousePos.y) , (position.x - mousePos.x))));

        boolean keyPressed = false;

        if(Gdx.input.isKeyPressed(Input.Keys.W)) {
            position.y += speed;
            linearVelocity.y = 100;
            keyPressed = true;
        }
        if(Gdx.input.isKeyPressed(Input.Keys.S)) {
            position.y -= speed;
            linearVelocity.y = -100;
            keyPressed = true;
        }
        if(Gdx.input.isKeyPressed(Input.Keys.A)) {
            position.x -= speed;
            linearVelocity.x = -100;
            keyPressed = true;
        }
        if(Gdx.input.isKeyPressed(Input.Keys.D)) {
            position.x += speed;
            linearVelocity.x = 100;
            keyPressed = true;
        }

        if(keyPressed) {
            keyPressed = false;
        } else {
            linearVelocity.set(0, 0);
        }

        if (Gdx.input.isKeyJustPressed(Input.Keys.K)) {
            Gdx.app.log(position.x + "", position.y + "");
        }

    }

    public Vector2 get2DPosition() {
        return new Vector2(position.x, position.y);
    }

    public int getID() {
        return ID;
    }

    public boolean isMain() {
        return this.isMain;
    }

    public Sprite obtainObject() {
        return sprite;
    }

    @Override
    public void dispose() {

    }
}

If it's any help, I'm stepping the world at 6 velocity iterations and 2 position iterations.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ It looks like you always force the physics body to match your position variable every frame (so if the physics engine tried to move the bodies apart, you undo that and force them back together), and you never read a new position out of the physics body to update this position variable. So the communication is one-way. No matter what happens in the physics simulation, your code doesn't care - it never asks the physics engine where it should move to. Am I misunderstanding your code? \$\endgroup\$
    – DMGregory
    Commented Aug 11, 2020 at 17:22
  • \$\begingroup\$ Now that you point that out, you're right. Since I'm setting the body position every frame, that makes sense. How do you suggest I read the new position based on where the physics engine pushed the body? \$\endgroup\$
    – Rohan
    Commented Aug 11, 2020 at 17:47
  • 1
    \$\begingroup\$ That might be a good question to edit your post to ask. "Not working" isn't really a question, but "How do I..." is. :) \$\endgroup\$
    – DMGregory
    Commented Aug 11, 2020 at 17:48

1 Answer 1

1
\$\begingroup\$

Setting the transform directly can lead to weird results (as you point out yourself), and it's often better to control your bodies using impulses, even if it is sometimes difficult to tune it to perfection to get just the sort of movement you're looking for.

Depending on the kind of movement you're looking for I'd recommend using applyLinearImpulse to move your avatar and setAngularVelocity to rotate it.

Using these two it's easy to get something like this up and running:

Keyboard controlled Box2D body

The full source code for that is:

import com.badlogic.gdx.Game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputAdapter;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.RandomXS128;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.*;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.ObjectMap;

public class Box2DKeyBoardControlledGame extends Game {

    private OrthographicCamera camera;
    private World world;
    private Box2DDebugRenderer renderer;
    private RandomXS128 rnd = new RandomXS128();

    private Body player;
    private Array<Body> obstacles = new Array<>();

    private final ObjectMap<Integer, Boolean> keyboardState = new ObjectMap<>();

    @Override
    public void create() {
        float aspectRatio = (float) Gdx.graphics.getHeight() / (float) Gdx.graphics.getWidth();

        camera = new OrthographicCamera(100.0f, 100.0f * aspectRatio);

        renderer = new Box2DDebugRenderer();
        world = new World(new Vector2(0.0f, 0.0f), false);

        player = createPlayer();
        for (int i = 0; i < 16; ++i) {
            obstacles.add(createObstacle(rnd.nextInt(100) - 50.0f, rnd.nextInt(100) - 50.0f));
        }

        Gdx.input.setInputProcessor(new InputAdapter() {
            @Override
            public boolean keyDown(int keycode) {
                keyboardState.put(keycode, true);
                return true;
            }

            public boolean keyUp(int keycode) {
                keyboardState.put(keycode, false);
                return true;
            }
        });
    }

    private Body createPlayer() {
        CircleShape shape = new CircleShape();
        shape.setRadius(1.0f);

        FixtureDef fixtureDef = new FixtureDef();
        fixtureDef.shape = shape;

        BodyDef bodyDef = new BodyDef();
        bodyDef.type = BodyDef.BodyType.DynamicBody;
        bodyDef.linearDamping = 8.0f; // You might want to tweak this to get the player to slow down at a different rate

        Body body = world.createBody(bodyDef);
        body.createFixture(fixtureDef);

        return body;
    }

    private Body createObstacle(float x, float y) {
        PolygonShape shape = new PolygonShape();
        shape.setAsBox(1.0f + rnd.nextFloat() * 2.0f, 1.0f + rnd.nextFloat() * 2.0f);

        FixtureDef fixtureDef = new FixtureDef();
        fixtureDef.shape = shape;

        BodyDef bodyDef = new BodyDef();
        bodyDef.type = BodyDef.BodyType.StaticBody;

        Body body = world.createBody(bodyDef);
        body.createFixture(fixtureDef);
        body.setTransform(x, y, rnd.nextFloat() * MathUtils.PI2);

        return body;
    }

    @Override
    public void render() {
        Gdx.gl.glClearColor(0, 0, 0, 0);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        final float accelerationFactor = 64.0f;
        final float turnFactor = 256.0f;

        Vector2 direction = (new Vector2(1.0f, 0.0f)).rotate(player.getAngle() * MathUtils.radiansToDegrees);
        Vector2 impulse = new Vector2();
        float turn = 0.0f;

        if (keyboardState.get(Input.Keys.UP, false))
            impulse.set(direction).scl(accelerationFactor);
        if (keyboardState.get(Input.Keys.DOWN, false))
            impulse.set(direction).scl(-accelerationFactor);
        if (keyboardState.get(Input.Keys.LEFT, false))
            turn = turnFactor;
        if (keyboardState.get(Input.Keys.RIGHT, false))
            turn = -turnFactor;

        player.setFixedRotation(false);
        player.applyLinearImpulse(impulse.scl(Gdx.graphics.getDeltaTime()), Vector2.Zero, true);
        player.setAngularVelocity(turn * Gdx.graphics.getDeltaTime());

        camera.update();
        world.step(Gdx.graphics.getDeltaTime(), 6, 2);

        renderer.render(world, camera.combined);
    }
}

The difficult part is to tune and tweak the parameters of your FixtureDefs, BodyDefs and impulses to get exactly the movement you're looking for.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .