my apologies if this is an easy thing for you, but my mind boggles. After several years of not programming at all, I am working on a pet project (2d tile based game engine) where I would like to use Java FX headless in order to make use of the graphics capabilities.
I have understood from here and here
that you need to a Java FX Application in order to have the graphics system initialized.
So I basically took the ImageViewer example and implemented Runnable:
package net.ck.game.test;
import java.io.BufferedReader;
import java.util.ArrayList;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Logger;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class ImageTest extends Application implements Runnable {
protected static final Logger logger = (Logger) LogManager.getLogger(ImageTest.class);
BufferedReader x;
#Override public void start(#SuppressWarnings("exports") Stage stage) {
logger.error(Thread.currentThread().getName() + ", executing run() method!");
Image standardImage = new Image("file:graphics/image1.png");
logger.error("image height image1: "+ standardImage.getHeight());
logger.error("image width image1:" + standardImage.getWidth());
Image movingImage = new Image("file:graphics/image2.png");
ArrayList<Image> images = new ArrayList<Image>();
images.add(movingImage);
images.add(standardImage);
ImageView iv1 = new ImageView();
iv1.setImage(standardImage);
ImageView iv2 = new ImageView();
iv2.setImage(movingImage);
Group root = new Group();
Scene scene = new Scene(root);
scene.setFill(Color.BLACK);
HBox box = new HBox();
box.getChildren().add(iv1);
box.getChildren().add(iv2);
root.getChildren().add(box);
stage.setTitle("ImageView");
stage.setWidth(415);
stage.setHeight(200);
stage.setScene(scene);
stage.sizeToScene();
stage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void run()
{
Application.launch(ImageTest.class);
}
}
When I run this as its own application, this works fine and displays the two images I want it to display.
When I run it like this in the "game" constructor:
public class Game {
private boolean animated;
public boolean isAnimated() {
return animated;
}
public void setAnimated(boolean animated) {
this.animated = animated;
}
public Game() {
setAnimated(true);
if (isAnimated() == true)
{
ImageTest imageTest = new ImageTest();
new Thread(imageTest).start();
}
}
There are no errors, ImageTest runs in its own thread, the application window opens, but it is empty.
I do not understand this at all, why is that?
Can someone pleaese shed some light on this?
UPDATE:
I had different working contexts by accident. Fixing this fixed the problem.
UPDATE: I had different working contexts by accident. Fixing this fixed the problem.
Related
I am trying to add GUIs, created from individual files and add them into my main code.
While it seems to be working, kind of, however, it is missing some elements. For example, in my GridPane, there are a label and a text, both of which are missing. Likewise, for my treeview, there is a treeitem within, however, that is missing as well.
What I am trying to attempt is to reduce the amount of code in the main field and as well as to call relevant events between the Guis, eg. if I select something in the TreeView, that selected TreeItem information will be populated in the GridPane.
Client.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Client extends Application
{
private treeviewGui tvGui;
private gridpaneGui inputFieldsGui;
public void init()
{
tvGui = new treeviewGui();
inputFieldsGui = new gridpaneGui();
}
#Override
public void start(Stage topView)
{
topView.setTitle("Test Application");
HBox mainLayout = new HBox(10);
mainLayout.getChildren().addAll(tvGui, inputFieldsGui);
Scene scene = new Scene(mainLayout);
topView.centerOnScreen();
topView.setScene(scene);
topView.show();
}
public static void main(String[] argv)
{
launch(argv);
}
}
treeviewGui.java
import javafx.scene.control.*;
public class treeviewGui extends TreeView
{
private TreeView treeview;
public treeviewGui()
{
treeview = new TreeView();
preload();
}
private void preload()
{
TreeItem<String> newTI = new TreeItem<>("blah");
treeview.setRoot(newTI);
}
}
gridPane.java
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.geometry.Pos;
import javafx.scene.text.Text;
public class gridpaneGui extends GridPane
{
private GridPane gridPane;
public Text fnameTxt;
public gridpaneGui()
{
gridPane = new GridPane();
gridPane.setAlignment(Pos.CENTER);
gridPane.setHgap(5);
gridPane.setVgap(5);
// First Name
Label fnameLbl = new Label("First Name");
fnameTxt = new Text("-");
gridPane.addRow(0, fnameLbl, fnameTxt);
}
public void setFname(String nameStr)
{
fnameTxt.setText(nameStr);
}
}
I am creating a game in which I am checking for collisions between two ImageView objects, but when I use the intersects method it doesn't quite work the way I want it to. Score in checkCollision() method is counted to huge size and even though first image is not touching second collision is already happening and I don't know why - my program should add a +1 whenever one object touches another.
Here code sample after improvemets based on minimal reproducible example:
import javafx.animation.PathTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.scene.shape.CubicCurveTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.ArrayList;
public class TestCollision extends Application {
private Thread collisionThread;
private Scene scene;
private Pane pane;
private ImageView wolfIv;
private ArrayList<ImageView> eggsList;
private int score;
#Override
public void start(Stage primaryStage) throws Exception {
pane = new Pane();
scene = new Scene(pane,800, 600);
Image wolf = new Image("/images/wolf.png");
wolfIv = new ImageView(wolf);
eggsList = new ArrayList<>();
eggsList.add(new ImageView(new Image(getClass().getResourceAsStream("/images/egg.png"))));
eggsList.get(0).setFitHeight(45);
eggsList.get(0).setFitWidth(35);
pane.getChildren().add(eggsList.get(0));
MoveTo moveToEgg = new MoveTo();
moveToEgg.setX(60.0f);
moveToEgg.setY(95.0f);
Path eggPath = new Path();
eggPath.getElements().add(moveToEgg);
eggPath.getElements().add(new CubicCurveTo(190,200,190, 200,190,480));
PathTransition pathTransition = new PathTransition();
pathTransition.setDuration(Duration.millis(4000));
pathTransition.setNode(eggsList.get(0));
pathTransition.setPath(eggPath);
pathTransition.setCycleCount(PathTransition.INDEFINITE);
pathTransition.setAutoReverse(false);
pathTransition.play();
wolfIv.setFitWidth(200);
wolfIv.setFitHeight(250);
wolfIv.setX(140);
wolfIv.setY(218);
pane.getChildren().addAll(wolfIv);
primaryStage.setResizable(false);
primaryStage.setScene(scene);
primaryStage.show();
collisionCheckThread();
}
public void checkCollision(ImageView imageView, ImageView imageView2){
if(imageView.getBoundsInParent().intersects(imageView2.getBoundsInParent())){
score++;
System.out.println(score);
System.out.println("Boom");
}
}
public void collisionCheckThread()
{
collisionThread = new Thread(){
#Override
public void run(){
while(scene.getWindow().isShowing() == true){
checkCollision(wolfIv,eggsList.get(0));
}
}
};
collisionThread.start();
}
public static void main(String[] args) {
launch(args);
}
}
The reason the score "becomes huge" is that the background thread is repeatedly checking for collisions as fast and as often as it can. Any time it checks and the bounds intersect, it adds one to the score.
The reason you see incorrect results sometimes, such as a collision being detected immediately when none should happen, is more complex. This occurs because values (in particular the boundsInParent of the two image views) are being changed on one thread (the FX Application thread) and observed on another thread (your background thread) without proper synchronization. JavaFX, like most UI toolkits, is designed as a single-threaded toolkit, and so there is no way to add synchronization to this.
What actually happens here is due to something called "hoisting". The code in your background thread is essentially
public void run(){
while(scene.getWindow().isShowing() == true){
if(imageView.getBoundsInParent().intersects(imageView2.getBoundsInParent())){
score++;
System.out.println(score);
System.out.println("Boom");
}
}
}
It is legal, per the Java Language Specification, for a JVM to optimize code in certain ways. For variables that are not declared volatile, and without synchronization, the JVM is allowed to reorder code assuming that each thread is independent of each other. Under this assumption, since there are no changes to the boundsInParent made in this thread, the code can be treated as being equivalent to
public void run(){
if(imageView.getBoundsInParent().intersects(imageView2.getBoundsInParent())){
while(scene.getWindow().isShowing() == true){
score++;
System.out.println(score);
System.out.println("Boom");
}
}
}
Furthermore, this optimization may be made at an arbitrary time (when the JVM decides it may be beneficial), so if you are accessing the boundsInParent from a thread other than the FX Application Thread, the behavior of this code becomes essentially non-deterministic.
For more information, I recommend reading the relevant items in Joshua Bloch's book Effective Java. (No one who has spent more than an hour programming in Java should be without this book.)
Using a background thread here is completely the wrong approach anyway.
What you actually want to do, I assume, is add one to the score (and perhaps perform other actions) when the state of the images changes from "not intersecting" to "intersecting". You can do this by creating a BooleanBinding with the correct value, and which is bound to the two boundsInParent properties. Then register a listener with that binding and react when it changes from false to true:
BooleanBinding collision = Bindings.createBooleanBinding(
() -> wolfIv.getBoundsInParent().intersects(eggsList.get(0).getBoundsInParent()),
wolfIv.boundsInParentProperty(),
eggsList.get(0).boundsInParentProperty()
);
collision.addListener((obs, wasCollision, isNowCollision) -> {
if (isNowCollision) {
score++;
System.out.println(score);
System.out.println("Boom");
}
});
Here's a complete runnable example which demonstrates this:
import java.util.ArrayList;
import javafx.animation.PathTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.CubicCurveTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TestCollision extends Application {
private Scene scene;
private Pane pane;
private ImageView wolfIv;
private ArrayList<ImageView> eggsList;
private int score;
#Override
public void start(Stage primaryStage) throws Exception {
pane = new Pane();
scene = new Scene(pane,800, 600);
WritableImage wolf = new WritableImage(1, 1);
wolf.getPixelWriter().setColor(0, 0, Color.RED);
wolfIv = new ImageView(wolf);
eggsList = new ArrayList<>();
WritableImage egg = new WritableImage(1, 1);
egg.getPixelWriter().setColor(0, 0, Color.YELLOW);
eggsList.add(new ImageView(egg));
BooleanBinding collision = Bindings.createBooleanBinding(
() -> wolfIv.getBoundsInParent().intersects(eggsList.get(0).getBoundsInParent()),
wolfIv.boundsInParentProperty(),
eggsList.get(0).boundsInParentProperty()
);
collision.addListener((obs, wasCollision, isNowCollision) -> {
if (isNowCollision) {
score++;
System.out.println(score);
System.out.println("Boom");
}
});
eggsList.get(0).setFitHeight(45);
eggsList.get(0).setFitWidth(35);
pane.getChildren().add(eggsList.get(0));
MoveTo moveToEgg = new MoveTo();
moveToEgg.setX(60.0f);
moveToEgg.setY(95.0f);
Path eggPath = new Path();
eggPath.getElements().add(moveToEgg);
eggPath.getElements().add(new CubicCurveTo(190,200,190, 200,190,480));
PathTransition pathTransition = new PathTransition();
pathTransition.setDuration(Duration.millis(4000));
pathTransition.setNode(eggsList.get(0));
pathTransition.setPath(eggPath);
pathTransition.setCycleCount(PathTransition.INDEFINITE);
pathTransition.setAutoReverse(false);
pathTransition.play();
wolfIv.setFitWidth(200);
wolfIv.setFitHeight(250);
wolfIv.setX(140);
wolfIv.setY(218);
pane.getChildren().addAll(wolfIv);
primaryStage.setResizable(false);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
My goal is to create a basic 2D game using the Java Fx library. And my subgoal is to create responsive controls and smooth movement of the character. My issue is the player in my game (ImageView node) is a little jerky when I use setTranslate on it. It seems to jerk for every second of continuous movement. Included in this post is an example. I've done research and I've been given conflicting answers... For instance, one person told me to use a WritableImage(because he said it's like BufferedImage), but that didn't seem to solve the jerkyness issue. Any help would be most appreciated.
UPDATE: Thank you for the responses. I found out what the issue was. I use a program called f.lux that automatically adjusts screen brightness based on the time of day and it was drastically affecting the game's performance for some reason.
package pleasehelp;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class PleaseHelp extends Application {
ImageView imageView;
public void start(Stage stage) {
Pane root = new Pane();
Scene scene = new Scene(root, 800, 200);
stage.setTitle("Please Help!");
stage.setScene(scene);
Animation animation = new Animation(this);
Image image = new Image("http://lessonpix.com/drawings/192/100x100/Gray+Square.png");
imageView = new ImageView();
imageView.setImage(image);
root.getChildren().add(imageView);
animation.start();
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
package pleasehelp;
import javafx.animation.AnimationTimer;
public class Animation extends AnimationTimer {
PleaseHelp please;
Animation(PleaseHelp please) {
this.please = please;
}
#Override
public void handle(long now) {
please.imageView.setTranslateX(please.imageView.getTranslateX() + 1);
}
public void start() {
super.start();
}
public void stop() {
super.stop();
}
}
When writing animations like this it is normally a good idea to compute the next position based on the given time (long now) and not just add a constant value to the current position. That would make your animation more predictive.
Instead of giving a fixed value as a target is there any way to continuously change the keyvalues's target value while the animation is running.
To achieve this goal I have bound the target value with a node's width property which changes continuously.But bind is not working at all when the animation starts the target value doesn't update and stuck.
This is the code for the animation
public void setGlowAnimation(){
System.out.println("WIDTH "+width.getValue());
KeyValue value = new KeyValue(glowshape.centerXProperty(),width.getValue(),Interpolator.EASE_OUT);
KeyFrame keyframe1 = new KeyFrame(Duration.millis(2000),value);
glow_timeline = new Timeline();
glow_timeline.setCycleCount(Timeline.INDEFINITE);
glow_timeline.setAutoReverse(true);
glow_timeline.getKeyFrames().add(keyframe1);
}
public void init(){
indetermination();
setStartAnimation();
createEllipse();
width = new SimpleDoubleProperty();
width.bind(this.widthProperty());
setGlowAnimation();
}
I don't think you can modify a Timeline while it is active like that. Consider using a Transition instead:
import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.Transition;
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;
import javafx.util.Duration;
public class AdaptiveAnimation extends Application {
#Override
public void start(Stage primaryStage) {
Circle circle = new Circle(0, 100, 25, Color.CORNFLOWERBLUE);
Pane pane = new Pane(circle);
Interpolator interp = Interpolator.EASE_BOTH ;
Transition transition = new Transition() {
{
setCycleDuration(Duration.millis(2000));
}
#Override
protected void interpolate(double frac) {
double x = interp.interpolate(0, pane.getWidth(), frac);
circle.setCenterX(x);
}
};
transition.setCycleCount(Animation.INDEFINITE);
transition.setAutoReverse(true);
Scene scene = new Scene(pane, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
transition.play();
}
public static void main(String[] args) {
launch(args);
}
}
This is a little jumpy while you're resizing the window, but it provides the basic idea.
I've recently started playing around with JavaFX :) I'm working on a little pet project and one of the issues I'm currently having is animating the resize of a VBox when a child is added or removed to/from the VBox.
I've got "the kids" fading out and then being removed from the VBox already. Once that animation completes I need the VBox height to resize preferably as an animation and not like the instant change it currently does.
The other threads that I've found are similar but I think they aren't quite exactly what I'm looking for.
Animation upon layout changes
Adding Node's animated to a VBox
Main Class:
package application;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.KeyCombination;
import javafx.scene.paint.Color;
import application.notifier.*;
public class Main extends Application
{
#Override
public void start(Stage stage)
{
try
{
stage.setTitle("Test");
Group root = new Group();
Scene scene = new Scene(root, (Screen.getPrimary().getBounds().getWidth()-100), (Screen.getPrimary().getBounds().getHeight()-100), Color.WHITE);
Notifier notice = new Notifier();
Notifier.addNotice("Testing Add Notice");
Notifier.addNotice("Testing Add Notice again!");
root.getChildren().add(Notifier.container);
stage.setFullScreen(true);
stage.setScene(scene);
stage.setFullScreenExitHint("");
//stage.setFullScreenExitKeyCombination(KeyCombination.NO_MATCH);
stage.show();
Button test = new Button("Remove");
test.setLayoutX(500.0);
test.setLayoutY(500.0);
test.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
Notifier.removeNotice();
}
});
root.getChildren().add(test);
}
catch(Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
launch(args);
}
}
Notifier Class:
import java.util.ArrayList;
import javafx.animation.FadeTransition;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.stage.Screen;
import javafx.util.Duration;
public class Notifier
{
private static int maxVisibleNotices = 5;
private static int currentVisible = 0;
private static ArrayList<String> msgOverflow;
public static VBox container;
public Notifier()
{
BackgroundFill bkgrndFill = new BackgroundFill(Color.rgb(0, 0, 0, .65), new CornerRadii(10.0), new Insets(0));
Background bkgrnd = new Background(bkgrndFill);
Notifier.container = new VBox();
Notifier.container.backgroundProperty().set(bkgrnd);
Notifier.container.setAlignment(Pos.TOP_CENTER);
Notifier.container.setMinWidth(Screen.getPrimary().getBounds().getWidth() - 50);
Notifier.container.setMaxWidth(Screen.getPrimary().getBounds().getWidth() - 50);
Notifier.container.setLayoutX((Screen.getPrimary().getBounds().getWidth() - (Screen.getPrimary().getBounds().getWidth() - 50))/2);
Notifier.container.setLayoutY(5.0);
Notifier.container.setSpacing(5.0);
Notifier.container.setPadding(new Insets(5.0));
Notifier.msgOverflow = new ArrayList<String>();
}
public static void addNotice(String msg)
{
if(Notifier.currentVisible < Notifier.maxVisibleNotices)
{
Text txt = new Text(msg);
txt.setFill(Color.rgb(255,255,255));
Notifier.container.getChildren().add(txt);
Notifier.currentVisible++;
}
else
{
Notifier.msgOverflow.add(msg);
}
}
public static void removeNotice()
{
if(Notifier.currentVisible > 0)
{
FadeTransition ft = new FadeTransition(Duration.millis(1000), Notifier.container.getChildren().get(0));
ft.setFromValue(1.0);
ft.setToValue(0.0);
ft.setCycleCount(0);
ft.setAutoReverse(false);
ft.play();
ft.setOnFinished(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
Notifier.container.getChildren().remove(0);
Notifier.currentVisible--;
}
});
}
}
}
I hope this is clear enough.
And thanks in advance for help or suggestions.
Probably not very helpful any more. I am currently working on the same thing.
You just need:
1. Add the same animation(if they all supposed to be same) to each of the remaining children in the VBox.
2. Use ParallelAnimation to make them run at the same time.
3. Use again a SequentialAnimation for you "kids" fading out animation and the parallel animation. This ensures they will not happen concurrently since multiple threads could run simultaneously.
4. To reach still the same view as what the instant update does, use animation/timeline.setonFinished(EventHandler()) to updates the screen