简体   繁体   中英

Nested for loops: Bug with collision detection javascript p5.js

I'm currently creating a little game to learn to code in p5.js.

The bug I'm facing is when I try to shoot missils[array of object] the Keyboard Arrow at comets that fly around [also an array of objects]. In order to understand if the missils would shoot the comet, I created a class method that compare their distance and if their distance is less than for ex 20, both the missil and the comets are getting spliced from their array.

It works randomly, sometimes it works for 20 comets and sometimes it lags at the first encounter. I suspect that this has to do with the nested for loops that I'm using.

First of all I'm creating 2 objects MOVER every 5 seconds in the Setup function and store them into an array.

for(let i=0; i<2; i++) { 
  {
    setInterval(
      function(){
        movertest = new Mover(random(400, width), random(400, height));
        mover.push(movertest);
      },
      5000)
  };
}

I basically created then a method called "destroy" in my object Mover, which indicates whether or not a missil is less then 20 pixels away.

destroy(px, py) {
  if( dist(this.position.x, this.position.y, px, py) <= 20) {
    return true
  } else {
    return false;
  }
}

and then when I summon those functions in the draw functions of P5.js, it is lagging.

for (i=0; i < missil.length; i++) {
  missil[i].show();
  missil[i].update();
  for (p=0; p < planets.length; p++) {
    missil[i].bounce(planets[p].position.x, planets[p].position.y, planets[p].lrect/2)
  };

  //if missil is in the canvas then check for collision
  if (missil[i].contains(width/2,height/2)) {
    for (let u = 0; u < mover.length; u++) {
      if (mover[u].destroy(missil[i].position.x, missil[i].position.y)) {
        mover.splice(u,1);
        missil.splice(i,1);
        console.log(u);
        console.log(i)
      };
    }
  } else {
    missil.splice(i,1)
  }
} // if missil is out of the canvas it get erased from the array.

If you would like to help me that would be super appreciated! If you want to play the game to understand the error, you can move green rectangle with your mouse, and avoid the comets and shoot them with the Keyboard arrow! thanks a lot ! :)

Here is the entire code:

 let mover = []; let x = 0; let collision = 0; //counter to know when to end the game let collu = 0; let lrect = 10; //size of the ship. let accslider, velslider; let shoot = false; //to know if to shoot a missil. let started = true; let movertest = []; //number of missil. let missil = []; let outofcanvas; let a = 0; let b = 0; let munitions = 100; let planets = []; let cron; function windowalert() { if (confirm("Votre score est de rejouer?")) { location.reload() } else { location.close(); } } function setup() { background(0, 1); cnv = createCanvas(900, 600); for (let i = 0; i < 2; i++) { setInterval(function() { movertest = new Mover(random(400, width), random(400, height)); mover.push(movertest); }, 5000) } for (p = 0; p < 4; p++) { planets[p] = new Planet(100, 100, 20 + p * 20, 20 + p * 20) } ship = new Ship(300, 300); accslider = createSlider(0, 255, 100); accslider.position(width + 20, 20); // noLoop(); // putted here since loop is when pressed the button start => function } function draw() { text(munitions, 80, 20); if (started) { background(0, 50); for (let i = 0; i < mover.length; i++) { mover[i].show(); mover[i].update(x); mover[i].edge(); // if rollover/ contains is true => then change collision +1 => collsison arrives at 255=> you are dead. if (mover[i].contains(mouseX, mouseY)) { collision += 1; lrect += 0.04; } } if (collision >= 255) { clearInterval(cron); started = false; windowalert() } ship.move(mouseX, mouseY); //ship.impact(mover[i].position.x,mover[i].position.y) ship.show(); ship.edge(); noCursor(); x += 0.00005; for (p = 0; p < planets.length; p++) { planets[p].show(50 + p * 10, 180 + p * 40, 120 + p * 10); planets[p].move(); } for (i = 0; i < missil.length; i++) { missil[i].show(); missil[i].update(); for (p = 0; p < planets.length; p++) { missil[i].bounce(planets[p].position.x, planets[p].position.y, planets[p].lrect / 2) } //if missil is in the canvas then check for collision if (missil[i].contains(width / 2, height / 2)) { for (let u = 0; u < mover.length; u++) { if (mover[u].destroy(missil[i].position.x, missil[i].position.y)) { mover.splice(u, 1); missil.splice(i, 1); console.log(u); console.log(i) }; } } else { missil.splice(i, 1) } } } } // if missil is out of the canvas it get erased from the array. setInterval(function() { if (munitions < 100) { munitions += 1 } }, 1000); function keyPressed() { if (munitions > 0) { if (keyIsDown(LEFT_ARROW)) { a = -1; munitions += -1 } // a is in the class munitions and represent the vector x. I did it like that so when we click both arrow it goes in diagonal. if (keyIsDown(RIGHT_ARROW)) { a = 1; munitions += -1 } if (keyIsDown(UP_ARROW)) { b = -1; munitions += -1 } if (keyIsDown(DOWN_ARROW)) { b = 1; munitions += -1 } for (let u = 0; u < 1; u++) { let mi = new Missil(mouseX, mouseY, a, b); missil.push(mi); } } } function keyReleased() { if (keyCode == LEFT_ARROW) { a = 0 } // this was implemented to reput the missil vector at the default value when released the key. if (keyCode == RIGHT_ARROW) { a = 0 } if (keyCode == UP_ARROW) { b = 0 } if (keyCode == DOWN_ARROW) { b = 0 } } class Missil { constructor(x, y, a, b) //partira de mx,my. { this.position = createVector(x, y); this.vel = createVector(a, b); this.vel.mult(random(2, 4)) } update() { this.position = this.position.add(this.vel); } show() { stroke(255); noStroke(); fill(255, 0, 0, 100); ellipse(this.position.x, this.position.y, 5); } contains(px, py) { if (dist(this.position.x, this.position.y, px, py) < width / 2) { return true } else { return false } } bounce(px, py, dista) { { if (dist(this.position.x, this.position.y, px, py) < dista) { let v = createVector(this.position.x - px, this.position.y - py); this.vel = this.vel.mult(1.5).reflect(v) } } } } class Planet { constructor(x, y, lrect, lrect2) { this.position = createVector(x, y) this.vel = p5.Vector.random2D(); this.lrect = lrect; this.lrect2 = lrect2; } show(r, g, b) { fill(r, g, b); noStroke(); ellipseMode(CENTER); ellipse(this.position.x, this.position.y, this.lrect, this.lrect2) stroke(255); } move() { let center = createVector(width / 2, height / 2) this.gravityacc = p5.Vector.sub(center, this.position); this.gravityacc.setMag(0.004); this.vel = this.vel.add(this.gravityacc); this.position = this.position.add(this.vel); this.vel.limit(1.3); } } class Mover { //those are the comets constructor(x, y) { this.position = createVector(x, y); this.vel = p5.Vector.random2D(); this.vel.mult(random(3)); //this.acc=p5.Vector.random2D();// acceleeration is aa random vector here. // this.acc.setMag(0.01); // magnitude of acceleration is slow. Acceleration is a vector. // this.vel.limit(3); // shrinks size of vector to 5, but if it smaller then 5 then it doent equal to 5 like in setMag(). } update(speed) { setInterval(function() { this.speed += 0.01 }, 3000); let mouse = createVector(mouseX, mouseY); this.acc = p5.Vector.sub(mouse, this.position); this.acc.setMag(0.04) this.acc.limit(0.1) this.vel.add(this.acc); /// add acceleration to velocitiy. this.position.add(this.vel); this.vel.limit(5); } show() { stroke(255, 10); strokeWeight(0); fill(map(this.position.x, 0, width, 0, 255), map(this.position.y, 0, height, 0, 255), 255, 255); ellipse(this.position.x, this.position.y, 5) } edge() { if (this.position.x >= width) { let n = createVector(-1, 0); this.vel = this.vel.reflect(n) } if (this.position.x <= 0) { let n = createVector(1, 0); this.vel = this.vel.reflect(n) } if (this.position.y >= height) { let n = createVector(0, -1); this.vel = this.vel.reflect(n) } if (this.position.y <= 0) { let n = createVector(0, 1); this.vel = this.vel.reflect(n) } } contains(px, py) { if (dist(this.position.x, this.position.y, px, py) < 5 + lrect) { return true } else { return false } } destroy(px, py) { if (dist(this.position.x, this.position.y, px, py) <= 20) { return true } else { return false; } } } class Ship { constructor(x, y) { this.position = createVector(x, y); this.vel = createVector(); this.acc = createVector(); } move(px, py) { this.position.x = px; this.position.y = py } //if key pressed. edge() { if (this.position.x >= width) { this.position.x = width } if (this.position.y >= height - 50) { this.position.y = height - 50 } } show() { stroke(255); strokeWeight(0); fill(collision * 1, 255 - collision * 2, 0, 100); rect(this.position.x, this.position.y, lrect, lrect) } } /* Without the HTML this isn't functional "use strict"; document.form_main.start.onclick = () => start(); document.form_main.pause.onclick = () => pause(); document.form_main.reset.onclick = () => reset(); function start() { pause(); cron = setInterval(() => { timer(); }, 10); started = true; // to indicate to start draw loop(); // noLoop in fucntion setup. } function pause() { clearInterval(cron); started = false; } function reset() { location.reload(); } function timer() { if ((millisecond += 10) == 1000) { millisecond = 0; second++; } if (second == 60) { second = 0; minute++; } if (minute == 60) { minute = 0; hour++; } document.getElementById('hour').innerText = returnData(hour); document.getElementById('minute').innerText = returnData(minute); document.getElementById('second').innerText = returnData(second); document.getElementById('millisecond').innerText = returnData(millisecond); } function returnData(input) { return input > 10 ? input : `0${input}` } */
 <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.js"></script>

The problem is in the the missil/mover collision detection loop:

  1. You are looping through all of the "missils"
  2. For each missil you loop through all the movers
  3. In the event of a collision you remove both the mover and the missil
  4. But you continue looping with the current value of i, which may now be past the end of the array!

As a result you are getting an error sometimes because on a subsequent pass through the mover loop missil[i] is undefined. Any time you update an array you are currently looping over you need to be careful to update your indices and re-check your length before continuing.

    // 1. You are looping through all of the "missils"
    for (i = 0; i < missil.length; i++) {
        // ...
        
        // 2. For each missil you loop through all the movers
        for (let u = 0; u < mover.length; u++) {
          if (mover[u].destroy(missil[i].position.x, missil[i].position.y)) {
            // 3. In the event of a collision you remove both the mover and the missil
            mover.splice(u, 1);
            missil.splice(i, 1);
            console.log(u);
            console.log(i)
          }

          // 4. But you continue looping with the current value of i, which may now be past the end of the array!
        }

Here is a fixed version of your sketch:

 let mover = []; let x = 0; let collision = 0; //counter to know when to end the game let collu = 0; let lrect = 10; //size of the ship. let accslider, velslider; let shoot = false; //to know if to shoot a missil. let started = true; let movertest = []; //number of missil. let missil = []; let outofcanvas; let a = 0; let b = 0; let munitions = 100; let planets = []; let cron; function windowalert() { if (confirm("Votre score est de rejouer?")) { location.reload() } else { location.close(); } } function setup() { background(0, 1); cnv = createCanvas(900, 600); for (let i = 0; i < 2; i++) { setInterval(function() { movertest = new Mover(random(400, width), random(400, height)); mover.push(movertest); }, 5000) } for (p = 0; p < 4; p++) { planets[p] = new Planet(100, 100, 20 + p * 20, 20 + p * 20) } ship = new Ship(300, 300); accslider = createSlider(0, 255, 100); accslider.position(width + 20, 20); // noLoop(); // putted here since loop is when pressed the button start => function } function draw() { text(munitions, 80, 20); if (started) { background(0, 50); for (let i = 0; i < mover.length; i++) { mover[i].show(); mover[i].update(x); mover[i].edge(); // if rollover/ contains is true => then change collision +1 => collsison arrives at 255=> you are dead. if (mover[i].contains(mouseX, mouseY)) { collision += 1; lrect += 0.04; } } if (collision >= 255) { clearInterval(cron); started = false; windowalert() } ship.move(mouseX, mouseY); //ship.impact(mover[i].position.x,mover[i].position.y) ship.show(); ship.edge(); noCursor(); x += 0.00005; for (p = 0; p < planets.length; p++) { planets[p].show(50 + p * 10, 180 + p * 40, 120 + p * 10); planets[p].move(); } for (i = 0; i < missil.length; i++) { missil[i].show(); missil[i].update(); for (p = 0; p < planets.length; p++) { missil[i].bounce(planets[p].position.x, planets[p].position.y, planets[p].lrect / 2) } //if missil is in the canvas then check for collision if (missil[i].contains(width / 2, height / 2)) { let colission = false; for (let u = 0; u < mover.length; u++) { if (mover[u].destroy(missil[i].position.x, missil[i].position.y)) { mover.splice(u, 1); missil.splice(i, 1); // Exit the mover loop immediately colission = true; break; } } if (colission) { // because we've deleted the item at i, the item that was at // i + 1 is now at i, so in order not to skip that item we // need to decrement i before continuing i--; } } else { missil.splice(i, 1); i--; } } } } // if missil is out of the canvas it get erased from the array. setInterval(function() { if (munitions < 100) { munitions += 1 } }, 1000); function keyPressed() { if (munitions > 0) { if (keyIsDown(LEFT_ARROW)) { a = -1; munitions += -1 } // a is in the class munitions and represent the vector x. I did it like that so when we click both arrow it goes in diagonal. if (keyIsDown(RIGHT_ARROW)) { a = 1; munitions += -1 } if (keyIsDown(UP_ARROW)) { b = -1; munitions += -1 } if (keyIsDown(DOWN_ARROW)) { b = 1; munitions += -1 } for (let u = 0; u < 1; u++) { let mi = new Missil(mouseX, mouseY, a, b); missil.push(mi); } } } function keyReleased() { if (keyCode == LEFT_ARROW) { a = 0 } // this was implemented to reput the missil vector at the default value when released the key. if (keyCode == RIGHT_ARROW) { a = 0 } if (keyCode == UP_ARROW) { b = 0 } if (keyCode == DOWN_ARROW) { b = 0 } } class Missil { constructor(x, y, a, b) //partira de mx,my. { this.position = createVector(x, y); this.vel = createVector(a, b); this.vel.mult(random(2, 4)) } update() { this.position = this.position.add(this.vel); } show() { stroke(255); noStroke(); fill(255, 0, 0, 100); ellipse(this.position.x, this.position.y, 5); } contains(px, py) { if (dist(this.position.x, this.position.y, px, py) < width / 2) { return true } else { return false } } bounce(px, py, dista) { { if (dist(this.position.x, this.position.y, px, py) < dista) { let v = createVector(this.position.x - px, this.position.y - py); this.vel = this.vel.mult(1.5).reflect(v) } } } } class Planet { constructor(x, y, lrect, lrect2) { this.position = createVector(x, y) this.vel = p5.Vector.random2D(); this.lrect = lrect; this.lrect2 = lrect2; } show(r, g, b) { fill(r, g, b); noStroke(); ellipseMode(CENTER); ellipse(this.position.x, this.position.y, this.lrect, this.lrect2) stroke(255); } move() { let center = createVector(width / 2, height / 2) this.gravityacc = p5.Vector.sub(center, this.position); this.gravityacc.setMag(0.004); this.vel = this.vel.add(this.gravityacc); this.position = this.position.add(this.vel); this.vel.limit(1.3); } } class Mover { //those are the comets constructor(x, y) { this.position = createVector(x, y); this.vel = p5.Vector.random2D(); this.vel.mult(random(3)); //this.acc=p5.Vector.random2D();// acceleeration is aa random vector here. // this.acc.setMag(0.01); // magnitude of acceleration is slow. Acceleration is a vector. // this.vel.limit(3); // shrinks size of vector to 5, but if it smaller then 5 then it doent equal to 5 like in setMag(). } update(speed) { setInterval(function() { this.speed += 0.01 }, 3000); let mouse = createVector(mouseX, mouseY); this.acc = p5.Vector.sub(mouse, this.position); this.acc.setMag(0.04) this.acc.limit(0.1) this.vel.add(this.acc); /// add acceleration to velocitiy. this.position.add(this.vel); this.vel.limit(5); } show() { stroke(255, 10); strokeWeight(0); fill(map(this.position.x, 0, width, 0, 255), map(this.position.y, 0, height, 0, 255), 255, 255); ellipse(this.position.x, this.position.y, 5) } edge() { if (this.position.x >= width) { let n = createVector(-1, 0); this.vel = this.vel.reflect(n) } if (this.position.x <= 0) { let n = createVector(1, 0); this.vel = this.vel.reflect(n) } if (this.position.y >= height) { let n = createVector(0, -1); this.vel = this.vel.reflect(n) } if (this.position.y <= 0) { let n = createVector(0, 1); this.vel = this.vel.reflect(n) } } contains(px, py) { if (dist(this.position.x, this.position.y, px, py) < 5 + lrect) { return true } else { return false } } destroy(px, py) { if (dist(this.position.x, this.position.y, px, py) <= 20) { return true } else { return false; } } } class Ship { constructor(x, y) { this.position = createVector(x, y); this.vel = createVector(); this.acc = createVector(); } move(px, py) { this.position.x = px; this.position.y = py } //if key pressed. edge() { if (this.position.x >= width) { this.position.x = width } if (this.position.y >= height - 50) { this.position.y = height - 50 } } show() { stroke(255); strokeWeight(0); fill(collision * 1, 255 - collision * 2, 0, 100); rect(this.position.x, this.position.y, lrect, lrect) } } /* Without the HTML this isn't functional "use strict"; document.form_main.start.onclick = () => start(); document.form_main.pause.onclick = () => pause(); document.form_main.reset.onclick = () => reset(); function start() { pause(); cron = setInterval(() => { timer(); }, 10); started = true; // to indicate to start draw loop(); // noLoop in fucntion setup. } function pause() { clearInterval(cron); started = false; } function reset() { location.reload(); } function timer() { if ((millisecond += 10) == 1000) { millisecond = 0; second++; } if (second == 60) { second = 0; minute++; } if (minute == 60) { minute = 0; hour++; } document.getElementById('hour').innerText = returnData(hour); document.getElementById('minute').innerText = returnData(minute); document.getElementById('second').innerText = returnData(second); document.getElementById('millisecond').innerText = returnData(millisecond); } function returnData(input) { return input > 10 ? input : `0${input}` } */
 <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.js"></script>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM