Search code examples
javabox2dlibgdxgame-physics

Topdown Spaceship, calculating movement


I am in the process of working on what is to be my main project, it is a top down 2d space shooter. I am using a Box2D dynamic body and the Libgdx library.

This seemed like a good idea at the time until I released my Math skills suck!!!

I have a problem with the movement, basically what I want is W to accelerate, A and D to rotate and S to decelerate or brake if that's a think in space.

Now the first hurdle was trying to get the ship to move in the direction it's facing by pressing W, success! Now regardless of which way you face, W will always accelerate you in the direction.

Now the problem, I press W and my ship starts to accelerate by applying an impulse, good, now when I push A or D to rotate the body and change the angle while holdin W, it just continues to fly in that direction unless I release W and repress. Now that does change the direction but then I have to sit and wait, depending on speed for the impulse to counter the current speed and actually start to accelerate in the right direct.

What I actually want it to do is accelerate with W and anytime I press A and D, it should calculate the new heading and start going there but I can't seem to figure it out. Here is my code:

The input processor (Player) :

public void update(float delta) {
    currentShip.shipAngle = currentShip.getBody().getAngle();

    // move the ship
    currentShip.getBody().applyForceToCenter(currentShip.getVelocity(), true);

    // Turn the ship
    currentShip.getBody().applyTorque(currentShip.steeringTorque, true);

}

@Override
public boolean keyDown(int keycode) {

    switch (keycode) {
    case Keys.D:
        if (currentShip.getEngine() != null) {
            currentShip.steeringTorque = -1f;
            break;
        } else {
            System.out.println("No engine fitted!");
            break;
        }
    case Keys.A:
        if (currentShip.getEngine() != null) {
            currentShip.steeringTorque = 1f;
            break;
        } else {
            System.out.println("No engine fitted!");
            break;
        }
    case Keys.W:
        if (currentShip.getEngine() != null) {
            currentShip.velocity.x = MathUtils
                    .cos(currentShip.shipAngle)
                    * currentShip.getEngine().getAccelleration().x;
            currentShip.velocity.y = MathUtils
                    .sin(currentShip.shipAngle)
                    * currentShip.getEngine().getAccelleration().x;

            break;
        } else {
            System.out.println("No engine fitted!");
            break;
        }
    case Keys.S:
        if (currentShip.getEngine() != null) {
            break;
        } else {
            System.out.println("No engine fitted!");
            break;
        }
    case Keys.B:
        currentShip.shipAngle = 0;
        currentShip.getBody().setLinearVelocity(0, 0);
        break;
    case Keys.SPACE:
            if (currentShip.getEngine() != null) {
            System.out.println("Already have an engine fitted!");
            break;
        } else {
            Ship.addModule(new Engine(new Vector2(10, 10f), 10));
            System.out.println("Engine fitted!");
            break;
        }
    }

    return false;
}

I don't think this is relevant but this is the Engine class:

public class Engine extends Module{

public Vector2 accelleration = new Vector2();
public Vector2 decelleration = new Vector2();
public float maxSpeed;

/**
 * 
 * @param accelleration - The max accelleration this engine can supply
 * @param maxSpeed - The max terminal velocity that this engine can have
 */
public Engine(Vector2 accelleration, float maxSpeed){
    this.accelleration = accelleration;
    this.maxSpeed = maxSpeed;
}

public Vector2 getAccelleration() {
    return accelleration;
}

public void setAccelleration(Vector2 accelleration) {
    this.accelleration = accelleration;
}

public void setMaxSpeed(float maxSpeed) {
    this.maxSpeed = maxSpeed;
}

}

This here is the Box2D properties of my ship:

    this.width = 70;
    this.height = 30;

    // Setup ships model
    bodyDef.type = BodyType.DynamicBody;
    bodyDef.position.set(position);

    body = world.createBody(bodyDef);

    chassis.setAsBox(width / GameScreen.WORLD_TO_BOX_WIDTH, height / GameScreen.WORLD_TO_BOX_HEIGHT);
    fixtureDef.shape = chassis;
    fixtureDef.friction = 0.225f;
    fixtureDef.density = 0.85f;

    fixture = body.createFixture(fixtureDef);
    sprite = new Sprite(new Texture(Gdx.files.internal("img/TestShip.png")));
    body.setUserData(sprite);

    chassis.dispose();

tl;dr I need to calculate the new angle when I press A or D and then change the direction the ship is moving without releasing the W key.


Solution

  • When you are pressing the A/W key, you are directly setting the velocity of your body. I think you should be applying forces to it to make it move in the direction you want it to go. This is a physics simulation, you should only be directly setting the velocity if you want to limit it to a maximum velocity your body can travel at. Or redistribute velocity (speed) when you want a body to only move along a certain direction (e.g. a missile).

    I also play a lot with 2-D space simulations. In one, I was creating a missile that moved in the direction that the user wanted. In that case, they were doing it by putting their finger on the screen (it is an iPad game).

    There are (at least) two ways to do this.

    1. Apply thrust backwards when you want to accelerate and turn the body (like a missile).
    2. Turn the body as you wish, and apply thrust in a direction that will force the body to move towards the direction you wish.

    Both have tradeoffs in the way they move and work. The biggest trade off is when you move from place to place, in (1), you will always move forward, which looks correct for what visually people tend to think of when they think of space ships (moves forward). You can try to use it for vehicle control as well, but you have to cancel the side acting forces (I did) or the motion is a bit funky and hard to control.

    In (2), the body turns to face the direction it is moving towards, which is more like the way I feel people move towards a position. Also, (2) allows you to have a body moving backwards while shooting, etc. So it's more useful for character controllers.

    In both cases, I found the easies way to do this was to have a "point" that you wanted the character to move towards and apply turning torque and thrust to the body to make it move towards that point. I have two different types of characters (c++ classes), one that moves like (1) and one that moves like (2).

    I gave part of the answer for (2) in this post.
    There is a full code base (C++) and demonstration video located here. It is in Cocos2d-x, but the code is well commented and there are posts on the site showing the structure of the classes, etc.

    Was this helpful?