21

衝突検出をしようとしています。このテストでは、単純な長方形を使用しShape、それらをチェックしてBound、それらが衝突しているかどうかを確認します。検出は期待どおりに機能しませんが。オブジェクトを移動するさまざまな方法(relocate、setLayoutX、Y)と、さまざまな境界チェック(boundsInLocal、boundsInParrentなど)を使用してみましたが、それでもこれを機能させることができません。ご覧のとおり、検出は1つのオブジェクトに対してのみ機能しますが、3つのオブジェクトがある場合でも、1つだけが衝突を検出します。これは、問題を示すいくつかの実用的なコードです。

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

import java.util.ArrayList;


public class CollisionTester extends Application {


    private ArrayList<Rectangle> rectangleArrayList;

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

    public void start(Stage primaryStage) {
        primaryStage.setTitle("The test");
        Group root = new Group();
        Scene scene = new Scene(root, 400, 400);

        rectangleArrayList = new ArrayList<Rectangle>();
        rectangleArrayList.add(new Rectangle(30.0, 30.0, Color.GREEN));
        rectangleArrayList.add(new Rectangle(30.0, 30.0, Color.RED));
        rectangleArrayList.add(new Rectangle(30.0, 30.0, Color.CYAN));
        for(Rectangle block : rectangleArrayList){
            setDragListeners(block);
        }
        root.getChildren().addAll(rectangleArrayList);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public void setDragListeners(final Rectangle block) {
        final Delta dragDelta = new Delta();

        block.setOnMousePressed(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent mouseEvent) {
                // record a delta distance for the drag and drop operation.
                dragDelta.x = block.getTranslateX() - mouseEvent.getSceneX();
                dragDelta.y = block.getTranslateY() - mouseEvent.getSceneY();
                block.setCursor(Cursor.NONE);
            }
        });
        block.setOnMouseReleased(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent mouseEvent) {
                block.setCursor(Cursor.HAND);
            }
        });
        block.setOnMouseDragged(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent mouseEvent) {

                block.setTranslateX(mouseEvent.getSceneX() + dragDelta.x);
                block.setTranslateY(mouseEvent.getSceneY() + dragDelta.y);
                checkBounds(block);

            }
        });
    }

    private void checkBounds(Rectangle block) {
        for (Rectangle static_bloc : rectangleArrayList)
            if (static_bloc != block) {
                if (block.getBoundsInParent().intersects(static_bloc.getBoundsInParent())) {
                    block.setFill(Color.BLUE);        //collision
                } else {
                    block.setFill(Color.GREEN);    //no collision
                }
            } else {
                block.setFill(Color.GREEN);    //no collision -same block
            }
    }

    class Delta {
        double x, y;
    }
}
4

1 に答える 1

38

checkBoundsルーチンにわずかな論理エラーがあるようです-(境界に基づいて)衝突を正しく検出していますが、同じルーチンで後続の衝突チェックを実行すると、ブロックの塗りつぶしが上書きされています。

次のようなことを試してください。衝突が検出されたことをルーチンが「忘れない」ようにフラグを追加します。

private void checkBounds(Shape block) {
  boolean collisionDetected = false;
  for (Shape static_bloc : nodes) {
    if (static_bloc != block) {
      static_bloc.setFill(Color.GREEN);

      if (block.getBoundsInParent().intersects(static_bloc.getBoundsInParent())) {
        collisionDetected = true;
      }
    }
  }

  if (collisionDetected) {
    block.setFill(Color.BLUE);
  } else {
    block.setFill(Color.GREEN);
  }
}

(親の境界に基づいて)実行しているチェックでは、同じ親グループ内のノードの表示されている境界を囲む長方形の交差が報告されることに注意してください。

代替実装

必要に応じて、元のサンプルを更新して、視覚的な形状の境界ボックスではなく、ノードの視覚的な形状に基づいてチェックできるようにしました。これにより、円などの非長方形の形状の衝突を正確に検出できます。このための鍵は、Shape.intersects(shape1、shape2)メソッドです。

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

import java.util.ArrayList;
import javafx.scene.shape.*;

public class CircleCollisionTester extends Application {

  private ArrayList<Shape> nodes;

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

  @Override public void start(Stage primaryStage) {
    primaryStage.setTitle("Drag circles around to see collisions");
    Group root = new Group();
    Scene scene = new Scene(root, 400, 400);

    nodes = new ArrayList<>();
    nodes.add(new Circle(15, 15, 30));
    nodes.add(new Circle(90, 60, 30));
    nodes.add(new Circle(40, 200, 30));
    for (Shape block : nodes) {
      setDragListeners(block);
    }
    root.getChildren().addAll(nodes);
    checkShapeIntersection(nodes.get(nodes.size() - 1));

    primaryStage.setScene(scene);
    primaryStage.show();
  }

  public void setDragListeners(final Shape block) {
    final Delta dragDelta = new Delta();

    block.setOnMousePressed(new EventHandler<MouseEvent>() {
      @Override public void handle(MouseEvent mouseEvent) {
        // record a delta distance for the drag and drop operation.
        dragDelta.x = block.getLayoutX() - mouseEvent.getSceneX();
        dragDelta.y = block.getLayoutY() - mouseEvent.getSceneY();
        block.setCursor(Cursor.NONE);
      }
    });
    block.setOnMouseReleased(new EventHandler<MouseEvent>() {
      @Override public void handle(MouseEvent mouseEvent) {
        block.setCursor(Cursor.HAND);
      }
    });
    block.setOnMouseDragged(new EventHandler<MouseEvent>() {
      @Override public void handle(MouseEvent mouseEvent) {
        block.setLayoutX(mouseEvent.getSceneX() + dragDelta.x);
        block.setLayoutY(mouseEvent.getSceneY() + dragDelta.y);
        checkShapeIntersection(block);
      }
    });
  }

  private void checkShapeIntersection(Shape block) {
    boolean collisionDetected = false;
    for (Shape static_bloc : nodes) {
      if (static_bloc != block) {
        static_bloc.setFill(Color.GREEN);

        Shape intersect = Shape.intersect(block, static_bloc);
        if (intersect.getBoundsInLocal().getWidth() != -1) {
          collisionDetected = true;
        }
      }
    }

    if (collisionDetected) {
      block.setFill(Color.BLUE);
    } else {
      block.setFill(Color.GREEN);
    }
  }

  class Delta { double x, y; }
}

サンプルプログラム出力。サンプルでは、​​円がドラッグされており、ユーザーは現在、別の円との衝突としてマークされている円をドラッグしています(青色でペイントすることにより)。デモンストレーションの目的で、現在ドラッグされている円のみに衝突の色がマークされています。

衝突

追加の質問に基づくコメント

以前のコメントで交差点デモアプリケーションに投稿したリンクは、特定のタイプの衝突検出サンプルとしてではなく、さまざまな境界タイプの使用を説明するためのものでした。ユースケースでは、変更リスナーの複雑さを増したり、さまざまな種類の境界タイプをチェックしたりする必要はありません。1つのタイプに落ち着くだけで十分です。ほとんどの衝突検出は、ノードのレイアウト境界やローカル境界などの他のJavaFX境界タイプではなく、視覚的な境界の交差にのみ関心があります。したがって、次のいずれかを実行できます。

  1. getBoundsInParentノードの視覚的な端を囲む最小の長方形のボックスで機能する(元の質問で行ったように)の交差を確認します。または
  2. Shape.intersect(shape1, shape2)視覚的形状の境界ボックスではなく、ノードの視覚的形状に基づいてチェックする必要がある場合は、ルーチンを使用します。

長方形にsetLayoutXまたはtranslateXを使用する必要があります

layoutXおよびlayoutYプロパティは、ノードの配置またはレイアウトを目的としています。translateXプロパティとtranslateYプロパティは、ノードの視覚的な位置を一時的に変更することを目的としています(たとえば、ノードがアニメーションを実行しているとき)。あなたの例では、どちらのプロパティも機能しますが、変換プロパティよりもレイアウトプロパティを使用する方がおそらく良い形式です。そうすれば、ノードでTranslateTransitionのようなものを実行したい場合は、開始点とこれらの値は、親グループ内の位置ではなく、ノードの現在のレイアウト位置を基準にしているため、最終変換値は次のようになります。

これらのレイアウトを使用して、サンプルで座標をタンデムに変換する別の方法は、ドラッグ操作の過程でキャンセルするESCのようなものがある場合です。layoutX、Yをノードの初期位置に設定し、translateX、Y値を設定するドラッグ操作を開始し、ユーザーがESCを押した場合は、translateX、Yを0に戻し、ドラッグ操作をキャンセルするか、ユーザーがマウスを離した場合に使用できます。 layoutX、YをlayoutX、Y + translateX、Yに設定し、translateX、Yを0に戻します。変換とは、ノードの視覚的な座標を元のレイアウト位置から一時的に変更するために値が使用されるということです。

円がアニメーション化されていても、交差は機能しますか?つまり、マウスで円をドラッグせずに、ランダムに動かした場合はどうなるでしょうか。この場合も色が変わりますか?

これを行うには、衝突検出関数が呼び出され、衝突ハンドラーが呼び出される場所を変更するだけです。マウスドラッグイベント(上記の例のように)に基づいて交差をチェックするのではなく、代わりに各ノードの変更リスナー内の衝突をチェックしますboundsInParentProperty()

block.boundsInParentProperty().addListener((observable, oldValue, newValue) -> 
        checkShapeIntersection(block)
);

注:アニメーション化されているシェイプがたくさんある場合は、ゲームループ内でフレームごとに1回衝突をチェックする方が、ノードが移動するたびに衝突チェックを実行するよりも効率的です(上記のboundsInParentProperty変更リスナーで実行されます)。

非長方形の形状の入力を処理するための追加情報

衝突検出ではなく入力検出の場合、質問に直接関係しないためnode.pickOnBounds、非長方形ノードとのマウスまたはタッチの相互作用が必要な場合は、設定を確認してください。

于 2013-02-22T00:15:14.720 に答える