Move a body emulating the movement of my finger on the screen at a constant speed

166 Views Asked by At

I need help with the following problem since I have invested many days without arriving at an acceptable solution.

I'm doing an Android game (using libgdx) where the main character (named Hero) is seen from above (top-down view game) and walks on a field. The user moves the character by moving his finger along the screen. The finger does't need to be on the character.

The character uses two animations, one animation when he moves forward (that is, when his "y" is greater than zero since the user looks at the game from the "sky") and another animation when he moves backwards (that is, when his "y" is less than zero, remember I'm developing a top-down view game).

Finally, I need the character to always move at a CONSTANT speed.

In short, I would like to handle the character with the finger and move it in the direction that marks my finger, always a CONSTANT speed.

This would be very easy if I could set the position of the character each delta time, but I'm using box2d which only knows about linearVelocity, impulses, forces, etc.

I tried using mouseJoint where hitbody is my main character (Hero) and groundBody is an invisible body.

// Invisible zero size ground body
// to which we can connect the mouse joint
Body groundBody;
BodyDef bodyDef = new BodyDef();
groundBody = world.createBody(bodyDef);

/* player is an instance of my Hero's class, which has a box2d body and
update, draw methods, etc.
*/
hitBody = player.getB2body(); 
...

InputProcessor:

@Override
public boolean touchDown(int i, int i1, int i2, int i3) {
        gameCam.unproject(testPoint.set(i, i1, 0));
        MouseJointDef def = new MouseJointDef();
        def.bodyA = groundBody;
        def.bodyB = hitBody;
        def.collideConnected = true;
        def.target.set(testPoint.x, testPoint.y);
        def.maxForce = 1000.0f * hitBody.getMass();
        mouseJoint = (MouseJoint) world.createJoint(def);
        hitBody.setAwake(true);
}

@Override
public boolean touchUp(int i, int i1, int i2, int i3) {
    player.getB2body().setLinearVelocity(0,0);

    // if a mouse joint exists we simply destroy it
    if (mouseJoint != null) {
        world.destroyJoint(mouseJoint);
        mouseJoint = null;
    }
    return false;
}

@Override
public boolean touchDragged(int i, int i1, int i2) {
    // if a mouse joint exists we simply update
    // the target of the joint based on the new
    // mouse coordinates
    if (mouseJoint != null) {
        gameCam.unproject(testPoint.set(i, i1, 0));
        mouseJoint.setTarget(target.set(testPoint.x, testPoint.y));
        evaluateMovementDirection();
    }
    return false;
}

private void evaluateMovementDirection() {
    float vy = player.getB2body().getLinearVelocity().y;
    float vx = player.getB2body().getLinearVelocity().x;

    // Test to Box2D for velocity on the y-axis.
    // If Hero is going positive in y-axis he is moving forward.
    // If Hero is going negative in y-axis he is moving backwards.
    if (vy > 0.0f) {
        player.onMovingUp(); // In draw, I'll use a "moving forward" animation
    } else if (vy < 0.0f) {
        player.onMovingDown(); // In draw, I'll use a "movieng backwards" animation
    } else {
        player.onStanding(); // vy == 0 In draw, I'll use a texture showing my Hero standig.
    }
}

The problem I get with this, is that if I move my finger very fast, the character moves very fast. I would like the character to always move AT CONSTANT SPEED.

The other approach I tried is to use the pan event:

GestureListener:

@Override
public boolean pan(float x, float y, float deltaX, float deltaY) {
    /*
    * DeltaX is positive when I move my finger to the left, negative otherwise.
    * DeltaY is positive when I move my finger down, negative otherwise.
    */

    // In b2body y-axes sign is the opposite.
    deltaY = -deltaY;

    // DeltaX and deltaY are in pixels, therefore delta is in metres.
    Vector2 delta = new Vector2(deltaX / Constants.PPM, deltaY / Constants.PPM);

    // Deltas too small are discarded
    if (delta.len() > Constants.HERO_SENSIBILITY_METERS) {
        /*
        * origin.x = player.getB2body().getPosition().x
        * origin.y = player.getB2body().getPosition().y
        *
        * destination.x = origin.x + delta.x
        * destination.y = origin.y + delta.y
        *
        * To go from origin to destination we must subtract their position vectors: destination - origin.
        * Thus destination - origin is (delta.x, delta.y).
        */
        Vector2 newVelocity = new Vector2(delta.x, delta.y);

        // Get the direction of the previous vector (normalization)
        newVelocity.nor();

        // Apply constant velocity on that direction
        newVelocity.x = newVelocity.x * Constants.HERO_LINEAR_VELOCITY;
        newVelocity.y = newVelocity.y * Constants.HERO_LINEAR_VELOCITY;

        // To avoid shaking, we only consider the newVelocity if its direction is slightly different from the direction of the actual velocity.
        // In order to determine the difference in both directions (actual and new) we calculate their angle.
        if (Math.abs(player.getB2body().getLinearVelocity().angle() - newVelocity.angle()) > Constants.HERO_ANGLE_SENSIBILITY_DEGREES) {
            // Apply the new velocity
            player.getB2body().setLinearVelocity(newVelocity);
            evaluateMovementDirection();
        }
    } else {
        // Stop
        player.getB2body().setLinearVelocity(0, 0);
        evaluateMovementDirection();
    }
    return true;
}

The problem I have with this is that the movement is very unstable and "dirty". The character is shaking.

I tried this approach (thanks @hexafraction). Using this code, my charecter moves more fluid along the screen. It's not perfect, but it's something...

@Override
public boolean pan(float x, float y, float deltaX, float deltaY) {
    /*
    * DeltaX is positive when I move my finger to the left, negative otherwise.
    * DeltaY is positive when I move my finger down, negative otherwise.
    * Both are in pixels, thus to get meters I must divide by Constants.PPM.
    */

    // In b2body y-axes sign is the opposite.
    deltaY = -deltaY;

    /*
    * origin.x = player.getB2body().getPosition().x
    * origin.y = player.getB2body().getPosition().y
    *
    * destination.x = origin.x + deltaX / Constants.PPM
    * destination.y = origin.y + deltaY / Constants.PPM
    *
    * To go from origin to destination we must subtract their position vectors: destination - origin.
    * Thus, destination - origin is (deltaX / Constants.PPM, deltaY / Constants.PPM).
    */
    candidateVelocity.x = deltaX / Constants.PPM;
    candidateVelocity.y = deltaY / Constants.PPM;

    // Get the direction of the previous vector (normalization)
    candidateVelocity.nor();

    // Apply constant velocity on that direction
    candidateVelocity.x = candidateVelocity.x * Constants.HERO_LINEAR_VELOCITY;
    candidateVelocity.y = candidateVelocity.y * Constants.HERO_LINEAR_VELOCITY;

    // Linear interpolation to avoid character shaking
    heroVelocity.lerp(candidateVelocity, Constants.HERO_ALPHA_LERP);

    // Apply the result
    player.getB2body().setLinearVelocity(heroVelocity);

    // Depending on the result, we change the animation if needed
    evaluateMovementDirection();
}
return true;
}

I need a suggestion on how to resolve this. I mean, move a box2d character with my finger along the screen at CONSTANT SPEED.

Thank you very much.

1

There are 1 best solutions below

2
On

Calculate the direction in which you want to move:

dragPos.sub(currentPos);

Normalize it and multiply with the constant speed:

dragPos.sub(currentPos).nor().scl(CONSTANT_SPEED);