Jafafx: looping collision detection and moving objects - javafx

I'm having a difficult time making the program loop moveBall and collisionDetection so that the program always checks if there is an collision and to move the balls.
At this moment the program is just checking for collision during the first iteration, after that, it doesn't do anything.
The ideal scenario is to get the balls to always sence if they're colliding or not.
I've included the whole Ball class and the code for the start-button. I've thought about doing a while loop around this little part of code but i never managed to get it working.
for (Ball b : balls) {
b.moveBall();
b.collisionDetection(balls);
}
Help & suggestions are appreciated!
Ball Class
package fx;
import java.util.ArrayList;
import javax.print.attribute.standard.MediaSize.Other;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Shape;
import javafx.util.Duration;
public class Ball {
public int id;
public Circle circle;
public int team;
public Ball(int x, int y, int _id, int _team) {
id = _id;
Circle ball = new Circle(10, Color.BLACK);
ball.relocate(x, y);
circle = ball;
team = _team;
}
public void moveBall() {
System.out.println(this.team);
//team blue
if (this.team == 0) {
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(5),
new KeyValue(this.circle.layoutXProperty(),
980-((Circle)this.circle).getRadius())));
timeline.setAutoReverse(true);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}//team orange
else if (this.team == 1) {
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(5),
new KeyValue(this.circle.layoutXProperty(),
35-((Circle)this.circle).getRadius())));
timeline.setAutoReverse(true);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
}
public Circle getCircle() {
return this.circle;
}
//collision detection
public void collisionDetection(ArrayList < Ball > balls) {
boolean collisionDetected = false;
for (Ball ball : balls) {
System.out.println(ball.id + " vs " + this.id);
if (ball.id == this.id) {
collisionDetected = false;
continue;
} else if (ball.id != this.id) {
Shape intersect = Shape.intersect(this.circle, ball.circle);
if (intersect.getBoundsInLocal().getWidth() != -1) {
collisionDetected = true;
}
}
}
if (collisionDetected == true) {
circle.setFill(Color.BROWN);
System.out.println("colided");
} else {
collisionDetected = false;
}
}
}
GUI Class
startBtn.setOnAction(e ->{
startBtn.setDisable(true);
// Get text
// Do a check. Gör typkontroll
int ballCount = Integer.parseInt(NrInput.getText());
Random r = new Random();
int Low = 20;
int High = 420;
int id = 0;
//System.out.println(bounds.getMaxX());
for (int i = 0; i < ballCount; i++) {
Ball ball;
// Randomize Y
int y = r.nextInt(High-Low) + Low;
//every other ball gets in different teams
if (i % 2 == 0) {
ball = new Ball(35, y, id, 0);
} else {
ball = new Ball(965, y, id, 1);
}
balls.add(ball);
canvas.getChildren().add(ball.getCircle());
id = id + 1;
}
for (Ball b : balls) {
b.moveBall();
b.collisionDetection(balls);
}
startBtn.setDisable(false);
});
The program looks like this
But the balls that i've circled with red will not change color even though they will collide.

But the balls that i've circled with red will not change color even though they will collide.
The reason is that the color doesn't change is because Ball.collisionDetection() is evaluated once, and it is evaluated when the collision hasn't occurred yet.
You need to use a binding to make sure that this is re-evaluated whenever the balls are being moved by the Timeline.
public Ball(int x, int y, int _id, int _team) {
id = _id;
Circle ball = new Circle(10, Color.BLACK);
ball.relocate(x, y);
circle = ball;
team = _team;
// New codes here
ball.fillProperty().bind(Bindings.createObjectBinding(() -> {
if (ball.checkCollision(balls)) {
return Color.BROWN;
}
else {
return Color.BLACK;
}
}, ball.layoutXProperty()));
}
// In Ball class
public boolean checkCollision(List<Ball> allBalls) {
// Your collision logic
}
You can move where you would want to put the binding at, since your collision logic probably require you to pass in a list of other Ball.

Related

How to display Pane with non-rectangle shape in JavaFX?

I've met a problem in JavaFX.
This is what show in Jasperserver (A server), Adhoc-Model (One of a model of server).
Image: Jasperserver Adhoc Layout
Image: Inner HTML Code
As you can see, the pink area, is a type of HTML < li > Tag. It can display UI with a non-rectangle shape. Like Tetris?
However, I'am trying to do the same thing in JavaFX but failed.
I used a FlowPane as Pane A, and put some Label into Pane A. Then I used a little FlowPane as Pane B, also put some Label into Pane B. At last, I put Pane B into Pane A.
It's shown as following image.
Image: Shown in JavaFX
As you can see, I failed to display the Pane B with a non-rectangle shape. And I think it is not very easy to do that.
Can anybody help me ? Your answer will be appreciated. Thank you very much.
If anyone need my code, please tell me. I will rewrite some simple code and put on here.
Thanks #James_D Very Much.
I've written a custom pane which extends FlowPane, and overridden layoutChildren() function.
It works !
Following is my code:
import java.util.ArrayList;
import java.util.List;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
public class LayoutFlowPane extends FlowPane {
private class Child {
Node node;
double x, y, w, h;
double blft, midy;
public Child(Node node,
double x, double y, double w, double h,
double blft, double midy) {
super();
this.node = node;
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.blft = blft;
this.midy = midy;
}
}
private List<Child> children = new ArrayList<>();
private List<Double> midys = new ArrayList<>();
public LayoutFlowPane() {
super();
}
#Override protected void layoutChildren() {
super.layoutChildren();
Insets insets = getInsets();
double width = getWidth();
double height = getHeight();
double top = insets.getTop();
double left = insets.getLeft();
double bottom = insets.getBottom();
double right = insets.getRight();
double maxw = width - left - right;
double maxh = height - top - bottom;
double hgap = snapSpace(this.getHgap());
double vgap = snapSpace(this.getVgap());
storeChildren();
adjustChildren(maxw, maxh, hgap, vgap);
children.forEach(child -> {
layoutInArea(
child.node, child.x, child.y,
child.w, child.h, child.blft, getMargin(child.node),
getColumnHalignmentInternal(), getRowValignmentInternal()
);
});
}
private void storeChildren() {
children = new ArrayList<>();
for (int i = 0; i < getChildren().size(); i ++) {
Node child = getChildren().get(i);
double x = child.getLayoutX();
double y = child.getLayoutY();
double w = 0;
double h = 0;
if (child instanceof Region) {
Region region = (Region) child;
w = region.getWidth();
h = region.getHeight();
} else {
throw new UnknowTypeException("Unknow type found.");
}
double blft = child.getBaselineOffset();
double midy = (y + y + h) / 2;
Child newChild = new Child(child, x, y, w, h, blft, midy);
children.add(newChild);
}
midys = new ArrayList<>();
for (int i = 0; i < children.size(); i ++) {
Child child = children.get(i);
if (midys.isEmpty() || midys.get(midys.size()-1) != child.midy) {
midys.add(child.midy);
}
}
}
private void adjustChildren(double maxw, double maxh, double hgap, double vgap) {
for (int i = 1; i < children.size(); i ++) { // begin with 1
Child pc = children.get(i-1);
Child ch = children.get(i);
boolean bothGridPane = (pc.node instanceof GridPane) && (ch.node instanceof GridPane);
double x = pc.x + pc.w + (bothGridPane ? 0 : hgap);
double midy;
if (x + ch.w <= maxw) { // ThisLine
midy = pc.midy;
ch.x = x;
ch.y = gety(ch.y, ch.midy, midy);
ch.midy = midy;
} else { // NextLine
midy = getNextMidy(pc.midy);
ch.x = children.get(0).x;
ch.y = gety(ch.y, ch.midy, midy);
ch.midy = midy;
}
}
}
private double getNextMidy(double midy) {
for (int i = 0; i < midys.size(); i ++) {
if (i + 1 < midys.size() && midys.get(i) == midy && midys.get(i+1) != midy) {
return midys.get(i+1);
} else if (i + 1 == midys.size() && midys.get(i) == midy) {
return midys.get(i);
}
}
throw new NotFoundException("Next midy not found.");
}
private double gety(double oldy, double oldMidy, double newMidy) {
return oldy + (newMidy - oldMidy);
}
private HPos getColumnHalignmentInternal() {
HPos localPos = getColumnHalignment();
return null == localPos ? HPos.LEFT : localPos;
}
private VPos getRowValignmentInternal() {
VPos localPos = getRowValignment();
return null == localPos ? VPos.CENTER : localPos;
}
private class UnknowTypeException extends RuntimeException {
private static final long serialVersionUID = 1L;
public UnknowTypeException(String message) {
super(message);
}
}
private class NotFoundException extends RuntimeException {
private static final long serialVersionUID = 1L;
public NotFoundException(String message) {
super(message);
}
}
}

2D Camera in JavaFX?

I am creating a top-down game in JavaFX, but I'm having trouble with the implementation of a Camera that moves with the player.
My attempt at creating something like this was to instead of moving the player, move the scene in the opposite direction the player wanted to go. This created the illusion that the player was moving, but it required constant movement of all the objects in the scene which obviously created a ton of performance issues. So after this I made a clip, and put all the terrain nodes inside a clipped rectangle.
Below is my TerrainRenderer class that creates the clipped rectangle and the contents inside of it. What it does is take an image and then generate a bunch of rectangle nodes in order to make a map that looks like the image.
private static final Pane tileContainer = new Pane();
private static final Rectangle rectClip = new Rectangle();
private static void clipChildren(Region region) {
region.setClip(rectClip);
region.layoutBoundsProperty().addListener((ov, oldValue, newValue) -> {
rectClip.setWidth(newValue.getWidth());
rectClip.setHeight(newValue.getHeight());
});
}
private static void drawTile(int x, int y, Color color) {
final int TILE_SIZE = 15;
Rectangle tile = new Rectangle(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
tile.setFill(color);
tileContainer.getChildren().add(tile);
}
public static Region generate() {
final Image map = new Image("main/Images/IcJR6.png");
for (int x = 0; x < (int) map.getWidth(); x++) {
for (int y = 0; y < (int) map.getHeight(); y++) {
drawTile(x, y, map.getPixelReader().getColor(x, y));
}
}
tileContainer.setPrefSize(Main.getAppWidth(), Main.getAppHeight());
clipChildren(tileContainer);
return tileContainer;
}
public static Rectangle getRectClip() {
return rectClip;
}
What you see below is my update method for the player that uses a sprite sheet. As of now this code only translates the clip node, but not the contents inside.
void update() {
int speed;
if (Main.isPressed(KeyCode.SHIFT)) speed = 6;
else speed = 3;
if (Main.isPressed(KeyCode.W)) {
getAnimation().play();
getAnimation().setOffsetY(96);
moveY(speed);
} else if (Main.isPressed(KeyCode.S)) {
getAnimation().play();
getAnimation().setOffsetY(0);
moveY(-speed);
} else if (Main.isPressed(KeyCode.D)) {
getAnimation().play();
getAnimation().setOffsetY(64);
moveX(-speed);
} else if (Main.isPressed(KeyCode.A)) {
getAnimation().play();
getAnimation().setOffsetY(32);
moveX(speed);
} else getAnimation().stop();
}
#Override
protected void moveX(int x) {
boolean right = x > 0;
for(int i = 0; i < Math.abs(x); i++) {
if (right) TerrainRenderer.getRectClip().setTranslateX(TerrainRenderer.getRectClip().getTranslateX() + 1);
else TerrainRenderer.getRectClip().setTranslateX(TerrainRenderer.getRectClip().getTranslateX() - 1);
}
}
#Override
protected void moveY(int y) {
boolean down = y > 0;
for (int i = 0; i < Math.abs(y); i++) {
if (down) TerrainRenderer.getRectClip().setTranslateY(TerrainRenderer.getRectClip().getTranslateY() + 1);
else TerrainRenderer.getRectClip().setTranslateY(TerrainRenderer.getRectClip().getTranslateY() - 1);
}
}
The result I want would look something like this (skip to 6:10), but how would I make something like this in JavaFX instead? Any suggestions?
You haven't posted a minimal, complete, and verifiable example showing what the actual problem is, so your question is difficult to answer completely.
I would approach something like this by drawing the background (e.g. on a canvas), and putting it in a pane with the moving parts (player, by the sounds of your description). Then show just a portion of the background by clipping and translating the pane.
Here's a very quick example; it just puts some random small rectangles on a large canvas and then moves a blue rectangle (player) around the scene on pressing the cursor (arrow) keys. The clip and translation of the main pane are bound to the player's position so the player always appears in the center, except when you get close to the edges of the pane.
This takes a little time to start up, and for some reason I sometimes see a blank screen until I have moved the player a couple of places; I didn't spend too much time on niceties so there may be some little bugs in there.
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class ScrollAndClipBackground extends Application {
private final int tileSize = 10 ;
private final int numTilesHoriz = 500 ;
private final int numTilesVert = 500 ;
private final int speed = 400 ; // pixels / second
private boolean up ;
private boolean down ;
private boolean left ;
private boolean right ;
private final int numFilledTiles = numTilesHoriz * numTilesVert / 8 ;
#Override
public void start(Stage primaryStage) {
Pane pane = createBackground();
Rectangle player = new Rectangle(numTilesHoriz*tileSize/2, numTilesVert*tileSize/2, 10, 10);
player.setFill(Color.BLUE);
pane.getChildren().add(player);
Scene scene = new Scene(new BorderPane(pane), 800, 800);
Rectangle clip = new Rectangle();
clip.widthProperty().bind(scene.widthProperty());
clip.heightProperty().bind(scene.heightProperty());
clip.xProperty().bind(Bindings.createDoubleBinding(
() -> clampRange(player.getX() - scene.getWidth() / 2, 0, pane.getWidth() - scene.getWidth()),
player.xProperty(), scene.widthProperty()));
clip.yProperty().bind(Bindings.createDoubleBinding(
() -> clampRange(player.getY() - scene.getHeight() / 2, 0, pane.getHeight() - scene.getHeight()),
player.yProperty(), scene.heightProperty()));
pane.setClip(clip);
pane.translateXProperty().bind(clip.xProperty().multiply(-1));
pane.translateYProperty().bind(clip.yProperty().multiply(-1));
scene.setOnKeyPressed(e -> processKey(e.getCode(), true));
scene.setOnKeyReleased(e -> processKey(e.getCode(), false));
AnimationTimer timer = new AnimationTimer() {
private long lastUpdate = -1 ;
#Override
public void handle(long now) {
long elapsedNanos = now - lastUpdate ;
if (lastUpdate < 0) {
lastUpdate = now ;
return ;
}
double elapsedSeconds = elapsedNanos / 1_000_000_000.0 ;
double deltaX = 0 ;
double deltaY = 0 ;
if (right) deltaX += speed ;
if (left) deltaX -= speed ;
if (down) deltaY += speed ;
if (up) deltaY -= speed ;
player.setX(clampRange(player.getX() + deltaX * elapsedSeconds, 0, pane.getWidth() - player.getWidth()));
player.setY(clampRange(player.getY() + deltaY * elapsedSeconds, 0, pane.getHeight() - player.getHeight()));
lastUpdate = now ;
}
};
primaryStage.setScene(scene);
primaryStage.show();
timer.start();
}
private double clampRange(double value, double min, double max) {
if (value < min) return min ;
if (value > max) return max ;
return value ;
}
private void processKey(KeyCode code, boolean on) {
switch (code) {
case LEFT:
left = on ;
break ;
case RIGHT:
right = on ;
break ;
case UP:
up = on ;
break ;
case DOWN:
down = on ;
break ;
default:
break ;
}
}
private Pane createBackground() {
List<Integer> filledTiles = sampleWithoutReplacement(numFilledTiles, numTilesHoriz * numTilesVert);
Canvas canvas = new Canvas(numTilesHoriz * tileSize, numTilesVert * tileSize);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.GREEN);
Pane pane = new Pane(canvas);
pane.setMinSize(numTilesHoriz * tileSize, numTilesVert * tileSize);
pane.setPrefSize(numTilesHoriz * tileSize, numTilesVert * tileSize);
pane.setMaxSize(numTilesHoriz * tileSize, numTilesVert * tileSize);
for (Integer tile : filledTiles) {
int x = (tile % numTilesHoriz) * tileSize ;
int y = (tile / numTilesHoriz) * tileSize ;
gc.fillRect(x, y, tileSize, tileSize);
}
return pane ;
}
private List<Integer> sampleWithoutReplacement(int sampleSize, int populationSize) {
Random rng = new Random();
List<Integer> population = new ArrayList<>();
for (int i = 0 ; i < populationSize; i++)
population.add(i);
List<Integer> sample = new ArrayList<>();
for (int i = 0 ; i < sampleSize ; i++)
sample.add(population.remove(rng.nextInt(population.size())));
return sample;
}
public static void main(String[] args) {
launch(args);
}
}
A more complex approach, which would be less memory intensive, would be a "tiling" mechanism where the main view consists of a number of tiles which are moved, and created as needed on demand. This is more complex but allows for essentially arbitrary-sized scenes.

How to position node inside a rotated group at mouse event coordinates?

Given 2D scene with a node inside a group which contains a 2d rotate transformation. How do I position the node inside the group to the scene x and y coordinates of the mouse upon click?
The node that I am trying to move to the position of the click event is a circle which is located inside a group that has been rotated. The rotation happens at a pivot at the upper right corner of the group. The group has other nodes in it too.
I have been fiddling trying to achieve this for a while with no luck. It just does not position the node at the place where the click happened if the parent of the node is rotated. I have tried various techniques including the localToScene bounds with no luck.
Is there a way to do this? Thank you for your time =)
Here is some code showing a minimum verifiable example of the problem. Run it for a demo
You can drag the circle and select circles with mouse clicks. Do this to see it works fine as long as the group is not rotated.
In order to rotate the group use the left and right direction keys on your keyboard. After the group has been rotated the dragging and the mouse coordinates are no longer accurate!
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javafx.animation.FadeTransition;
import javafx.animation.ParallelTransition;
import javafx.animation.ScaleTransition;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Duration;
public class DemoBounds extends Application {
private static final int WIDTH = 600;
private static final int HEIGHT = 700;
private static final int CIRCLE_COUNT = 12;
private static final int RECTANGLE_COUNT = 3;
private static final int CIRCLE_DISTANCE = 150;
private static final int RECTANGLE_DISTANCE = 20;
private Color selectedColor = Color.RED;
private Color normalColor = Color.YELLOW;
private Rotate rotator = new Rotate();
private List<Circle> circles = new ArrayList<>();
private List<Rectangle> rectangles = new ArrayList<>();
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage stage) {
Rotate rotate = new Rotate();
Group root = new Group();
Pane pane = new Pane(root);
createRectangles();
createCircles();
root.getChildren().addAll(rectangles);
root.getChildren().addAll(circles);
root.getTransforms().add(rotate);
Scene scene = new Scene(pane, WIDTH, HEIGHT, Color.BLACK);
AddRotateControls(root);
assignActionHandling(pane);
stage.sizeToScene();
stage.setScene(scene);
stage.setTitle("Example");
stage.show();
}
private void AddRotateControls(Group root) {
root.getTransforms().add(rotator);
rotator.setPivotX(150);
rotator.setPivotY(150);
rotator.setAngle(0);
root.getScene().setOnKeyPressed(e -> {
switch(e.getCode()){
case RIGHT:
rotator.setAngle(rotator.getAngle() + 1);
break;
case LEFT:
rotator.setAngle(rotator.getAngle() - 1);
break;
default:
break;
}
});
}
private void assignActionHandling(Pane pane) {
pane.setOnMousePressed(e -> {
Circle circle = new Circle(e.getSceneX(), e.getSceneY(), 1, Color.DEEPSKYBLUE);
pane.getChildren().add(circle);
Duration duration = Duration.millis(350);
ScaleTransition scale = new ScaleTransition(duration, circle);
FadeTransition fade = new FadeTransition(duration, circle);
ParallelTransition pack = new ParallelTransition(circle, scale, fade);
scale.setFromX(1);
scale.setFromY(1);
scale.setToX(20);
scale.setToY(20);
fade.setFromValue(1);
fade.setToValue(0);
pack.setOnFinished(e2 -> {
pane.getChildren().remove(circle);
});
pack.play();
Circle selected = circles.stream().filter(c -> ((CircleData) c.getUserData()).isSelected()).findFirst().orElse(null);
if (selected != null) {
selected.setCenterX(e.getSceneX());
selected.setCenterY(e.getSceneY());
}
});
}
private void createRectangles() {
int width = 100;
int height = HEIGHT / 3;
int startX = ((WIDTH / 2) - (((width / 2) * 3) + (RECTANGLE_DISTANCE * 3))) + (RECTANGLE_DISTANCE * 2);
int startY = (HEIGHT / 2) - (height / 2);
for(int i = 0; i<RECTANGLE_COUNT; i++){
Rectangle rect = new Rectangle();
rect.setFill(Color.MEDIUMTURQUOISE);
rect.setWidth(width);
rect.setHeight(height);
rect.setX(startX);
rect.setY(startY);
rectangles.add(rect);
startX += (width + RECTANGLE_DISTANCE);
}
}
private void createCircles() {
Random randon = new Random();
int centerX = WIDTH / 2;
int centerY = HEIGHT / 2;
int minX = centerX - CIRCLE_DISTANCE;
int maxX = centerX + CIRCLE_DISTANCE;
int minY = centerY - CIRCLE_DISTANCE;
int maxY = centerY + CIRCLE_DISTANCE;
int minRadius = 10;
int maxRadius = 50;
for (int i = 0; i < CIRCLE_COUNT; i++) {
int x = minX + randon.nextInt(maxX - minX + 1);
int y = minY + randon.nextInt(maxY - minY + 1);
int radius = minRadius + randon.nextInt(maxRadius - minRadius + 1);
Circle circle = new Circle(x, y, radius, Color.ORANGE);
circle.setStroke(normalColor);
circle.setStrokeWidth(5);
circle.setUserData(new CircleData(circle, i, false));
circles.add(circle);
}
assignCircleActionHandling();
}
private double mouseX;
private double mouseY;
private void assignCircleActionHandling() {
for (Circle circle : circles) {
circle.setOnMousePressed(e -> {
mouseX = e.getSceneX() - circle.getCenterX();
mouseY = e.getSceneY() - circle.getCenterY();
((CircleData) circle.getUserData()).setSelected(true);
unselectRest(((CircleData) circle.getUserData()).getId());
});
circle.setOnMouseDragged(e -> {
double deltaX = e.getSceneX() - mouseX;
double deltaY = e.getSceneY() - mouseY;
circle.setCenterX(deltaX);
circle.setCenterY(deltaY);
});
circle.setOnMouseReleased(e -> {
e.consume();
});
}
}
private void unselectRest(int current) {
circles.stream().filter(c -> ((CircleData) c.getUserData()).getId() != current).forEach(c -> {
((CircleData) c.getUserData()).setSelected(false);
});
}
public class CircleData {
private int id;
private boolean selected;
private Circle circle;
public CircleData(Circle circle, int id, boolean selected) {
super();
this.id = id;
this.circle = circle;
this.selected = selected;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean selected) {
this.selected = selected;
if (selected) {
circle.setStroke(selectedColor);
} else {
circle.setStroke(normalColor);
}
}
}
}
You don't give the details of your code but there may be a problem with the pivot of your rotation. This can drive you nuts if you try to understand the rotation behaviour in some cases if you are not aware of this mechanism. Every time when you move some nodes which are attached to your group, this pivot for the rotation is recomputed which can result in unwanted effects although in some cases it is just what you want.
If you want to have full control of your rotation you should use some code similar to the one described here: http://docs.oracle.com/javafx/8/3d_graphics/overview.htm
Update:
In your method assignActionHandling modify these few lines. In order for this to work you somehow have to make root available there.
if (selected != null) {
Point2D p = root.sceneToLocal(e.getSceneX(), e.getSceneY());
selected.setCenterX(p.getX());
selected.setCenterY(p.getY());
}
The reason for you problem is that you are mixing up coordinate systems. The center points of your circles are defined relative to the root coordinate system but that is rotated with respect to pane as well as the scene. So you have to transform the scene coordinates into the local root coordinates before you set the new center of the circle.

How can I make a ball move up or down in JavaFX?

I have javafx gui exercise to do and I have to make the ball move either up or down or right and left. Right now the ball goes randomly anywhere I suppose. Can you guys help me with the code? So that when I press the plus button, a ball will be added and goes up and down or right and left. And any other ball added has to shift either up or down? Any help would be awesome.Thanks.
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollBar;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.util.Duration;
public class MultipleBounceBall 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: black");
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, 350, 450);
primaryStage.setTitle("Multiple Bounce Ball"); // 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.play(); // Start animation
}
public void add() {
Color color = new Color(Math.random(),
Math.random(), Math.random(), 0.5);
getChildren().add(new Ball(30, 30, 20, color));
}
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());
}
}
}
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);
}
}
The problem is that the displacement elements of the ball movement (dx and dy) are both assigned to 1. Thus the ball moves to both x and y directions i.e. diagonally.
Change the declaration of these variables to:
private double dx = 1, dy = 0; // The ball will move right
private double dx = -1, dy = 0; // The ball will move left
private double dx = 0, dy = 1; // The ball will move down
private double dx = 0, dy = -1; // The ball will move up
If you would like the balls to move in random directions, you can do it for example by changing your Ball class:
class Ball extends Circle {
private double dx;
private double dy;
Ball(double x, double y, double radius, Color color) {
super(x, y, radius);
setFill(color); // Set ball color
switch (ThreadLocalRandom.current().nextInt(0, 4)) {
case 0:
dx = 0;
dy = 1;
break;
case 1:
dx = 0;
dy = -1;
break;
case 2:
dx = 1;
dy = 0;
break;
case 3:
dx = -1;
dy = 0;
break;
}
}
}
And if you would like them to start from random locations, you can do it by changing your MultipleBallPane.add() method:
public void add() {
Color color = new Color(Math.random(),
Math.random(), Math.random(), 0.5);
int radius = 20;
int x = ThreadLocalRandom.current().nextInt(radius, (int) getWidth() - radius);
int y = ThreadLocalRandom.current().nextInt(radius, (int) getHeight() - radius);
getChildren().add(new Ball(x, y, radius, color));
}

Need help inserting buttons into Tic Tac Toe game

I have a tic tac toe game that is made for us but I have to insert a button or two so the user can select if they want to be an X or and O. Can you guys help me modify this code please? I am unsure of how to do this because every time I try to add something it doesn't compile. Your help is much appreciated.
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Ellipse;
public class TicTacToe extends Application {
private char whoseTurn = 'X';
private Cell[][] cell = new Cell[3][3];
private Label lblStatus = new Label("X's turn to play");
#Override
public void start(Stage primaryStage) {
GridPane pane = new GridPane();
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
pane.add(cell[i][j] = new Cell(), j, i);
BorderPane borderPane = new BorderPane();
borderPane.setCenter(pane);
borderPane.setBottom(lblStatus);
Scene scene = new Scene(borderPane, 550, 470);
primaryStage.setTitle("Wild TicTacToe");
primaryStage.setScene(scene);
primaryStage.show();
primaryStage.setResizable(false);
}
/** Determine if the cell are all occupied */
public boolean isFull() {
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
if (cell[i][j].getToken() == ' ')
return false;
return true;
}
/** Determine if the player with the specified token wins */
public boolean isWon(char token) {
for (int i = 0; i < 3; i++)
if (cell[i][0].getToken() == token
&& cell[i][1].getToken() == token
&& cell[i][2].getToken() == token) {
return true;
}
for (int j = 0; j < 3; j++)
if (cell[0][j].getToken() == token
&& cell[1][j].getToken() == token
&& cell[2][j].getToken() == token) {
return true;
}
if (cell[0][0].getToken() == token
&& cell[1][1].getToken() == token
&& cell[2][2].getToken() == token) {
return true;
}
if (cell[0][2].getToken() == token
&& cell[1][1].getToken() == token
&& cell[2][0].getToken() == token) {
return true;
}
return false;
}
public class Cell extends Pane {
private char token = ' ';
public Cell() {
setStyle("-fx-border-color: black");
this.setPrefSize(2000, 2000);
this.setOnMouseClicked(e -> handleMouseClick());
}
/** Return token */
public char getToken() {
return token;
}
/** Set a new token */
public void setToken(char c) {
token = c;
if (token == 'X') {
Line line1 = new Line(10, 10,
this.getWidth() - 10, this.getHeight() - 10);
line1.endXProperty().bind(this.widthProperty().subtract(10));
line1.endYProperty().bind(this.heightProperty().subtract(10));
Line line2 = new Line(10, this.getHeight() - 10,
this.getWidth() - 10, 10);
line2.startYProperty().bind(
this.heightProperty().subtract(10));
line2.endXProperty().bind(this.widthProperty().subtract(10));
// Add the lines to the pane
this.getChildren().addAll(line1, line2);
}
else if (token == 'O') {
Ellipse ellipse = new Ellipse(this.getWidth() / 2,
this.getHeight() / 2, this.getWidth() / 2 - 10,
this.getHeight() / 2 - 10);
ellipse.centerXProperty().bind(
this.widthProperty().divide(2));
ellipse.centerYProperty().bind(
this.heightProperty().divide(2));
ellipse.radiusXProperty().bind(
this.widthProperty().divide(2).subtract(10));
ellipse.radiusYProperty().bind(
this.heightProperty().divide(2).subtract(10));
ellipse.setStroke(Color.BLACK);
ellipse.setFill(Color.WHITE);
getChildren().add(ellipse); // Add the ellipse to the pane
}
}
/* Handle a mouse click event */
private void handleMouseClick() {
// If cell is empty and game is not over
if (token == ' ' && whoseTurn != ' ') {
setToken(whoseTurn); // Set token in the cell
// Check game status
if (isWon(whoseTurn)) {
lblStatus.setText(whoseTurn + " won! The game is over.");
whoseTurn = ' '; // Game is over
}
else if (isFull()) {
lblStatus.setText("Draw! The game is over.");
whoseTurn = ' '; // Game is over
}
else {
// Change the turn
whoseTurn = (whoseTurn == 'X') ? 'O' : 'X';
// Display whose turn
lblStatus.setText(whoseTurn + "'s turn.");
}
}
}
}
/**
* 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);
}
}
Can't really see why adding a button would be a problem when you've already created this program using fairly advanced JavaFX code.
Just see the Button documentation on how to create a Button. Then add your button(s) to e.g. a HBox that you then add to your BorderPane:
Button xButton = new Button("X");
Button obutton = new Button("O");
HBox buttonBar = new HBox();
buttonBar.getChildren().addAll(xButton, oButton);
borderPane.setTop(buttonBar);
With that said. In your scenario, maybe a ToggleButton would be a better option. It allows the button to be selected, which would help in providing some visual feedback that O or X has been chosen properly.

Resources