HTML5画布粒子爆炸

时间:2017-04-19 14:40:31

标签: javascript html5 canvas

我正试图让粒子爆炸起作用。它工作,但看起来有些帧不会被渲染。如果我多次点击几次爆炸就会开始呃......“滞后/断断续续”。有什么我忘记做的事吗?当我点击多次时,浏览器可能会挂起。在彼此内部有2个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>

2 个答案:

答案 0 :(得分: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>

答案 1 :(得分:1)

循环,休息并继续。

当您检查空粒子阵列以及找到要移除的粒子时,会导致此问题。

错误

以下两个语句和块导致了问题

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

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

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

继续并中断

你可以在javascript中使用continue标记跳过for的其余部分,而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.

或者你可以完全突破循环

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;
}

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

您的修复示例

您的代码与修复程序。因为我发现它我开始改变东西。

小东西。 requestAnimationFrame以毫秒为单位传递时间,精确到微秒。

您正在设置错误,并且会丢失帧。我改变了使用参数时间的时间,然后将其设置为绘制帧的时间。

还有一些其他问题,没有什么重要的,更多的是编码风格的东西。您应该将使用新

创建的对象大写
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>