2013-02-21 86 views
12

我正在嘗試做一些碰撞檢測。對於這個測試,我使用簡單的長方形Shape,並檢查他們的Bound,以確定它們是否發生碰撞。雖然檢測不能按預期工作。我嘗試過使用不同的方法來移動對象(relocate,setLayoutX,Y),還有不同的綁定檢查(boundsInLocal,boundsInParrent等),但我仍然無法得到這個工作。正如你所看到的,檢測只適用於一個物體,即使你有三個物體只有一個物體檢測到碰撞。這是一些工作的代碼演示問題:檢查形狀與JavaFX的碰撞

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; 
    } 
} 
+2

試着玩弄我寫的這個[intersection demo application](https://gist.github.com/jewelsea/1441960)來演示JavaFX中各種邊界類型的交集關係。 – jewelsea 2013-02-21 23:11:47

+0

好吧,看起來我現在感興趣的所有內容都在該文件的第一課。我選擇的一件重要事情是用於檢查衝突的changeListener。在檢查上也使用LayoutBounds(??)。我應該使用setLayoutX還是translateX作爲矩形?我看到你正在使用setX,但那是私人的,我猜測,在doc上它不清楚哪一個是改變相同屬性的公共方法。 – Giannis 2013-02-21 23:34:23

+0

更新回答解決其他問題。 – jewelsea 2013-02-22 00:58:05

回答

23

看起來你在你的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; } 
} 

示例程序輸出。在示例中,圓圈被拖動,用戶正在拖動已標記爲與另一個圓圈相撞的圓圈(通過將其繪成藍色) - 爲了演示目的,僅當前正在拖動的圓圈標記了碰撞顏色。

collisions

基於其他問題

我張貼到intersection demo application在現有評論的鏈接

評論是示出使用各種邊界類型的,而不是作爲特定類型的碰撞檢測樣品的。對於您的用例,您不需要更改偵聽器的額外複雜性並檢查各種不同類型的邊界類型 - 只需要解決一種類型就足夠了。大多數碰撞檢測只會對可視邊界的交集感興趣,而不是其他JavaFX邊界類型,例如節點的佈局邊界或局部邊界。所以,你可以:

  1. 檢查的getBoundsInParent路口(如你在原來的問題所做的那樣),其上最小的矩形框,這將包括該節點的視覺四肢或
  2. 使用Shape.intersect(shape1, shape2)例行程序的工作原理您需要根據節點的視覺形狀而不是視覺形狀的邊界框進行檢查。

我應該使用setLayoutX或平移X爲layoutX和layoutY屬性旨在用於定位或鋪設節點矩形

translateX和translateY屬性用於臨時更改節點的可視位置(例如節點正在進行動畫時)。對於你的例子,雖然兩個屬性都可以工作,但使用佈局屬性可能比翻譯屬性更好,那樣的話,如果你想在節點上運行一個類似TranslateTransition的東西, end轉換值應該是這些值將相對於節點的當前佈局位置而不是父組中的位置。

另一種可以在樣本中使用這些佈局和座標轉換的方法是,如果您在拖動操作過程中有類似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) 
); 

注意:如果你有很多的形狀被設計爲動畫,然後game loop內爲每幀一次碰撞檢查會比運行碰撞檢查更有效,只要任何節點移動(如在boundsInParentProperty變化監聽器完成以上)。

+0

非常感謝您的幫助,它確實清除了一些概念。 – Giannis 2013-02-22 01:19:20

+0

仍然在這個項目上工作,我面臨着另一個問題。你能否建議比在scenebuilder中組合幾個形狀更好的方法,以創建類似於Scratch塊或Google Blocky的塊?問題在於將塊轉換​​爲適合其他人。 – Giannis 2013-03-20 17:51:17

+0

形狀組合是一個與原始不同的問題,形狀組合問題也不是那麼清晰,我建議提出一個新問題,提供更多描述和清晰的示例圖像,以展示您需要的某些形狀和形狀組合。 – jewelsea 2013-03-20 18:46:07