Graphics Context will only draw in one thread - java

I am making a game of snake, but whenever I try to update my canvas in the draw() method, the new snake won't draw. It draws in the run thread. I have tried a bunch of different things, but I can't seem to get it working.
Imports:
import javafx.application.Application;
import javafx.event.*;
import javafx.scene.*;
import javafx.stage.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.geometry.*;
import java.io.*;
import java.util.*;
import javafx.scene.input.KeyEvent;
import javafx.event.EventHandler;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.scene.text.*;
import javafx.scene.paint.*;
import javafx.scene.canvas.*;
Actual class:
public class Snake extends Application implements Runnable
{
//Snake parts and locations
boolean dead;
int headX=0;
int headY=0;
// for the game
Group root = new Group();
Scene snakeG = new Scene(root, 550, 550,Color.BLACK);
final Canvas canvas = new Canvas(550,550);
GraphicsContext gc = canvas.getGraphicsContext2D();
//Start Game
VBox newGame = new VBox(3);
Scene startC = new Scene(newGame, 200, 200);
Label info = new Label("Snake Game \nCreated by: Austin");
Label rules = new Label("Rules\n1.) If you hit the edge you die\n2.) If you touch your snake you die\n3.)Collect fruit to increase your snakes size");
Button startBut = new Button("Start Game");
Stage startS;
public static void main ( String[] args )
{
launch(args);
}
#Override
public void start ( Stage primaryStage )throws Exception
{
startS=primaryStage;
startS.setTitle("Snake");
newGame.getChildren().addAll(info,rules,startBut);
newGame.setAlignment(Pos.CENTER);
startS.setScene(startC);
startS.show();
startBut.setOnAction(e ->
{
startGame();
});
}
public void startGame()
{
System.out.println("Success");
headX=275;
headY=275;
dead = false;
gc.clearRect(0,0,800,800);
startS.setScene(snakeG);
gc.setFill(Color.GREEN);
gc.fillRect(headX,headY,10,10);
root.getChildren().add(canvas);
(new Thread ( new Snake())).start();
}
public void run()
{
draw();
}
// draws the snake
public void draw()
{
System.out.println("DRAW STARTED");
gc.setFill(Color.GREEN);
gc.fillRect(50,50,10,10);
}
}
If you know of a better way to draw graphics in JavaFX, please tell me. This is the only way I could find for what I am doing.

There are some problems with your approach.
You don't need your own thread.
You can only modify the active scene graph (including a canvas) using the JavaFX Thread. Read the JavaFX concurrency documentation:
The JavaFX scene graph, which represents the graphical user interface of a JavaFX application, is not thread-safe and can only be accessed and modified from the UI thread also known as the JavaFX Application thread.
Your main application class should not implement Runnable.
Some suggestions:
You may find it easier to use SceneGraph nodes for your game objects rather than a Canvas, but either should work.
You can implement your game loop using an AnimationTimer as demoed here.
Here is a sample of using an AnimationTimer for display.

Related

Screen sharing in the ImageView of JavaFX

I am trying to build a JavaFX application, where I have a button named "Start" and an ImageView. With the robot class of JavaFX-12, I am trying to take a screenshot of the laptop screen when the button is clicked and show the images one by one during the runtime in the ImageView. My problem is that the JavaFX window does not respond and the program crashes (probably). Even putting the thread into sleep does not seem to work. I assume that it isn't working as I have not set any fps rule, but how can I do that? At the moment, I am creating writable images, converting them into a separate image with a number, saving them, and again reusing them. My goal is to create a screen sharing of the same laptop in the image view. I know that's difficult. I'm new to the JavaFx robot class (not he awt one). Any help is appreciated.
P.S.: The images are properly formed in the directory.
package sample;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Rectangle2D;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.VBox;
import javafx.scene.robot.Robot;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Hello World");
ImageView iv = new ImageView();
iv.setFitWidth(100);
iv.setFitHeight(100);
Button b = new Button("Start");
VBox v = new VBox(10);
v.getChildren().addAll(b,iv);
b.setOnAction(event -> {
Robot r = new Robot();
WritableImage wi = new WritableImage(300,300);
WritableImage i;
Rectangle2D rect = Screen.getPrimary().getVisualBounds();
while(true){
i = r.getScreenCapture(wi,rect);
try {
ImageIO.write(SwingFXUtils.fromFXImage(i,null),"png",new File("F:/Pic/pic" + x + ".png"));
iv.setImage(new Image(new FileInputStream("F:/Pic/pic" + x + ".png")));
//Thread.sleep(500);
//iv.setImage(null);
} catch (Exception e) {
System.out.println(e);
}
}
});
primaryStage.setScene(new Scene(v, 500, 500));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
JavaFX is, like most UI frameworks, single-threaded. You must never block (e.g. sleep) or otherwise monopolize (e.g. while (true)) the JavaFX Application Thread. That thread is responsible for everything related to the UI and if it's not free to do its job then the UI will become unresponsive. Note that a render pass cannot happen until the FX thread returns from whatever it's doing, so setting the image of an ImageView in a loop will have no visible effect until some time after the loop terminates.
Also, a full-throttle while loop will attempt to get a screen capture as fast as the CPU can execute said loop. That is likely to be much faster than the rate at which your UI refreshes and is thus a waste of resources. The rate of screen captures should not exceed the frame rate.
If you need to loop on the FX thread and/or be constrained by the (JavaFX's) frame rate then use the javafx.animation API. In your case, an AnimationTimer seems apt. Here's an example which continuously screenshots the primary screen:
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.StackPane;
import javafx.scene.robot.Robot;
import javafx.stage.Screen;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage primaryStage) {
ImageView view = new ImageView();
view.setFitWidth(720);
view.setFitHeight(480);
// Keep a reference to the AnimationTimer instance if
// you want to be able to start and stop it at will
new AnimationTimer() {
final Robot robot = new Robot();
final Rectangle2D bounds = Screen.getPrimary().getBounds();
#Override
public void handle(long now) {
WritableImage oldImage = (WritableImage) view.getImage();
WritableImage newImage = robot.getScreenCapture(oldImage, bounds);
if (oldImage != newImage) {
view.setImage(newImage);
}
}
}.start();
primaryStage.setScene(new Scene(new StackPane(view)));
primaryStage.show();
}
}
Some notes:
The AnimationTimer#handle(long) method is invoked once per frame. JavaFX tries to target 60 frames-per-second. If that's too fast (the above lags somewhat on my computer) then you can use the method's argument, which is the timestamp of the current frame in nanoseconds, to throttle the rate of screen captures. You could also look into using a Timeline or PauseTransition instead of an AnimationTimer. See JavaFX periodic background task for more information.
The above gives a Droste Effect (I think that's the term?) since the screen capture is displayed on the screen which is being captured.
My example does not include saving each image to a file. You seem to already understand how to do that so you should be able to easily adapt the code. It'd probably be a good idea, however, to move the I/O to a background thread. Unfortunately, that will likely require using different WritableImage instances for each capture to avoid the image being mutated by the FX thread while the background thread reads it. It may also require some tuning or dropped images; I'm not sure how well the I/O will keep up with the influx of screen captures (i.e. standard producer-consumer problems).
As an aside, your question explains you're attempting to share the entire screen. If that's the case then continue using Robot. However, if you only need to share something from the JavaFX application itself then consider using Node#snapshot(SnapshotParameters,WritableImage) or Scene#snapshot(WritableImage) (assuming you can't just send model data instead of images).

"The type must implement the inherited abstract method Application.start(Stage)" while method is both included and overridden

Java FX noob, but I've tried all I know and can find from others on the web. I've verified the JVM to be at version 1.8 and cleaned/rebuilt the entire package.
Trying to make a program that displays 3 objects one after the other, forever on an empty background, until program crash/close, within a class called Painter.
It is called/driven by a Controller class within the same package.
The methods in Painter are start(), run() and main(), the first two have errors as well as the class declaration itself.
The class declaration has the titular error.
run() is overridden but still has the complaint "must override or inherit a supertype method".
start() has a few errors. I attempted to make it implement Application directly but it still complains about it.
package anonAssignment2; //#author anon, course#
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.*;
import javafx.scene.paint.*;
import javafx.scene.canvas.*;
import javafx.scene.canvas.Canvas;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.scene.text.*;
import javafx.scene.paint.Color;
import javafx.event.EventHandler;
import javafx.event.ActionEvent;
import javafx.animation.KeyFrame;
//Handles animation timing
import javafx.util.Duration;
import java.util.Timer; // ->
import java.lang.*; //Thread.sleep(int milliseconds)
/*
* Painter- handles shape drawing
* Extends application to be within spec, called by controller
* Implements EventHandler for 3000ms changing of shape
*/
public class Painter extends Application {
public static void main(String[] args) {
Application.launch(args);
}
Painter painter = new Painter();
Pane pane = new Pane();
private Canvas canvas = new Canvas(300, 300);
GraphicsContext gc = canvas.getGraphicsContext2D();
/** Method to start continuously displaying GUI shapes/objects
*
*/
#Override
public void run() {
while(true) {
//Always true, runs until program killed/crashes
gc.setStroke(Color.RED);
gc.strokeOval(150, 150, 100, 100); //circle
java.lang.Thread.sleep(3000);
gc.clearRect(100, 100, 100, 100); //For purposes of this program, blank background, clearing rectangle works
gc.setStroke(Color.GREEN);
gc.strokeRect(100, 100, 100, 100); //Square
java.lang.Thread.sleep(3000);
gc.clearRect(100, 100, 100, 100);
gc.setStroke(Color.BLUE);
gc.strokeText("Course # and Title", 100, 140); //Text, approximately centered
java.lang.Thread.sleep(3000);
gc.clearRect(100, 100, 100, 100);
}
}
/**Start command, uses primaryStage instance
* Called by Controller
*
*/
#Override
public void start(Stage primaryStage) implements Application throws Exception {
Pane pane = new Pane();
pane.setPrefWidth(300);
pane.setPrefHeight(300);
Scene scene = new Scene(pane);
primaryStage.setTitle("Rheault Project 2");
primaryStage.setScene(scene);
primaryStage.show();
Controller instance = new Controller(painter);
//Controller constructor with argument of painter instance
}
}
//controller.java class:
//#author anon, course #
package anonAssignment2;
public class Controller extends Thread {
private Painter painter;
public Controller(Painter painter) {
this.painter = painter;
Painter.start();
}
public void run() {
while (true) {
painter.paint();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
''''
first of all copying your code into an IDE it seems like
you're trying to run an Application from a main inside..the application?
Like this:
public class Painter extends javafx.application.Application {
public static void main(String[] args) {
javafx.application.Application.launch(args);
}
I think breaking out the painter class into it's own class would be a lot better.
You could still keep this main part in its own mainclass and just break out the painter logic.
Then I looked a bit at your code:
Changing your start method like this, gives me at least no errors in the IDE:
#Override
public void start(javafx.stage.Stage primaryStage) throws Exception {
javafx.scene.layout.Pane pane = new javafx.scene.layout.Pane();
pane.setPrefWidth(300);
pane.setPrefHeight(300);
javafx.scene.Scene scene = new javafx.scene.Scene(pane);
primaryStage.setTitle("Rheault Project 2");
primaryStage.setScene(scene);
primaryStage.show();
Controller instance = new Controller(painter);
//Controller constructor with argument of painter instance
}
I skipped the Implements part since it's already clear you're overriding the superclass's method, you can even click on it in your IDE and it will go to the superclass method, to confirm it.
Finally I also looked at the run method. It doesn't overwrite anything since it doesn't exist in the superclass Application. Try clicking it and you'll see.
public void run
But maybe this is because I copied your code wrong. Well those are some of the things I found looking at your code. Hope it helps.
This appears to be an assignment so I don't know what's needed and what's not. If you don't need Threads, don't use them. If you can use Timeline, I would recommend using it. This sample app most likely doesn't do what you need it to do but hopefully, it can help.
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
*
* #author blj0011
*/
public class JavaFXApplication335 extends Application
{
#Override
public void start(Stage primaryStage)
{
Canvas canvas = new Canvas(300, 300);
GraphicsContext gc = canvas.getGraphicsContext2D();
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(3), (ActionEvent event1) -> {
gc.setStroke(Color.RED);
gc.strokeOval(150, 150, 100, 100); //circle
System.out.println("running 1");
}), new KeyFrame(Duration.seconds(6), (ActionEvent event1) -> {
gc.clearRect(100, 100, 100, 100); //For purposes of this program, blank background, clearing rectangle works
gc.setStroke(Color.GREEN);
gc.strokeRect(100, 100, 100, 100); //Square
System.out.println("running 2");
//java.lang.Thread.sleep(3000);
}), new KeyFrame(Duration.seconds(9), (ActionEvent event1) -> {
gc.clearRect(100, 100, 100, 100);
gc.setStroke(Color.BLUE);
gc.strokeText("Course # and Title", 100, 140); //Text, approximately centered
System.out.println("running 3");
//java.lang.Thread.sleep(3000);
}), new KeyFrame(Duration.seconds(12), (ActionEvent event1) -> {
gc.clearRect(100, 100, 100, 100);
System.out.println("running 4");
}));
timeline.setCycleCount(Timeline.INDEFINITE);
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction((ActionEvent event) -> {
timeline.play();
});
StackPane root = new StackPane();
root.getChildren().add(new VBox(canvas, btn));
Scene scene = new Scene(root, 500, 500);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}

JavaFX: Drawing a infinite symbol and moving along

I need to create a JavaFX application that generates a path in the form of an infinite symbol, and then create a rectangle that will move across that path.
So far I know to create a circle and square and with transitionPath to move that rectangle , but how to create an infinity shape? I'm very fresh in JavaFx (and in development as well) so please don't be harsh :)
Here is my code with Circle shape:
import javafx.animation.PathTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class PathTransitionDemo extends Application {
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
Rectangle rectangle = new Rectangle (0, 0, 25, 50);
rectangle.setFill(Color.ORANGE);
Circle circle = new Circle(125, 100, 50);
circle.setFill(Color.WHITE);
circle.setStroke(Color.BLACK);
pane.getChildren().add(circle);
pane.getChildren().add(rectangle);
PathTransition pt = new PathTransition();
pt.setDuration(Duration.millis(4000));
pt.setPath(circle);
pt.setNode(rectangle);
pt.setOrientation(
PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
pt.setCycleCount(Timeline.INDEFINITE);
pt.setAutoReverse(true);
pt.play();
circle.setOnMousePressed(e -> pt.pause());
circle.setOnMouseReleased(e -> pt.play());
Scene scene = new Scene(pane, 250, 200);
primaryStage.setTitle("PathTransitionDemo"); // Unos nayiva pozornice e
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I looked everywhere for some hint, but without luck :(
I found on the web an SVG path to draw an "infinity" shape, so replace your circle with:
SVGPath svg = new SVGPath();
svg.setFill(Color.TRANSPARENT);
svg.setStrokeWidth(1.0);
svg.setStroke(Color.BLACK);
svg.setContent("M 787.49,150 C 787.49,203.36 755.56,247.27 712.27,269.5 S 622.17,290.34 582.67,279.16 508.78,246.56 480,223.91 424.93,174.93 400,150 348.85,98.79 320,76.09 256.91,32.03 217.33,20.84 130.62,8.48 87.73,30.5 12.51,96.64 12.51,150 44.44,247.27 87.73,269.5 177.83,290.34 217.33,279.16 291.22,246.56 320,223.91 375.07,174.93 400,150 451.15,98.79 480,76.09 543.09,32.03 582.67,20.84 669.38,8.48 712.27,30.5 787.49,96.64 787.49,150 z");
and use it for drawing, transition and event catching.
You may need to adapt it to your need.
If you are looking for better "infinity" shapes, then search for "lemniscate".

Java FX Canvas doesn't show until complete

I want to create a java FX application that draws lines on a Canvas one step at a time, with a noticable time between line segments. In the below application I have what I imagined would draw a diagonal line, stall a second and then draw the next diagonal line. Instead, the FX window pops up blank, waits 2 seconds, and then shows the two diagonal lines at the same time. How do I achieve the effect I am looking for? Is javafx.scene.canvas.Canvas not the right object to be using?
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
public class FrameCanvas extends Application{
public static void main(String[] args){
launch(args);
}
#Override
public void start(Stage primaryStage)throws Exception{
////////////////////Basic FX stuff
Canvas theCanvas = new Canvas(900,900);
StackPane theLayout = new StackPane();
theLayout.getChildren().add(theCanvas);
Scene theScene = new Scene(theLayout,900,900);
primaryStage.setScene(theScene);
primaryStage.show();
///////////////////////
/////Drawing an X
///////////////////////
GraphicsContext gc = theCanvas.getGraphicsContext2D();
Thread.sleep(1000);
gc.strokeLine(0,0,200,200);
Thread.sleep(1000);
gc.strokeLine(200,0,0,200);
/////////////////////////////
}
}
Don't block (e.g. Thread.sleep(...)) the FX Application Thread. That thread is responsible for rendering the scene, so you will prevent any updates from being rendered.
Instead, use an animation for functionality like this (after all, an animation is really what you're creating here):
public void start(Stage primaryStage)throws Exception{
////////////////////Basic FX stuff
Canvas theCanvas = new Canvas(900,900);
StackPane theLayout = new StackPane();
theLayout.getChildren().add(theCanvas);
Scene theScene = new Scene(theLayout,900,900);
primaryStage.setScene(theScene);
primaryStage.show();
///////////////////////
/////Drawing an X
///////////////////////
GraphicsContext gc = theCanvas.getGraphicsContext2D();
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(1), e -> gc.strokeLine(0,0,200,200)),
new KeyFrame(Duration.seconds(2), e -> gc.strokeLine(200,0,0,200))
);
timeline.play();
/////////////////////////////
}

Javafx spawning arraylists of shapes

I need to spawn multiple shapes with different properties in javafx but when I run the program none of the shapes are being added. Any guidance in the right direction is appreciated. I would create individual objects but I need to continuously remove and recreate the objects with random properties once they go out of bounds.
package game
import java.util.ArrayList;
import javafx.animation.AnimationTimer;
import static javafx.application.Application.launch;
import javafx.scene.shape.Circle;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Game extends Application {
static ArrayList <Circle> circles = new ArrayList();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Pane root = new Pane();
Scene scene = new Scene(root,800, 600);
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
spawnCircles();
}
};
timer.start();
root.getChildren().addAll(circles);
primaryStage.setScene(scene);
primaryStage.show();
}
void spawnCircles() {
if (circles.size() < 20) {
createCircle();
}
}
void createCircle() {
Circle circle = new Circle(Math.random() * 100, Color.RED);
circle.setCenterX(Math.random()* 800);
circle.setCenterY(Math.random()* 600);
circles.add(circle);
}
}
In your start method you add an empty ArrayList to your root pane and later your AnimationTimer adds circles to your ArrayList. How do you expect them to magically show up in your root pane. Just add them to the root pane instead of the ArrayList or make this a Group instead of an ArrayList.

Categories

Resources