Bruno Battaglia
Bruno Battaglia

Reputation: 75

bullet trajectory

First of all, I want to shoot a plane with a cannon. I've setted this Timeline for the trajectory, but I don't see the bullet on my Scene. It's very likely that my trajectory's code isn't correct. I tried to look on the internet about formula for projectile motion, but I understand nothing about physics;

import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Bounds;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Game_1 extends Application {

    private final double gravity = 9.81;
    private Timeline timeline;
    private ImageView plane;
    private Circle circle;
    private AnchorPane ap;

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Test");
        Group group = new Group();
        Scene scene = new Scene(group, 600, 350);
        scene.setFill(Color.BLACK);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private void shoot() {
        double x = 65.0f;
        double y = 408;
        double speed = 200;
        double t = 2;
        double angle = -45;
        double dx = Math.cos(angle) * speed;
        double dy = Math.sin(angle) * speed;
        circle = new Circle(x, y, 5, Color.BLACK);
        double x2 = x + dx * t;
        double y2 = (Math.tan(angle) * y - (gravity / (2 * Math.pow(speed, 2) * Math.cos(angle))) * Math.pow(x, 2));
        timeline = new Timeline();
        KeyValue xKV = new KeyValue(circle.centerXProperty(), x2);
        KeyValue yKV = new KeyValue(circle.centerYProperty(), y2, new Interpolator() {
            @Override
            protected double curve(double t) {
                return y + dy * t - 0.5 * gravity * t * t;
            }
        });
        KeyFrame xKF = new KeyFrame(Duration.seconds(t), xKV);
        KeyFrame yKF = new KeyFrame(Duration.seconds(t), yKV);
        timeline.getKeyFrames().addAll(xKF, yKF);
        ap.getChildren().add(circle);
        timeline.play();
        collision();
    }

    private void collision() {
        circle.boundsInParentProperty().addListener((ObservableValue<? extends Bounds> arg0, Bounds oldValue2, Bounds newValue2) -> {
            if (circle.getBoundsInParent().intersects(plane.getBoundsInParent())) {
                timeline.stop();
                ap.getChildren().remove(circle);
            }
        });
    }
}

Upvotes: 1

Views: 927

Answers (1)

fabian
fabian

Reputation: 82461

The curve method should map to the interval [0, 1]. Your method however maps to much higher values. The value val at time t of a animation from t0 to t1 for a interpolator i given start value val0 and end value val1 is calculated as follows:

val = val0 + (val1 - val0) * i.curve((t - t0) / (t1 - t0))

The parameter of the curve method is the relative position in the time interval (0 = start of animation; 1 = end of animation). The result of the method is used to determine how close the value is to the end value (0 = still at the start value; 1 = at the end value).

Therefore you should probably calculate the top point hMax in the cannonball's curve (as described e.g. here on Wikipedia) and use a different interpolator:

Interpolator interpolator = new Interpolator() {
    @Override
    protected double curve(double t) {
        // parabola with zeros at t=0 and t=1 and a maximum of 1 at t=0.5
        return 4 * t * (1 - t);
    }
};

KeyValue yKV = new KeyValue(circle.centerYProperty(), hMax, interpolator);

Note that upward movement means decreasing the y coordinate for the UI so in this case hMax should be smaller than the y value at the start.


Appart from that your shoot method is never called and some fields are not initialized which would result in a NPE in case it was called. Furthermore if those 2 issues are fixed, a black circle on a black background will be hard to see...

Example

Note that this is not using any physical fromulae and instead just uses some values chosen by me:

@Override
public void start(Stage primaryStage) {
    Circle circle = new Circle(10);
    circle.setManaged(false);
    Pane pane = new Pane(circle);

    circle.setCenterX(20);
    circle.setCenterY(800);

    Timeline timeline = new Timeline(new KeyFrame(Duration.ZERO,
            new KeyValue(circle.centerXProperty(), 20),
            new KeyValue(circle.centerYProperty(), 800)
        ), new KeyFrame(Duration.seconds(3),
            new KeyValue(circle.centerXProperty(), 380),
            new KeyValue(circle.centerYProperty(), 10, new Interpolator() {
                @Override
                protected double curve(double t) {
                    // parabola with zeros at t=0 and t=1 and a maximum of 1 at t=0.5
                    return 4 * t * (1 - t);
                }
            })
        )
    );

    Scene scene = new Scene(pane, 400, 800);
    scene.setOnMouseClicked(evt -> timeline.playFromStart());
    primaryStage.setScene(scene);
    primaryStage.show();
}

Note that Interpolator.curve is supposed to return 0 for parameter 0 and 1 for parameter 1. Anything else will probably result in jumps, should the property be animated further. Maybe the y-movement in 2 parts would be more appropriate, in case you want to move the ball around after the animation is finished.

I.e.

Interpolator 1: t * (2 - t)
Interpolator 2: t * t

using half the time interval each with end values of the top and start y coordinate of the curve respectively.

Upvotes: 2

Related Questions