I have to detect when two "balls" collide in a javaFX program. Each time a button is clicked, a new ball is added to the pane. I know that getChildren() returns an observable list that contains the node for each ball, and when I print the list with two circles it will print, for example, Circle[centerX=30.0, centerY=30.0, radius=20.0, fill=0x9ac26780], Circle[centerX=224.0, centerY=92.0, radius=20.0, fill=0x9ac26780]
My idea was to use nested loops to compare the (x,y) coordinates of each ball to every other ball. How do I access centerX and centerY from each Circle in order to compare them? I tried getChildren().sublist(0,0); thinking that I would get the centerX value for the first element, but that does not work. I also tried getCenterX, because Ball extends Circle, but that also failed. Thanks for your time.
public class Exercise20_05 extends Application {
@Override // Override the start method in the Application class
public void start(Stage primaryStage) {
MultipleBallPane ballPane = new MultipleBallPane();
ballPane.setStyle("-fx-border-color: yellow");
Button btAdd = new Button("+");
Button btSubtract = new Button("-");
HBox hBox = new HBox(10);
hBox.getChildren().addAll(btAdd, btSubtract);
hBox.setAlignment(Pos.CENTER);
// Add or remove a ball
btAdd.setOnAction(e -> ballPane.add());
btSubtract.setOnAction(e -> ballPane.subtract());
// Pause and resume animation
ballPane.setOnMousePressed(e -> ballPane.pause());
ballPane.setOnMouseReleased(e -> ballPane.play());
// Use a scroll bar to control animation speed
ScrollBar sbSpeed = new ScrollBar();
sbSpeed.setMax(20);
sbSpeed.setValue(10);
ballPane.rateProperty().bind(sbSpeed.valueProperty());
BorderPane pane = new BorderPane();
pane.setCenter(ballPane);
pane.setTop(sbSpeed);
pane.setBottom(hBox);
// Create a scene and place the pane in the stage
Scene scene = new Scene(pane, 250, 150);
primaryStage.setTitle("MultipleBounceBall"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
}
private class MultipleBallPane extends Pane {
private Timeline animation;
public MultipleBallPane() {
// Create an animation for moving the ball
animation = new Timeline(
new KeyFrame(Duration.millis(50), e -> moveBall()));
animation.setCycleCount(Timeline.INDEFINITE); //animation will play indefinitely
animation.play(); // Start animation
}
public void add() {
Color color = new Color(Math.random(),
Math.random(), Math.random(), 0.5);
//creates a new Ball at (30, 30) with a radius of 20
getChildren().add(new Ball(30, 30, 20, color));
ballCollision();
}
public void subtract() {
if (getChildren().size() > 0) {
getChildren().remove(getChildren().size() - 1);
}
}
public void play() {
animation.play();
}
public void pause() {
animation.pause();
}
public void increaseSpeed() {
animation.setRate(animation.getRate() + 0.1);
}
public void decreaseSpeed() {
animation.setRate(
animation.getRate() > 0 ? animation.getRate() - 0.1 : 0);
}
public DoubleProperty rateProperty() {
return animation.rateProperty();
}
protected void moveBall() {
for (Node node: this.getChildren()) {
Ball ball = (Ball)node;
// Check boundaries
if (ball.getCenterX() < ball.getRadius() ||
ball.getCenterX() > getWidth() - ball.getRadius()) {
ball.dx *= -1; // Change ball move direction
}
if (ball.getCenterY() < ball.getRadius() ||
ball.getCenterY() > getHeight() - ball.getRadius()) {
ball.dy *= -1; // Change ball move direction
}
// Adjust ball position
ball.setCenterX(ball.dx + ball.getCenterX());
ball.setCenterY(ball.dy + ball.getCenterY());
ballCollision();
}
}
//check for ball collisions
protected void ballCollision() {
/*System.out.println(getChildren().size());
getChildren returns an observableList; this observableList is what
keeps track of the balls (specifically, the nodes added to ballPane)
added each time the + button is clicked
*/
ObservableList ballList = getChildren();
System.out.println(ballList.get(0));
//if there are 2 or more balls, check for collision
if (ballList.size() > 1) {
//compare each (x,y) coordinate value to every other (x,y) value
for (int i = 0; i < ballList.size(); i++) {
for (int k = 0; k < ballList.size(); k++) {
// if (ballList.sublist(i,i) < 1) {
//
// }
}
}
}
}
}
class Ball extends Circle {
private double dx = 1, dy = 1;
Ball(double x, double y, double radius, Color color) {
super(x, y, radius);
setFill(color); // Set ball color
}
}
/**
* The main method is only needed for the IDE with limited
* JavaFX support. Not needed for running from the command line.
*/
public static void main(String[] args) {
launch(args);
}
}
Edit: Thanks to a couple of people, I was able to get the collision check to work. One ball will get removed, but I get the ConcurrentModificationException. Here is the updated method:
protected void ballCollision() {
ObservableList ballList = getChildren();
//if there are 2 or more balls, check for collision
if (ballList.size() > 1) {
//compare each (x,y) coordinate value to every other (x,y) value
for (int i = 0; i < ballList.size(); i++) {
for (int k = i + 1; k < ballList.size(); k++) {
Circle c1 = (Circle) ballList.get(i);
Circle c2 = (Circle) ballList.get(k);
if ((c1.getCenterX() <= c2.getCenterX() * 1.10 &&
(c1.getCenterX() >= c2.getCenterX()*.90)) &&
((c1.getCenterY() <= c2.getCenterY() * 1.10) &&
c1.getCenterY() >= c2.getCenterY() * .90)){
ballList.remove(c2);
}
}
}
}
}
Final Edit: Thanks to David Wallace for taking his time to help me. The issue was that I was calling ballCollision inside the for-each loop of the moveBall method. Once I moved it outside the loop, it worked perfectly.
Circle
s, just doballList.get(i)
, cast to aCircle
and callgetCenterX()
, etc. – James_D