1

I have a project that creates an array list of balls (ellipses). When I press my mouse down (left button) and hold it an ellipse follows my mouse. When I let go the ellipse is placed on the screen where my mouse was.

I wanna be able to right click and hold over an ellipse (any random one) and have it following my mouse again like previous. and again if I let go of the mouse button, it should be placed back on the screen where my mouse is currently positioned.

I am struggling to understand how I can find the x y position of the ellipse that is already on the screen and remove the ellipse from the list and have it follow my mouse again.

Any suggestion let me know- Here is my main class

ArrayList<Ball> ballList = new ArrayList<Ball>();boolean touching;
void setup() {
  size(600, 600);

}

void draw() {  
  background(150);

  // if the mouse button is held down, set the ball's coordinates to the mouse coordinates
  if (ballList.size() > 0 && mousePressed && mouseButton==LEFT) {
    ballList.get(ballList.size() - 1).xPos = mouseX;  // 'ballList.get(ballList.size() - 1)' is the java way to get the last item added to an arrayList
    ballList.get(ballList.size() - 1).yPos = mouseY;
  }
 for (Ball b : ballList) {
    b.drawBall();
 }

}

// this method will trigger once every time the user press a mouse button
void mousePressed() {
  if (mouseButton==LEFT) {
    ballList.add(new Ball(mouseX, mouseY));
  }
}

here is my ball class

class Ball {
  float xPos, yPos; 


  Ball(float xPos, float yPos) {
    this.xPos= xPos;
    this. yPos= yPos;
  }
  void drawBall() {
    ellipse(xPos, yPos, 50, 50);
    println("X" + xPos + " Y:"+ yPos);
  }

 
  void moveBall(){
    
    
  }
}
Jane
  • 17
  • 5

1 Answers1

0

You can check if a ball is under the cursor by checking if the distance(dist()) between the ball's position and the mouse's position:

if(dist(ball.x, ball.y, mouseX, mouseY) < ball.radius){
  println("mouse over ball");
}

Currently you're hardcoding the ball diameter (50), but you could easily add a radius property:

class Ball {
  
  float xPos, yPos; 
  float diameter = 50;
  float radius = diameter * 0.5;

  Ball(float xPos, float yPos) {
    this.xPos= xPos;
    this. yPos= yPos;
  }
  void drawBall() {
    ellipse(xPos, yPos, diameter, diameter);
  }

}

The condition can be wrapped in a for loop to do the same check on every ball and into a function which either returns the first ball matching the condition or null (in case there is no ball under the cursor):

Ball getBall(float x, float y){
  // for each ball
  for(Ball ball : ballList){
    // check if the x,y coordinates are inside any of the existing balls
    if(dist(ball.xPos, ball.yPos, x, y) < ball.radius){
      // return the 1st match
      return ball;
    }
  }
  // return null if nothing was found
  return null;
}

You're already using classes and functions, but just in case the above syntax looks unfamiliar:

  • the Ball type at the begining of the function replaces void and it means the function must return an object of type Ball (as opposed to void)
  • the return keyword both exits the function but also returns the reference to the ball (if found, null otherwise)

To discern whether there's a selected ball or not (when switching between left and right click) you could use a Ball variable which initially is null, but gets assigned either then left click is pressed or right click is pressed and there's a mouse coordinates fall within the ball's position/radius.

Here's a modified version of your code using the above:

ArrayList<Ball> ballList = new ArrayList<Ball>();
// reference to selection
Ball selectedBall;

void setup() {
  size(600, 600);
}

void draw() {  
  background(150);
  // render all balls
  for (Ball ball : ballList) {
    ball.drawBall();
  }
}

// this method will trigger once every time the user press a mouse button
void mousePressed() {

  if (mouseButton == LEFT) {
    // reset the selection to the newest ball
    selectedBall = new Ball(mouseX, mouseY);
    // append it to the list
    ballList.add(selectedBall);
    println("added new ball and updated selection", selectedBall);
  }

  if (mouseButton == RIGHT) {
    // check if a ball is under the cursor, if so select it
    selectedBall = getBall(mouseX, mouseY);
    println("right click selection", selectedBall);
  }
}

void mouseDragged() {
  // update dragged ball coordinates if there is a previous selection
  if (selectedBall != null) {
    selectedBall.xPos = mouseX;
    selectedBall.yPos = mouseY;
    println("dagging selected ball", selectedBall);
  }
}

void mouseReleased() {
  // clear selection
  selectedBall = null;
  println("selection cleared");
}

Ball getBall(float x, float y) {
  // for each ball
  for (Ball ball : ballList) {
    // check if the x,y coordinates are inside any of the existing balls
    if ( dist(ball.xPos, ball.yPos, x, y) < ball.radius ) {
      // return the 1st match (exits loop and function immediately)
      return ball;
    }
  }
  // return null if nothing was found
  return null;
}

class Ball {

  float xPos, yPos; 
  float diameter = 50;
  float radius   = diameter * 0.5;

  Ball(float xPos, float yPos) {
    this.xPos = xPos;
    this.yPos = yPos;
  }
  
  void drawBall() {
    ellipse(xPos, yPos, diameter, diameter);
  }
  // pretty print info
  String toString(){
    return "[Ball x=" + xPos + " y="+ yPos + "]";
  }
}

I've removed a few unused variables, added toString() (so it displays info nicely using println()) and sprinkled a few optional println() statements so it's easier to see what's going as you test the code.

Final notes:

  • currently if you left click multiple times you can add mulitple overlapping balls. You can tweak the implementation to update the check if there's a ball there first and if so only add a new ball if there aren't any existing balls at that locations already
  • looping though every ball and checking distance (which uses sqrt()) can get computationally expensive for a large number of balls. At this stage code readability is more important, but in case you code develops into something a lot more complex you could used squared distance instead of dist() and use other optimisation techniques.

Update Here's a tweaked version of the above sketch which only adds a new ball if there isn't one already at the mouse location (allowing mouse left only dragging):

ArrayList<Ball> ballList = new ArrayList<Ball>();
// reference to selection
Ball selectedBall;

void setup() {
  size(600, 600);
}

void draw() {  
  background(150);
  // render all balls
  for (Ball ball : ballList) {
    ball.drawBall();
  }
}

// this method will trigger once every time the user press a mouse button
void mousePressed() {
  // update selection
  selectedBall = getBall(mouseX, mouseY);
  
  // if there isn't a ball already, add one:
  if (selectedBall == null) {
    ballList.add(new Ball(mouseX, mouseY));
  }
}

void mouseDragged() {
  // update dragged ball coordinates if there is a previous selection
  if (selectedBall != null) {
    selectedBall.xPos = mouseX;
    selectedBall.yPos = mouseY;
  }
}

void mouseReleased() {
  // clear selection
  selectedBall = null;
}

Ball getBall(float x, float y) {
  // for each ball
  for (Ball ball : ballList) {
    // check if the x,y coordinates are inside any of the existing balls
    if ( dist(ball.xPos, ball.yPos, x, y) < ball.radius ) {
      // return the 1st match (exits loop and function immediately)
      return ball;
    }
  }
  // return null if nothing was found
  return null;
}

class Ball {

  float xPos, yPos; 
  float diameter = 50;
  float radius   = diameter * 0.5;

  Ball(float xPos, float yPos) {
    this.xPos = xPos;
    this.yPos = yPos;
  }
  
  void drawBall() {
    ellipse(xPos, yPos, diameter, diameter);
  }
  // pretty print info
  String toString(){
    return "[Ball x=" + xPos + " y="+ yPos + "]";
  }
}

If you want to pick the newly added ball immediately you can off course both add the new ball and update the selection:

ArrayList<Ball> ballList = new ArrayList<Ball>();
// reference to selection
Ball selectedBall;

void setup() {
  size(600, 600);
}

void draw() {  
  background(150);
  // render all balls
  for (Ball ball : ballList) {
    ball.drawBall();
  }
}

// this method will trigger once every time the user press a mouse button
void mousePressed() {
  // update selection
  selectedBall = getBall(mouseX, mouseY);
  
  // if there isn't a ball already, add one:
  if (selectedBall == null) {
    selectedBall = new Ball(mouseX, mouseY);
    ballList.add(selectedBall);
  }
}

void mouseDragged() {
  // update dragged ball coordinates if there is a previous selection
  if (selectedBall != null) {
    selectedBall.xPos = mouseX;
    selectedBall.yPos = mouseY;
  }
}

void mouseReleased() {
  // clear selection
  selectedBall = null;
}

Ball getBall(float x, float y) {
  // for each ball
  for (Ball ball : ballList) {
    // check if the x,y coordinates are inside any of the existing balls
    if ( dist(ball.xPos, ball.yPos, x, y) < ball.radius ) {
      // return the 1st match (exits loop and function immediately)
      return ball;
    }
  }
  // return null if nothing was found
  return null;
}

class Ball {

  float xPos, yPos; 
  float diameter = 50;
  float radius   = diameter * 0.5;

  Ball(float xPos, float yPos) {
    this.xPos = xPos;
    this.yPos = yPos;
  }
  
  void drawBall() {
    ellipse(xPos, yPos, diameter, diameter);
  }
  // pretty print info
  String toString(){
    return "[Ball x=" + xPos + " y="+ yPos + "]";
  }
}
George Profenza
  • 50,687
  • 19
  • 144
  • 218
  • 1
    First of all, thank you for such a detailed answer! How could I make the ball move when I release the mouse? – Jane Mar 01 '22 at 21:41
  • The code above already shows how to release: notice in the the `mouseReleased()` event handler the selection is cleared. if you right click and drag on an area with no balls you'll notice nothing is dragging. Currently the most recently added ball is automatically dragged (as it becomes the selection). You can simply do something like `if (mouseButton == LEFT) {ballList.add(new Ball(mouseX, mouseY));} ` to add a ball but not iniate dragging: this would leave right click as the only option to drag. – George Profenza Mar 01 '22 at 21:45
  • @Jane see the updates in the answer above – George Profenza Mar 01 '22 at 22:05
  • The ball should also move when i release the mouse. for example, I create a ball and drag it around the screen, when I let go of the mouse it should move to the right by itself. I have tried adding a speed in as a parameter in the Ball class and when the mouse is released the speedX is 10, but this only works for the ball after the one that has been created. I know this is because the ball is created (with the speed) before the mouse is released and therefore only has the speed from the ball before. Any ideas? – Jane Mar 02 '22 at 10:17
  • It sounds like you want to release the ball on `mouseReleased()` (same as the sketch I posted above before the update): this would allow the ball to move on it's own if you added speed and you're updating the ball position. I suspect a boolean property to know wether a ball is dragged or not would be handy so position be updated based on speed or mouse depending on the case. Feel free to post your code attempt as another question. – George Profenza Mar 04 '22 at 11:13