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

时间:2016-04-23 02:58:04

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

我对在JavaFX中实现鼠标拖动事件的正确方法有疑问。

我的playGame()方法目前使用onMouseClicked,但这只是现在的占位符

理想情况下,我希望'飞盘'在鼠标拖动的方向上被“抛出”。

这样做的好方法是什么?

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();

    }
}

1 个答案:

答案 0 :(得分:1)

<强>动画

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

从那些可用的,PathTransition将运作良好。通过转换用户“扔”飞盘的方向,您可以生成飞盘Path可以遵循的Node。通过修改周期并应用反转,您还可以使飞盘表现得像回旋镖

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

<小时/> 应用动画

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

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

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

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

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

<小时/> 的实现:

我已添加评论以澄清某些步骤,但如果有任何不明确的地方,或者您有一些改进,请添加评论

mouseReleased approach:

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应用于飞盘而不是块颜色,可以使旋转更加明显

例如:

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 | drag-and-drop | mouseReleased with rotation

(注意拖放实现中的光标更改)

Frisbee Released Frisbee Drag Frisbee Released with rotation