首页 > 解决方案 > 嵌套 for 循环:带有碰撞检测的错误 javascript p5.js

问题描述

我目前正在创建一个小游戏来学习在 p5.js 中编码。

我面临的错误是当我尝试向飞来飞去的彗星发射导弹[对象数组]键盘箭头[也是对象数组]。为了了解导弹是否会射击彗星,我创建了一个类方法来比较它们的距离,如果它们的距离小于 ex 20,则导弹和彗星都从它们的阵列中拼接起来。

它随机工作,有时它适用于 20 颗彗星,有时它在第一次相遇时会滞后。我怀疑这与我正在使用的嵌套 for 循环有关。

首先,我在 Setup 函数中每 5 秒创建 2 个对象 MOVER 并将它们存储到一个数组中。

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

然后我基本上在我的对象 Mover 中创建了一个名为“destroy”的方法,它指示导弹是否距离小于 20 像素。

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

然后当我在 P5.js 的绘图函数中调用这些函数时,它是滞后的。

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.

如果您愿意帮助我,将不胜感激!如果您想玩游戏了解错误,可以用鼠标移动绿色矩形,避开彗星并用键盘箭头射击它们!多谢 !:)

这是整个代码:

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 a a 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>

标签: javascriptcollision-detectionp5.js

解决方案


问题出在导弹/移动器碰撞检测循环中:

  1. 您正在遍历所有“导弹”
  2. 对于每颗导弹,您都会遍历所有移动器
  3. 如果发生碰撞,您可以同时移除移动器和导弹
  4. 但是您继续循环使用 i 的当前值,它现在可能超过了数组的末尾!

结果,有时您会收到错误,因为在随后通过移动器循环时missil[i]未定义。每次更新当前循环的数组时,都需要小心更新索引并重新检查长度,然后再继续。

    // 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!
        }

这是您的草图的固定版本:

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 a a 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>


推荐阅读