currently stuck with a problem in JavaFX, trying to create a cookie clicker game, so i've created a button "Clicker" which automatically, once every 5 seconds, clicks the cookie. Im trying to make my timeline 0.1 seconds faster everytime i buy another clicker, currently, i've been trying it like this:
private double clickerSpeed = 5.1;
Timeline clickerPoints = new Timeline();
KeyFrame kfClicker = new KeyFrame(Duration.seconds(clickerSpeed),
event -> {
cookieClicker.cookies += cookieClicker.cursor.getProdRate();
cookieButton.setText("Cookies = " + cookieClicker.cookies);
});
clickerPoints.getKeyFrames().add(kfClicker);
clickerPoints.setCycleCount(Timeline.INDEFINITE);
buyClicker.setOnAction(event -> {
if (clickerSpeed >= 1 && cookieClicker.cookies >=
cookieClicker.clicker.getCosts()) {
clickerSpeed -= 0.1; ## In theory, this should make the keyframe faster?
}
cookieClicker.buyClicker();
clickerLabel.setText(cookieClicker.clicker.getName() + "s: " +
cookieClicker.clicker.getLevel());
buyClicker.setText("Buy clicker for: " +
cookieClicker.clicker.getCosts());
cookieButton.setText("Cookies = " + cookieClicker.cookies);
clickerPoints.play();
Modifying a primitive double does not have any effect on values read before; the duration is not automatically adjusted.
To speed up the Timeline you should modify the rate property instead.
The following example shows how to update the duration an animation takes to animate fading a Rectangle from 10 seconds to 1 second in 1 second steps:
#Override
public void start(Stage primaryStage) throws Exception {
Rectangle rect = new Rectangle(100, 100);
Button btn = new Button("Speed Up");
HBox root = new HBox(rect, btn);
Timeline timeline = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(rect.opacityProperty(), 1d)),
new KeyFrame(Duration.seconds(10), new KeyValue(rect.opacityProperty(), 0d))
);
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
btn.setOnAction(new EventHandler<ActionEvent>() {
private double duration = timeline.getCycleDuration().toSeconds();
#Override
public void handle(ActionEvent event) {
if (duration > 1) {
duration--;
timeline.setRate(timeline.getCycleDuration().toSeconds() / duration);
}
}
});
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
Related
I need some help figuring out how to implement pause functionality into my program.
I'm am creating a timer that I want to be able to pause and play from the same point in time.
Expected outcome: After the pauseButton has been pressed, the startButton picks up right where the pause button left off and displays the same time.
Current outcome: After the pauseButton has been pressed, pressing the startButton starts the timer 2 units less than where the pause button left off. So if the time was paused at 10:58, the startButton picks up the time at 8:56.
Another issue I'm having is that after the pauseButton has been pressed, each iteration of the time change decrements the secondTime timeline by 2 instead of 1 and the minuteTime timeline never changes.
Any help is greatly appreciated. Thank you
// Constant values for time
private final Integer START_TIME_MINUTES = 4;
private final Integer START_TIME_SECONDS = 5;
private final Integer START_TIME_MINUTES = 11;
private final Integer START_TIME_SECONDS = 60;
// Modifiable copies of constants to manipulate
private Integer minutes = START_TIME_MINUTES;
private Integer seconds = START_TIME_SECONDS;
private Integer secondsCopy = START_TIME_SECONDS;
// Creating format in which integers are to be displayed
private final NumberFormat formatter = new DecimalFormat("00");
// Storing string value copies of constants in labels
private final Label minuteLabel = new Label(minutes.toString());
private final Label secondLabel = new Label(seconds.toString());
private final Label secondLabel = new Label(formatter.format(seconds));
// Creating a colon label to add to layout
private final Label colonLabel = new Label(":");
// Creating a label made up of copies of constants
private final Label countDownLabel = new Label("Countdown-> " + minuteLabel.getText() + ":" + secondLabel.getText());
private Timeline minuteTime = new Timeline();
private Timeline secondTime = new Timeline();
private Button pauseButton = new Button("Pause");
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Timer Example"); // Title
System.out.println(countDownLabel);
Button startButton = new Button("Start" ); // Button
startButton.setOnAction(actionEvent -> { // Event Handler
doTime(); // Calling doTime() function
});
startButton.setOnAction(actionEvent -> { doTime(); });
pauseButton.setOnAction(actionEvent -> { pauseTime(); });
HBox layout = new HBox(startButton, minuteLabel, colonLabel, secondLabel); // Layout
// Changing display of seconds when minute mark reached
if (seconds == 60)
secondLabel.setText("00");
HBox layout = new HBox(startButton, pauseButton, minuteLabel, colonLabel, secondLabel); // Layout
Scene myScene = new Scene(layout, 200, 100); // Adding layout to scene
primaryStage.setScene(myScene); // Setting scene to stage
primaryStage.show(); // Displaying stage
}
private void pauseTime(){
minuteTime.pause();
secondTime.pause();
}
private void doTime() {
Timeline minuteTime = new Timeline();
// Creating a minute timeline
minuteTime.setCycleCount(Timeline.INDEFINITE);
Timeline secondTime = new Timeline();
// Creating a seconds timeline
secondTime.setCycleCount(Timeline.INDEFINITE);
if (minuteTime != null && secondTime != null) {}
minuteTime.stop();
KeyFrame minuteFrame = new KeyFrame(Duration.seconds(5), actionEvent -> {
minutes--;
minuteLabel.setText(minutes.toString());
if (minutes <= 0) {
minuteTime.stop();
Alert minutesAlert = new Alert(Alert.AlertType.INFORMATION);
minutesAlert.setHeaderText("Minutes KeyFrame Ended");
minutesAlert.show();
}
});
if (pauseButton.isPressed()){
minuteTime.playFrom(minuteTime.getCurrentTime());
secondTime.playFrom(secondTime.getCurrentTime());
} else {
// KeyFrame to decrement minuteTime every minute
KeyFrame minuteFrame = new KeyFrame(Duration.seconds(60), actionEvent -> {
minutes--;
minuteLabel.setText(minutes.toString()); // Modifying minute label
if (minutes <= 0)
minuteTime.stop();
});
KeyFrame secondFrame = new KeyFrame(Duration.seconds(1), actionEvent -> {
seconds--;
secondLabel.setText(seconds.toString());
// continues seconds timer until minute timer is up
if (seconds <= 0 && minuteTime.getStatus() == Animation.Status.STOPPED) {
// KeyFrame to decrement secondTime every second
KeyFrame secondFrame = new KeyFrame(Duration.seconds(1), actionEvent -> {
seconds--; // decrement seconds
secondLabel.setText(formatter.format(seconds)); // modify seconds label
// stops secondTime if seconds reaches 0 and minuteTime stopeed
if (seconds <= 0 && minuteTime.getStatus() == Animation.Status.STOPPED)
secondTime.stop();
Alert secondsAlert = new Alert(Alert.AlertType.INFORMATION);
secondsAlert.setHeaderText("Seconds KeyFrame Ended");
secondsAlert.show();
// if secondTime reaches 0, but minuteTime is still running, reset seconds
if (seconds <= 0 && minuteTime.getStatus() == Animation.Status.RUNNING)
seconds = START_TIME_SECONDS;
}
// Changes seconds label to 00 when minute mark is reaches
if (seconds == 60)
secondLabel.setText("00");
});
if (seconds <= 0 && minuteTime.getStatus() == Animation.Status.RUNNING){
seconds = START_TIME_SECONDS;
}
});
minuteTime.getKeyFrames().add(minuteFrame);
minuteTime.playFromStart(); // execute the timeline
secondTime.getKeyFrames().add(secondFrame);
secondTime.playFromStart();
minuteTime.getKeyFrames().add(minuteFrame); // Adding frame to timeline
minuteTime.playFrom(Duration.seconds(59)); // execute the timeline
secondTime.getKeyFrames().add(secondFrame);
secondTime.playFromStart();
}
}`
So, for my class project I am making a game. I want to add a volume slider to change the music volume. I am very stuck here. Please help me.
I was following tutorial, and I was able to display it on the scree. But, the volume doesn't change, even though I slide the bar. Can somebody give me advice? Thank you.
private Scene createOptionScene() {
String path = "E:\\All Computer Science Materials\\Java 240 Project\\PrinceFX\\Music\\"
+ songs.getSong(1) + ".mp3";
Media media = new Media(new File(path).toURI().toString());
currentPlay = new AudioClip(media.getSource());
currentPlay.setCycleCount(MediaPlayer.INDEFINITE);
currentPlay.play();
// Volume Control
volumeSlider.setValue(currentPlay.getVolume() * 100);
volumeSlider.valueProperty().addListener(new InvalidationListener() {
#Override
public void invalidated(Observable observable) {
currentPlay.setVolume(volumeSlider.getValue() / 100);
}
});
HBox temp = new HBox();
temp.getChildren().addAll(volumeSlider);
temp.setTranslateX(850);
temp.setTranslateY(410);
volumeSlider.setMinWidth(300);
Image image = new Image(new File("E:\\All Computer Science Materials\\" +
"Java 240 Project\\PrinceFX\\image\\" + picture.getImage(2) + ".png").toURI().toString());
//Setting the image view
ImageView imageView = new ImageView(image);
//Setting the position of the image
imageView.setX(0);
imageView.setY(0);
//setting the fit height and width of the image view
imageView.setFitHeight(primaryScreenBounds.getHeight());
imageView.setFitWidth(primaryScreenBounds.getWidth());
//Setting the preserve ratio of the image view
imageView.setPreserveRatio(true);
Label labelOption = new Label();
Button goBack = new Button("Go Back to Main");
goBack.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
currentPlay.stop();
sceneStart = createStartScene();
stageOne.setScene(sceneStart);
}
});
// Button to the main page.
HBox layoutOp = new HBox(15);
layoutOp.getChildren().addAll(labelOption, goBack);
// Button coordinate.
layoutOp.setTranslateX(1400);
layoutOp.setTranslateY(750);
Group gOption = new Group(imageView, layoutOp, temp);
return new Scene(gOption, 200, 200);
}
I am designing a UI of a graph structure with draggable nodes. In the graph I have a component called relation(it is a pane) which shows the link between two nodes.
I want relation to stay and move along with line at mid of line.
Current UI design is as shown below
And the expected one is like:
You need to refresh the position of the node when the line's end coordinates are modified. To avoid triggering the calculation multiple times per layout pass, I recommend doing this from the layoutChildren method of the parent, but you could also do this from a listener to the startX, endY, ... properties. This will lead to some unnecessary computations though.
As for calcualting the position of the node: The center of the node needs to align with the midpoint of the line, so you need to solve the following equation for markTopLeft:
markTopLeft + (markWidth, markHeight) / 2 = (lineStart + lineEnd) / 2
markTopLeft = (lineStart + lineEnd - (markWidth, markHeight)) / 2
Example
Pane allowing for custom layout calculations
public class PostProcessPane extends Pane {
private final Set<Node> modifiedChildren = new HashSet<>();
private final Set<Node> modifiedChildrenUnmodifiable = Collections.unmodifiableSet(modifiedChildren);
private final List<Consumer<Set<Node>>> postProcessors = new ArrayList<>();
public List<Consumer<Set<Node>>> getPostProcessors() {
return postProcessors;
}
private final ChangeListener listener = (o, oldValue, newValue) -> modifiedChildren.add((Node) ((ReadOnlyProperty) o).getBean());
private void initListener() {
getChildren().addListener((ListChangeListener.Change<? extends Node> c) -> {
while (c.next()) {
if (c.wasRemoved()) {
for (Node n : c.getRemoved()) {
n.boundsInParentProperty().removeListener(listener);
}
}
if (c.wasAdded()) {
for (Node n : c.getAddedSubList()) {
n.boundsInParentProperty().addListener(listener);
}
}
}
});
}
public PostProcessPane() {
initListener();
}
public PostProcessPane(Node... children) {
super(children);
initListener();
for (Node n : children) {
n.boundsInParentProperty().addListener(listener);
}
}
#Override
protected void layoutChildren() {
super.layoutChildren();
if (!modifiedChildren.isEmpty()) {
for (Consumer<Set<Node>> processor : postProcessors) {
processor.accept(modifiedChildrenUnmodifiable);
}
modifiedChildren.clear();
}
}
}
Usage
#Override
public void start(Stage primaryStage) throws Exception {
Rectangle r1 = new Rectangle(200, 50, Color.BLUE);
Rectangle r2 = new Rectangle(200, 50, Color.RED);
Rectangle mark = new Rectangle(200, 50, Color.YELLOW);
Line line = new Line();
r1.setX(20);
r2.setX(380);
r2.setY(450);
PostProcessPane root = new PostProcessPane(line, r1, r2, mark);
root.getPostProcessors().add(changedNodes -> {
if (changedNodes.contains(r1) || changedNodes.contains(r2) || changedNodes.contains(mark)) {
Bounds bounds1 = r1.getBoundsInParent();
Bounds bounds2 = r2.getBoundsInParent();
// refresh line ends
line.setStartX(bounds1.getMinX() + bounds1.getWidth() / 2);
line.setStartY(bounds1.getMaxY());
line.setEndX(bounds2.getMinX() + bounds2.getWidth() / 2);
line.setEndY(bounds2.getMinY());
// recalculate mark position
mark.setX((line.getStartX() + line.getEndX() - mark.getWidth()) / 2);
mark.setY((line.getStartY() + line.getEndY() - mark.getHeight()) / 2);
}
});
// add some movement for the nodes
Timeline timeline = new Timeline(
new KeyFrame(Duration.ZERO,
new KeyValue(r1.xProperty(), r1.getX()),
new KeyValue(r1.yProperty(), r1.getY()),
new KeyValue(r2.xProperty(), r2.getX())),
new KeyFrame(Duration.seconds(1),
new KeyValue(r2.xProperty(), r1.getX())),
new KeyFrame(Duration.seconds(2),
new KeyValue(r1.xProperty(), r2.getX()),
new KeyValue(r1.yProperty(), r2.getY() / 2))
);
timeline.setAutoReverse(true);
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
I am trying to figure out how to make the label update and display the current time like the analog clock is thank you in advance. For some reason this site won't let me post unless I make a long drawn out explanation that is full of words. Either that or you have to be much smarter than I am in order to use the site. I hope this is wordy enough.
public class Homework7 extends Application {
#Override // Override the start method in the Application class
public void start(Stage primaryStage) {
// Create a clock and a label
ClockPane clock = new ClockPane();
String timeString = clock.getHour() + ":" + clock.getMinute()
+ ":" + clock.getSecond();
Label lblCurrentTime = new Label(timeString);
// Place clock and label in border pane
BorderPane pane = new BorderPane();
pane.setCenter(clock);
pane.setBottom(lblCurrentTime);
BorderPane.setAlignment(lblCurrentTime, Pos.TOP_CENTER);
// Create a scene and place it in the stage
Scene scene = new Scene(pane, 250, 250);
primaryStage.setTitle("Christian Beckman"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
// Create a handler for animation
EventHandler<ActionEvent> eventHandler = e -> {
clock.setCurrentTime(); // Set a new clock time
};
// Create an animation for a running clock
Timeline animation = new Timeline(
new KeyFrame(Duration.millis(1000), eventHandler));
animation.setCycleCount(Timeline.INDEFINITE);
animation.play(); // Start animation
// Create a scene and place it in the stage
// Scene scene = new Scene(clock, 250, 250);
primaryStage.setTitle("Christian Beckman"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
}
}
Just update the Label text after updating the clock:
...
EventHandler<ActionEvent> eventHandler = e -> {
clock.setCurrentTime(); // Set a new clock time
update(lblCurrentTime, clock);
};
...
private static void update(Label label, Clock clock) {
String text = ...;
label.setText(text);
}
If the ClockPane class returns correctly formatted Strings (e.g. "07" for seconds = 7) simply compute the text as in the initialization of the Label text:
String text = clock.getHour() + ":" + clock.getMinute() + ":" + clock.getSecond();
If those methods return ints, you could use a DateTimeFormatter:
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
String text = LocalTime.of(clock.getHour(), clock.getMinute(), clock.getSecond()).format(FORMATTER);
Is there a special way (some kind of event/transition queue?) in JavaFX with which you can play animations/transitions in sequence?
In a solitaire game at the end the cards are usually already in sequence (K->-Q->J-10->-9->...) and I want to finish them automatically. So first card 9 has to move from the tableau to the foundation, then 10, then J, etc.
I'd like to do the path transition in a transition queue, rather than all at once.
I can't use a SequentialTranstition because you can't modify its children once the animation plays.
public final ObservableList getChildren()
A list of Animations that will be played sequentially.
It is not possible to change the children of a running
SequentialTransition. If the children are changed for a running
SequentialTransition, the animation has to be stopped and started
again to pick up the new value.
Verifiable with this minimal example:
public class PathTransitionDemo extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Group root = new Group();
List<Rectangle> rectangles = new ArrayList<>();
double w = 50;
double h = 50;
double spacing = 10;
for( int i=0; i < 10; i++) {
Rectangle rectangle = new Rectangle ( spacing * (i+1) + w * i, 50, w, h);
rectangle.setStroke( Color.BLUE);
rectangle.setFill( Color.BLUE.deriveColor(1, 1, 1, 0.2));
rectangles.add( rectangle);
}
root.getChildren().addAll( rectangles);
primaryStage.setScene(new Scene(root, 1024, 768));
primaryStage.show();
// transition
SequentialTransition seq = new SequentialTransition();
for( Rectangle rectangle: rectangles) {
Path path = new Path();
path.getElements().add (new MoveTo ( rectangle.getX(), rectangle.getY()));
path.getElements().add (new LineTo( 900, 600));
PathTransition pathTransition = new PathTransition();
pathTransition.setDuration(Duration.millis(1000));
pathTransition.setNode(rectangle);
pathTransition.setPath(path);
//pathTransition.play();
seq.getChildren().add( pathTransition);
if( seq.getStatus() != Status.RUNNING)
seq.play();
}
// seq.play();
}
}
The sequential transition stops after it played the first path transition.
A solution would be to modfiy the setOnFinished handler of the PathTransitions. But that's ugly. And it plays only exaclty 1 transtition after the other. Let's say one card transition lasts 2000 ms, it e. g. limits the possibility to start another transition of the queue within 500ms.
The animation in the Video Wall that I created has a similar requirement. There I solved it with an AnimationTimer. Excerpt from the code:
AnimationTimer timer = new AnimationTimer() {
long last = 0;
#Override
public void handle(long now) {
if( (now - last) > 40_000_000)
{
if( transitionList.size() > 0) {
ParallelTransition t = transitionList.remove(0);
t.getNode().setVisible(true);
t.play();
}
last = now;
}
if( transitionList.size() == 0) {
stop();
}
}
};
Is there a better way to solve this in JavaFX?
Thank you very much!