替代使用过多的for()循环来搜索数组中的匹配项

时间:2016-02-06 16:17:16

标签: javascript arrays canvas

You can view the full code on codepen here

我正在使用HTML5 Canvas进行物理模拟项目 目前我的代码使用了很多for()循环,绘制网格,绘制每个粒子,检查碰撞等 这基本上有效,但是在将150多个粒子添加到画布后会导致FPS下降,每个粒子必须循环通过150长阵列并检查碰撞。

数组中的每个粒子都存储在这样的字典中:

{
    x : 10,
    y : 15,
    color : "#FFF"
}  

简单的碰撞检测循环遍历数组中的每个粒子,并检查y值是否等于当前粒子位置+ 1.



// Begin loop
for (var part in particles) {
   // p = current particle being drawn & Updated to the canvas
   if (p.y + grid.size == particles[part].y && p.x == particles[part].x) {
      move.down = false;
   }
}
// move the particle if allowed
if (move.down) {
   p.y += grid.size;
}




这是粒子阵列在填充10个粒子后的样子。

[{"x":195,"y":505,"color":"skyblue"},{"x":195,"y":500,"color":"skyblue"},{"x":195,"y":495,"color":"skyblue"},{"x":195,"y":490,"color":"skyblue"},{"x":195,"y":485,"color":"skyblue"},{"x":135,"y":505,"color":"skyblue"},{"x":245,"y":505,"color":"skyblue"},{"x":160,"y":505,"color":"skyblue"},{"x":435,"y":505,"color":"skyblue"},{"x":355,"y":505,"color":"skyblue"}]  

有更有效的方法吗?使用for()循环的替代方法?

You can view the full code on codepen here



var fps_last;
var fps = 0;

var grid = {
   size: 5,
   height: 500,
   width: 500,
   padding: 5,
   color: "rgba(100,100,100,0.3);"
};

var canvas = $("#canvas")[0];
var canvas_width = canvas.width = grid.width + (grid.padding * 2) + 1;
var canvas_height = canvas.height = grid.height + (grid.padding * 2) + 1;
var ctx = canvas.getContext('2d');

var particles = [];
var mouse = {};

function draw() {
   animationFrame();
   $(".fps").html(fps + " FPS");
   $(".part").html("Particles: " + particles.length);
   ctx.clearRect(0, 0, canvas.height, canvas.width);
  // Loop
   for (var x = 0; x <= canvas.width; x += grid.size) {
      ctx.moveTo(0.5 + x, 0);
      ctx.lineTo(0.5 + x, canvas.height);
   }
  // Loop
   for (var x = 0; x <= canvas.height; x += grid.size) {
      ctx.moveTo(0, 0.5 + x);
      ctx.lineTo(canvas.width, 0.5 + x);
   }
   ctx.strokeStyle = grid.color;
   ctx.stroke();
   // Loop
   for (var i = 0; i < particles.length; i++) {
      var p = particles[i];
      ctx.beginPath();
      ctx.fillStyle = p.color;
      ctx.rect(p.x+1, p.y+1, grid.size-1, grid.size-1);
      ctx.closePath();
      ctx.fill();
      var move = {
         down : true
      }
      // Another loop.
      for(var part in particles){
         if(p.y+grid.size == particles[part].y && p.x == particles[part].x){
            move.down = false;
         }
      }
      
      if(move.down && p.y < grid.height+grid.padding){
         p.y += grid.size;
      }
      
   }
}

function particle() {
   this.x = 0;
   this.y = 0;
   this.color = "#FFF";
}

function create_particle(x, y) {
   var npart = new particle();
  // more loops
   for (var i; i < particles.length; i++) {
      if (x == particles[i].x && y == particles[i].y) {
         return;
      }
   }
   npart.x = x * grid.size;
   npart.y = y * grid.size;
   npart.color = "skyblue";
   particles.push(npart);
}

function animationFrame() {
  if(!fps_last) {
     fps_last = Date.now();
     fps = 0;
     return;
  }
  delta = (Date.now() - fps_last)/1000;
  fps_last = Date.now();
  var d = Math.floor(1/delta);
  d > 200 ? fps = 200 : fps = d;
} 

canvas.onclick = function(e) {
   var c = $("#canvas");
   var x = Math.floor((e.x - c.offset().left) / 5);
   var y = Math.floor((e.y - c.offset().top) / 5);
   create_particle(x, y);
}

canvas.onmousemove = function(e) {
   if (mouse.down) {
      var c = $("#canvas");
      var x = Math.floor((e.x - c.offset().left) / 5);
      var y = Math.floor((e.y - c.offset().top) / 5);
      create_particle(x, y);
   }
}

$(document).mousedown(function() {
   mouse.down = true;
}).mouseup(function() {
   mouse.down = false;
});

setInterval(draw, 1);
&#13;
@import url(https://fonts.googleapis.com/css?family=Slabo+27px|Titillium+Web:400,300,600,700|Raleway:400,300,500,600,700|PT+Sans:400,400italic,700);
html,
body {
  background: #111;
  color: #CCC;
  height: 100%;
  width: 100%;
}

#project_container {
  font-family: 'Raleway', serif;
  padding: 15px;
  width: 80%;
  margin: 0px auto;
}

#project_container h2 {
  font-family: 'Titillium Web', serif;
  font-size: 20pt;
  letter-spacing: 3px;
}

#project_container p a {
  color: white;
}

#project_container p a:hover {
  color: orange;
}

#project_container p {
  color: gray;
  padding: 5px;
  font-size: 10pt;
}

#projectbox {
  margin-top: 1em;
}

body,
html {
   cursor: default;
}

#canvas {
   outline: solid 1px #FFF;
   display: block;
   margin-bottom: 0.5em;
}
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="project_container">
   <h2>Canvas Testing</h2>
   <p>Testing using the HTML5 Canvas</p>
   <div id="projectbox">
      <!-- Start project HTML -->
      <canvas id="canvas" width="500" height="500"></canvas>
      <button onclick='particles=[];'>Delete all particles</button>
      <p class='fps'></p>
      <p class='part'></p>
      
      <!-- End Project HTML -->
   </div>
</div>
&#13;
&#13;
&#13;

5 个答案:

答案 0 :(得分:3)

如何在矩阵(2D数组)中表示粒子而不是列表。 这样您就可以检查O(1)

中的直接邻居

答案 1 :(得分:1)

要优化代码,您可以在循环中使用break statement并使用经典的for循环:

for (var i, length = particles.length; i < length; i++) {
   if (p.y + grid.size == particles[i].y && p.x == particles[i].x) {
      move.down = false;
      break;
   }
}
if (move.down) {
   p.y += grid.size;
}

答案 2 :(得分:1)

您可以考虑使用Array.prototype.every()

的其他结构
move.down = particles.every(function (particle) {
    return p.y + grid.size !== particle.y || p.x !== particle.x;
});

或者如果你更喜欢Array.prototype.some()

move.down = !particles.some(function (particle) {
    return p.y + grid.size == particle.y && p.x == particle.x;
});

答案 3 :(得分:1)

与样式和绘图技术相关的一些改进:

更快的空网格绘图

在应用程序开头只创建一次“空”网格,并将其保存到内存中的画布中。

然后ctx.drawImage(inMemoryCanvas,0,0)在可见画布上绘制空网格。

drawImagefillRect每个单元格快得多。

示例代码:重用现有网格

// create an "empty" grid on an in-memory canvas
var emptyGrid=drawEmptyGrid();

function drawEmptyGrid(){
    var c=document.createElement('canvas');
    var cctx=c.getContext('2d');
    c.width=canvas_width;
    c.height=canvas_height;
    cctx.fillStyle='gray';
    cctx.fillRect(0,0,c.width,c.height);
    for (var x = 0; x <= canvas.width; x += grid.size) {
      cctx.moveTo(0.5 + x, 0);
      cctx.lineTo(0.5 + x, canvas.height);
    }
    for (var x = 0; x <= canvas.height; x += grid.size) {
      cctx.moveTo(0, 0.5 + x);
      cctx.lineTo(canvas.width, 0.5 + x);
    }
    cctx.strokeStyle = grid.color;
    cctx.stroke();
    return(c);
}

// drawImage the empty grid instead of fillRect-ing each cell
function draw() {
   $(".part").html("Particles: " + particles.length);
   // redraw the entire empty grid from the saved in-memory canvas
   ctx.drawImage(emptyGrid,0,0);
   ...

更高效的样式:

在循环内反复重置context.fillStyle效率很低。相反,在循环之前设置一次粒子颜色。

示例代码:在for循环

之前只设置一次fillStyle
// Particles
ctx.fillStyle = p.color;
for (var i = 0; i < particles.length; i++) {

更高效的粒子绘制

您可以一次绘制所有粒子,而不是为每个粒子绘制.beginpath, .rect and .fill

示例代码:一次绘制每个粒子而不是单独绘制每个粒子

// Particles
ctx.fillStyle = 'skyblue';
ctx.beginPath();
for (var i = 0; i < particles.length; i++) {
   var p = particles[i];
   ctx.rect(p.x+1, p.y+1, grid.size-1, grid.size-1);
   ...
}
ctx.fill();

答案 4 :(得分:0)

一个选项:使用x使用命名字典作为具有x值和不同y值的粒子数组的键:

particle_object = { x:1, y:2, color:"green"};
if ( !particles[particle_object.x] ) {
     particles[x] = [];
}
particles[x].push(particle_object);

然后寻找碰撞,只需在粒子[particle_object.x]中循环遍历数组以检查碰撞(并且,如另一个答案所述,break一旦找到碰撞就会发现。