Reputation: 17351
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
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