Phillip Lagoc
Phillip Lagoc

Reputation: 53

JavaFX: Moving rectangle suddenly with keyboard results in rectangle getting "stuck"

I am trying to create a Pong-like game, and I have started implementing some code regarding the movement of the paddle. Everything works fine with regards to the movement of the paddle itself. However, problems arise when I suddenly change directions of the paddle (more specifically, when I switch immediately from moving the paddle upwards to downwards, and vice versa). The paddle freezes in place for a couple of seconds, then goes in the desired direction.

I have tried seeing what happens when I remove the canvas.setOnKeyReleased(new KeyReleasedHandler());, but then the paddle moves in the same direction without stopping. I want to be able to stop the movement of paddle once I release the key.

Here is the MainApplication:

    public class Pong2App extends Application {
private static final double CANVAS_WIDTH = 900;
private static final double CANVAS_HEIGHT = 500;

private static final double ELAPSED_TIME_SPEED = 1.85;

Ball testBall;
Paddle leftPaddle;

@Override
public void start(Stage primaryStage) throws Exception {
    Group root = new Group();
    Scene scene = new Scene(root);
    primaryStage.setScene(scene);

    Canvas canvas = new Canvas(CANVAS_WIDTH, CANVAS_HEIGHT);
    Bounds canvasBounds = canvas.getBoundsInLocal();
    root.getChildren().add(canvas);

    GraphicsContext gc = canvas.getGraphicsContext2D();

    // TODO: remove testBall and leftPaddle; Gives testBall random vX and vY
    testBall = new Ball(CANVAS_WIDTH / 2, CANVAS_HEIGHT / 2.5, 30, 30, gc);
    Random tempRand = new Random();
    testBall.setvX((tempRand.nextDouble() * 4) * (tempRand.nextBoolean() ? -1 : 1));
    testBall.setvY((tempRand.nextDouble() * 4) * (tempRand.nextBoolean() ? -1 : 1));

    leftPaddle = new Paddle(70, CANVAS_HEIGHT / 2.5, 10, 100, "Left", gc);

    canvas.setFocusTraversable(true);
    canvas.setOnKeyPressed(new KeyPressedHandler());
    canvas.setOnKeyReleased(new KeyReleasedHandler());

    new AnimationTimer() {

        @Override
        public void handle(long currentTime) {

            testBall.didCollideWithWalls(canvas);

            if (leftPaddle.getY() <= canvasBounds.getMinY() + 5)
                leftPaddle.setY(canvasBounds.getMinY() + 5);

            if (leftPaddle.getY() >= canvasBounds.getMaxY() - 105)
                leftPaddle.setY(canvasBounds.getMaxY() - 105);

            if (leftPaddle.didCollideWith(testBall)) {
                testBall.setvX(-testBall.getvX());
                testBall.setX(testBall.getX() + testBall.getvX());
            }

            testBall.update(ELAPSED_TIME_SPEED);

            leftPaddle.update(ELAPSED_TIME_SPEED);

            gc.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);

            testBall.render(gc);

            leftPaddle.render(gc);

        }

    }.start();

    primaryStage.show();
}

public static void main(String[] args) {
    launch(args);
}

/**
 * Handles what happens when a key is pressed. Depending on what key is pressed,
 * the paddle will move up or down.
 * 
 * @author Phillip
 *
 */
private class KeyPressedHandler implements EventHandler<KeyEvent> {

    @Override
    public void handle(KeyEvent event) {
        switch (event.getCode()) {
        case W:
            leftPaddle.moveUp();
            break;
        case S:
            leftPaddle.moveDown();
            break;
        default:
            break;
        }

    }

}

/**
 * Handles what happens when a key is released. Depending on what key is
 * released, the paddle will stop moving.
 * 
 * @author Phillip
 *
 */
private class KeyReleasedHandler implements EventHandler<KeyEvent> {

    @Override
    public void handle(KeyEvent event) {
        switch (event.getCode()) {
        case W:
        case S:
            leftPaddle.setvY(0);
            break;
        default:
            break;
        }
    }

}

}

Here is the Paddle Class:

    public class Paddle extends Sprite {
private int points;
private String name;

private static final double PADDLE_SPEED = 2.5;

public Paddle(double x, double y, double width, double height, String name, GraphicsContext gc) {
    super(x, y, width, height, gc);
    this.points = 0;
    this.name = name;
}

@Override
public boolean didCollideWith(Sprite other) {
    Ball ball = (Ball) other;
    double ballCenterX = ball.getCenterX();
    double ballRadius = ball.getRadius();
    double ballCenterY = ball.getCenterY();

    double halfWidth = this.getHalfWidth();
    double halfHeight = this.getHalfHeight();
    double centerX = this.getCenterX();
    double centerY = this.getCenterY();

    if (getName().equals("Left")) {
        boolean hitXBounds = ballCenterX - ballRadius <= centerX + halfWidth;
        boolean hitTopPartOfBall = ballCenterY - ballRadius <= centerY + halfHeight
                && ballCenterY - ballRadius >= centerY - halfHeight;
        boolean hitBotPartOfBall = ballCenterY + ballRadius <= centerY + halfHeight
                && ballCenterY + ballRadius >= centerY - halfHeight;

        return hitXBounds && (hitTopPartOfBall || hitBotPartOfBall);
    }
    return false;
}

@Override
public void render(GraphicsContext gc) {
    gc.fillRect(getX(), getY(), getWidth(), getHeight());
}

@Override
public boolean didCollideWithWalls(Canvas canvas) {
    Bounds bounds = canvas.getBoundsInLocal();

    boolean atTopWall = this.getY() <= bounds.getMinY();
    boolean atBotWall = this.getY() >= bounds.getMaxY();

    if (atTopWall || atBotWall) {
        return true;
    }

    return false;
}

public int getPoints() {
    return points;
}

public void setPoints(int points) {
    this.points = points;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public void moveUp() {
    this.setvY(-PADDLE_SPEED);
}

public void moveDown() {
    this.setvY(PADDLE_SPEED);
}

}

Thank you!

Upvotes: 1

Views: 497

Answers (1)

James_D
James_D

Reputation: 209358

If you press, e.g. W, then press S, then release W, you call, in order,

  • setvY(-PADDLE_SPEED),
  • setvY(PADDLE_SPEED),
  • setvY(0),

which is probably not what you intend to do: it results in vY=0, even though S is still pressed.

At some point shortly after that, the native keyboard repeat will fire another press on S, so you call setvY(PADDLE_SPEED) again, and the paddle starts moving. Consider using two booleans (moveUp and moveDown) and setting them to true and false, and then updating the velocity based on both their values. E.g.

public class Paddle extends Sprite {

    private boolean moveUp ;
    private boolean moveDown ;

    // ...

    public void setMoveUp(boolean moveUp) {
        this.moveUp = moveUp ;
        updateVy();
    }

    public void setMoveDown(booleam moveDown) {
        this.moveDown = moveDown ; 
        updateVy();
    }

    private void updateVy() {
        setvY(
            (moveUp ? -PADDLE_SPEED : 0) +
            (moveDown ? PADDLE_SPEED : 0) 
        );
    }

    // ...
}

Then in your handlers do

private class KeyPressedHandler implements EventHandler<KeyEvent> {

    @Override
    public void handle(KeyEvent event) {
        switch (event.getCode()) {
        case W:
            leftPaddle.setMoveUp(true);
            break;
        case S:
            leftPaddle.setMoveDown(true);
            break;
        default:
            break;
        }

    }

}

and

private class KeyReleasedHandler implements EventHandler<KeyEvent> {

    @Override
    public void handle(KeyEvent event) {
        switch (event.getCode()) {
        case W:
            leftPaddle.setMoveUp(false);
            break ;
        case S:
            leftPaddle.setMoveDown(false);
            break;
        default:
            break;
        }

    }

}

Upvotes: 2

Related Questions