Okay so I have a custom pane class that I want to reference from inside FXML. When I try to change the node class to BallPane, it says it doesn't exist.
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.util.Duration;
public class BallPane extends Pane {
public final double radius = 20;
private double x = radius, y = radius;
private double dx = 1, dy = 1;
private Circle circle = new Circle(x, y, radius);
private Timeline animation;
public BallPane() {
circle.setFill(Color.GREEN);
getChildren().add(circle);
animation = new Timeline(
new KeyFrame(Duration.millis(50), e -> moveBall()));
animation.setCycleCount(Timeline.INDEFINITE);
animation.play(); // Start animation
}
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() {
// Check boundaries
if (x < radius || x > getWidth() - radius) {
dx *= -1; // Change ball move direction
}
if (y < radius || y > getHeight() - radius) {
dy *= -1; // Change ball move direction
}
// Adjust ball position
x += dx;
y += dy;
circle.setCenterX(x);
circle.setCenterY(y);
}
}
I have tried this nested inside my FXML
<BallPane layoutX="14.0" layoutY="14.0" prefHeight="334.0" prefWidth="462.0" />
and then this in my Controller
#FXML
BallPane bpane;
What am I missing?
Related
I am using JavaFX and AnimationTimer to get a circle to move to a random location triggered by clicking the circle. The issue I am seeing is that the circle will not go toward the the top-right or bottom-right of the screen, I am not sure why.
Here is my code:
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.layout.*;
import javafx.scene.shape.*;
import javafx.stage.*;
import javafx.scene.paint.*;
import java.util.*;
import javafx.scene.*;
import javafx.animation.AnimationTimer;
public class MovingCircle extends Application {
double WIDTH = 800;
double HEIGHT = WIDTH;
class TheCircle extends Circle {
public TheCircle(){
super();
setFill(Color.GREEN);
setCenterX(WIDTH / 2);setCenterY(HEIGHT / 2);
setRadius(50);
setOnMouseClicked(event -> {
double newX = random(50, HEIGHT - 50); double newY = random(50, WIDTH - 50);
move(newX, newY);
});
}
void move(double newX, double newY){
double xVelocity;
double yVelocity;
if (newX == getCenterX()){
xVelocity = 0;
}else {
xVelocity = getCenterX() < newX ? 1 : -1;
}
if (newY == getCenterY()){
yVelocity = 0;
}else {
yVelocity = getCenterY() < newY ? 1 : -1;
}
AnimationTimer h = new AnimationTimer() {
#Override
public void handle(long l) {
setCenterY(getCenterX() + xVelocity);
setCenterX(getCenterY() + yVelocity);
//System.out.println(getCenterX() + ", " + getCenterY() + " ; " + newX + ", " + newY);
if (getCenterX() == newX && getCenterY() == newY){
this.stop();
}
}
};
System.out.println("begin!");
h.start();
}
}
public void start(Stage stage){
Pane root = new Pane();
TheCircle mover = new TheCircle();
root.getChildren().add(mover);
stage.setScene(new Scene(root, WIDTH, HEIGHT));
stage.show();
}
public static void main(String[] args){
launch(args);
}
double random(double min, double max){
return Math.floor(Math.random() * (max - min + 1) + min);
}
}
It seems that if I force the xVelocity and yVelocity to animate the circle toward those areas the circle either just stalls or the window freezes up. How can I fix this issue?
In the JavaFX code below outputs two vertex(circles) with a directed edge(line and arrow) from the source to the target. However, the arrow is always the center of the circle. I want the arrow to always point at the edge of the circle when dragged or not.
main class(TestArrow.java):
import java.util.ArrayList;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class TestArrow extends Application
{
int startX = 20,
startY = 20,
endX = 200,
endY = 200;
ArrayList<ArrowSecond> arrows = new ArrayList<>();
#Override
public void start(Stage primaryStage)
{
AnchorPane root = new AnchorPane();
Line line = new Line(startX, startY, endX, endY);
line.setStrokeWidth(3);
line.setStroke(Color.BLACK);
AnchorSecond start = new AnchorSecond(Color.BLACK, line.startXProperty(), line.startYProperty(), this);
AnchorSecond end = new AnchorSecond(Color.BLACK, line.endXProperty(), line.endYProperty(), this);
double[] points = {0.0, 10.0, -10.0, -10.0, 10.0, -10.0};
arrows.add(new ArrowSecond(points, line));
root.getChildren().addAll(arrows);
root.getChildren().addAll(line, start, end);
primaryStage.setScene(new Scene(root, 500, 500));
primaryStage.show();
}
public ArrayList<ArrowSecond> getArrows(){
return arrows;
}
public static void main(String[] args){
launch(args);
}
}
Arrow class(ArrowSecond.java):
import javafx.scene.shape.Line;
import javafx.scene.shape.Polygon;
public class ArrowSecond extends Polygon
{
private Line line;
public ArrowSecond(double[] points, Line line)
{
super(points);
this.line = line;
initialize();
}
private void initialize()
{
double angle = Math.atan2(line.getEndY() - line.getStartY(), line.getEndX() - line.getStartX()) * 180 / 3.14;
setRotate(angle - 90);
setTranslateX(line.getStartX());
setTranslateY(line.getStartY());
setTranslateX(line.getEndX());
setTranslateY(line.getEndY());
}
public void update(){
initialize();
}
}
vertex class(AnchorSecond.java):
import javafx.beans.property.DoubleProperty;
import javafx.event.EventHandler;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.StrokeType;
public class AnchorSecond extends Circle
{
private double x, y;
TestArrow app;
public AnchorSecond(Color color, DoubleProperty xx, DoubleProperty yy, TestArrow app)
{
// x point y point radius
super(xx.get(), yy.get(), 12);
this.app = app;
setFill(color.deriveColor(1, 1, 1, 0.5));
setStroke(color);
setStrokeWidth(2);
setStrokeType(StrokeType.OUTSIDE);
xx.bind(centerXProperty());
yy.bind(centerYProperty());
setOnMousePressed(new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent mouseEvent)
{
x = getCenterX() - mouseEvent.getX();
y = getCenterY() - mouseEvent.getY();
}
});
setOnMouseDragged(new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent event)
{
setCenterX(event.getSceneX() - x);
setCenterY(event.getSceneY() - y);
// update arrow positions when circles are dragged
for(ArrowSecond arrow : app.getArrows())
arrow.update();
}
});
}
}
Here is my solution:
I subtracted from the end X and end Y coordinates based on the radius of your nodes. The math is pretty simple - I just subtracted from the hypotenuse as a ratio.
TestArrow class:
public class TestArrow extends Application {
int startX = 20, startY = 20, endX = 200, endY = 200;
ArrayList<ArrowSecond> arrows = new ArrayList<>();
#Override
public void start(Stage primaryStage)
{
double radius = 12;
AnchorPane root = new AnchorPane();
Line line = new Line(startX, startY, endX, endY);
line.setStrokeWidth(3);
line.setStroke(Color.BLACK);
AnchorSecond start = new AnchorSecond(Color.BLACK, line.startXProperty(), line.startYProperty(), radius, this);
AnchorSecond end = new AnchorSecond(Color.BLACK, line.endXProperty(), line.endYProperty(), radius, this);
double[] points = {0.0, 10.0, -10.0, -10.0, 10.0, -10.0};
arrows.add(new ArrowSecond(points, line, radius));
root.getChildren().addAll(arrows);
root.getChildren().addAll(start, end, line);
primaryStage.setScene(new Scene(root, 500, 500));
primaryStage.show();
}
public ArrayList<ArrowSecond> getArrows(){
return arrows;
}
public static void main(String[] args){
launch(args);
}
}
ArrowSecond class:
public class ArrowSecond extends Polygon {
private Line line;
double radius;
public ArrowSecond(double[] points, Line line, double AnchorRadius) {
super(points);
this.line = line;
this.radius = AnchorRadius * 2;
initialize();
}
private void initialize() {
double angle = Math.atan2(line.getEndY() - line.getStartY(), line.getEndX() - line.getStartX()) * 180 / 3.14;
double height = line.getEndY() - line.getStartY();
double width = line.getEndX() - line.getStartX();
double length = Math.sqrt(Math.pow(height, 2) + Math.pow(width, 2));
double subtractWidth = radius * width / length;
double subtractHeight = radius * height / length;
setRotate(angle - 90);
setTranslateX(line.getStartX());
setTranslateY(line.getStartY());
setTranslateX(line.getEndX() - subtractWidth);
setTranslateY(line.getEndY() - subtractHeight);
}
public void update(){
initialize();
}
}
AnchorSecond class:
public class AnchorSecond extends Circle {
private double x, y;
TestArrow app;
public AnchorSecond(Color color, DoubleProperty xx, DoubleProperty yy, double radius, TestArrow app) {
// x point y point radius
super(xx.get(), yy.get(), radius);
this.app = app;
setFill(color.deriveColor(1, 1, 1, 0.5));
setStroke(color);
setStrokeWidth(2);
setStrokeType(StrokeType.OUTSIDE);
xx.bind(centerXProperty());
yy.bind(centerYProperty());
setOnMousePressed(new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent mouseEvent)
{
x = getCenterX() - mouseEvent.getX();
y = getCenterY() - mouseEvent.getY();
}
});
setOnMouseDragged(new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent event)
{
setCenterX(event.getSceneX() - x);
setCenterY(event.getSceneY() - y);
// update arrow positions when circles are dragged
for(ArrowSecond arrow : app.getArrows())
arrow.update();
}
});
}
}
Sorry for bad formatting- first time answering a SO question
I am trying to build a game myself using JavaFX. It features a circle that must follow my cursor (using a smooth translation animation, so it will not go directly to the location of my cursor)
For now I have written this piece of code
root.setOnMouseMoved(event -> {
TranslateTransition tt = new TranslateTransition(Duration.millis(2000), circle);
x[0] = event.getSceneX();
y[0] = event.getSceneY();
location.setText(x[0] + ", " + y[0]);
if (x[0] != oldX[0] || y[0] != oldY[0]) {
tt.stop();
tt.setToX(event.getSceneX());
tt.setToY(event.getSceneY());
oldX[0] = x[0];
oldY[0] = y[0];
}
tt.play();
});
The location.setText(..) is just a label to see whether the coords are recognized by the program. And in fact they are: for each pixel my cursor moves it updated these numbers instantly.
However, my circle will only go to the location of my cursor when it does not move. I want the shape to follow my cursor on the go as well but it just won't.
So my problem is this: how can I make my circle follow my mouse, even when it is moving?
Use an AnimationTimer for the movement. You may also want to check Mike's Blog about a usage example.
Read The Nature of Code by Daniel Shiffman, especially the Chapter Vectors, 1.10 Interactivity with Acceleration. The webpage has a running example, easily to convert to JavaFX.
Here's the code from the book implemented in JavaFX:
Walker.java
import java.util.Random;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
public class Walker extends Pane {
private static Random random = new Random();
PVector location;
PVector velocity;
PVector acceleration;
float topspeed;
double width = 30;
double height = width;
double centerX = width / 2.0;
double centerY = height / 2.0;
double radius = width / 2.0;
Circle circle;
public Walker() {
location = new PVector(random.nextDouble() * width, random.nextDouble() * height, 0);
velocity = new PVector(0, 0, 0);
topspeed = 4;
circle = new Circle(radius);
circle.setCenterX(radius);
circle.setCenterY(radius);
circle.setStroke(Color.BLUE);
circle.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.3));
getChildren().add(circle);
}
public void step(PVector mouse) {
PVector dir = PVector.sub(mouse, location);
dir.normalize();
dir.mult(0.5);
acceleration = dir;
velocity.add(acceleration);
velocity.limit(topspeed);
location.add(velocity);
}
public void checkBoundaries() {
if (location.x > Settings.SCENE_WIDTH) {
location.x = 0;
} else if (location.x < 0) {
location.x = Settings.SCENE_WIDTH;
}
if (location.y > Settings.SCENE_HEIGHT) {
location.y = 0;
} else if (location.y < 0) {
location.y = Settings.SCENE_HEIGHT;
}
}
public void display() {
relocate(location.x - centerX, location.y - centerY);
}
}
Main.java
import java.util.ArrayList;
import java.util.List;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
Pane playfield;
List<Walker> allWalkers = new ArrayList<>();
PVector mouse = new PVector(0,0,0);
#Override
public void start(Stage primaryStage) {
// create containers
BorderPane root = new BorderPane();
StackPane layerPane = new StackPane();
// playfield for our walkers
playfield = new Pane();
layerPane.getChildren().addAll(playfield);
root.setCenter(layerPane);
Scene scene = new Scene(root, Settings.SCENE_WIDTH, Settings.SCENE_HEIGHT);
primaryStage.setScene(scene);
primaryStage.show();
// add 1 walker
addWalker();
// capture mouse position
scene.addEventFilter(MouseEvent.ANY, e -> {
mouse.set(e.getX(), e.getY(), 0);
});
// process all walkers
AnimationTimer loop = new AnimationTimer() {
#Override
public void handle(long now) {
// move
allWalkers.forEach((walker) -> walker.step(mouse));
// check border
allWalkers.forEach(Walker::checkBoundaries);
// update in fx scene
allWalkers.forEach(Walker::display);
}
};
loop.start();
}
/**
* Add single walker to list of walkers and to the playfield
*/
private void addWalker() {
Walker walker = new Walker();
allWalkers.add(walker);
playfield.getChildren().add(walker);
}
public static void main(String[] args) {
launch(args);
}
}
Settings.java
public class Settings {
public static double SCENE_WIDTH = 800;
public static double SCENE_HEIGHT = 600;
}
PVector.java (you can get the full source from the Processing source code)
public class PVector {
public double x;
public double y;
public double z;
public PVector(double x, double y, double z) {
super();
this.x = x;
this.y = y;
this.z = z;
}
public void normalize() {
double m = mag();
if (m != 0 && m != 1) {
div(m);
}
}
public void div(double value) {
x /= value;
y /= value;
z /= value;
}
public void mult(double value) {
x *= value;
y *= value;
z *= value;
}
public void add(PVector v) {
x += v.x;
y += v.y;
z += v.z;
}
public void sub(PVector v) {
x -= v.x;
y -= v.y;
z -= v.z;
}
public void limit(float max) {
if (mag() > max) {
normalize();
mult(max);
}
}
public double mag() {
return Math.sqrt(x * x + y * y + z * z);
}
public static PVector sub(PVector v1, PVector v2) {
return sub(v1, v2, null);
}
public static PVector sub(PVector v1, PVector v2, PVector target) {
if (target == null) {
target = new PVector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z);
} else {
target.set(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z);
}
return target;
}
public void set(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
}
So using the tips Roland gave me I have created this piece of code which works just the way I want to be (like I described in my question)
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
TranslateTransition tt = new TranslateTransition(Duration.millis(250), circle);
Point mouse = MouseInfo.getPointerInfo().getLocation();
x[0] = mouse.getX();
y[0] = mouse.getY();
location.setText(x[0] + ", " + y[0]);
tt.setToX(x[0]);
tt.setToY(y[0]);
tt.play();
}
};
timer.start();
The major change is the use of the AnimationTimer instead of an event. This caused me to change the way I retrieve the location of the mouse, now I use the awt.MouseInfo to get the X and Y of my cursor.
Would like to first state that I am a computer science student and my University is strict on direct code copying so if you could please refrain from answers such as "it should look like this" it would be greatly appreciated. So basically my assignment is to create a JavaFX program that contains multiple green balls, and one red ball. All the balls must wrap around the pane (in an asteroids type of way) and if a red ball happens to collide with the green ball, the green ball is removed. I have multiple issues, but the key one I need to focus on is how to get all the balls from my ArrayList along with my one red ball to show up in the pane and move independently. They are all there in the pane (and bounce currently) but its like they are all bound together. I have given each ball random xy values and they should also all have random dx/dy values(their velocity). And I believe that is where my problem lies, each ball does not have their own dx/dy, thus I created a class that extends Circle to try and solve that problem but my getters and setters do not seem to be accessible. I also know the root of the problem lies in the moveBall() method as it is the method that needs to adjust values, but I cannot figure out the algorithm, and since my professor's office hours are over, I have came here in hopes of someone pointing me in the right direction. Here are the classses(I would also like to state, this is not fully my code; it's modified code from a Case Study exercise from "Intro To Java Programming 10th ed. by Y. Daniel Liang):
EDIT: Corrected a small error. Main problem is getting all the balls to now move independently via the moveBall() method.
EDIT 2: Assignment is OVER. Answers are are no longer restricted. Also added other methods that are not doing what I want addBallToPane() and checkForCollision() If anyone can tell me how I could repair these that would be great. Still want to solve this on my own, but with help :)
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.util.Duration;
import java.util.ArrayList;
public class BallPane extends Pane {
MovementGenerator mG = new MovementGenerator();
public final double radius = 20;
private double x = mG.position(), y = mG.position();
private double dx = mG.velocity(), dy = mG.velocity();
private Circle predator = new MovableCircle(x, y, radius, Color.RED);
private Timeline animation;
ArrayList<MovableCircle> circles = new ArrayList<>();
public BallPane() {
for (int i = 0; i < 10; i++) {
circles.add(i, new MovableCircle(x, y, radius, Color.GREEN));
circles.get(i).setDx(dx);
circles.get(i).setDy(dy);
}
getChildren().addAll(circles);
predator.setFill(Color.RED);
getChildren().add(predator);
// Create an animation for moving the ball
animation = new Timeline(
new KeyFrame(Duration.millis(5), e -> moveBall()));
animation.setCycleCount(Timeline.INDEFINITE);
animation.play(); // Start animation
}
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 void addBallToPane() {
incrementalEnding++;
circles.add(new MovableCircle(radius, Color.GREEN));
getChildren().add(circles.get(incrementalEnding));
}
public void checkForCollison() {
for (int i = 0; i < getChildren().size(); i++) {
if ((predator.intersects(circles.get(i).getBoundsInLocal()))) {
getChildren().remove(circles.get(i));
}
}
protected void moveBall() {
// Check boundaries
if (x < radius || x > getWidth() - radius) {
dx *= -1; // Change ball move direction
}
if (y < radius || y > getHeight() - radius) {
dy *= -1; // Change ball move direction
}
// Adjust ball position
for (int i = 0; i < circles.size(); i++) {
x += dx;
y += dy;
circles.get(i).setCenterX(x);
circles.get(i).setCenterY(y);
}
predator.setCenterX(x);
predator.setCenterY(y);
}
}
Side note: I wanna set the dx/dy as the objects are created in the ArrayList, but as I stated my accessors and mutators are not accessible.
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
public class BounceBallControl extends Application {
#Override // Override the start method in the Application class
public void start(Stage primaryStage) {
BallPane ballPane = new BallPane(); // Create a ball pane
// Pause and resume animation
// ballPane.setOnMousePressed(e -> ballPane.pause());
// ballPane.setOnMouseReleased(e -> ballPane.play());
// Increase and decrease animation
ballPane.setOnKeyPressed(e -> {
if (e.getCode() == KeyCode.UP) {
ballPane.increaseSpeed();
}
else if (e.getCode() == KeyCode.DOWN) {
ballPane.decreaseSpeed();
}
});
// Create a scene and place it in the stage
Scene scene = new Scene(ballPane, 500, 500);
primaryStage.setTitle("BounceBallControl"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
// Must request focus after the primary stage is displayed
ballPane.requestFocus();
}
/**
* 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);
}
}
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
public class MovableCircle extends Circle{
MovementGenerator mG = new MovementGenerator();
private double dx, dy;
MovableCircle(double x, double y, double radius, Color color){
super(x, y, radius);
setFill(color);
}
public double getDx() {
return dx;
}
public void setDx(double newDx) {
if (newDx < -10 || newDx > 10) {
newDx = mG.velocity();
} else {
dx = newDx;
}
}
public double getDy() {
return dy;
}
public void setDy(double newDy) {
if (newDy < -10 || newDy > 10) {
newDy = mG.velocity();
} else {
dy = newDy;
}
}
}
This next class doesn't do much besides generate random values, probably didn't need to make a whole new class for its function but eh. And it is just here for reference just in case:
import java.util.Random;
public class MovementGenerator {
private static int movement;
private static int spawnPoint;
Random rand = new Random();
MovementGenerator(){
movement = 0;
spawnPoint = 0;
}
public static int getMovement() {
return movement;
}
public static void setMovement(int movement) {
MovementGenerator.movement = movement;
}
public static int getSpawnPoint() {
return spawnPoint;
}
public static void setSpawnPoint(int spawnPoint) {
MovementGenerator.spawnPoint = spawnPoint;
}
public int velocity(){
movement = rand.nextInt(1);
if (movement == 1) {
movement = rand.nextInt(10);
}
else if (movement == 0) {
movement = (rand.nextInt(10)*-1);
}
return movement;
}
public int position(){
return spawnPoint = rand.nextInt(500);
}
}
This is a partial answer to give you the chance to start dealing with your project...
Following James_D suggestion, not only you need a moveBall method in MovableCircle, but also you need to get rid of x,y,dx,dy on BallPane, and transfer the control to each ball.
This is what I think should be done for starters:
MovableCircle
public class MovableCircle extends Circle {
private final MovementGenerator mG = new MovementGenerator();
private double x = mG.position(), y = mG.position();
private double dx = mG.velocity(), dy = mG.velocity();
private final double radius;
MovableCircle(double radius, Color color){
this.setCenterX(x);
this.setCenterY(y);
this.radius=radius;
this.setRadius(radius);
this.setFill(color);
setDx(dx);
setDy(dy);
}
public double getDx() { return dx; }
public final void setDx(double newDx) {
while (newDx < -10 || newDx > 10) {
newDx = mG.velocity();
}
dx = newDx;
}
public double getDy() { return dy; }
public final void setDy(double newDy) {
while(newDy < -10 || newDy > 10) {
newDy = mG.velocity();
}
dy = newDy;
}
public void moveBall() {
// Check boundaries
if (x < radius || x > BounceBallControl.WIDTH - radius) {
dx *= -1; // Change ball move direction
}
if (y < radius || y > BounceBallControl.HEIGHT - radius) {
dy *= -1; // Change ball move direction
}
// Adjust ball position
x += dx;
y += dy;
setCenterX(x);
setCenterY(y);
}
}
BallPane
public class BallPane extends Pane {
public final double radius = 20;
private final MovableCircle predator = new MovableCircle(radius, Color.RED);
private final Timeline animation;
private final List<MovableCircle> circles;
public BallPane() {
circles=IntStream.range(0,10).mapToObj(i->new MovableCircle(radius, Color.GREEN))
.collect(Collectors.toList());
getChildren().addAll(circles);
predator.setFill(Color.RED);
getChildren().add(predator);
setWidth(BounceBallControl.WIDTH);
setHeight(BounceBallControl.HEIGHT);
// Create an animation for moving the ball
animation = new Timeline(new KeyFrame(Duration.millis(20), e -> moveBall()));
animation.setCycleCount(Timeline.INDEFINITE);
animation.play(); // Start animation
}
public final void moveBall() {
circles.forEach(MovableCircle::moveBall);
predator.moveBall();
}
...
}
where
public class BounceBallControl extends Application {
public static final int WIDTH = 500;
public static final int HEIGHT = 500;
...
}
Run and you'll find something like this:
Now it's time to figure out the way to remove green balls...
I'm building a little "pong" game in Java.
I'm trying to add a scorekeeper up top that shows the updated score (+1) everytime the player saves the ball with the paddle.
I'm trying to use a JLabel but the problem is that I can't think of a way to continuously update the JLabel each time the paddle is hit.
Any ideas?
My code:
MainPanel Class (the one with the Paddle and Ball and Label)
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
//import swing.graphics.BounceFrame;
//import swing.graphics.Circle;
public class MainPanel extends JPanel implements ActionListener, KeyListener, Runnable{
public Paddle paddle;
public Ball ball;
public MainPanel(){
ball = new Ball(50, 50, 10); //centerX, centerY, radius
setSize(300, 300);
paddle = new Paddle();
JLabel scoreKeeper = new JLabel("Score" + ball.getScore());
add(scoreKeeper);
Thread thread = new Thread(this);
thread.start();
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2 = (Graphics2D)g;
paddle.draw(g2);
ball.draw(g2);
}
public void actionPerformed(ActionEvent e) {
String direction = e.getActionCommand();
switch(direction){
case "left": Paddle.movePaddleLeft(); break;
case "right": Paddle.movePaddleRight(); break;
}
this.repaint();
}
public void run() {
try {
while(true){
ball.move(getBounds());
repaint();
Thread.sleep(500/30);
}
}
catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == 37){
Paddle.movePaddleLeft();
}
if (e.getKeyCode() == 39){
Paddle.movePaddleRight();
}
}
#Override
public void keyReleased(KeyEvent e) {
}
#Override
public void keyTyped(KeyEvent e) {
}
}
And my Ball class:
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.util.Random;
public class Ball {
private Ellipse2D ball;
private double radius;
private double ballCircumference;
private Color color;
private double x;
private double y;
private double dx = 5;
private double dy = 5;
private int score = 0;
public int getScore() {
return score;
}
//Boundaries to determine if ball is hit by paddle
private double criticalBoundaryX;
private double criticalBoundaryY1;
private double criticalBoundaryY2;
private double paddleHalfwayPoint;
private boolean inGame = true;
public void recalculateCriticals(){
criticalBoundaryX = Paddle.getYPosition() - ballCircumference;
criticalBoundaryY1 = Paddle.getXPosition()- ballCircumference; //Left boundary
criticalBoundaryY2 = Paddle.getXPosition()+Paddle.getPaddleWidth()+ballCircumference; //Right Boundary
paddleHalfwayPoint = (Paddle.getXPosition()+Paddle.getPaddleWidth())/2;
}
public Ball(int centerX, int centerY, int radius) {
this.x = centerX - radius;
this.y = centerY - radius;
this.radius = radius;
ballCircumference = 2*radius;
Random randomRGB = new Random();
color = new Color(randomRGB.nextInt(255), randomRGB.nextInt(255), randomRGB.nextInt(255));
this.ball = new Ellipse2D.Double(x, y, 2*radius, 2*radius);
}
public void move(Rectangle2D bounds) {
recalculateCriticals();
x += dx;
y += dy;
if (x < bounds.getMinX()) {
x = bounds.getMinX();
dx = -dx;
}
if (x + 2*radius >= bounds.getMaxX()) {
//System.out.println(bounds.getMaxX());
x = bounds.getMaxX() - 2*radius;
dx = -dx;
}
if (y < bounds.getMinY()) {
y = bounds.getMinY();
dy = -dy;
}
if (y > criticalBoundaryX){
if (x < criticalBoundaryY1 || x > criticalBoundaryY2){
inGame = false;
}
if (!inGame && hittingEdge(x))
dx = -dx;
}
if (y > criticalBoundaryX && inGame){ //When it hits the paddle
changeColor();
score++;
y = criticalBoundaryX;
dy = -dy;
}
if (y > bounds.getMaxY()){
System.out.println("Game Over");
System.exit(0);
}
recalculateCriticals();
ball.setFrame(x, y, 2*radius, 2*radius);
}
public boolean onPaddle(double x){
return ((x > Paddle.getXPosition()) && (x < Paddle.getXPosition()+Paddle.getPaddleWidth()) && (y > Paddle.getYPosition()-10));
}
public boolean hittingEdge(double x){
return ((x >= criticalBoundaryY1 && x < paddleHalfwayPoint)
||
(x <= criticalBoundaryY1 && x > paddleHalfwayPoint)); //Return true if x is hitting the side edge of the paddle
}
public void changeColor(){
Random randomColor = new Random();
color = new Color(randomColor.nextInt(255), randomColor.nextInt(255), randomColor.nextInt(255));
}
public void draw(Graphics2D g2) {
g2.setColor(color);
g2.fill(ball);
}
}
The "Java way" of doing this would be to define a listener interface, for example:
public interface BallListener {
void paddleHit();
}
In the Ball class, you should add a field
private List<BallListener> listeners;
as well as methods
public void addBallListener(BallListener l) { listeners.add(l); }
public void removeBallListener(BallListener l) { listeners.remove(l); }
When the paddle is hit, you go:
for (BallListener l : listeners)
l.paddleHit();
The main class should implement the BallListener interface, and register itself with the ball (ball.addBallListener(this)).
This approach also enables you to, when needed, inform other parts of your program about different events that happen to the ball (i.e. add a new method to BallListener for each event you'd like to signal).