3

I'm in the process of building an entity system for a canvas game. This started from a simple particle emitter/updater which I am altering to accommodate a multi-particle/entity generator. Whilst I am usually ok with JavaScript/jQuery I am running into the limits of my experience as it concerns arrays and would gratefully accept any help on the following:

When I need a new particle/entity my current system calls a function to push an object into an array which contains variables for the entity updates.

Then the update function runs a for loop over the array, checking on the type variable to update the particle (position/colour/etc...). Previously I would then [array.splice] the particle, based on some condition. When I needed further particles/entities I would then push new particles.

What I would like to achieve here is:

In the makeParticle function, check over the particle array for any "dead" particles and if any are available reuse them, or push a new particle if not I have created a particleAlive var as a flag for this purpose.

var particles = [];
var playing = false;

function mousePressed(event) {
playing = !playing;
}

if(playing) {
makeParticle(1, 200, 200, 10, "blueFlame");
makeParticle(1, 300, 200, 10, "redFlame");
}

function makeParticle(numParticles, xPos, yPos, pRadius, pType) {
  var i;
  for (i = 0; i < numParticles; i++) {
    var p = {
        type : pType,
        x : xPos,
        y : yPos,
        xVel : random(-0.5, 0.5),
        yVel : random(-1, -3),
        particleAlive : true,
        particleRender : true,
        size : pRadius
      }; // close var P

      particles.push(p);

// instead of pushing fresh particles all the time I would like the function, here, to check for free objects in the array

  } // close for loop

} // close function makeParticle

function runtime() {

  for(var i=0; i<particles.length; i++) {

  var p = particles[i];
  var thisType = p.type; 

  switch (thisType) {

    case "blueFlame":
      c.fillStyle = rgb(100,100,255); 
  c.fillCircle(p.x,p.y,p.size);
  p.x += p.xVel;
  p.y += p.yVel;
  p.size*=0.9;

      if (particles.size < 0.5) {
        particleAlive = false;
        particleRender = false;
      } // close if
  break;

case "redFlame":
  c.fillStyle = rgb(255,100,100); 
  c.fillCircle(p.x,p.y,p.size);
  p.x -= p.xVel;
  p.y -= p.yVel;
  p.size*=0.95;
      if (particles.size < 0.5) {
    particleAlive = false;
        particleRender = false;
      } // close if
  break;
} // close switch
} // close function runtime

I've found previous answers to relate questions, but I've been unable to get it working within the makeParticle function, like how to assign the attributes of p to particle[j]:

var particleUseOldOrNew = function() {

for (var j = 0, len = particles.length; j < len; j++) {

    if (particles[j].particleAlive === false)
        // particles[j] = p;
     return particle[j];
}
return null; // No dead particles found, create new "particles.push(p);" perhaps?
}
halfer
  • 19,824
  • 17
  • 99
  • 186
Bojangles
  • 33
  • 4

1 Answers1

0

My personal opinion on the matter is that if you are making a new particle, it should be a new object, not a "re-using" of an old one with properties changed. Each new object should have a unique identifier, so if you need to track them (for development purposes, debugging, or later re-use), it is easy to do. Or at least keep a counter of the number of times you've re-used a particle object to represent a "new" particle! Though I guess if you've found that "re-using" improves performance (have you?), that's the way to go.

Anyway, enough pontificating, here is how I would do what you're asking (I assume speed is your main concern, so I did this with only native JS):

var particles = [];

//Function to create brand spanking new particle
function makeNewParticle(xPos, yPos, pRadius, pType){
    return  {
        type : pType,
        x : xPos,
        y : yPos,
        xVel : random(-0.5, 0.5),
        yVel : random(-1, -3),
        particleAlive : true,
        particleRender : true,
        size : pRadius
    };
};



//Function to change the properties of an old particle to make a psuedo-new particle (seriously, why do you want to do this?)
function changeExistingParticle(existing, xPos, yPos, pRadius, pType){
    existing.x = xPos;
    existing.y = yPos;
    existing.size = pRadius;
    existing.type = pType;
    return existing;
};



//Figure out the keys of dead particles in the particles[] array
function getDeadParticleKeys() {
    var keys = [];
    for(var p = 0; P < particles.length; p++) {
        if (!particles[p].particleAlive) {
            keys.push(p);
        }
    }
};



function makeParticle(numParticles, xPos, yPos, pRadius, pType) {
    var d, i, deadParticles;

    //Grab the "dead" particle keys
    deadParticleKeys = getDeadParticleKeys();
    numParticles -= deadParticleKeys.length;

    //Replace each dead particle with a "live" one at a specified key
    for (d = 0; d < deadParticleKeys.length; d++) {
        particles[ deadParticleKeys[d] ] = changeExistingParticle(particles[ deadParticleKeys[d] ], xPos, yPos, pRadius, pType)
    }

    //If we had more particles than there were dead spaces available, add to the array
    for (i = 0; i < numParticles; i++) {
        particles.push( makeNewParticle(xPos, yPos, pRadius, pType) );
    }
};

Now, here's how I recommend doing it: abandon the idea or "re-using" particles, make a separate constructor for each particle (will help immensely if you add methods to your particles in the future), and just scrap dead particles every time one is added:

//Make a constructor for a particle
var Particle = function(props){
    if (typeof props === 'function') {
       props = props();
    }
    this.type = props.type;
    this.x = props.x;
    this.y = props.y;
    this.size = props.size;
};
Paticle.prototype.particleAlive = true;
Paticle.prototype.particleRender = true;

//Global particles list
var particles = [];

//Remove all dead element from a ParticleList
particles.clean = function(){
    var p, keys;
    for (p = this.length; p >= 0; p--) {
        if (!p.particleAlive) {
            this.splice(p, 1);
        }
    }
};

//Method for adding x amount of new particles - if num parameter isn't provided, just assume it to be 1
particles.add = function(props, num){
    //First, clean out all the garbage!
    this.clean();

    //Now, append new particles to the end
    var n, limit = (num && typeof num === 'number') ? num : 1;
    for (n = 0; n < limit; n++){
        particles.push( new Particle(props) );
    }
};

//A couple examples
particles.add({ //Add a single blueFlame
    type: "blueFlame",
    size: 10,
    x: 200,
    y: 200
});

particles.add({ //Add 4 redFlames
    type: "redFlame",
    size: 10,
    x: 300,
    y: 200
}, 4);

particles.add(function(){//Add 4 greenFlames, with randomized XY cooridinates
    this.x = Math.round(Math.random() * 1000);
    this.y = Math.round(Math.random() * 1000);
    this.size = 20;
    this.type = "greenFlame";
}, 4);

Way less code to manage. I'm not sure which way is faster, but I'd bet the speed difference is negligible. Of course, you could check for yourself by making a quick jsPerf.

AlexZ
  • 11,515
  • 3
  • 28
  • 42
  • Hi AlexZ, thanks for your time in looking at this. I'd read that even with the splice method it would leave references in the array that could slow proportional. Plus I was worried I'd end up with too many for loops. You're right I'm looking for performance. In a test with the splice method I was starting to grind at about 1k particles and getting slower and slower. I'm looking at turning this emitter into an entity tool, not just particles but enemies/bullets/special items etc... i get a bit lost with the whole prototype/constructor stuff. When I get time I'll integrate it and let you know – Bojangles Apr 23 '14 at 10:18
  • I've been trying to integrate this into a working test but I'm getting 'Uncaught ReferenceError: props is not defined' errors. This is appearing at 'particles.push( new Particle(props) );'. Do i need to declare **props** in some way? – Bojangles Apr 23 '14 at 15:27
  • Typo in my original code, I had "proprs" instead of "props" as the first parameter for particles.add(). Should be fixed. Did you end up figuring out which way is faster - I'm genuinely curious. – AlexZ Apr 26 '14 at 01:05
  • Figured out the typo and got a test system going. Didn't fluctuate from 60fps so that's good although I haven't properly stress tested it. I was toying with perhaps ditching the clean method as in a game situation I can splice the relevant entities case by case ,opposed to running the clean everytime I wanted to kill a particle, saving me a for loop. Also noticed that adding 10 particles, and randomising the x,y,etc the variables would only evaluate once, ie. the 10 particles in one Add statement all have the same params, so might have to extract the array push into a runtime for loop. – Bojangles Apr 28 '14 at 08:28
  • Awesome news, glad its working out. You can easily add a boolean parameter to the end of 'particles.add()' that will determine if 'clean()' gets fired. You're right, doing it one every add operation is probably overkill. Also, I modified the 'Particle' constructor a little bit so that you can pass a function instead of just a plain object literal; the new example at the end shows how to use that function to randomize stuff. – AlexZ Apr 29 '14 at 01:06