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.
Related
I am working on a class assignment, a pong game.
Below is the requirement.
A rectangular paddle moves back and forth via mouse drag along the bottom of the pane;
If the ball connects with the paddle, then it bounces at a 90-degree angle back into the pane space;
The mouse and keyboard actions do not work properly, no response to mouse and keys.
I can't figure out the problem. Any helps will be really appreciated.
PongForOne.java
public class PongForOne extends Application {
#Override
public void start(Stage primaryStage) {
BallPane ball = new BallPane();
PaddlePane paddle = new PaddlePane();
StackPane stkPane = new StackPane();
stkPane.getChildren().addAll(paddle,ball);
paddle.setOnMouseDragged(e -> {
PaddlePane.paddle.setX(e.getX());
});
paddle.setOnKeyPressed(e -> {
switch(e.getCode()){
case LEFT: PaddlePane.paddle.setX(PaddlePane.paddle.getX() - 10); break;
case RIGHT: PaddlePane.paddle.setX(PaddlePane.paddle.getX() + 10); break;
default: break;
}
});
Scene scene = new Scene(stkPane, 300, 400);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
ball.requestFocus();
paddle.requestFocus();
}
public static void main(String[] args) {
launch(args);
}
}
BallPane.java
public class BallPane extends Pane {
private final double radius = 20;
private double x = radius, y = radius;
private double dx = 1, dy = 1;
private int numBallHitsPaddle = 0;
private int numBallMissPaddle = 0;
private final Circle circle = new Circle(x,y, radius);
private final Text text= new Text (x, y, Integer.toString(numBallHitsPaddle));
private final Timeline animation;
public BallPane(){
circle.setFill(Color.GREEN);
getChildren().addAll(circle, text);
animation = new Timeline(new KeyFrame(Duration.millis(5), e -> moveBall()));
animation.setCycleCount(Timeline.INDEFINITE);
animation.play();
}
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 colorChange(){
circle.setFill(Color.color(Math.random(), Math.random(), Math.random()));
}
public DoubleProperty rateProperty(){
return animation.rateProperty();
}
public boolean ballHitsPaddle(){
boolean status = false;
if (x + radius > PaddlePane.getPaddleX() && x - radius < PaddlePane.getPaddleX() + PaddlePane.getPaddleWidth() && y + radius == PaddlePane.getPaddleY()){
status = true;
}
return status;
}
/**
* defines ball's movement
* changes color every time when ball hits paddle and counts the number of hit
*/
public void moveBall(){
if (x < radius || x > getWidth() - radius){
dx *= -1;
}
if ( y > getHeight() - radius){
x = getWidth() / 2;
y = 50;
dx *= -1;
numBallMissPaddle++;
}
if (y < radius || ballHitsPaddle()){
if(ballHitsPaddle()) {
text.setText(Integer.toString(++numBallHitsPaddle));
colorChange();
}
dy *= -1;
}
x += dx;
y += dy;
circle.setCenterX(x);
circle.setCenterY(y);
text.setX(x - 4);
text.setY(y + 3);
}
}
PaddlePane.java
public class PaddlePane extends Pane{
private static final double width = 40, hight = 5;
public static Rectangle paddle = new Rectangle(width, hight);
public PaddlePane(){
paddle.xProperty().bind(widthProperty().divide(2).subtract(width / 2));
paddle.yProperty().bind(heightProperty().subtract(100));
getChildren().add(paddle);
}
public static double getPaddleX(){
return paddle.getX();
}
public static double getPaddleY(){
return paddle.getY();
}
public static double getPaddleWidth(){
return paddle.getWidth();
}
public static double getPaddleHeight(){
return paddle.getHeight();
}
public void colorChange(){
paddle.setFill(Color.color(Math.random(), Math.random(), Math.random()));
}
}
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?
I've written a java implementation of Craig Reynolds Boids. I recently updated each object to be represented by a .png image. Ever since I've been having the display issue in the image.
What's the best way to fix the issue?
I've tried using a Polygon but when one of my coordinates is a negative the triangle doesn't display properly.
Main Class:
public void paint(final GraphicsContext g) {
new AnimationTimer() {
#Override
public void handle(long now) {
flock.updateBoidsPostion();
g.clearRect(0, 0, width, height);
flock.drawBoids(g);
}
}.start();
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Boids Flocking Algorithm");
Group root = new Group();
Canvas canvas = new Canvas(width, height);
GraphicsContext gc = canvas.getGraphicsContext2D();
root.getChildren().add(canvas);
primaryStage.setScene(new Scene(root));
primaryStage.show();
paint(gc);
}
Flock:
/**
* Paint each boid comprising the flock the canvas.
* #param g
*/
public void drawBoids(GraphicsContext g) {
for(Boid aBoid : boids) {
aBoid.draw(g);
}
}
Boid:
public void draw(GraphicsContext g) {
//coordinates for the tip of the boid
int x = (int)this.position.xPos;
int y = (int)this.position.yPos;
//Calculate a angle representing the direction of travel.
Rotate r = new Rotate(angle, x, y);
g.setTransform(r.getMxx(), r.getMyx(), r.getMxy(), r.getMyy(), r.getTx(), r.getTy());
g.drawImage(image, x, y);
}
The problem with your code is that you rotate the GraphicsContext, not the image. Or at least you don't rotate the GraphicsContext back after you rotated it.
I was curious about the link you mentioned, i. e. the Boids Pseudocode.
Here's a quick implementation. Drag the rectangle to have the flock follow it.
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;
// Boids implementation in JavaFX
// Pseudo code by Conrad Parker: http://www.kfish.org/boids/pseudocode.html
public class Main extends Application {
int numBoids = 50;
double boidRadius = 10d;
double boidMinDistance = boidRadius * 2d + 5; // +5 = arbitrary value
double initialBaseVelocity = 1d;
double velocityLimit = 3d;
double movementToCenter = 0.01; // 1% towards center
List<Boid> boids;
static Random rnd = new Random();
double sceneWidth = 1024;
double sceneHeight = 768;
Pane playfield;
Rectangle rectangle;
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
playfield = new Pane();
playfield.setPrefSize(sceneWidth, sceneHeight);
Text infoText = new Text( "Drag the rectangle and have the flock follow it");
root.setTop(infoText);
root.setCenter(playfield);
Scene scene = new Scene(root, sceneWidth, sceneHeight, Color.WHITE);
//scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
// create boids
createBoids();
// add boids to scene
playfield.getChildren().addAll(boids);
double w = 20;
double h = 20;
rectangle = new Rectangle( w, h);
rectangle.relocate(sceneWidth / 2 - w/2, sceneHeight / 4 - h/2);
playfield.getChildren().add(rectangle);
MouseGestures mg = new MouseGestures();
mg.makeDraggable(rectangle);
// animation loop
AnimationTimer loop = new AnimationTimer() {
#Override
public void handle(long now) {
boids.forEach(Boid::move);
boids.forEach(Boid::updateUI);
}
};
loop.start();
}
private void createBoids() {
boids = new ArrayList<>();
// margin from top/left/bottom/right, so we have the boids initially more in the center
double marginX = sceneWidth / 4;
double marginY = sceneHeight / 4;
for (int i = 0; i < numBoids; i++) {
// random position around the center
double x = rnd.nextDouble() * (sceneWidth - marginX * 2) + marginX;
double y = rnd.nextDouble() * (sceneHeight - marginY * 2) + marginY;
// initial random velocity depending on speed
double v = Math.random() * 4 + initialBaseVelocity;
Boid boid = new Boid(i, x, y, v);
boids.add(boid);
}
}
// Rule 1: Boids try to fly towards the centre of mass of neighbouring boids.
public Point2D rule1(Boid boid) {
Point2D pcj = new Point2D(0, 0);
for( Boid neighbor: boids) {
if( boid == neighbor)
continue;
pcj = pcj.add( neighbor.position);
}
if( boids.size() > 1) {
double div = 1d / (boids.size() - 1);
pcj = pcj.multiply( div);
}
pcj = (pcj.subtract(boid.position)).multiply( movementToCenter);
return pcj;
}
// Rule 2: Boids try to keep a small distance away from other objects (including other boids).
public Point2D rule2(Boid boid) {
Point2D c = new Point2D(0, 0);
for( Boid neighbor: boids) {
if( boid == neighbor)
continue;
double distance = (neighbor.position.subtract(boid.position)).magnitude();
if( distance < boidMinDistance) {
c = c.subtract(neighbor.position.subtract(boid.position));
}
}
return c;
}
// Rule 3: Boids try to match velocity with near boids.
public Point2D rule3(Boid boid) {
Point2D pvj = new Point2D(0, 0);
for( Boid neighbor: boids) {
if( boid == neighbor)
continue;
pvj = pvj.add( neighbor.velocity);
}
if( boids.size() > 1) {
double div = 1d / (boids.size() - 1);
pvj = pvj.multiply( div);
}
pvj = (pvj.subtract(boid.velocity)).multiply(0.125); // 0.125 = 1/8
return pvj;
}
// tend towards the rectangle
public Point2D tendToPlace( Boid boid) {
Point2D place = new Point2D( rectangle.getBoundsInParent().getMinX() + rectangle.getBoundsInParent().getWidth() / 2, rectangle.getBoundsInParent().getMinY() + rectangle.getBoundsInParent().getHeight() / 2);
return (place.subtract(boid.position)).multiply( 0.01);
}
public class Boid extends Circle {
int id;
Point2D position;
Point2D velocity;
double v;
// random color
Color color = randomColor();
public Boid(int id, double x, double y, double v) {
this.id = id;
this.v = v;
position = new Point2D( x, y);
velocity = new Point2D( v, v);
setRadius( boidRadius);
setStroke(color);
setFill(color.deriveColor(1, 1, 1, 0.2));
}
public void move() {
Point2D v1 = rule1(this);
Point2D v2 = rule2(this);
Point2D v3 = rule3(this);
Point2D v4 = tendToPlace(this);
velocity = velocity
.add(v1)
.add(v2)
.add(v3)
.add(v4)
;
limitVelocity();
position = position.add(velocity);
constrainPosition();
}
private void limitVelocity() {
double vlim = velocityLimit;
if( velocity.magnitude() > vlim) {
velocity = (velocity.multiply(1d/velocity.magnitude())).multiply( vlim);
}
}
// limit position to screen dimensions
public void constrainPosition() {
double xMin = boidRadius;
double xMax = sceneWidth - boidRadius;
double yMin = boidRadius;
double yMax = sceneHeight - boidRadius;
double x = position.getX();
double y = position.getY();
double vx = velocity.getX();
double vy = velocity.getY();
if( x < xMin) {
x = xMin;
vx = v;
}
else if( x > xMax) {
x = xMax;
vx = -v;
}
if( y < yMin) {
y = yMin;
vy = v;
}
else if( y > yMax) {
y = yMax;
vy = -v;
}
// TODO: modification would be less performance consuming => find out how to modify the vector directly or create own Poin2D class
position = new Point2D( x, y);
velocity = new Point2D( vx, vy);
}
public void updateUI() {
setCenterX(position.getX());
setCenterY(position.getY());
}
}
public static Color randomColor() {
int range = 220;
return Color.rgb((int) (rnd.nextDouble() * range), (int) (rnd.nextDouble() * range), (int) (rnd.nextDouble() * range));
}
public static class MouseGestures {
class DragContext {
double x;
double y;
}
DragContext dragContext = new DragContext();
public void makeDraggable( Node node) {
node.setOnMousePressed( onMousePressedEventHandler);
node.setOnMouseDragged( onMouseDraggedEventHandler);
node.setOnMouseReleased( onMouseReleasedEventHandler);
}
EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if( event.getSource() instanceof Circle) {
Circle circle = ((Circle) (event.getSource()));
dragContext.x = circle.getCenterX() - event.getSceneX();
dragContext.y = circle.getCenterY() - event.getSceneY();
} else {
Node node = ((Node) (event.getSource()));
dragContext.x = node.getTranslateX() - event.getSceneX();
dragContext.y = node.getTranslateY() - event.getSceneY();
}
}
};
EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if( event.getSource() instanceof Circle) {
Circle circle = ((Circle) (event.getSource()));
circle.setCenterX( dragContext.x + event.getSceneX());
circle.setCenterY( dragContext.y + event.getSceneY());
} else {
Node node = ((Node) (event.getSource()));
node.setTranslateX( dragContext.x + event.getSceneX());
node.setTranslateY( dragContext.y + event.getSceneY());
}
}
};
EventHandler<MouseEvent> onMouseReleasedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
}
};
}
public static void main(String[] args) {
launch(args);
}
}
A 3D version is just a matter of using Points3D instead of Points2D and Spheres and Boxes instead of Circles and Rectangles.
I also suggest you read the excellent book The Nature of Code by Daniel Shiffman, especially the chapter Autonomous Agents. It deals in detail with the Boids.
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...
import java.awt.Graphics;
import javax.swing.JApplet;
import javax.swing.JPanel;
public class Circle extends JPanel {
int x = 75;
int y = 100;
int diameter = 50;
public void setAnimationY(int y) {
this.y = y;
}
public int getAnimationY() {
return y;
}
public int getDiameter() {
return diameter;
}
public void setDiameter(int startDiameter) {
diameter = startDiameter;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawOval(x, y, diameter, diameter);
}
}
import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JApplet;
import javax.swing.JPanel;
import javax.swing.Timer;
public class BouncingBall extends JApplet {
private int speed = 5;
private Timer timer;
private Circle draw;
#Override
public void init() {
super.init();
setLayout(new BorderLayout());
draw = new Circle();
add(draw);
timer = new Timer(30, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
int y = draw.getAnimationY();
int diameter = draw.getDiameter();
int roof = getHeight();
y += speed;
if (y < 0) {
y = 0;
speed *= -1;
} else if (y + diameter > roof) {
y = roof - diameter;
speed *= -1;
}
draw.setAnimationY(y);
repaint();
}
});
}
#Override
public void start() {
super.start();
timer.start();
}
#Override
public void stop() {
timer.stop();
super.stop();
}
}
I am trying to create a JApplet that contains a ball that is bouncing up and down. So far I have been able to get the ball to go up and down but now I am trying to make the ball more "life-like" so I want the height of the ball to decrease each time the ball bounces until eventually it stops.
I have attempted to do a while loop using the roof variable that I created for the getHeight() method but for some reason when I tried to use it either the ball didn't move at all or the loop had no affect on the ball.
I have also tried a for loop but I ran into the same problem that I got into with the while loop. I believe the problem is that I am not placing this for loop in the correct spot for it to work correctly.
thanks in advance.
Little modifications to your code that can give you some trails:
#Override
public void actionPerformed(ActionEvent e) {
int y = draw.getAnimationY();
int diameter = draw.getDiameter();
int roof = getHeight();
y += speed;
//
// Reduce the ball size at the bottom of the screen
//
if(y + diameter > roof) {
if(diameter > minDiameter) {
diameter -= (roof - y);
} else {
diameter = minDiameter;
}
} else if (diameter < maxDiameter) {
diameter++;
}
draw.setDiameter(diameter);
if (y < 0) {
y = 0;
speed *= -1;
} else if (y + diameter > roof) {
y = roof - diameter;
speed *= -1;
}
// Simulates a little gravity
speed += 0.5;
draw.setAnimationY(y);
repaint();
}
For more realism, the best way would to find an equation that is function of the ball position and a coefficient of hardness for the ball and would give you the ball size.
Well let use continue with #MadProgrammer's solution from your other related question:
In your class of DrawPane we can easily define the height, getAnimationHeight() and setAnimationHeight(int) to control the height decrease as soon as it touches the ground. Please remember that in java left-top co-ordinate is (0, 0) and right-bottom co-ordinate is (getWidth(), getHeight()). Suppose that it starts from height = 0(top). Then it will start from y = height(top) and eventually move to the getHeight()(bottom) of your container. We will increase the height(top y) using setAnimationHeight() by adding an amount(say 30) to current height(which getAnimationHeight() will return) .
So, this little tweak made to #MadeProgrammer's solution in your other question will be the following demo.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.Timer;
public class Circle extends JApplet {
private int delta = 8;
private Timer timer;
private DrawPane drawPane;
#Override
public void init() {
super.init();
setLayout(new BorderLayout());
drawPane = new DrawPane();
add(drawPane);
timer = new Timer(100, new ActionListener() {
int frameCount = 0;
#Override
public void actionPerformed(ActionEvent e) {
int y = drawPane.getAnimationY();
int diameter = drawPane.getDiameter();
y += delta;
if (y < drawPane.getAnimationHeight()) {
y = drawPane.getAnimationHeight();
delta *= -1;
} else if (y + diameter > getHeight()) {
y = getHeight()- diameter;
delta *= -1;
int animationHeight = drawPane.getAnimationHeight();
animationHeight = animationHeight + (getHeight() - diameter - animationHeight)/2;
drawPane.setAnimationHeight(animationHeight);
if(animationHeight + diameter + 2 >= getHeight())
{
System.out.println("true");
drawPane.setAnimationY(getHeight() - diameter);
repaint();
timer.stop();
return;
}
}
drawPane.setAnimationY(y);
repaint();
}
});
}
#Override
public void start() {
super.start();
timer.start();
}
#Override
public void stop() {
timer.stop();
super.stop();
}
public class DrawPane extends JPanel {
int x = 100;
int y = 0;
int diameter = 50;
int height = 0;
public void setAnimationX(int x) {
this.x = x;
}
public void setAnimationY(int y) {
this.y = y;
}
public void setAnimationHeight(int h)
{
height = h;
}
public int getAnimationHeight()
{
return height;
}
public int getAnimationX() {
return x;
}
public int getAnimationY() {
return y;
}
public int getDiameter() {
return diameter;
}
public void setDiameter(int startDiameter) {
diameter = startDiameter;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawOval(x, y, diameter, diameter);
}
}
}
NOTE: As soon as it touches the bottom finally, you should stop the Timer to get rid of the flickering of the ball. This task is left as an exercise for you.