Moving an Image on Canvas with KeyPressed - java

My KeyPressed is working but there is no change in avatarDX from my moveLeft method. Did I do something wrong with moveLeft? It seems like it should be simple but I'm not sure what I did wrong. I don't have any error messages.
I'm drawing an image on a canvas with
gc.drawImage(avatar, avatarSX, avatarSY, avatarSW, avatarSH, avatarDX, avatarDY, avatarDW, avatarDH);
For KeyPressed I have
canvas.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent ke) {
double speed = 2;
switch(ke.getCode()) {
case A:
System.out.println("pressed a");
moveLeft(avatarDX, speed);
System.out.println(avatarDX);
break;
}
}
});
And moveLeft is
private void moveLeft(double avatarDX, double speed) {
while (avatarDX > 0) {
avatarDX -= speed;
}
}
I would appreciate any help I am very new at this.

A reason (there may be others) the program doesn't act as you expect is that you are trying to process via a while loop to control your avatar.
This will just pause the JavaFX application until the loop is completed and then update the screen to the final state at the end of the loop. JavaFX is not an immediate drawing system, it is a retained drawing system that only renders a scene when you cede control of the JavaFX application thread back to the JavaFX system.
Instead, you need to use a callback to update the scene on each pulse.
I suggest you use the following approach:
As you are new at this, try writing the application using the JavaFX scene graph rather than a Canvas. Programming the scene graph is simply easier for many things. If, at a later stage, you find that Canvas is a better fit, then you can always convert to Canvas at that time when you better understand the JavaFX programming model.
Review this sample of moving a character around the scene graph using key presses.
Don't try to loop to move your character. Instead, either use the in-built high level JavaFX animation facilities such as transitions, or (when those aren't a good fit, as is likely the case here), use the low level AnimationTimer interface.
Read up on game loops and JavaFX and apply the knowledge you learn there to your program.

Related

Changing canvas color in runtime JavaFX

So I was experimenting with javaFX a bit since it is a part of one of my subjects.
I have a catapult that shoots a ball in certain direction. I was wondering if you could somehow change the color of a canvas background in a runtime on a certain occasion - when a ball hits the wall for example.
I already figured out how to make the ball bounce of the wall, but I cannot figure out how to change the bg color in runtime.
I'm using import javafx.scene.canvas.GraphicsContext; as it is what we kinda "have" to work with.
I thought something like this would work, I found some threads about sleep so I gave it a try.
public void flashCanvas() {
try
{
gc.setFill(Color.WHITESMOKE);
Thread.sleep(100);
gc.setFill(Color.BLACK);
Thread.sleep(100);
}
catch(InterruptedException ex)
{
Thread.currentThread().interrupt();
}
}
I thought that this would just change the color, then wait and over again.
Thanks for help!
Short Answer
You can make your desired behaviour using this part of code
Runnable collide = new Runnable() {
#Override
public void run() {
Platform.runLater(() ->
damageEffect()
);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Platform.runLater(() ->
resetEffect()
);
}
};
new Thread(collide).start();
Explanation
you can't use Thread.Sleep() on the main thread that will cause your UI to freeze so you need to make it on another thread and for more safety when changing anything in the UI/Main thread you should use Platform.runLater()
you can write whatever the effect in these function boomEffect() and resetEffect()
if you want to change the canvas fill color you can use the code provided by #Barudar in the comment like this
private void resetEffect() {
canvas.getGraphicsContext2D().setFill(Color.RED);
canvas.getGraphicsContext2D().fillRect(0,0,canvas.getWidth(),canvas.getHeight());
}
where fillRect() take startingX , startingY , endX , endY as a parameters
You're blocking the JavaFX application thread with Thread.sleep which prevents the change from being rendered. (In your code there actually is no change, since you're simply replacing the color used to draw, assuming the color is not used anywhere else, but this is what would happen, if you did draw something in addition to modifying the fill property.)
I recommend using one of the classes from the javafx.animation package to change this.
In this case a PauseTransition seems the simplest way to go, but if you want to include a update loop, I recommend going with AnimationTimer or Timeline instead and integrating this kind of logic into the update "loop". Using one of the latter possiblities you could simply save the last time the ball hit a wall and decide based on this info which color to use to draw the background.
Example using PauseTransition:
gc.setFill(Color.WHITESMOKE);
gc.fillRect(x, y, w, h);
// schedule update without blocking
PauseTransition pause = new PauseTransition(Duration.millis(100));
pause.setOnFinished(evt -> {
gc.setFill(Color.BLACK);
gc.fillRect(x, y, w, h);
});
pause.play();
Note that you'll probably need to redraw more than just the background, since using fillRect replaces all the content currently in the rectangle.

Translate at fixed rate while key pressed

I'm trying to have an ImageView move inside a pane as long as a key is pressed. Because I want the animation to be as smooth as possible, I figured the JavaFX animation toolkit would be my best option, but I cannot find out how to properly control the speed of the animation. This was my idea:
ImageView image;
TranslateTransition translate;
#Override public void start(Stage primaryStage) throws Exception {
image = new ImageView("file:D:\\test.jpg");
image.setFocusTraversable(true);
AnchorPane pane = new AnchorPane(image);
pane.setOnKeyPressed(event -> translate.play());
pane.setOnKeyReleased(event -> translate.stop());
translate = new TranslateTransition();
translate.setOnFinished(event -> translate.playFromStart());
translate.setDuration(Duration.seconds(1.0));
translate.setByX(20.0);
translate.setNode(image);
primaryStage.setScene(new Scene(pane));
primaryStage.show();
}
Which works as I expected, aside from a very noticeable pause between each cycle of animation. The reason why I was going with this approach was to be able to control the speed of the ImageView, here for instance a constant speed of 20 pixels per second.
It would seem more appropriate to use an AnimationTimer, but it is my understanding that this timer is dependent on the frame rate at which the stage is being refreshed, which could vary between computers, in turn producing varying animation speed.
My question is two-fold. First, is it possible to modify my code in order to remove the delay between one cycle finishing and the next starting? Second, what better approach can you suggest?
The problem being that I cannot simply loop the animation using Animation.setCycleCount(Animation.INDEFINITE) because when it loops the image will start back from its original position.

Libgdx Detect horizontal scrolling

I'm working on a desktop only project using the libgdx game library, and I'm looking to implement a way for when the user scrolls horizontally to pan the camera. I'd like to avoid using libgdx's scene2d if possible. However, I can't see to find a way to capture horizontal scrolls. Is there anyway to detect a horizontal mouse scroll in Libgdx? I'd like to avoid using libgdx's scene2d if possible. And if there is not, is there a way to do it using plain java, without using awt, swing, or javafx?
Horizontal mouse scrolls? There is no such thing unless you are using a mouse with multiple scroll wheels. Usually the mouse has one scroll wheel.
However, if you mean moving the mouse horizontally, you should be able to capture that using getDeltaX(). Note that it's a raw input, so you will want to divide it by the screen's width in order to make movement the same on any monitor you use. You may also want to include a sensitivity multiplier so that the user may choose how fast panning is.
It's often helpful to allow rotation of the camera using the same input by checking whether a key is held down. Consider this pseudocode:
void updateCamera() {
if (LeftShiftPressed()) {
RotateCamera(getNormalizedX());
} else {
PanCamera(getNormalizedX());
}
}
float getNormalizedX() {
return float(getDeltaX()) / float(getScreenWidth())
}
void PanCamera(float x_movement) {
// pan by x_movement * pan_multiplier
}
void RotateCamera(float x_movement) {
// rotate by x_movement * rotation_multiplier
}

How to Update Graphics Quickly in a JPanel

I'm currently in the process of making a 2D turn based strategy game in Java. At this stage in development I am testing out different varieties of AIs.
The AIs interact with the game by sending a x and y variable to a function called gameLoop, which would would normally be called by a mouseClicked function. This allows them to directly emulate a human user which has made having them interact with the game much easier. Below is an overview for some of my code:
paintComponent() {
drawTiles();
drawUnits();
if(unitSelected && gameState == DISP_MOVE)
drawMoveSpots();
}
gameLoop(int x, int y) {
// various logic based
// on the values of x and y
// logic may set unitSelected to true or false
// may also change gameState bteween normal and displayMoveSpots
repaint();
}
My issue right now stems from the fact that the AI is executing code faster than repaint happens. When this happens, I start getting errors such as NullPointerException's and ConcurrentModificationException's in my various drawing functions because the logic inside of gameLoop is changing variables faster than repaint can happen. If I add in a manual delay to how often the AI calls gameLoop or only have 2 humans playing, the errors disappear.
From my research and reading of the Java docs, I understand that repaint returns immediately and all the graphics calls are put onto the EventQueue until Java decides that there are enough calls to warrant an actual repaint. The only solution I have been able to find so far is putting the logic code that happens in gameLoop into a anonymous runnable class and call the code using SwingUtilities.invokeAndWait, but this is not a valid solution for me, because this causes actual human interaction from clicking to bug out. I have considered using a lock to provide mutual exclusion to gameLoop's logic section/repaint (so only one can execute at a time), but I'm not sure if this is a good practice when dealing with Java graphics.
At this point I'm open to any suggestions on what I can do to alleviate my problem.
Thanks.
My advise is that you should use a swing timer
import javax.swing.Timer;
public static void main(String[] args) {
/*this is 1 sec/ 1000ms*/
Timer aiTimer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
//move AI
//repaint
}
});
aiTimer.start();
}
By doing that you will not repaint so often. Hope that helps!

Force a UI update

I have a piece of code designed to take a screenshot of a node in JavaFX:
public BufferedImage getSnapshot(final Node... hideNodes) {
Window window = getScene().getWindow();
Bounds b = localToScene(getBoundsInLocal());
int x = (int) Math.round(window.getX() + getScene().getX() + b.getMinX());
int y = (int) Math.round(window.getY() + getScene().getY() + b.getMinY());
int w = (int) Math.round(b.getWidth());
int h = (int) Math.round(b.getHeight());
try {
Robot robot = new Robot();
for(Node node : hideNodes) {
node.setOpacity(0);
node.getParent().requestLayout();
}
BufferedImage image = robot.createScreenCapture(new java.awt.Rectangle(x, y, w, h));
for(Node node : hideNodes) {
node.setOpacity(1);
node.getParent().requestLayout();
}
return image;
}
catch(AWTException ex) {
return null;
}
}
It has a twist, and that is it should hide the given nodes before taking the screenshot (in case they overlap with the node, which in some cases is definite.)
However, I'm stuck finding a way to force a redraw to include the opacity change before taking the screenshot - the only reference I found was to requestLayout(), but no joy there.
What method(s) should I call to force and wait for a redraw to complete?
I find your code quite strange:
Why use node.setOpacity(0) to make it invisible, rather than node.setVisible(false)?
Why return an AWT BufferedImage rather than a JavaFX Image?
Why use a robot to capture of the screen rather than taking a snapshot of the scene?
Why mix Swing and JavaFX and end up having to worry about rendering order?
Perhaps there are reasons for these things which I don't understand, but I'd just do it this way:
public Image takeSnapshot(Scene scene, final Node... hideNodes) {
for (Node node: hideNodes) node.setVisible(false);
Image image = scene.snapshot(null);
for (Node node: hideNodes) node.setVisible(true);
return image;
}
I created a small sample app which uses the above routine.
The primary window includes a group with a circle and a rectangle. When a snapshot command is issued, the rectangle is hidden in the primary, a snapshot of the primary is taken, then the rectangle is made visible in the primary again.
To answer your question's title about forcing a UI update - you can't really. The JavaFX application thread and JavaFX rendering thread are to be treated as two separate things. What you need to do is run your processing on the JavaFX application thread, seed control back to the JavaFX system, wait for it to do it's rendering, then examine the results. The scene.snapshot method will take care of that synchronization for you so you don't need to worry about it.
If, for whatever reason, scene.snapshot won't work for you and you wanted to maintain something similar to your original strategy, then what you would do is:
Issue some update commands (e.g. setting node opacity to 0) on the JavaFX application thread.
Issue a Platform.runLater call and take your robotic snapshot in the runLater body.
Once the snapshot has really been taken (notification in some awt callback), issue another Platform.runLater command to get back on the JavaFX application thread.
Back in the JavaFX application thread, issue some more update commands (e.g. setting node opacity back to 1).
This should work as it will allow the JavaFX system to perform another pulse which performs a rendering layout of the screen with the opacity changes before your robot actually takes the snapshot. An alternate mechanism is to use a JavaFX AnimationTimer which will provide you with a callback whenever a JavaFX pulse occurs. Maintaining proper synchronization of all of this between the AWT and JavaFX threads, would be annoying.

Categories

Resources