P5js Class issue

I want to drag multiple squares around, that’s why I put it in a class. Also because the subject at school is OOP.

Why exactly isn’t it working?
It worked perfectly fine without using a class.

Online editor: p5.js Web Editor

class Sheep {
  constructor(x, y) {
    this.pos = createVector(x, y);
    this.size = 20;
    this.conf = false;
  }
  move() {
    this.prevX = this.x;
    this.prevY = this.y;
    if (
      mouseX > this.pos.x - this.size / 2 &&
      mouseY > this.pos.y - this.size / 2 &&
      mouseX < this.pos.x + this.size / 2 &&
      mouseY < this.pos.y + this.size / 2
    ) {
      this.conf = true;
    } else {
      this.conf = false;
    }
    console.log(this.conf);
  }
  show() {
    rectMode(CENTER);
    rect(this.pos.x, this.pos.y, this.size);
  }
}

let a = [];
let newSheep;

function setup() {
  createCanvas(400, 400);
  background(220);

  newSheep = new Sheep(20, 20);
  newSheep.show();
}
function draw() {
  newSheep.move();
}
function mouseDragged() {
  if (newSheep.conf === true) {
    newSheep.pos.x = mouseX;
    newSheep.pos.y = mouseY;
  }
}

Hello, @anoniem, and welcome to the Processing Forum!

The code below is almost identical to your original. Note that among the several changes that were made, the most significant is that this line was added to the move() method, so that the Sheep gets displayed whenever its position changes:

    this.show(); // ***** added to display this Sheep

p5.js code:

class Sheep {
  constructor(x, y) {
    this.pos = createVector(x, y);
    this.size = 20;
    this.conf = false;
  }
  move() {
    this.prevX = this.pos.x;
    this.prevY = this.pos.y;
    if (
      mouseX > this.pos.x - this.size / 2 &&
      mouseY > this.pos.y - this.size / 2 &&
      mouseX < this.pos.x + this.size / 2 &&
      mouseY < this.pos.y + this.size / 2
    ) {
      this.conf = true;
    } else {
      this.conf = false;
    }
    console.log(this.conf);
    this.show(); // ***** added to display this Sheep
  }
  show() {
    rectMode(CENTER);
    rect(this.pos.x, this.pos.y, this.size);
  }
}

let a = [];
let newSheep;

function setup() {
  createCanvas(400, 400);
  background(220);

  newSheep = new Sheep(60, 60);
  newSheep.show();
}
function draw() {
  newSheep.move();
}
function mouseDragged() {
  if (newSheep.conf === true) {
    newSheep.pos.x = mouseX;
    newSheep.pos.y = mouseY;
  }
}

It is unclear what role these lines serve:

    this.prevX = this.x;
    this.prevY = this.y;

Note in the above that they were revised slightly.

EDITED on December 1, 2021 to clarify what changes were made to the original code.

Thank you so much Javagar,

I accidentally left some of my older code, my apologise.

1 Like

From an OOP perspective, there are still additional improvements that should be made to the code. For example, it is best, in general, for changes to properties of instances of a class to be made via methods, rather than directly via external functions.

Note these statements within the mouseDragged() method:

    newSheep.pos.x = mouseX;
    newSheep.pos.y = mouseY;

Updating the affected properties should be the responsibility of the move() method of the Sheep class, rather than to be performed directly by that external method. This will be especially beneficial if multiple instances of Sheep are to be created.

If you continue to refine the code and need additional assistance, we will be glad to provide it.

Oh, thank you, but I don’t see how I can create a method of the mouseDragged function.

You can revise the code so that the move() method does the work of looking at mouseX and mouseY. Then remove that task from the mouseDragged() function, and let it just call the move() method to do that job.

It’s definitely optimized now, but when I drag too fast, I lose the sheep.

class Sheep {
  constructor(x, y) {
    this.pos = createVector(x, y);
    this.size = 20;
    this.active = false;
  }
  move() {
    this.prevX = this.x;
    this.prevY = this.y;
    if (
      mouseX > this.pos.x - this.size / 2 &&
      mouseY > this.pos.y - this.size / 2 &&
      mouseX < this.pos.x + this.size / 2 &&
      mouseY < this.pos.y + this.size / 2
    ) {
      this.active = true;
    } else {
      this.active = false;
    }

    if (this.active) {
      this.pos.x = mouseX;
      this.pos.y = mouseY;
    }

    // reset when sheep is outside viewport
    if (
      this.pos.x > width ||
      this.pos.x < 0 ||
      this.pos.y > height ||
      this.pos.y < 0
    ) {
      this.pos.x = width / 2;
      this.pos.y = height / 2;
    }
    console.log(this.active);
    this.show();
  }
  show() {
    rectMode(CENTER);
    rect(this.pos.x, this.pos.y, this.size);
  }
}
let newSheep;
let myCanvas;

function setup() {
  myCanvas = createCanvas(500, 500);
  background(0);

  newSheep = new Sheep(20, 20);
}

function draw() {
  background(220);
  newSheep.show();
}
function mouseDragged() {
  newSheep.move();
}

Yes, it is possible for the mouse to outrun the Sheep. With there being only one Sheep, you can comment out a line to overcome the problem:

    } else {
      // this.active = false;
    }

However, when you have more than one Sheep, you’ll probably need to maintain a variable that keeps track of which one is current. Then, that would be the one that goes wherever the mouse does, even when the mouse outruns it. You can set things up so that a click on a Sheep designates it as current.

Hi @anoniem,

Please check out the following code:

class Sheep {
  constructor(x, y) {
    this.pos = createVector(x, y);
    this.size = 20;
    this.active = false;
  }
  move() {
    this.prevX = this.x;
    this.prevY = this.y;

    if (this.active) {
      this.pos.x = mouseX;
      this.pos.y = mouseY;
    }

    // reset when sheep is outside viewport
    if (
      this.pos.x > width ||
      this.pos.x < 0 ||
      this.pos.y > height ||
      this.pos.y < 0
    ) {
      this.pos.x = width / 2;
      this.pos.y = height / 2;
    }
    console.log(this.active);
    this.show();
  }
  show() {
    rectMode(CENTER);
    rect(this.pos.x, this.pos.y, this.size);
  }
  point_is_within(x, y) {
    if (
      x > this.pos.x - this.size / 2 &&
      y > this.pos.y - this.size / 2 &&
      x < this.pos.x + this.size / 2 &&
      y < this.pos.y + this.size / 2
    ) {
      return true;
    } else {
      return false;
    }
  }
  activate() {
    this.active = true;
  }
  deactivate() {
    this.active = false;
  }
}
let newSheep;
let myCanvas;

function setup() {
  myCanvas = createCanvas(500, 500);
  background(0);
  newSheep = new Sheep(20, 20);
}

function draw() {
  background(220);
  newSheep.show();
}

function mouseDragged() {
  newSheep.move();
}

function mousePressed() {
  x = mouseX;
  y = mouseY;
  if (newSheep.point_is_within(x, y)) {
    newSheep.activate();
  } else {
    newSheep.deactivate();
  }
}

Note that some new methods have been added to class Sheep, and note how those methods are used to interact with the instance, newSheep.

A mousePressed() function has also been added that enables newSheep to be activated when the mouse is pressed within its boundaries. Then it can be dragged. The instance newSheep is deactivated whenever the mouse is pressed outside its boundaries.

The changes made are only suggestions. You may prefer a different way of organizing the code. However, the suggested changes may help prepare for managing multiple Sheep, which could be organized and managed by a new class, perhaps named Flock. A feature of this Flock class could be an Array of Sheep instances. Every time the mouse is pressed, the instances of Sheep would be checked for possible activation. Then, when the mouse is dragged, only the active Sheep would be moved.

Hi Javagar,

Again, thanks for answering.

I also fixed the issue

class Sheep {
    constructor(x, y, size) {
    this.pos = createVector(x, y);
    this.size = size;
    this.active = false;
    
    this.speed = createVector(random(-1,1), random(-1,1));
    //this.speed.mult(0.1);
    }

    update(){
        this.pos.add(this.speed);
    }

    hover() {
        if (
            mouseX > this.pos.x - this.size / 2 &&
            mouseY > this.pos.y - this.size / 2 &&
            mouseX < this.pos.x + this.size / 2 &&
            mouseY < this.pos.y + this.size / 2
        ) {
            this.active = true;
        } else {
            this.active = false;
        }
    }

    drag(){
        if (this.active) {
            this.pos.x = mouseX;
            this.pos.y = mouseY;
        }

        // reset when sheep is outside viewport
        if (this.pos.x > width ||
            this.pos.x < 0 ||
            this.pos.y > height ||
            this.pos.y < 0)
            {
            this.pos.x = width / 2;
            this.pos.y = height / 2;
            }
    }

    bounce(){
        if (this.pos.x > width - this.size / 2 ||
            this.pos.x < 0 + this.size / 2)
            {
            this.speed.x *= -1;
            }
        if (this.pos.y > height - this.size / 2 ||
            this.pos.y < 0 + this.size / 2)
            {
            this.speed.y *= -1;
            }
    }
    show() {
    fill(10,10,10,20)

    rectMode(CENTER)
    rect(this.pos.x, this.pos.y, this.size);
    }
}
let myCanvas;
const sheeps = [];
let size = 20;

function setup() {
    myCanvas = createCanvas(400, 400);

    for(i = 0; i < 10; i++) {
    
    // only spawn within the viewport
    let newSheep = new Sheep(random(0 + (size / 2), width - (size / 2)), random(0  + (size / 2), height - (size / 2)), size);
    sheeps.push(newSheep);
    // console.log(sheeps.length);
    }
}
function draw() {
    background(220);
    sheeps.forEach(sheep => sheep.show());
    sheeps.forEach(sheep => sheep.update());
    sheeps.forEach(sheep => sheep.bounce());
    sheeps.forEach(sheep => sheep.hover());
}
function mouseDragged() {
    sheeps.forEach(sheep => sheep.drag())
}

But the problem with this, if I hover on any object, it is equal to the mouse position. So I can drag mutliple sheeps, which is not the best solution.

1 Like

Your sketch with the moving Sheep looks great!

This project is really interesting, so I have also been experimenting with your code, specifically to instantiate multiple Sheep.

Yes, I noticed that when multiple Sheep are dragged, they all occupy the same bounds. So this was my solution, developed in order to drag only one Sheep at a time:

class Sheep {
  constructor(x, y, id) {
    this.pos = createVector(x, y);
    this.size = 20;
    this.active = false;
    this.id = id;
  }
  move() {
    this.prevX = this.x;
    this.prevY = this.y;

    if (this.active) {
      this.pos.x = mouseX;
      this.pos.y = mouseY;
    }

    // reset Sheep to center when Sheep is outside viewport
    if (
      this.pos.x > width ||
      this.pos.x < 0 ||
      this.pos.y > height ||
      this.pos.y < 0
    ) {
      this.pos.x = width / 2;
      this.pos.y = height / 2;
    }
    console.log(this.active);
    this.show();
  }
  show() {
    rect(this.pos.x, this.pos.y, this.size);
    text(this.id, this.pos.x, this.pos.y);
  }
  point_is_within(x, y) {
    if (
      x > this.pos.x - this.size / 2 &&
      y > this.pos.y - this.size / 2 &&
      x < this.pos.x + this.size / 2 &&
      y < this.pos.y + this.size / 2
    ) {
      return true;
    } else {
      return false;
    }
  }
  activate() {
    this.active = true;
  }
  deactivate() {
    this.active = false;
  }
}

class Flock {
  constructor() {
    this.sheep = [];
    this.active_sheep = [];
  }
  addSheep(sh) {
    this.sheep.push(sh);
  }
  activateSheep(sh) {
    sh.activate();
    this.active_sheep.push(sh);
  }
  deactivateAllSheep() {
    for (let i = 0; i < this.active_sheep.length; i++) {
      this.active_sheep[i].deactivate();
      this.active_sheep = [];
    }
  }
  pollSheep() {
    let x = mouseX;
    let y = mouseY;
    this.deactivateAllSheep();
    for (let i = this.sheep.length - 1; i >= 0; i--) {
      if (this.sheep[i].point_is_within(x, y)) {
        this.activateSheep(this.sheep[i]);
        break; // drag only one Sheep at a time
      }
    }
  }
  moveSheep() {
    for (let i = 0; i < this.active_sheep.length; i++) {
      this.active_sheep[i].move();
    }
  }
  showSheep() {
    for (let i = 0; i < this.sheep.length; i++) {
      this.sheep[i].show();
    }
  }
}

let myCanvas;
let myFlock;

function setup() {
  myCanvas = createCanvas(500, 500);
  background(0);
  rectMode(CENTER);
  textAlign(CENTER, CENTER);
  myFlock = new Flock();
  for (let i = 0; i < 10; i++) {
    myFlock.addSheep(new Sheep(random(width), random(height), "" + i));
  }
}

function draw() {
  background(220);
  myFlock.showSheep();
}

function mouseDragged() {
  myFlock.moveSheep();
}

function mousePressed() {
  myFlock.pollSheep();
  console.log(myFlock.active_sheep);
}

This is the line that limits the dragging to one Sheep:

        break; // drag only one Sheep at a time

However, your solution represents the the best path to proceed, especially with the animation.

Great work!

1 Like

Thank you so much Javagar, how can I thank you?

I’m working on a intersection now, but can’t find out how to let it work.

I must be doing something wrong: here’s what I got.


class Sheep {
    constructor(x, y, size) {
    this.pos = createVector(x, y);
    this.size = size;
    this.active = false;
    this.speed = createVector(random(-1,1), random(-1,1));
    this.speed.mult(0.5);
    this.angle = 0;
    }

    move(){
        this.pos.add(this.speed);
    }

    drag() {
        if (this.active) {
            this.pos.x = mouseX;
            this.pos.y = mouseY;
        }

        if (this.pos.x > width ||
            this.pos.x < 0 ||
            this.pos.y > height ||
            this.pos.y < 0)
            {
            this.pos.x = width / 2;
            this.pos.y = height / 2;
            }
    }

    bounce() {
        if (this.pos.x > width - this.size / 2 ||
            this.pos.x < 0 + this.size / 2)
            {
            this.speed.x *= -1;
            }
        if (this.pos.y > height - this.size / 2 ||
            this.pos.y < 0 + this.size / 2)
            {
            this.speed.y *= -1;
            }
    }
    show() {
    
    let angle = 72;
    let headSize = this.size / 3;

    let aangle = this.speed.heading();

    push()
    translate(this.pos.x, this.pos.y);

    rotate(aangle);

    fill("black");
    circle(this.size / 2.2, 0, headSize);
    angleMode(DEGREES);

    for(let i = -angle/2; i < 360; i+=angle){
      push()
      noStroke()
      fill("black")
        rotate(i);
        translate(this.size / 4, 0)
        circle(0,0,this.size / 1.7)
      pop()
    }

    for(let i = -angle / 2; i < 360; i+=angle){
      push()
      noStroke()
      fill("white")
        rotate(i);
        translate(this.size / 4, 0)
        circle(0,0,this.size / 2)
      pop()
    }
    pop()
    }

    hover(x, y) {
        if (
            x > this.pos.x - this.size / 2 &&
            y > this.pos.y - this.size / 2 &&
            x < this.pos.x + this.size / 2 &&
            y < this.pos.y + this.size / 2
        ) {
            return true;
        } else {
            return false;
        }
    }
    activate() {
        this.active = true;
    }
    deactivate() {
        this.active = false;
    }
}
class Herd{
    constructor() {

    this.sheep = [];
    this.active_sheep = [];
}
    addSheep(sh) {
    this.sheep.push(sh);
    }
    /* activeer */
    activateSheep(sh) {
    sh.activate();
    this.active_sheep.push(sh);
    }

    deactivateAllSheep() {
    for (let i = 0; i < this.active_sheep.length; i++) {
        this.active_sheep[i].deactivate();
        this.active_sheep = [];
    }
    }

    pollSheep() {
    let x = mouseX;
    let y = mouseY;
    this.deactivateAllSheep();
    for (let i = this.sheep.length - 1; i >= 0; i--) {
        if (this.sheep[i].hover(x, y)) {
        this.activateSheep(this.sheep[i]);
        break; // drag only one Sheep at a time
        }
    }
    }

    intersects(otherSheep){
        let d = dist(this.pos.x, this.pos.y, otherSheep.pos.x, otherSheep.pos.y);
        if(d < this.size + otherSheep.size){
            return true;
        }else{
            return false;
        }
    }

    collission(){
        for(let i = 0; i < sheep.length; i++){
            let currentSheep = this.sheep[i];
      
            for(let j = 0; this.sheep.length; j++){
                if(i != j){
                    let otherSheep = this.sheep[j];
                    let sheepsIntersected = currentSheep.intersects(otherSheep);
                    if(sheepsIntersected){
                        console.log("intersection");
                    }
                }
            }
        }
    }

    dragSheep() {
        for (let i = 0; i < this.active_sheep.length; i++) {
            this.active_sheep[i].drag();
        }
    }

    showSheep() {
        for (let i = 0; i < this.sheep.length; i++) {
            this.sheep[i].show();
        }
    }

    moveSheep() {
        for (let i = 0; i < this.sheep.length; i++) {
            this.sheep[i].move();
        }
    }

    bounceSheep() {
        for (let i = 0; i < this.sheep.length; i++) {
            this.sheep[i].bounce();
        }
    }
}

let myCanvas;
let myHerd;
let size = 40;

let herd = [];

function setup() {
    myCanvas = createCanvas(500, 500);

    rectMode(CENTER);
    myHerd = new Herd();
    for (let i = 0; i < 5; i++) {
    herd.push(myHerd.addSheep(new Sheep(random(0 + (size / 2), width - (size / 2)), random(0 + (size / 2), height - (size / 2)), size)))
}
}
function draw() {
    background("#5AD455");
 
    myHerd.showSheep();
    myHerd.moveSheep();
    myHerd.bounceSheep();
}

function mouseDragged() {
    myHerd.dragSheep();
}

function mousePressed() {
    myHerd.pollSheep();
    console.log(myHerd.active_sheep);
}
function keyPressed(){
    if (keyCode === UP_ARROW) {
        herd.push(myHerd.addSheep(new Sheep(random(0 + (size / 2), width - (size / 2)), random(0 + (size / 2), height - (size / 2)), size)))
    }

}

This method is included in class Herd:

  intersects(otherSheep) {
    let d = dist(this.pos.x, this.pos.y, otherSheep.pos.x, otherSheep.pos.y);
    if (d < this.size + otherSheep.size) {
      return true;
    } else {
      return false;
    }
  }

What is the purpose of that method? Was the intention to make it part of class Sheep, instead?

Well, I’m not sure where I have to put it.

That methods checks if two sheeps intersect based on their radius. So I thought I needed to put this in the Herd Class

The significant detail is that the method uses this to refer to the current instance of a class, for instance, here:

  let d = dist(this.pos.x, this.pos.y, otherSheep.pos.x, otherSheep.pos.y);

With that method situated in the definition of class Herd, this refers to the current instance of Herd, and therefore the statement attempts to compute a distance between the Herd and a Sheep. Instead, what is needed is a distance between the current Sheep and another Sheep. Therefore, the method should be moved into the definition for class Sheep. So, the best action to take would be to make that move, then to continue with testing and refinement of the code.

1 Like