鼠标/拖动事件上的 JavaFX 飞盘运动

标签 java javafx drag-and-drop javafx-8

我有一个关于在 JavaFX 中实现鼠标拖动事件的正确方法的问题。

我的 playGame() 方法当前使用 onMouseClicked,但这只是一个占位符

理想情况下,我希望“飞盘”沿着鼠标拖动的方向“ throw ”。

什么是做到这一点的好方法?

package FrisbeeToss;

import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class FrisbeeTossMain extends Application {

private Text info = new Text();
private Entity frisbee, target;

private static final int APP_W = 800;
private static final int APP_H = 600;

private static class Entity extends Parent {
    public Entity(double x, double y, double r, Color c) {
        setTranslateX(x);
        setTranslateY(y);
        Circle circ = new Circle(r, c);
        getChildren().add(circ);
    }
}

private Parent createContent() {
    Pane root = new Pane();
    root.setPrefSize(APP_W, APP_H);

    info.setTranslateX(50);
    info.setTranslateY(50);

    target = new Entity(APP_W /2, APP_H /2, 75, Color.RED);
    frisbee = new Entity(APP_W -20, APP_H -20, 60, Color.GREEN);

    root.getChildren().addAll(info, target, frisbee);

    return root;
}

private void checkCollision(Entity a, Entity b){
    if (a.getBoundsInParent().intersects(b.getBoundsInParent())) {
        info.setText("Target caught frisbee!");
    }
    else {
        info.setText("");
    }
}

private void playGame() {
    frisbee.setOnMouseClicked(event -> {
        System.out.println("Frisbee clicked");

        checkCollision(frisbee, target);
    });
}

@Override
public void start(Stage primaryStage) throws Exception {
    Scene scene = new Scene(createContent());

    primaryStage.setTitle("Frisbee Toss");
    primaryStage.setScene(scene);
    primaryStage.show();

    playGame();

    }
}

最佳答案

动画

有几种方法可以做到这一点,但考虑到飞盘的概念,过渡会很有效。这里有一个官方教程:

从可用的情况来看,PathTransition 可以很好地工作。通过转换用户“扔”飞盘的方向,您可以生成飞盘节点可以遵循的路径。通过修改周期并应用反转,您还可以使飞盘表现得像回旋镖

作为飞盘的旋转,您还可以利用 RotationTransition 并将其与沿路径的运动一起应用

<小时/> 应用动画

您可以仅通过飞盘上的 mouseReleased 事件应用上述转换,但正如您特别提到的拖动,我修改了下面的代码以显示这两种方法。一个使用已发布的事件,另一个使用拖放

如果您想了解有关拖放功能的更多信息,请参阅此处:

<小时/> 对原始来源进行了细微更改

在下面的实现中,我删除了您的 Entity 类,并将其替换为 Circle,因为 Entity 没有添加任何内容,目的似乎只是创建一个Circle

我还删除了 static 声明。在这个特定的示例中,拥有或删除它们没有任何好处,但是 static 关键字应该只在需要的地方使用。希望这篇热门文章能够更好地解释原因:

<小时/> 实现:

我添加了评论来澄清一些步骤,但如果有任何不清楚的地方,或者您有一些改进,请添加评论

鼠标释放方法:

public class FrisbeeTossMain extends Application {
    private Pane root;
    private Text info = new Text();
    private Circle frisbee, target;
    private PathTransition transition;

    private final int APP_W = 800;
    private final int APP_H = 600;
    private final double frisbeeX = APP_W -20;
    private final double frisbeeY = APP_H -20;

    private Parent createContent() {
        root = new Pane();
        root.setPrefSize(APP_W, APP_H);

        info.setTranslateX(50);
        info.setTranslateY(50);

        target = new Circle(75, Color.RED);
        target.setLayoutX(APP_W /2);
        target.setLayoutY(APP_H /2);

        frisbee = new Circle(60, Color.GREEN);
        frisbee.setLayoutX(frisbeeX);
        frisbee.setLayoutY(frisbeeY);
        frisbee.setFill(new LinearGradient(0, 0, 1, 0, true, CycleMethod.NO_CYCLE,
                new Stop[] { new Stop(0, Color.BLACK), new Stop(1, Color.GREEN)}));

        SimpleBooleanProperty isFrisbeeVisuallyCollidingWithTarget = new SimpleBooleanProperty(false);
        frisbee.boundsInParentProperty().addListener((observable, oldValue, newValue) -> {
            isFrisbeeVisuallyCollidingWithTarget.set(
                    Shape.intersect(frisbee, target).getBoundsInParent().getWidth() >= 0 ? true : false);
        });

        isFrisbeeVisuallyCollidingWithTarget.addListener((observable, oldValue, newValue) -> {
            if(newValue && transition != null){
                //Stop the animation making it appear as though the frisbee was caught
                transition.stop();
            }
        });

        info.textProperty().bind(Bindings.when(isFrisbeeVisuallyCollidingWithTarget)
                .then("Target caught frisbee!").otherwise(""));
        root.getChildren().addAll(info, target, frisbee);

        return root;
    }

    private void playGame() {
        frisbee.setOnMouseReleased(event -> {
            //Starting point for the line
            double fromX = frisbeeX - frisbee.getRadius();
            double fromY = frisbeeY - frisbee.getRadius();

            //Only "throw" the frisbee if the user has released outside of the frisbee itself
            if(frisbee.getBoundsInParent().contains(event.getSceneX(), event.getSceneY())){
                return;
            }

            //Create a path between the frisbee and released location
            Line line = new Line(fromX, fromY, event.getSceneX(), event.getSceneY());
            transition = new PathTransition(Duration.seconds(1), line, frisbee);
            transition.setAutoReverse(true); //Set the node to reverse along the path
            transition.setCycleCount(2); //2 cycles, first to navigate the path, second to return
            frisbee.relocate(0, 0); //Allow the path to control the location of the frisbee

            RotateTransition rotateTransition =
                    new RotateTransition(Duration.seconds(1), frisbee);
            rotateTransition.setByAngle(360f);
            rotateTransition.setCycleCount(2);
            rotateTransition.setAutoReverse(true);

            rotateTransition.play();
            transition.play();
        });
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        Scene scene = new Scene(createContent());

        primaryStage.setTitle("Frisbee Toss");
        primaryStage.setScene(scene);
        primaryStage.show();

        playGame();
    }
}
<小时/>

拖放实现:

与上面的唯一区别是在 playGame 方法中:

private void playGame() {
    frisbee.setId("frisbee");

    frisbee.setOnDragDetected(event -> {
        Dragboard db = frisbee.startDragAndDrop(TransferMode.ANY);
        ClipboardContent content = new ClipboardContent();
        // Store node ID in order to know what is dragged.
        content.putString(frisbee.getId());
        db.setContent(content);
        event.consume();
    });

    root.setOnDragOver(event -> {
        event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
        event.consume();
    });

    root.setOnDragDropped(event -> {
        //Starting point for the line
        double fromX = frisbeeX - frisbee.getRadius();
        double fromY = frisbeeY - frisbee.getRadius();

        //Only "throw" the frisbee if the user has released outside of the frisbee itself
        if(frisbee.getBoundsInParent().contains(event.getSceneX(), event.getSceneY())){
            return;
        }

        //Create a path between the frisbee and released location
        Line line = new Line(fromX, fromY, event.getSceneX(), event.getSceneY());
        transition = new PathTransition(Duration.seconds(1), line, frisbee);
        transition.setAutoReverse(true); //Set the node to reverse along the path
        transition.setCycleCount(2); //2 cycles, first to navigate the path, second to return
        frisbee.relocate(0, 0); //Allow the path to control the location of the frisbee

        transition.setOnFinished(finishedEvent -> {
            event.setDropCompleted(true);
            event.consume();
        });
        transition.play();
    });
}

<小时/> 添加旋转:

可以通过在播放 PathTransition 之前预先添加以下代码片段来应用旋转:

RotateTransition rotateTransition = new RotateTransition(Duration.seconds(1), frisbee);
rotateTransition.setByAngle(360f);
rotateTransition.setCycleCount(2);
rotateTransition.setAutoReverse(true);
rotateTransition.play();

您可以通过对飞盘应用GradientFill(而不是 block 颜色)来使旋转更加明显

例如:

frisbee.setFill(new LinearGradient(0, 0, 1, 0, true, CycleMethod.NO_CYCLE,
    new Stop[] { new Stop(0, Color.BLACK), new Stop(1, Color.GREEN)}));

<小时/> 视觉输出

订单:mouseReleased |拖放|鼠标旋转释放

(注意拖放实现中光标的变化)

Frisbee Released Frisbee Drag Frisbee Released with rotation

关于鼠标/拖动事件上的 JavaFX 飞盘运动,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36806141/

相关文章:

java - 如何使用 SwingWorker 避免 GUI 卡住

java - 关于空指针异常

java - 如何在 JavaFX 的 TitledPane 中更改 header 组件

vb.net - 如何在同一数据 GridView 中拖放行

C# GTK 拖放

java - 将实例变量传递给 super 构造函数的解决方法是什么?

java - 如何在Java中的正则表达式中转义 "["

使用数据类填充 TableView 时出现 java.lang.ClassCastException

css - 如何使用 JavaFX 以编程方式设置 AreaChart 系列的样式

javascript - 如何在拖放 JavaScript 中控制源 TD 样式?