Robin van Heukelum
Robin van Heukelum

Reputation: 63

JavaFX Circle must follow cursor

I am trying to build a game myself using JavaFX. It features a circle that must follow my cursor (using a smooth translation animation, so it will not go directly to the location of my cursor)

For now I have written this piece of code

    root.setOnMouseMoved(event -> {
        TranslateTransition tt = new TranslateTransition(Duration.millis(2000), circle);

        x[0] = event.getSceneX();
        y[0] = event.getSceneY();

        location.setText(x[0] + ", " + y[0]);

        if (x[0] != oldX[0] || y[0] != oldY[0]) {
            tt.stop();
            tt.setToX(event.getSceneX());
            tt.setToY(event.getSceneY());

            oldX[0] = x[0];
            oldY[0] = y[0];
        }

        tt.play();
    });

The location.setText(..) is just a label to see whether the coords are recognized by the program. And in fact they are: for each pixel my cursor moves it updated these numbers instantly.

However, my circle will only go to the location of my cursor when it does not move. I want the shape to follow my cursor on the go as well but it just won't.

So my problem is this: how can I make my circle follow my mouse, even when it is moving?

Upvotes: 1

Views: 5349

Answers (2)

Roland
Roland

Reputation: 18425

Use an AnimationTimer for the movement. You may also want to check Mike's Blog about a usage example.

Read The Nature of Code by Daniel Shiffman, especially the Chapter Vectors, 1.10 Interactivity with Acceleration. The webpage has a running example, easily to convert to JavaFX.


Here's the code from the book implemented in JavaFX:

Walker.java

import java.util.Random;

import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;

public class Walker extends Pane {

    private static Random random = new Random();

    PVector location;
    PVector velocity;
    PVector acceleration;
    float topspeed;

    double width = 30;
    double height = width;
    double centerX = width / 2.0;
    double centerY = height / 2.0;
    double radius = width / 2.0;

    Circle circle;

    public Walker() {

        location = new PVector(random.nextDouble() * width, random.nextDouble() * height, 0);
        velocity = new PVector(0, 0, 0);
        topspeed = 4;

        circle = new Circle(radius);
        circle.setCenterX(radius);
        circle.setCenterY(radius);

        circle.setStroke(Color.BLUE);
        circle.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.3));

        getChildren().add(circle);

    }

    public void step(PVector mouse) {

        PVector dir = PVector.sub(mouse, location);
        dir.normalize();
        dir.mult(0.5);
        acceleration = dir;
        velocity.add(acceleration);
        velocity.limit(topspeed);
        location.add(velocity);

    }

    public void checkBoundaries() {

        if (location.x > Settings.SCENE_WIDTH) {
            location.x = 0;
        } else if (location.x < 0) {
            location.x = Settings.SCENE_WIDTH;
        }

        if (location.y > Settings.SCENE_HEIGHT) {
            location.y = 0;
        } else if (location.y < 0) {
            location.y = Settings.SCENE_HEIGHT;
        }
    }

    public void display() {
        relocate(location.x - centerX, location.y - centerY);
    }
}

Main.java

import java.util.ArrayList;
import java.util.List;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class Main extends Application {

    Pane playfield;

    List<Walker> allWalkers = new ArrayList<>();

    PVector mouse = new PVector(0,0,0);

    @Override
    public void start(Stage primaryStage) {

        // create containers
        BorderPane root = new BorderPane();

        StackPane layerPane = new StackPane();

        // playfield for our walkers
        playfield = new Pane();

        layerPane.getChildren().addAll(playfield);

        root.setCenter(layerPane);

        Scene scene = new Scene(root, Settings.SCENE_WIDTH, Settings.SCENE_HEIGHT);
        primaryStage.setScene(scene);
        primaryStage.show();

        // add 1 walker
        addWalker();

        // capture mouse position
        scene.addEventFilter(MouseEvent.ANY, e -> {
            mouse.set(e.getX(), e.getY(), 0);
        });

        // process all walkers
        AnimationTimer loop = new AnimationTimer() {

            @Override
            public void handle(long now) {

                // move
                allWalkers.forEach((walker) -> walker.step(mouse));

                // check border
                allWalkers.forEach(Walker::checkBoundaries);

                // update in fx scene
                allWalkers.forEach(Walker::display);

            }
        };

        loop.start();

    }

    /**
     * Add single walker to list of walkers and to the playfield
     */
    private void addWalker() {

        Walker walker = new Walker();

        allWalkers.add(walker);

        playfield.getChildren().add(walker);

    }

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

Settings.java

public class Settings {

    public static double SCENE_WIDTH = 800;
    public static double SCENE_HEIGHT = 600;

}

PVector.java (you can get the full source from the Processing source code)

public class PVector {

    public double x;
    public double y;
    public double z;

    public PVector(double x, double y, double z) {
        super();
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public void normalize() {
        double m = mag(); 
        if (m != 0 && m != 1) { 
          div(m); 
        } 
    }

    public void div(double value) {
        x /= value;
        y /= value;
        z /= value;
    }

    public void mult(double value) {
        x *= value;
        y *= value;
        z *= value;
    }

    public void add(PVector v) {
        x += v.x;
        y += v.y;
        z += v.z;
    }

    public void sub(PVector v) {
        x -= v.x;
        y -= v.y;
        z -= v.z;
    }

    public void limit(float max) {
        if (mag() > max) {
            normalize();
            mult(max);
        }
    }

    public double mag() {
        return Math.sqrt(x * x + y * y + z * z);
    }

    public static PVector sub(PVector v1, PVector v2) {
        return sub(v1, v2, null);
    }

    public static PVector sub(PVector v1, PVector v2, PVector target) {
        if (target == null) {
            target = new PVector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z);
        } else {
            target.set(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z);
        }
        return target;
    }

    public void set(double x, double y, double z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}

Upvotes: 3

Robin van Heukelum
Robin van Heukelum

Reputation: 63

So using the tips Roland gave me I have created this piece of code which works just the way I want to be (like I described in my question)

   AnimationTimer timer = new AnimationTimer() {
        @Override
        public void handle(long now) {
            TranslateTransition tt = new TranslateTransition(Duration.millis(250), circle);
            Point mouse = MouseInfo.getPointerInfo().getLocation();

            x[0] = mouse.getX();
            y[0] = mouse.getY();

            location.setText(x[0] + ", " + y[0]);

            tt.setToX(x[0]);
            tt.setToY(y[0]);

            tt.play();
        }
    };

    timer.start();

The major change is the use of the AnimationTimer instead of an event. This caused me to change the way I retrieve the location of the mouse, now I use the awt.MouseInfo to get the X and Y of my cursor.

Upvotes: 0

Related Questions