(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;
答案 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)
在可见画布上绘制空网格。
drawImage
比fillRect
每个单元格快得多。
示例代码:重用现有网格
// 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
一旦找到碰撞就会发现。