HTML5画布粒子爆炸

5
我正在尝试让这个粒子爆炸效果正常运行。它能够工作,但似乎有一些帧没有被渲染出来。如果我连续点击多次以触发多个爆炸,它会开始出现“卡顿/短暂停顿”的情况。我忘记做了什么吗?当我点击多次时,它可能看起来像浏览器挂起了。在内部嵌套两个for循环是不是太多了?
附上我的代码,这样你就能看到。只需尝试多次点击,即可直观地看到问题。

// Request animation frame
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
// Canvas
var c = document.getElementById('canvas');
var ctx = c.getContext('2d');
// Set full-screen
c.width = window.innerWidth;
c.height = window.innerHeight;
// Options
var background = '#333'; // Background color
var particlesPerExplosion = 20;
var particlesMinSpeed = 3;
var particlesMaxSpeed = 6;
var particlesMinSize = 1;
var particlesMaxSize = 3;
var explosions = [];
var fps = 60;
var now, delta;
var then = Date.now();
var interval = 1000 / fps;
// Optimization for mobile devices
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
    fps = 29;
}
// Draw
function draw() {
    // Loop
    requestAnimationFrame(draw);
    // Set NOW and DELTA
    now = Date.now();
    delta = now - then;
    // New frame
    if (delta > interval) {
        // Update THEN
        then = now - (delta % interval);
        // Our animation
        drawBackground();
        drawExplosion();
    }
}
// Draw explosion(s)
function drawExplosion() {
    if (explosions.length == 0) {
        return;
    }
    for (var i = 0; i < explosions.length; i++) {
        var explosion = explosions[i];
        var particles = explosion.particles;
        if (particles.length == 0) {
            explosions.splice(i, 1);
            return;
        }
        for (var ii = 0; ii < particles.length; ii++) {
            var particle = particles[ii];
            // Check particle size
            // If 0, remove
            if (particle.size < 0) {
                particles.splice(ii, 1);
                return;
            }
            ctx.beginPath();
            ctx.arc(particle.x, particle.y, particle.size, Math.PI * 2, 0, false);
            ctx.closePath();
            ctx.fillStyle = 'rgb(' + particle.r + ',' + particle.g + ',' + particle.b + ')';
            ctx.fill();
            // Update
            particle.x += particle.xv;
            particle.y += particle.yv;
            particle.size -= .1;
        }
    }
}
// Draw the background
function drawBackground() {
    ctx.fillStyle = background;
    ctx.fillRect(0, 0, c.width, c.height);
}
// Clicked
function clicked(e) {
    var xPos, yPos;
    if (e.offsetX) {
        xPos = e.offsetX;
        yPos = e.offsetY;
    } else if (e.layerX) {
        xPos = e.layerX;
        yPos = e.layerY;
    }
    explosions.push(new explosion(xPos, yPos));
}
// Explosion
function explosion(x, y) {
    this.particles = [];
    for (var i = 0; i < particlesPerExplosion; i++) {
        this.particles.push(new particle(x, y));
    }
}
// Particle
function particle(x, y) {
    this.x = x;
    this.y = y;
    this.xv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
    this.yv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
    this.size = randInt(particlesMinSize, particlesMaxSize, true);
    this.r = randInt(113, 222);
    this.g = '00';
    this.b = randInt(105, 255);
}
// Returns an random integer, positive or negative
// between the given value
function randInt(min, max, positive) {
    if (positive == false) {
        var num = Math.floor(Math.random() * max) - min;
        num *= Math.floor(Math.random() * 2) == 1 ? 1 : -1;
    } else {
        var num = Math.floor(Math.random() * max) + min;
    }
    return num;
}
// On-click
$('canvas').on('click', function(e) {
    clicked(e);
});
draw();
<!DOCTYPE html>
<html>

<head>
    <style>
        * {
            margin: 0;
            padding: 0;
            overflow: hidden;
        }
    </style>
</head>

<body>
    <canvas id="canvas"></canvas>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

</html>


我猜,每次渲染时你都会绘制背景和粒子。这意味着如果有多个爆炸同时发生,当渲染并执行“drawBackground()”时,爆炸2将覆盖爆炸1,而只有在下一次爆炸1渲染时才能再次看到它。 - Herr Derb
尝试注释掉背景绘制部分,但它仍然像以前一样非常卡顿/颤抖。 - Kaizokupuffball
2个回答

5

如果其中一个粒子太小,您正在返回迭代过程中。这会导致该爆炸的其他粒子仅在下一帧中呈现。

我有一个可用的版本:

// Request animation frame
const requestAnimationFrame = window.requestAnimationFrame ||
  window.mozRequestAnimationFrame ||
  window.webkitRequestAnimationFrame ||
  window.msRequestAnimationFrame;

// Canvas
const c   = document.getElementById('canvas');
const ctx = c.getContext('2d');

// Set full-screen
c.width  = window.innerWidth;
c.height = window.innerHeight;

// Options
const background            = '#333';                    // Background color
const particlesPerExplosion = 20;
const particlesMinSpeed     = 3;
const particlesMaxSpeed     = 6;
const particlesMinSize      = 1;
const particlesMaxSize      = 3;
const explosions            = [];

let fps        = 60;
const interval = 1000 / fps;

let now, delta;
let then = Date.now();

// Optimization for mobile devices
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
  fps = 29;
}

// Draw
function draw() {
  // Loop
  requestAnimationFrame(draw);

  // Set NOW and DELTA
  now   = Date.now();
  delta = now - then;

  // New frame
  if (delta > interval) {

    // Update THEN
    then = now - (delta % interval);

    // Our animation
    drawBackground();
    drawExplosion();

  }

}

// Draw explosion(s)
function drawExplosion() {

  if (explosions.length === 0) {
    return;
  }

  for (let i = 0; i < explosions.length; i++) {

    const explosion = explosions[i];
    const particles = explosion.particles;

    if (particles.length === 0) {
      explosions.splice(i, 1);
      return;
    }

    const particlesAfterRemoval = particles.slice();
    for (let ii = 0; ii < particles.length; ii++) {

      const particle = particles[ii];

      // Check particle size
      // If 0, remove
      if (particle.size <= 0) {
        particlesAfterRemoval.splice(ii, 1);
        continue;
      }

      ctx.beginPath();
      ctx.arc(particle.x, particle.y, particle.size, Math.PI * 2, 0, false);
      ctx.closePath();
      ctx.fillStyle = 'rgb(' + particle.r + ',' + particle.g + ',' + particle.b + ')';
      ctx.fill();

      // Update
      particle.x += particle.xv;
      particle.y += particle.yv;
      particle.size -= .1;
    }

    explosion.particles = particlesAfterRemoval;

  }

}

// Draw the background
function drawBackground() {
  ctx.fillStyle = background;
  ctx.fillRect(0, 0, c.width, c.height);
}

// Clicked
function clicked(e) {

  let xPos, yPos;

  if (e.offsetX) {
    xPos = e.offsetX;
    yPos = e.offsetY;
  } else if (e.layerX) {
    xPos = e.layerX;
    yPos = e.layerY;
  }

  explosions.push(
    new explosion(xPos, yPos)
  );

}

// Explosion
function explosion(x, y) {

  this.particles = [];

  for (let i = 0; i < particlesPerExplosion; i++) {
    this.particles.push(
      new particle(x, y)
    );
  }

}

// Particle
function particle(x, y) {
  this.x    = x;
  this.y    = y;
  this.xv   = randInt(particlesMinSpeed, particlesMaxSpeed, false);
  this.yv   = randInt(particlesMinSpeed, particlesMaxSpeed, false);
  this.size = randInt(particlesMinSize, particlesMaxSize, true);
  this.r    = randInt(113, 222);
  this.g    = '00';
  this.b    = randInt(105, 255);
}

// Returns an random integer, positive or negative
// between the given value
function randInt(min, max, positive) {

  let num;
  if (positive === false) {
    num = Math.floor(Math.random() * max) - min;
    num *= Math.floor(Math.random() * 2) === 1 ? 1 : -1;
  } else {
    num = Math.floor(Math.random() * max) + min;
  }

  return num;

}

// On-click
$('canvas').on('click', function (e) {
  clicked(e);
});

draw();
<!DOCTYPE html>

<html>

 <head>
  <style>* {margin:0;padding:0;overflow:hidden;}</style>
 </head>

    <body>
        <canvas id="canvas"></canvas>
    </body>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    </html>


哦,是的,那很有道理。当你这么说时,我看到它会破坏剩余的粒子。谢谢!只是想知道,在for循环中,“let”是什么意思? - Kaizokupuffball
1
let 是一个新的 ES6 关键字,用于声明块级作用域变量。 - thedude

1

循环,中断和继续。

当您检查空粒子数组并找到要删除的粒子时,就会出现问题。

错误

以下两个语句和代码块引起了问题。

if (particles.length == 0) {
    explosions.splice(i, 1);
    return;
}

and

if (particles.size < 0) {
    explosions.splice(ii, 1);
    return;
}

返回结果:

返回值停止了粒子的渲染,因此有时你会在绘制单个粒子之前返回,只是因为第一个爆炸为空或第一个粒子太小。

继续和中断

您可以在 JavaScript 中使用 continue 标记来跳过 for、while、do 循环的其余部分。

for(i = 0; i < 100; i++){
    if(test(i)){
        // need to skip this iteration
        continue;
    }
    // more code 
    // more code 
    // continue skips all the code upto the closing }

} << continues to here and if i < 100 the loop continues on.

或者你可以使用break完全跳出循环

for(i = 0; i < 100; i++){
    if(test(i)){
        // need to exit the for loop
        break;
    }
    // more code 
    // more code 
    // break skips all the code to the first line after the closing }

} 
<< breaks to here and if i remains the value it was when break was encountered

修复方案

if (particles.length == 0) {
    explosions.splice(i, 1);
    continue;
}

"and"
if (particles.size < 0) {
    explosions.splice(ii, 1);
    continue;
}

你的修复示例

修复后的代码。在我发现它之前,我开始改变一些东西。

小问题。 requestAnimationFrame传递毫秒级别的时间,因此可以精确到微秒级别。

您将其设置不正确,并且可能会丢失帧。我更改了定时以使用参数时间,然后仅在绘制帧时将其设置为时间。

还有一些其他问题,没有什么大问题,更多是编码风格方面的问题。您应该大写new创建的对象。

function Particle(...

不要。
function particle(...

你的随机数生成器过于复杂。
function randInt(min, max = min - (min = 0)) {
    return Math.floor(Math.random() * (max - min) + min);
}

or

function randInt(min,max){
     max = max === undefined ? min - (min = 0) : max;
     return Math.floor(Math.random() * (max - min) + min);
}

randInt(100); // int 0 - 100
randInt(10,20); // int 10-20
randInt(-100); // int -100 to 0
randInt(-10,20); // int -10 to 20

this.xv = randInt(-particlesMinSpeed, particlesMaxSpeed);
this.yv = randInt(-particlesMinSpeed, particlesMaxSpeed);
this.size = randInt(particlesMinSize, particlesMaxSize);

如果您在变量中使用相同的名称,创建一个对象是个不错的选择。

var particlesPerExplosion = 20;
var particlesMinSpeed = 3;
var particlesMaxSpeed = 6;
var particlesMinSize = 1;
var particlesMaxSize = 3;

可能是。
const settings = {
    particles : {
         speed : {min : 3, max : 6 },
         size : {min : 1 : max : 3 },
         explosionCount : 20,
    },
    background : "#000",
 }

无论如何,你的代码。

var c = canvas;
var ctx = c.getContext('2d');
// Set full-screen
c.width = innerWidth;
c.height = innerHeight;
// Options
var background = '#333'; // Background color
var particlesPerExplosion = 20;
var particlesMinSpeed = 3;
var particlesMaxSpeed = 6;
var particlesMinSize = 1;
var particlesMaxSize = 3;
var explosions = [];
var fps = 60;
var now, delta;
var then = 0;  // Zero start time 
var interval = 1000 / fps;
// Optimization for mobile devices
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
    fps = 29;
}
// Draw
// as time is passed you need to start with requestAnimationFrame
requestAnimationFrame(draw);
function draw(time) {  //requestAnimationFrame frame passes the time
    requestAnimationFrame(draw);
    delta = time - then;
    if (delta > interval) {
        then = time
        drawBackground();
        drawExplosion();
    }
}
// Draw explosion(s)
function drawExplosion() {
    if (explosions.length == 0) {
        return;
    }
    for (var i = 0; i < explosions.length; i++) {
        var explosion = explosions[i];
        var particles = explosion.particles;
        if (particles.length == 0) {
            explosions.splice(i, 1);
            //return;
            continue;
        }
        for (var ii = 0; ii < particles.length; ii++) {
            var particle = particles[ii];
            // Check particle size
            // If 0, remove
            if (particle.size < 0) {
                particles.splice(ii, 1);
               // return;
               continue;
            }
            ctx.beginPath();
            ctx.arc(particle.x, particle.y, particle.size, Math.PI * 2, 0, false);
            ctx.closePath();
            ctx.fillStyle = 'rgb(' + particle.r + ',' + particle.g + ',' + particle.b + ')';
            ctx.fill();
            // Update
            particle.x += particle.xv;
            particle.y += particle.yv;
            particle.size -= .1;
        }
    }
}
// Draw the background
function drawBackground() {
    ctx.fillStyle = background;
    ctx.fillRect(0, 0, c.width, c.height);
}
// Clicked
function clicked(e) {
    var xPos, yPos;
    if (e.offsetX) {
        xPos = e.offsetX;
        yPos = e.offsetY;
    } else if (e.layerX) {
        xPos = e.layerX;
        yPos = e.layerY;
    }
    explosions.push(new explosion(xPos, yPos));
}
// Explosion
function explosion(x, y) {
    this.particles = [];
    for (var i = 0; i < particlesPerExplosion; i++) {
        this.particles.push(new particle(x, y));
    }
}
// Particle
function particle(x, y) {
    this.x = x;
    this.y = y;
    this.xv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
    this.yv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
    this.size = randInt(particlesMinSize, particlesMaxSize, true);
    this.r = randInt(113, 222);
    this.g = '00';
    this.b = randInt(105, 255);
}
// Returns an random integer, positive or negative
// between the given value
function randInt(min, max, positive) {
    if (positive == false) {
        var num = Math.floor(Math.random() * max) - min;
        num *= Math.floor(Math.random() * 2) == 1 ? 1 : -1;
    } else {
        var num = Math.floor(Math.random() * max) + min;
    }
    return num;
}
// On-click
$('canvas').on('click', function(e) {
    clicked(e);
});
<!DOCTYPE html>
<html>

<head>
    <style>
        * {
            margin: 0;
            padding: 0;
            overflow: hidden;
        }
    </style>
</head>

<body>
    <canvas id="canvas"></canvas>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

</html>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接