If you're seeing this message, it means we're having trouble loading external resources on our website.

Si estás detrás de un filtro de páginas web, por favor asegúrate de que los dominios *.kastatic.org y *.kasandbox.org estén desbloqueados.

Contenido principal

Un sistema de partículas

Hasta ahora logramos crear una sola partícula que hacemos renacer cada vez que muere. Ahora queremos crear un flujo continuo de partículas, agregando una nueva con cada ciclo por medio de draw(). Podríamos simplemente crear un arreglo y agregarle (push) una nueva partícula cada vez:
var particles = [];
draw = function() {
  background(133, 173, 242);
  particles.push(new Particle(new PVector(width/2, 50)));

  for (var i = 0; i < particles.length; i++) {
    var p = particles[i];
    p.run();
  }
};
Si pruebas y ejecutas ese código por unos minutos, probablemente comenzarás a ver la velocidad de los cuadros de animación más y más lentos hasta que el programa se paralice. Eso es porque creamos más y más partículas que tenemos que procesar y deplegar, sin nunca quitar ninguna. Una vez que las partículas están muertas, son inútiles, así que mejor podemos ahorrarle trabajo innecesario a nuestro programa y eliminar esas partículas.
Para eliminar elementos de un arreglo en JavaScript, podemos usar el método splice(), al especificar el índice que deseamos eliminar y el número a borrar (solo uno). Hacemos eso después de consultar si la partícula en efecto está muerta:
var particles = [];
draw = function() {
  background(133, 173, 242);
  particles.push(new Particle(new PVector(width/2, 50)));

  for (var i = 0; i < particles.length; i++) {
    var p = particles[i];
    p.run();
    if (p.isDead()) {
      particles.splice(i, 1);
    }
  }
};
Aunque el código anterior funcionará bien (y el programa nunca se paralizará), hemos abierto una caja de Pandora mediana. Cada vez que manipulamos el contenido de un arreglo mientras iteramos sobre ese mismo arreglo, podemos meternos en problemas. Considera, por ejemplo, el siguiente código:
for (var i = 0; i < particles.length; i++) {
  var p = particles[i];
  p.run();
  particles.push(new Particle(new PVector(width/2, 50)));
}
Este es un ejemplo algo extremo (con lógica errónea), pero prueba el punto. En el caso anterior, para cada partícula en el arreglo, agregamos una nueva partícula al arreglo (lo que cambia la propiedad length del arreglo). Esto resultará en un bucle infinito, ya que i nunca puede aumentar más allá de particles.length.
Mientras que quitar elementos del arreglo de partículas durante un bucle no causa que el programa se bloquee (como lo hace cuando le agregamos), el problema es casi más malicioso porque no deja ninguna evidencia. Para descubrir el problema, primero debemos establecer un hecho importante. Cuando se elimina un elemento de un arreglo, todos los elementos son desplazados un lugar a la izquierda. Observa el siguiente diagrama, donde se elimina la partícula C (índice 2). Las partículas A y B mantienen el mismo índice, mientras que las partículas D y E cambian de 3 y 4 a 2 y 3, respectivamente.
Supongamos que somos i recorriendo el arreglo.
  • cuando i = 0 → revisa partícula A → no borrar
  • cuando i = 1 → revisa partícula B → no borrar
  • cuando i = 2 → revisa partícula C → ¡borrar!
  • (desplazar a las partículas D y E de las posiciones 3 y 4 a las 2 y 3)
  • cuando i = 3 → revisa partícula E → no borrar
¿Te das cuenta del problema? ¡Nunca revisamos a la partícula D! Cuando C fue eliminada de la posición #2, D se movió a la posición #2, pero i ya se había movido a la posición #3. Esto no es un desastre, ya que la partícula D será revisada la próxima vez. Aún así, la expectativa es que estamos escribiendo código para iterar sobre cada elemento individual del arreglo. Saltarse un elemento es inaceptable.
Hay una solución sencilla para este problema: simplemente iterar hacia atrás sobre el arreglo. Si estás desplazando elementos de derecha a izquierda a medida que los elementos se quitan, es imposible omitir un elemento por accidente. Todo lo que tenemos que hacer es modificar las tres partes en el bucle for:
  for (var i = particles.length-1; i >= 0; i--) {
    var p = particles[i];
    p.run();
    if (p.isDead()) {
      particles.splice(i, 1);
    }
  }
Juntándolo todo, tenemos esto:
Bien. Ya hicimos dos cosas. Escribimos un objeto para describir un Particle individual (partícula en inglés). Averiguamos cómo utilizar arreglos para manejar muchos objetos Particle (con la posibilidad de agregarlos y borrarlos a voluntad).
Podríamos parar aquí. Sin embargo, un paso adicional que podemos y debemos tomar, es crear un objeto para describir la colección de objetos Particle: el objeto ParticleSystem (sistema de partículas). Esto nos permitirá quitar la lógica engorrosa de iterar sobre todas las partículas de la pestaña principal, así como abrir la posibilidad de tener más de un sistema de partículas.
Si recuerdas el objetivo que definimos al principio de esta lección, queríamos que nuestro programa se pareciera a esto:
var ps = new ParticleSystem(new PVector(width/2, 50));

draw = function() {
  background(0, 0, 0);
  ps.run();
};
Tomemos el programa que escribimos arriba y veamos cómo encajaría en el objeto ParticleSystem.
Esto es lo que teníamos antes:
var particles = [];

draw = function() {
  background(133, 173, 242);
  particles.push(new Particle(new PVector(width/2, 50)));

  for (var i = particles.length-1; i >= 0; i--) {
    var p = particles[i];
    p.run();
    if (p.isDead()) {
      particles.splice(i, 1);
    }
  }
};
Aquí está cómo podemos volver a escribir eso en un objeto. Vamos a hacer que el arreglo particles sea una propiedad del objeto, hacer un método que lo envuelvaaddParticle para agregar nuevas partículas. y poner toda la lógica de ejecutar la partícula en run:
var ParticleSystem = function() {
  this.particles = [];
};

ParticleSystem.prototype.addParticle = function() {
  this.particles.push(new Particle());
};

ParticleSystem.prototype.run = function() {
  for (var i = this.particles.length-1; i >= 0; i--) {
      var p = this.particles[i];
      p.run();
      if (p.isDead()) {
        this.particles.splice(i, 1);
      }
    }
};
También podríamos agregar algunas nuevas características al propio sistema de partículas. Por ejemplo, sería útil para el objeto ParticleSystem llevar un seguimiento de un punto de origen de dónde se crean las partículas. Esto empata con la idea de que el sistema de partículas sea un “emisor”, un lugar donde nacen las partículas y de donde son enviadas al mundo. El punto de origen debería inicializarse en el constructor.
var ParticleSystem = function(position) {
  this.origin = position.get();
  this.particles = [];
};

ParticleSystem.prototype.addParticle = function() {
  this.particles.push(new Particle(this.origin));
};
Aquí está todo junto:

¿Quieres unirte a la conversación?

¿Sabes inglés? Haz clic aquí para ver más discusiones en el sitio en inglés de Khan Academy.