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.
Related
I am trying to make an rpg style game in javafx, which means that I need to repaint the screen every time the player moves or does an action. My game currently works so that it creates a new scene every time the player does an action, but this has proved to make the game very slow, as well as cause a host of other issues (music restarting). Would there be a way to update the canvas that I put on the screen and have that show to the scene but not recreate the whole scene?
I have tried to create a new canvas but the scene does not recognize the change in the canvas.
The system is a bit convoluted to show the entire path, but if anyone has questions regarding a specific aspect of the structure I would be happy to answer.
Basically, the scene has many elements on it, one of which is the canvas that is fetched from another class that has a method called getCavnas that returns the canvas.
I have a PackingCanvas that draws elements on the screen, which is an extension of the JavaFX Canvas control. I want my elements to be drawn on resize, but only when the resize is done, as redrawing the entire canvas on every resize event might be too costly in some situations.
What I have now is the following:
widthProperty().addListener(evt -> draw());
heightProperty().addListener(evt -> draw());
I have tried doing this with threads; add a small delay, see if new a new thread is created, if not; draw(), else abort current thread.
However, this doesn't seem like an elegant solution to me. Is there an idiom to deal with these problems?
you can cache width & height inside draw() function.
void draw()
{
cacheHeight = getHeight();
cacheWidth = getWidth();
}
then
heightProperty().addListener(evt -> {if(getHeight()!=cacheHeight) draw();});
I subclassed GridPane to display tiles on my game (2048), and since animations don't work well on a GridPane, whenever the player shifts the tiles, I have a transparent Pane overlay that sits on top of the Gridpane that I shift a tile to and is the surface that my animations are run on. My view is very "dumb" and knows nothing about the game state and simply runs animations whenever told.
I want to be able to update a few things on the Pane and the GridPane after all of the animations are complete (which it currently isn't able to do since when the animation is run it doesn't know if it's a first, intermediate, or last animation for the current game move). This is what I use for updates after a single animation is done:
transition.setOnFinished(event -> {
// executed after a single animation
}
However, I want to be able to do something after all active animations are complete. Is this possible? If not what would be the best way around this?
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.
As succinctly as I can manage: Given that I need the layout information of a node (the actual height/width of a node as rendered) to compute my animation, how can I get that information before javafx draws a frame with it?
A little bit longer explanation:
I've got a TreeItem that has child items appearing in it (at the front). What I'd like to have is an animation to cause all existing children to slide down to make room for the new item which would slide in. Each child tree-items contents are different and only known at run-time, meaning the height of each child tree item cannot be expressed as a constant.
This got me writing code along these lines:
groupController.groupTreeItem.getChildren().addListener(
new ListChangeListener<TreeItem<Node>>() {
#Override public void onChanged(Change<? extends TreeItem<Node>> c) {
while(c.next()){
if ( ! c.wasAdded()){
continue;
}
TreeItem newItem = c.getAddedSublist().get(0)
new Timeline(
new KeyFrame(
seconds(0),
new KeyValue(view.translateYProperty(), -1 * newItem.getHeight())
),
new KeyFrame(
seconds(1),
new KeyValue(view.translateYProperty(), 0)
)
);
}
}
}
);
the issue here is that as when a treeItem is added to another, its components aren't laid out by the time the invalidation event is fired, meaning newItem.view.getHeight() returns 0.
My next thought was to then have the animation performed as a reaction to both a change in the list content and a sequential change to the height property, (which got me to write some really hideous code that I'd rather not share --listeners adding listeners is not something I really want to write). This almost works, except that javaFX will draw a single frame with the height property set but without the animations translation applied. I could hack down this road further and try to work something out with opacity being toggled and jobs being enqueued for later, but I figured that this would be the path to madness.
I'm wondering if there's some pseudo-class foo or some clever use of a layout property I could use to help me here. I've been poking around at various combinations of various properties, and haven't gotten anywhere. It seems that as soon as the component has a height, it is rendered, regardless of any listeners you put in or around that height assignment.
Any help much appreciated!
have you tried, overriding this
#Override
protected void updateBounds() {
super.updateBounds();
}