0

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.

Alvaro
  • 25
  • 1
  • 8
  • Can you verify whether the raw touchscreen values are sufficiently "clean" for steady movement? If not, consider performing a low-pass filtering operation. – nanofarad Dec 26 '17 at 21:27
  • Thanks for the piece of advice. I found out that libgdx has a function "Lerp" in Vector2 which was useful. As far as I know, Lerp (linear interpolation) is not the same as low-pass filter, but I think it works (or at least most of the time). My character seems to me more stable when moves around the screen. Could you please check out "my new approach" in the question? Do you have another suggestion? I really appreciate it. Thks. – Alvaro Dec 27 '17 at 19:17
  • I'm not sure that `lerp` does exactly what you want it to do, since it interpolates rather than filtering. It's plausible that it could work, but it's not a method of smoothing that I've ever come across in the past. You could make a simple first-order-ish filter by always taking the movement to be (0.7*input + 0.3*lastFrameMovement) or so, but it would still be imprecise unless you involved `deltaTime` in the rolloff itself. – nanofarad Dec 27 '17 at 20:57
  • This is the code from libgdx library: `public Vector2 lerp(Vector2 target, float alpha) { float invAlpha = 1.0F - alpha; this.x = this.x * invAlpha + target.x * alpha; this.y = this.y * invAlpha + target.y * alpha; return this; }` I think it's exaclty that you suggested. Anyway, it's much better than before, but not as much as I'd like. Thanks very much for your idea, I'm going to use it in my code but I'll continue investigating if I can add something else. – Alvaro Dec 27 '17 at 21:49

1 Answers1

0

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);
  • Yes exactly, that's what I did in my second approach: `Vector2 newVelocity = new Vector2(delta.x, delta.y); newVelocity.nor(); newVelocity.x = newVelocity.x * Constants.HERO_LINEAR_VELOCITY; newVelocity.y = newVelocity.y * Constants.HERO_LINEAR_VELOCITY;` It seems to me that the problem is related with the high number of values that I receive in the event. I need some kind of low-pass filtering as hexafraction says previously. – Alvaro Dec 27 '17 at 16:03
  • It looks overly complicated for what you want. My approach will move at constant speed. If you are using deltas make sure you account for delta time as well, might be why it jitters for you – Andreas Toresäter Dec 28 '17 at 10:23