Search code examples
animationjavafxkeyevent

JavaFX how to use AnimationTimer in separate class?


I've seen many examples that write AnimationTimer and KeyListener in Main class but not seen in another class.

I've tried How to write a KeyListener for JavaFX in "VolleyController", but I don't know why it did not work.

First, I used KeyEvent to move the image just like the first code. The third code is my Main class. I want to rewrite the methods to move images just like the second code.

But I went into some error written in the second code. How can I deal with it?

import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyEvent;

public class VolleyController implements EventHandler<KeyEvent> {
    double pika1LocationY = 556;
    double pika2LocationY = 556;
    double pika1LocationX = 0;
    double pika2LocationX = 1050;
    
    boolean pika2MoveLeft = false;  
    boolean pika2MoveRight = false;
    boolean pika1MoveLeft = false;
    boolean pika1MoveRight = false;
    
    @FXML
    ImageView pika1,pika2,ball;
    
    @Override
    public void handle(KeyEvent e) {
        switch (e.getCode()) {
        case ESCAPE:
            Main.currentStage.setScene(Main.menuScene);
            break;
        case UP:
            break;
        case LEFT:
            pika2MoveLeft = true;
            break;
        case RIGHT:
            pika2MoveRight = true;
            break;
        case T:
            break;
        case F:
            pika1MoveLeft = true;
            break;
        case H:
            pika1MoveRight = true;
            break;

        }   
        move();
    }
    
    public void released(KeyEvent r) {
        switch (r.getCode()) {
        case LEFT:
            pika2MoveLeft = false;
            break;
        case RIGHT:
            pika2MoveRight = false;
            break;
        case F:
            pika1MoveLeft = false;
            break;
        case H:
            pika1MoveRight = false;
            break;
        }
        
    }
    
    public void move() {
        if(pika1MoveLeft && pika1.getLayoutX()>=0) 
            pika1.setLayoutX(pika1.getLayoutX()-10);
        if(pika1MoveRight && pika1.getLayoutX()<=440 ) 
            pika1.setLayoutX(pika1.getLayoutX()+10);
        if(pika2MoveLeft && pika2.getLayoutX()>600) 
            pika2.setLayoutX(pika2.getLayoutX()-10);
        if(pika2MoveRight && pika2.getLayoutX()<1050) 
            pika2.setLayoutX(pika2.getLayoutX()+10);
    }
    
}
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyEvent;

public class VolleyController implements EventHandler<KeyEvent> {
    double pika1LocationY = 556;
    double pika2LocationY = 556;
    double pika1LocationX = 0;
    double pika2LocationX = 1050;
    
    boolean pika2MoveLeft = false;  
    boolean pika2MoveRight = false;
    boolean pika1MoveLeft = false;
    boolean pika1MoveRight = false;
    
    @FXML
    ImageView pika1,pika2,ball;
                    //this gives me a syntax error about getScene(), but the following did not
    Main.currentStage.getScene().setOnKeyPressed(new EventHandler<KeyEvent>() {
        @Override
        public void handle(KeyEvent e) {

            switch (e.getCode()) {
            case ESCAPE:
                Main.currentStage.setScene(Main.menuScene);
                break;
            case UP:
                break;
            case LEFT:
                pika2MoveLeft = true;
                break;
            case RIGHT:
                pika2MoveRight = true;
                break;
            case T:
                break;
            case F:
                pika1MoveLeft = true;
                break;
            case H:
                pika1MoveRight = true;
                break;

            }   
    }}); //this told me to delete tokens
  
                  //this did not give me a syntax error
    Main.currentStage.getScene().setOnKeyReleased(new EventHandler<KeyEvent>() {
        @Override
        public void handle(KeyEvent r) {
            switch (r.getCode()) {
            case LEFT:
                pika2MoveLeft = false;
                break;
            case RIGHT:
                pika2MoveRight = false;
                break;
            case F:
                pika1MoveLeft = false;
                break;
            case H:
                pika1MoveRight = false;
                break;
            }
        }
    });
    
    AnimationTimer timer=new AnimationTimer() {
        @Override
        public void handle(long now)
        {   
            int pika1dx=0, pika2dx=0;
            
            if(pika1MoveLeft) pika1dx-=10;
            if(pika1MoveRight) pika1dx+=10;
            if(pika2MoveLeft) pika2dx-=10;
            if(pika2MoveLeft) pika2dx-=10;
            
            PikaMoveBy(pika1dx,pika2dx);
            
        }
    };
    
    timer.start();
}
public void PikaMoveBy(int pika1dx,int pika2dx)
{
    if(pika1dx==0 || pika2dx==0) return;
    
    final double pika1cx=pika1.getBoundsInLocal().getWidth()/2; 
    final double pika2cx=pika2.getBoundsInLocal().getWidth()/2;
    
    double pika1x=pika1cx+pika1.getLayoutX()+pika1dx;
    double pika2x=pika2cx+pika2.getLayoutX()+pika2dx;
    
    PikaMoveTo(pika1x, pika2x);
    
}

public void PikaMoveTo(double pika1x, double pika2x)
{
    final double pika1cx=pika1.getBoundsInLocal().getWidth()/2;
    final double pika2cx=pika2.getBoundsInLocal().getWidth()/2;
    
    if(pika1x-pika1cx>=0 && pika1x+pika1cx<450 && pika2x-pika2cx>600 && pika2x+pika2cx<1050)
    {
        pika1.relocate(pika1x-pika1cx, pika1_lct_y);
        pika2.relocate(pika2x-pika2cx, pika2_lct_y);
    }
}
import java.net.URL;
import java.util.ResourceBundle;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

public class Main extends Application {
    
    public static Stage currentStage;
    public static Scene menuScene;

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        currentStage=primaryStage;
        Parent root=FXMLLoader.load(getClass().getResource("Menu.fxml"));
        Scene MenuScene = new Scene(root);
        currentStage.setScene(MenuScene); //use setScene() to switch the scene
        currentStage.setTitle("PikaVolley");
        currentStage.show();
                
    }
}

Solution

  • To get key handlres and AnimationTimer working use a simple FXML (Volley.fxml):

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.control.Button?>
    <?import javafx.scene.control.Label?>
    <?import javafx.scene.image.ImageView?>
    <?import javafx.scene.layout.Pane?>
    <?import javafx.scene.layout.VBox?>
    
    <VBox spacing="10.0" xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="test.VolleyController">
       <children>
          <Label alignment="CENTER" contentDisplay="CENTER" prefHeight="17.0" prefWidth="248.0" text="Press Start.Use Arows to set direction" />
          <Pane  prefHeight="250.0" prefWidth="250.0">
             <children>
                <ImageView fx:id="ball" fitHeight="50.0" fitWidth="50.0" pickOnBounds="true" preserveRatio="true" />
             </children>
          </Pane>
          <Button fx:id="startBtn" mnemonicParsing="false" onAction="#start" text="Start" />
       </children>
    </VBox>
    

    And its controller (see comments):

    import javafx.animation.AnimationTimer;
    import javafx.event.EventHandler;
    import javafx.fxml.FXML;
    import javafx.scene.control.Button;
    import javafx.scene.image.Image;
    import javafx.scene.image.ImageView;
    import javafx.scene.input.KeyEvent;
    
    public class VolleyController implements EventHandler<KeyEvent> {
    
        //always use publicly available resources when posting
        private static final String BALL = "https://cdn3.iconfinder.com/data/icons/softwaredemo/PNG/32x32/Circle_Red.png";
        boolean moveRight = false, moveLeft = false, moveUp = false, moveDown = false;
        private static final int DX = 1, DY = 1;
    
        @FXML
        ImageView ball;
        @FXML 
        Button startBtn;
    
        @FXML
        void initialize(){
            ball.setImage(new Image(BALL)); //initialize image view
        }
    
        @FXML
        void start(){
    
            ball.getScene().setOnKeyPressed(this);  //add Key press and release handlers to scene
            ball.getScene().setOnKeyReleased(this);
    
            //construct and invoke AnimationTimer
            AnimationTimer timer = new AnimationTimer(){
                @Override
                public void handle(long now) {
                    move();  //repeatedly invoke move
                }
            };
            timer.start();
    
            startBtn.setDisable(true);
        }
    
        @Override
        public void handle(KeyEvent e) {
             moveRight = false; moveLeft = false;  moveUp = false; moveDown = false;
            //change movement directions based on key events
            switch (e.getCode()) {
                case UP:
                    moveUp = true;
                    break;
                case LEFT:
                    moveLeft = true;
                    break;
                case RIGHT:
                    moveRight = true;
                    break;
                case DOWN:
                    moveDown = true;
                    break;
            }
        }
    
        //move if any of the direction control booleans is true
        private void move() {
            if(moveLeft) {
                ball.setLayoutX(ball.getLayoutX()-DX);
            }else  if(moveRight) {
                ball.setLayoutX(ball.getLayoutX()+DX);
            }else if(moveDown) {
                ball.setLayoutY(ball.getLayoutY()+DY);
            }else if(moveUp) {
                ball.setLayoutY(ball.getLayoutY()-DY);
            }
        }
    }
    

    test it using :

    public class Main extends Application {
    
        @Override
        public void start(Stage currentStage) throws Exception {
    
            Parent root=FXMLLoader.load(getClass().getResource("Volley.fxml"));
            currentStage.setScene(new Scene(root));
            currentStage.show();
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }