Jonathan Lam
Jonathan Lam

Reputation: 17351

How to create JavaFX Transition with Equally-Timed Elements?

I'm experimenting with JavaFX and animations, especially PathTransition. I'm creating a simple program that makes a ball "bounce," without using the QuadCurveTo class. Here is my code so far:

Ellipse ball = new Ellipse(375, 250, 10, 10);
root.getChildren().add(ball);

Path path = new Path();
path.getElements().add(new MoveTo(375, 500));

int posX = 375;
int posY = 500;
int changeX = 10;
int changeY = 50;
int gravity = 10; // approximate in m/s^2
int sec = 0;

for(; posY<=500; sec++, posX-=changeX, posY-=changeY, changeY-=gravity)
    path.getElements().add(new LineTo(posX, posY));
    // How do I equally space these elements?

PathTransition pathTransition = new PathTransition();
pathTransition.setDuration(Duration.millis(sec*1000));
pathTransition.setNode(ball);
pathTransition.setAutoReverse(true);
pathTransition.setCycleCount(Timeline.INDEFINITE);
pathTransition.setInterpolator(Interpolator.LINEAR);
pathTransition.setPath(path);
pathTransition.play();

I have the for loop running through a quadratic sequence, and the ball moves in the correct motion (a curved path).

However, I want it to move slower at the top of the curve (vertex) because it is moving less distance (as changeY, the vertical increment variable, is decreasing as the loop goes on) to simulate a more realistic curve. However, it is traveling in a linear speed throughout the full time.

Is there any way to make each of the elements equally spaced (throughout) time, so that this "bounce" would show correctly? Thanks.

Upvotes: 4

Views: 724

Answers (1)

James_D
James_D

Reputation: 209339

I wouldn't use a timeline or transition at all for this. Use an AnimationTimer and compute the new coordinates based on the elapsed time since the last frame. The AnimationTimer has a handle method which is invoked once per rendering frame, taking a value that represents a timestamp in nanoseconds.

SSCCE (with elasticity added to the physics):

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;

public class BouncingBall extends Application {

    @Override
    public void start(Stage primaryStage) {
        Circle ball = new Circle(20, 80, 10);
        ball.setFill(Color.DARKBLUE);
        Pane pane = new Pane(ball);

        AnimationTimer timer = new AnimationTimer() {

            long lastUpdate = 0 ;
            double changeX = 0.1 ;
            double changeY = 0 ;
            double gravity = 10 ;
            double elasticity = 0.95 ;

            @Override
            public void handle(long now) {
                if (lastUpdate == 0) {
                    lastUpdate = now ;
                    return ;
                }
                long elapsedNanos = now - lastUpdate;
                double elapsedSeconds = elapsedNanos / 1_000_000_000.0 ;
                lastUpdate = now ;
                ball.setCenterX(ball.getCenterX() + changeX);
                if (ball.getCenterY() + changeY + ball.getRadius() >= pane.getHeight()) {
                    changeY = - changeY * elasticity;
                } else {
                    changeY = changeY + gravity * elapsedSeconds ;
                }
                ball.setCenterY(Math.min(ball.getCenterY() + changeY, pane.getHeight() - ball.getRadius()));
            }

        };

        primaryStage.setScene(new Scene(pane, 400, 400));
        primaryStage.show();
        timer.start();
    }

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

Upvotes: 4

Related Questions