I'm working on a program that displays circles colliding with the wall and with themselves.
I'm having trouble with the method that will compensate for collisions.
public class bouncyFX extends Application {
public ArrayList<Ball> arr = new ArrayList<Ball>();
public static void main(String[] args) {
launch(args);
}
static Pane pane;
#Override
public void start(final Stage primaryStage) {
pane = new Pane();
final Scene scene = new Scene(pane, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
pane.setOnMouseClicked(new EventHandler<MouseEvent>() {
public void handle(final MouseEvent event) {
final Ball ball = new Ball(event.getX(), event.getY(), 40, Color.AQUA);
ball.circle.relocate(event.getX(), event.getY());
pane.getChildren().addAll(ball.circle);
arr.add(ball);
final Bounds bounds = pane.getBoundsInLocal();
final Timeline loop = new Timeline(new KeyFrame(Duration.millis(10), new EventHandler<ActionEvent>() {
double deltaX = ball.ballDeltaX;
double deltaY = ball.ballDeltaY;
public void handle(final ActionEvent event) {
ball.circle.setLayoutX(ball.circle.getLayoutX() + deltaX);
ball.circle.setLayoutY(ball.circle.getLayoutY() + deltaY);
final boolean atRightBorder = ball.circle.getLayoutX() >= (bounds.getMaxX()-ball.circle.getRadius());
final boolean atLeftBorder = ball.circle.getLayoutX() <= (bounds.getMinX()+ball.circle.getRadius());
final boolean atBottomBorder = ball.circle.getLayoutY() >= (bounds.getMaxY()-ball.circle.getRadius());
final boolean atTopBorder = ball.circle.getLayoutY() <= (bounds.getMinY()+ball.circle.getRadius());
if(atRightBorder || atLeftBorder)
deltaX *= -1;
if(atBottomBorder || atTopBorder)
deltaY *= -1;
for(int i = 0; i<arr.size(); i++){
for(int j = i+1; j<arr.size()-1; j++){
arr.get(i).collisionMagnitued(arr.get(j));
}
}
}
}));
loop.setCycleCount(Timeline.INDEFINITE);
loop.play();
}
});
}
class Ball{
public Circle circle;
public double ballDeltaX = 3;
public double ballDeltaY = 3;
public void AddBall(Ball b){
arr.add(b);
}
public Ball(double X, double Y, double Rad, Color color) {
circle = new Circle(X, Y, Rad);
circle.setFill(color);
}
private boolean defineCollision(Ball b){
double xd = this.circle.getLayoutX() - b.circle.getLayoutX();
double yd = this.circle.getLayoutY() - b.circle.getLayoutY();
double sumRad = this.circle.getRadius() + b.circle.getRadius();
double squareRad = Math.pow(sumRad, 2);
double distSquare = Math.pow(xd, 2) + Math.pow(yd, 2);
if(distSquare <= squareRad){
return true;
}return false;
}
public void collisionMagnitued(Ball b){
if(this.defineCollision(b)){
double tempDeltaX = ballDeltaX;
double tempDeltaY = ballDeltaY;
if((this.ballDeltaX < 0 && b.ballDeltaX > 0) || (this.ballDeltaX >0 && b.ballDeltaX <0)){
this.ballDeltaX *= -this.ballDeltaX;
b.ballDeltaX *= -b.ballDeltaX;
System.out.println("tredje");
}
if((this.ballDeltaY < 0 && b.ballDeltaY > 0) || (this.ballDeltaY > 0 && b.ballDeltaY < 0)){
this.ballDeltaY *= -this.ballDeltaY;
b.ballDeltaY *= -b.ballDeltaY;
System.out.println("fjärde");
}
else{
System.out.println("Knull");
this.ballDeltaX *= -1;
b.ballDeltaX *= -1;
}
}
}
}
}
The Balls (or circles) are created and are bouncing against the Bounds as expected.
The Collision detection method works as I'm getting print statements inside the last method. However, it seems that there's something wrong with either my ArrayList not being filled with objects or the method trying to compare the parameter Ball and the Ball that calls the method.
Am I way off? Not sure how I'm suppossed to go forth from here.
I see a few issues with your logic:
The first problem is that when the balls "bounce" off the boundaries of the pane, you don't change their ballDeltaX or ballDeltaY values (you just change a local value in the animation loop and use the local value to update the position). So the first time two balls collide, both of their ballDeltaX and ballDeltaY values are equal to +3 (the initial value), which may not represent the actual direction the animation loop is moving them in. In fact, you never actually use any updated values of ballDeltaX or ballDeltaY to compute the new positions; you get the initial values of those variables, copy them into deltaX and deltaY, and then just compute the new positions using deltaX and deltaY. So if you change ballDeltaX and ballDeltaY, the animation loop never sees the change.
The for loops look wrong to me; I don't think they compare the last two elements of the list. (When i = arr.size()-2 in the penultimate iteration of the outer loop, your inner loop is for (int j = arr.size() - 1; j < arr.size() -1; j++) {...} which of course never iterates.) I think you want the bounding conditions to be i < arr.size() - 1 and j < arr.size(), i.e. the other way around.
And then your if/else structure in collisionMagnitued(...) is probably not exactly what you want. I'm not sure what you're trying to implement there, but the else clause only kicks in if the second if is false, and no matter what happens in the first if.
Finally, you are starting a new animation on each mouse click. So, for example, if you have three balls bouncing around, you have three animation loops running, each of which is updating values when the balls collide. You need to start just one loop; it shouldn't do any harm if it refers to an empty list.
Related
I'm trying to make a pong game using JavaFX and I've decided to use Point2D for the paddle and ball positions.
I created this method to check for wall collisions
public void checkWallCollision(){
boolean ballHitBottom = posBall.getY() > 500;
boolean ballHitTop = posBall.getY() < 50;
boolean ballHitLeft = posBall.getX() < 0;
boolean ballHitRight = posBall.getX() > 725;
if (ballHitTop || ballHitBottom){
ballDirVector = ballDirVector.multiply(-1);
}
if(ballHitLeft || ballHitRight){
ballDirVector = ballDirVector.multiply(-1);
}
}
But with a Point2D object like that, I can't just multiply it like that. I need the angle to be reflected. I guess another thing I could show to help is how I launch the ball. In this method I do have the angle, but I'm not sure what way I should go about accessing it.
public void launchBall(){
// Launch the ball
boolean ballDirection = random.nextBoolean();
// bound to an acute angle on start
double ballAngle = Constants._PADDLE_ANGLES[random.nextInt(5) + 1];
if (ballDirection){
ballAngle *= -1;
}
ballSpeed = Constants._BallStartSpeed;
ballDirVector = new Point2D(Math.cos(ballAngle), Math.sin(ballAngle));
}
Hello Stack Overflow people :)
I'm a huge newbie when it comes to coding, and I've just ran into a problem that my brain just won't get over...
Before I start blabbering about this issue, I'll paste my code so as to give a little bit of context (sorry in advance if looking at it makes you wanna puke). The main focus of the issue is commented and should therefore be fairly visible :
Main
ArrayList<Individual> individuals = new ArrayList<Individual>();
void setup()
{
size(500,500);
for(int i = 0; i < 2; i++)
{
individuals.add(new Individual());
}
println(frameRate);
}
void draw()
{
background(230);
for(int i = 0; i < individuals.size(); i++)
{
individuals.get(i).move();
individuals.get(i).increaseTimers();
individuals.get(i).display();
}
}
Individual
class Individual
{
float x;
float y;
int size = 5;
Timer rdyBreed; /* Here is the object that appears to be shared
between individuals of the ArrayList */
float breedRate;
float breedLimit;
Individual()
{
x = random(0, width);
y = random(0, height);
rdyBreed = new Timer("rdyBreed", 0);
breedRate = random(.2, 3);
breedLimit = random(10, 20);
}
void move()
{
int i = (int)random(0, 1.999);
int j = (int)random(0, 1.999);
if (i == 0)
{
x = x + 1;
} else
{
x = x - 1;
}
if (j == 0)
{
y = y + 1;
} else
{
y = y - 1;
}
checkWalls();
}
void checkWalls()
{
if (x < size/2)
{
x = width - size/2;
}
if (x > width - size/2)
{
x = size/2;
}
if (y < size/2)
{
y = width - size/2;
}
if (y > width - size/2)
{
y = size/2;
}
}
void display()
{
noStroke();
if (!rdyBreed.finished)
{
fill(255, 0, 0);
} else
{
fill(0, 255, 0);
}
rect(x - size/2, y - size/2, size, size);
}
void increaseTimers()
{
updateBreedTimer();
}
void updateBreedTimer()
{
rdyBreed.increase(frameRate/1000);
rdyBreed.checkLimit(breedLimit);
rdyBreed.display(x, y);
}
}
Timer
class Timer
{
float t;
String name;
boolean finished = false;
Timer(String name, float t)
{
this.t = t;
this.name = name;
}
void increase(float step)
{
if (!finished)
{
t = t + step;
}
}
void checkLimit(float limit)
{
if (t >= limit)
{
t = 0;
finished = true;
}
}
void display(float x, float y)
{
textAlign(RIGHT);
textSize(12);
text(nf(t, 2, 1), x - 2, y - 2);
}
}
Now that that's done, let's get to my question.
Basically, I'm trying to create some sort of a personal Conway's Game of Life, and I'm encountering a lot of issues right off the bat.
Now my idea when writing this piece of code was that every individual making up the small simulated "society" would have different timers and values for different life events, like mating to have children for example.
Problem is, I'm not a huge pro at object-oriented programming, and I'm therefore quite clueless as to why the objects are not having each their own Timer but both a reference to the same timer.
I would guess making an ArrayList of timers and using polymorphism to my advantage could make a change, but I'm not really certain of it or really how to do it so... yeah, I need help.
Thanks in advance :)
EDIT : Here is a screenshot of the debugger. The values keep being the same with each iteration of the updates.
Screenshot
What makes you think they reference the same Timer object? The values of t displayed in the debugger are going to be the same until one of them reaches the breedLimit and gets set to 0, because they're being initialized at the same time.
Try this and see that the values of t are different.
void setup() {
size(500,500);
}
void mouseClicked() {
individuals.add(new Individual());
}
I'd recommend setting the breakpoint somewhere around here:
t = 0;
finished = true;
They do not share the same timer, you create a new Timer object for each Individual.
class Individual {
// ...
Timer rdyBreed;
Individual() {
// ...
rdyBreed = new Timer("rdyBreed", 0);
//...
The only way they could be sharing the same Timer is if you were setting rdyBreed elsewhere, but since you don't want that I recommend making it final.
If you did want to share the same Timer instance across all individuals then you could declare it static.
I have to detect when two "balls" collide in a javaFX program. Each time a button is clicked, a new ball is added to the pane. I know that getChildren() returns an observable list that contains the node for each ball, and when I print the list with two circles it will print, for example,
Circle[centerX=30.0, centerY=30.0, radius=20.0, fill=0x9ac26780], Circle[centerX=224.0, centerY=92.0, radius=20.0, fill=0x9ac26780]
My idea was to use nested loops to compare the (x,y) coordinates of each ball to every other ball. How do I access centerX and centerY from each Circle in order to compare them?
I tried getChildren().sublist(0,0); thinking that I would get the centerX value for the first element, but that does not work. I also tried getCenterX, because Ball extends Circle, but that also failed. Thanks for your time.
public class Exercise20_05 extends Application {
#Override // Override the start method in the Application class
public void start(Stage primaryStage) {
MultipleBallPane ballPane = new MultipleBallPane();
ballPane.setStyle("-fx-border-color: yellow");
Button btAdd = new Button("+");
Button btSubtract = new Button("-");
HBox hBox = new HBox(10);
hBox.getChildren().addAll(btAdd, btSubtract);
hBox.setAlignment(Pos.CENTER);
// Add or remove a ball
btAdd.setOnAction(e -> ballPane.add());
btSubtract.setOnAction(e -> ballPane.subtract());
// Pause and resume animation
ballPane.setOnMousePressed(e -> ballPane.pause());
ballPane.setOnMouseReleased(e -> ballPane.play());
// Use a scroll bar to control animation speed
ScrollBar sbSpeed = new ScrollBar();
sbSpeed.setMax(20);
sbSpeed.setValue(10);
ballPane.rateProperty().bind(sbSpeed.valueProperty());
BorderPane pane = new BorderPane();
pane.setCenter(ballPane);
pane.setTop(sbSpeed);
pane.setBottom(hBox);
// Create a scene and place the pane in the stage
Scene scene = new Scene(pane, 250, 150);
primaryStage.setTitle("MultipleBounceBall"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
}
private class MultipleBallPane extends Pane {
private Timeline animation;
public MultipleBallPane() {
// Create an animation for moving the ball
animation = new Timeline(
new KeyFrame(Duration.millis(50), e -> moveBall()));
animation.setCycleCount(Timeline.INDEFINITE); //animation will play indefinitely
animation.play(); // Start animation
}
public void add() {
Color color = new Color(Math.random(),
Math.random(), Math.random(), 0.5);
//creates a new Ball at (30, 30) with a radius of 20
getChildren().add(new Ball(30, 30, 20, color));
ballCollision();
}
public void subtract() {
if (getChildren().size() > 0) {
getChildren().remove(getChildren().size() - 1);
}
}
public void play() {
animation.play();
}
public void pause() {
animation.pause();
}
public void increaseSpeed() {
animation.setRate(animation.getRate() + 0.1);
}
public void decreaseSpeed() {
animation.setRate(
animation.getRate() > 0 ? animation.getRate() - 0.1 : 0);
}
public DoubleProperty rateProperty() {
return animation.rateProperty();
}
protected void moveBall() {
for (Node node: this.getChildren()) {
Ball ball = (Ball)node;
// Check boundaries
if (ball.getCenterX() < ball.getRadius() ||
ball.getCenterX() > getWidth() - ball.getRadius()) {
ball.dx *= -1; // Change ball move direction
}
if (ball.getCenterY() < ball.getRadius() ||
ball.getCenterY() > getHeight() - ball.getRadius()) {
ball.dy *= -1; // Change ball move direction
}
// Adjust ball position
ball.setCenterX(ball.dx + ball.getCenterX());
ball.setCenterY(ball.dy + ball.getCenterY());
ballCollision();
}
}
//check for ball collisions
protected void ballCollision() {
/*System.out.println(getChildren().size());
getChildren returns an observableList; this observableList is what
keeps track of the balls (specifically, the nodes added to ballPane)
added each time the + button is clicked
*/
ObservableList ballList = getChildren();
System.out.println(ballList.get(0));
//if there are 2 or more balls, check for collision
if (ballList.size() > 1) {
//compare each (x,y) coordinate value to every other (x,y) value
for (int i = 0; i < ballList.size(); i++) {
for (int k = 0; k < ballList.size(); k++) {
// if (ballList.sublist(i,i) < 1) {
//
// }
}
}
}
}
}
class Ball extends Circle {
private double dx = 1, dy = 1;
Ball(double x, double y, double radius, Color color) {
super(x, y, radius);
setFill(color); // Set ball color
}
}
/**
* The main method is only needed for the IDE with limited
* JavaFX support. Not needed for running from the command line.
*/
public static void main(String[] args) {
launch(args);
}
}
Edit: Thanks to a couple of people, I was able to get the collision check to work. One ball will get removed, but I get the ConcurrentModificationException. Here is the updated method:
protected void ballCollision() {
ObservableList ballList = getChildren();
//if there are 2 or more balls, check for collision
if (ballList.size() > 1) {
//compare each (x,y) coordinate value to every other (x,y) value
for (int i = 0; i < ballList.size(); i++) {
for (int k = i + 1; k < ballList.size(); k++) {
Circle c1 = (Circle) ballList.get(i);
Circle c2 = (Circle) ballList.get(k);
if ((c1.getCenterX() <= c2.getCenterX() * 1.10 &&
(c1.getCenterX() >= c2.getCenterX()*.90)) &&
((c1.getCenterY() <= c2.getCenterY() * 1.10) &&
c1.getCenterY() >= c2.getCenterY() * .90)){
ballList.remove(c2);
}
}
}
}
}
Final Edit: Thanks to David Wallace for taking his time to help me. The issue was that I was calling ballCollision inside the for-each loop of the moveBall method. Once I moved it outside the loop, it worked perfectly.
You can treat the ObservableList just like any other List. You will probably want to cast the elements to the right class, as shown here. Use the hypot method of the Math class to calculate the distance between the centres.
for (int first = 0; first < ballList.size(); first++) {
Ball firstBall = (Ball) ballList.get(first);
for (int second = first + 1; second < ballList.size(); second++) {
Ball secondBall = (Ball) ballList.get(second);
double distanceBetweenCentres = Math.hypot(
firstBall.getCenterX() - secondBall.getCenterX(),
firstBall.getCenterY() - secondBall.getCenterY());
if (distanceBetweenCentres <= firstBall.getRadius() + secondBall.getRadius()) {
System.out.println("Collision between ball " + first + " and ball " + second);
}
}
}
Need some help modifying this code. I was working through some tutorials on particle systems, and I'm currently trying to write seom logic that says:
"If this particle system has been running for 10 seconds, stop adding particles to it. When the last of the particles are dead, and the system is empty,remove it from the systems ArrayList."
What is going on now:
- The timer counts down and particles stop being added to the particle system except it is acting like a timer for every particle system in the array list.
- the timer does not reset when you add a new particle system
What I need help with:
- Where to reset the timer, or re-initialize the timer when you make a new system.
- only having the timer affect the system its in (rather than all of them on screen)
// particle system
class ParticleSystem {
ArrayList<Particle> plist;
PVector origin; // An origin point for where particles are birthed
float c;
int t;
int countdown; // 10 seconds.
boolean end;
ParticleSystem(float col, int num, PVector v){
plist = new ArrayList<Particle>();
origin = v.get();
c = col;
end = false;
countdown = 10;
t = 10;
for(int i = 0; i < num; i++){
plist.add(new Particle(c,origin));
}
}
void applyForce(PVector force){
for (Particle p : plist){
p.applyForce(force);
}
}
void run(){
// iterate through array of single particles backwards
// remove single particles when they are dead.
t = countdown-int(millis()/1000);
print(t);
for (int i = plist.size()-1; i > 0; i--){
Particle p = plist.get(i);
p.run();
if (p.isDead()){
plist.remove(i);
}
}
if(t > 0){
addParticle();
} else {
dead();
}
//print(plist.size());
}
void addParticle(){
//println("AP: "+r);
float r = random(1);
if (r<0.4) {
plist.add(new SquareParticle(c,origin));
}else{
plist.add(new Particle(c,origin));
}
}
boolean dead(){
if(plist.isEmpty() || plist.size() == 1){
t = 10;
return true;
}else{
return false;
}
}
}
// main tab
ArrayList<ParticleSystem> systems;
PVector windRight = new PVector(0.1,0);
PVector sortaSpeed = new PVector(0,0.1);
PVector gravity = new PVector(0,0.05);
boolean wR = false;
boolean sP = false;
void setup() {
size(640,480);
systems = new ArrayList<ParticleSystem>();
noStroke();
}
void draw() {
background(0);
if(!systems.isEmpty()){
for (int i =0; i < systems.size(); i++){
ParticleSystem ps = systems.get(i);
ps.applyForce(gravity);
ps.run();
if(wR){
ps.applyForce(windRight);
}
if(sP){
ps.applyForce(sortaSpeed);
}
if(ps.dead()){
systems.remove(ps);
}
//print(systems.size());
}
} else {
fill(255);
text("'w' controls wind, 'a' controls speed, 's' adds particle systems",1,height-30);
}
}
void keyPressed() {
if(key == 'w'){
wR = true;
} else if(key == 'a'){
//print('a');
sP = true;
}else{
systems.add(new ParticleSystem(random(100,200),10,new PVector(random(10,630),10))); //random(480)
}
}
void keyReleased(){
if(key == 'w'){
wR = false;
} else if(key == 'a'){
sP = false;
}
}
In the future, please try to post an MCVE. Right now we can't run your code because it contains compiler errors. We don't need to see your entire sketch anyway though, just a small example that gets the point across.
But looking at your code, there is a problem here:
for (int i =0; i < systems.size(); i++){
...
if(ps.dead()){
systems.remove(ps);
}
Run through an example using a piece of paper and a pencil. Let's say you have 3 ParticleSystem instances in your systems list, and the loop is on the second one. You then remove the second one, moving the third one into the second index. The next iteration of the loop moves to the third index... but now there's nothing there!
To get around this problem, you could iterate through the ArrayList backwards, or better yet, you could use an Iterator.
From there it's just a matter of keeping track of each instance's startTime and comparing that to millis(), which you aren't doing right now.
Here's an MCVE that demonstrates using millis() and an Iterator to kill off Particle instances after 10 seconds:
import java.util.Iterator;
ArrayList<Particle> particles = new ArrayList<Particle>();
void setup() {
size(500, 500);
}
void draw() {
background(0);
Iterator<Particle> particleIterator = particles.iterator();
while (particleIterator.hasNext()) {
Particle p = particleIterator.next();
p.draw();
if (p.isDead()) {
particleIterator.remove();
}
}
}
void mousePressed() {
particles.add(new Particle(mouseX, mouseY));
}
class Particle {
int startTime;
float x;
float y;
public Particle(float x, float y) {
startTime = millis();
this.x = x;
this.y = y;
}
void draw() {
x += random(-2, 2);
y += random(-2, 2);
ellipse(x, y, 10, 10);
}
boolean isDead() {
return millis() > startTime + 10*1000;
}
}
Note that you'll have to use this logic twice: once for your individual particles, and again for the particle systems themselves. But the logic is the same: record a start time, then compare that to millis(), and use an Iterator to remove stuff when it has timed out.
Also note that Iterator is specific to Java mode. If you want to deploy as JavaScript, you might want to go with the backwards loop approach instead.
class DrawPane extends JPanel
{
//size is the size of the square, x and y are position coords
double size = 1, x = 0, y = 0;
double start = (-1) * size;
public void paintComponent(Graphics shape)
{
for(x = start; x <= scWidth; x += size)
{
shape.drawRect((int)x, (int)y , (int)size, (int)size);
//When a row is finished drawing, add another
if(x >= scWidth)
{
x = start; y += size;
}
//Redraws the entire grid; makes the for loop infnite
else if(y >= scHeight)
{
x = start; y = start;
}
}
}
}
I'm confused as to why JPanel refuses to work with the loop once I make it infinite. How would I go about allowing it to do so?
When you make the loop "infinite" you effectively tie up and freeze the Swing event thread preventing Swing from doing anything. Instead use a Swing Timer to drive your animation.
e.g.,
class DrawPane extends JPanel {
//size is the size of the square, x and y are position coords
double size = 1, x = 0, y = 0;
double start = (-1) * size;
public DrawPane() {
int timerDelay = 200;
new Timer(timerDelay, new ActionListener(){
public void actionPerformed(ActionEvent e) {
x += size;
if (x >= scWidth) {
x = start;
y += size;
}
if (y >= scHeight) {
x = start;
y = start;
}
repaint();
}
}).start();
}
public void paintComponent(Graphics g) {
super.paintComponent(g); // Don't forget to call this!
g.drawRect((int)x, (int)y , (int)size, (int)size);
}
}
The paint function is supposed to update the Paint and get out of the way. You really shouldn't be putting in complex logic and definitely not infinite loops there.
Just do what you have (except get rid of the reset stuff that makes your loop infinite) and put repaint() in an infinite loop (preferably with some timer logic) somewhere else in your program.
It will never break out of the paintComponent loop and update the GUI. The GUI will only update once the paintComponent method finishes. If you want to make the loop infinite, you need to take the code out of your event handler and be calling repaint() from elsewhere, possibly using a timer to do so.