5

I have a very simple program where you can use your W, A, S, D and space keys to shoot. All of the shooting and moving animations work, but I'm not exactly sure how I'd implement a system in which the program is constantly checking if the bullets have touched a node, such as a circle.

I was thinking I could have an ArrayList to store all the bullets and then use a TimerTask to check if the bullets have touched a node; but I feel like that would slow down the program, and also the bullet could pass them in the time the TimerTask is waiting to go again.

Any suggestions would help.

Code: Pastebin

import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.animation.*;
import javafx.util.Duration;
public class functionalShooter extends Application {
    private String currentDirection = "W";
    public static void main(String[] args){ launch(args);   }
    @Override public void start(Stage stage) throws Exception{
        Pane root = new Pane();
        Scene scene = new Scene(root, 600, 400);
        stage.setScene(scene);
        stage.setResizable(false);
        stage.show();

        Circle player = new Circle(10);
        player.setLayoutX(300);
        player.setLayoutY(200);

        Circle other = new Circle(100, 100, 10);

        root.getChildren().addAll(player, other);

        //key events
        scene.setOnKeyPressed(event -> {
            switch(event.getCode()){
                case A:
                    player.setLayoutX(player.getLayoutX()-10);
                    currentDirection = "A";
                    break;
                case D:
                    player.setLayoutX(player.getLayoutX()+10);
                    currentDirection = "D";
                    break;
                case W:
                    player.setLayoutY(player.getLayoutY()-10);
                    currentDirection = "W";
                    break;
                case S:
                    player.setLayoutY(player.getLayoutY()+10);
                    currentDirection = "S";
                    break;
                case SPACE:
                    if (currentDirection.equals("D")){
                        Circle newCircle = new Circle(player.getLayoutX()+10, player.getLayoutY(), 5);
                        root.getChildren().add(newCircle);
                        shoot(newCircle);
                    }
                    else if (currentDirection.equals("A")){
                        Circle newCircle = new Circle(player.getLayoutX()-10, player.getLayoutY(), 5);
                        root.getChildren().add(newCircle);
                        shoot(newCircle);
                    }
                    else if (currentDirection.equals("S")){
                        Circle newCircle = new Circle(player.getLayoutX(), player.getLayoutY()+10, 5);
                        root.getChildren().add(newCircle);
                        shoot(newCircle);
                    }
                    else {
                        Circle newCircle = new Circle(player.getLayoutX(), player.getLayoutY()-10, 5);
                        root.getChildren().add(newCircle);
                        shoot(newCircle);
                    }
                    break;
            }
        });
    }

    private void shoot(Circle bullet){
        Timeline timeline = new Timeline();
        if (currentDirection.equals("D")){
            KeyValue start = new KeyValue(bullet.translateXProperty(), 0);
            KeyValue end = new KeyValue(bullet.translateXProperty(), 800);
            KeyFrame startF = new KeyFrame(Duration.ZERO, start);
            KeyFrame endF = new KeyFrame(Duration.seconds(10), end);
            timeline.getKeyFrames().addAll(startF, endF);
        }
        else if (currentDirection.equals("A")){
            KeyValue start = new KeyValue(bullet.translateXProperty(), 0);
            KeyValue end = new KeyValue(bullet.translateXProperty(), -800);
            KeyFrame startF = new KeyFrame(Duration.ZERO, start);
            KeyFrame endF = new KeyFrame(Duration.seconds(10), end);
            timeline.getKeyFrames().addAll(startF, endF);
        }
        else if (currentDirection.equals("S")){
            KeyValue start = new KeyValue(bullet.translateYProperty(), 0);
            KeyValue end = new KeyValue(bullet.translateYProperty(), 800);
            KeyFrame startF = new KeyFrame(Duration.ZERO, start);
            KeyFrame endF = new KeyFrame(Duration.seconds(10), end);
            timeline.getKeyFrames().addAll(startF, endF);
        }
        else{
            KeyValue start = new KeyValue(bullet.translateYProperty(), 0);
            KeyValue end = new KeyValue(bullet.translateYProperty(), -800);
            KeyFrame startF = new KeyFrame(Duration.ZERO, start);
            KeyFrame endF = new KeyFrame(Duration.seconds(10), end);
            timeline.getKeyFrames().addAll(startF, endF);
        }
        timeline.setAutoReverse(false);
        timeline.setCycleCount(1);
        timeline.play();
    }
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
zzzzzz
  • 143
  • 7

3 Answers3

5

You can check if the bullet intersects other using Shape.intersect() in a custom Interpolator supplied to each relevant KeyValue. The fragment below adds an Interpolator to shoot(), but you'll need an identical one for each direction. The implementation is linear, simply returning t unchanged, but you might also look at this parabolic interpolator. I also made other a class variable, accessible to shoot(). I put a dozen bullets into the air with no perceptible delay. Note that you don't need a start value: "one will be synthesized using the target values that are current at the time" the animation is played.

private Circle other = new Circle(100, 100, 10);
…
else {
    KeyValue end = new KeyValue(bullet.translateYProperty(), -800, new Interpolator() {
        @Override
        protected double curve(double t) {
            if (!Shape.intersect(bullet, other).getBoundsInLocal().isEmpty()) {
                System.out.println("Intersection");
            }
            return t;
        }
    });
    KeyFrame endF = new KeyFrame(Duration.seconds(10), end);
    timeline.getKeyFrames().addAll(endF);
}

More here and here; as tested:

import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Shape;
import javafx.util.Duration;

/**
 * @see https://stackoverflow.com/a/39188588/230513
 */
public class FunctionalShooter extends Application {

    private static final double S = 600;
    private String currentDirection = "A";

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

    @Override
    public void start(Stage stage) {
        Group root = new Group();
        Scene scene = new Scene(root, S, 2 * S / 3);
        stage.setScene(scene);
        Circle other = new Circle(S / 3, S / 3, 25);
        Circle player = new Circle(10);
        player.setLayoutX(2 * S / 3);
        player.setLayoutY(S / 3);
        root.getChildren().addAll(other, player);
        stage.show();

        //key events
        scene.setOnKeyPressed(event -> {
            switch (event.getCode()) {
                case A -> {
                    player.setLayoutX(player.getLayoutX() - 10);
                    currentDirection = "A";
                }
                case D -> {
                    player.setLayoutX(player.getLayoutX() + 10);
                    currentDirection = "D";
                }
                case W -> {
                    player.setLayoutY(player.getLayoutY() - 10);
                    currentDirection = "W";
                }
                case S -> {
                    player.setLayoutY(player.getLayoutY() + 10);
                    currentDirection = "S";
                }
                case SPACE -> {
                    if (currentDirection.equals("D")) {
                        Circle newCircle = new Circle(player.getLayoutX() + 10, player.getLayoutY(), 5);
                        root.getChildren().add(newCircle);
                        shoot(newCircle, other);
                    } else if (currentDirection.equals("A")) {
                        Circle newCircle = new Circle(player.getLayoutX() - 10, player.getLayoutY(), 5);
                        root.getChildren().add(newCircle);
                        shoot(newCircle, other);
                    } else if (currentDirection.equals("S")) {
                        Circle newCircle = new Circle(player.getLayoutX(), player.getLayoutY() + 10, 5);
                        root.getChildren().add(newCircle);
                        shoot(newCircle, other);
                    } else {
                        Circle newCircle = new Circle(player.getLayoutX(), player.getLayoutY() - 10, 5);
                        root.getChildren().add(newCircle);
                        shoot(newCircle, other);
                    }
                }
            }
        });
    }

    private static class Intersector extends Interpolator {

        private final Circle bullet;
        private final Circle other;

        public Intersector(Circle bullet, Circle other) {
            this.bullet = bullet;
            this.other = other;
        }

        @Override
        protected double curve(double t) {
            if (!Shape.intersect(bullet, other).getBoundsInLocal().isEmpty()) {
                System.out.println("Intersect :" + t);
            }
            return t;
        }
    }

    private void shoot(Circle bullet, Circle other) {
        Timeline timeline = new Timeline();
        switch (currentDirection) {
            case "D" -> {
                KeyValue end = new KeyValue(bullet.translateXProperty(), S, new Intersector(bullet, other));
                KeyFrame endF = new KeyFrame(Duration.seconds(10), end);
                timeline.getKeyFrames().add(endF);
            }
            case "A" -> {
                KeyValue end = new KeyValue(bullet.translateXProperty(), -S, new Intersector(bullet, other));
                KeyFrame endF = new KeyFrame(Duration.seconds(10), end);
                timeline.getKeyFrames().add(endF);
            }
            case "S" -> {
                KeyValue end = new KeyValue(bullet.translateYProperty(), S, new Intersector(bullet, other));
                KeyFrame endF = new KeyFrame(Duration.seconds(10), end);
                timeline.getKeyFrames().add(endF);
            }
            default -> {
                KeyValue end = new KeyValue(bullet.translateYProperty(), -S, new Intersector(bullet, other));
                KeyFrame endF = new KeyFrame(Duration.seconds(10), end);
                timeline.getKeyFrames().add(endF);
            }
        }
        timeline.setAutoReverse(false);
        timeline.setCycleCount(1);
        timeline.play();
    }
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
0

What you need is something that will check for collision every time you draw a frame in your game. A timer would run in a separate thread which could work, but would be much harder to synchronize with the game.

Kelvin
  • 574
  • 3
  • 13
  • 3
    You can execute code on every frame with an [`AnimationTimer`](http://docs.oracle.com/javase/8/javafx/api/javafx/animation/AnimationTimer.html), as the `handle()` method is called exactly once per frame. – James_D Aug 27 '16 at 23:21
  • Similarly in a `Timeline`, a custom `Interpolator` will be called periodically, as shown [here](http://stackoverflow.com/a/39188588/230513). – trashgod Aug 29 '16 at 08:32
0

In general, when you must "constantly observe" something in an OO language, the best solution is to use the Observer pattern.

Andres
  • 10,561
  • 4
  • 45
  • 63