Particle Effects Code

Dec 25, 2009

What are Particle Effects?

Puffs of dirt kicked up from tires, smoke after explosions, empty shell casings ejecting from automatic rifles, and splashes of water are all particle effects. Shattered pieces of scored gems in a puzzle game, splatters of blood from gunfire, broken glass, chunks of wood, snowflakes, sparks, and shards of plants are all particle effects.

Particle effects are categorically different from most other objects in a videogame. Note the following differences:

  • Particles don’t influence gameplay. They don’t knock things over, respond to being stepped on by enemies, or inflict damage. A game that has particle effects, generally speaking, would play out exactly the same without the particle effects.
  • The game needs to move and draw many particles at once. Dozens or even hundreds of particles may be constantly present – perhaps as rocket thrust, boat wake, or damage smoke – and during intense action the game may need to handle many thousands of particles. This means they must be small in memory and require minimal processing.
  • Particle effects are dynamic. Particles spin, rotate, fade, bounce, drift, fall, and sometimes even produce sounds (when they start, fizzle out, or hit the ground).
  • The number of particles must be limited to protect the game’s framerate. Drawing and moving thousands of particles every second can pose a serious risk to game performance.
  • Particles spawn with some randomness. Because many particles can be spawned from a single event, introducing slight randomness into the initial location, velocity, and lifetime avoids particles overlapping one another, increasing the odds that each one using processing and rendering time gets seen.
  • Particles only last a few seconds. What makes particles exciting is their movement. Once the particle’s action is complete it needs to be made available for a future effect.

Benefits of a Fixed-Size Array

There are dozens of container structures that are more complex, robust, and flexible than fixed-size (i.e. normal, basic, no frills) arrays – linked lists, vectors, and so on. Why do I recommend using fixed-size arrays for particle effects?

  • Arrays are extremely fast to access.
  • Arrays avoid overhead from memory allocation or deallocation, and in so doing also avoid cumulative memory fragmentation.
  • The usual downside to using fixed-size arrays – that they have a maximum number of elements allowed at once – is desirable for particle effects! This ensures the number of particle effects are kept within a sane limit.

Code, Applied to Asteroids Source

In main.cpp of the sample Asteroids starter source

After:

#define ROCK_NUM 25

Add:

#define PFX_NUM 1000 // maximum number of particle effects

After:

obj_typ ast[ROCK_NUM];

Add:

struct pfx_typ {
  float xv, yv; // component velocity
  float x, y; // position
  int life; // frames until it vanishes
  int color; // color of this particle
};

int pfx_alive = 0;
pfx_typ pfx[PFX_NUM];

Before:

void drawThings() {

Add:

void pfxJet(int spawnNum, obj_typ *fromObj) {
  int toSpawn;

  if( pfx_alive + spawnNum >= PFX_NUM ) {
    toSpawn = PFX_NUM-1 - pfx_alive;
  } else {
    toSpawn = spawnNum;
  }

  float cosPower = -cos(fromObj->ang) * 0.5;
  float sinPower = -sin(fromObj->ang) * 0.5;

  int colorForThisSet;

  colorForThisSet = 224+(rand()&0x1F); // shade from 224 to 255

  if(rand() & 0x01 == 1) {
    // bold reds
    colorForThisSet = makecol(colorForThisSet, 0, 0);
  } else {
    // bold yellow
    colorForThisSet = makecol(colorForThisSet, colorForThisSet, 0);
  }

  for(int i=0;ix = fromObj->x - 3 + (rand()&0x07);
    effect->y = fromObj->y - 3 + (rand()&0x07);
    effect->xv = fromObj->xv+float(-127+(rand()&0xFF))*0.002+cosPower;
    effect->yv = fromObj->yv+float(-127+(rand()&0xFF))*0.002+sinPower;
    effect->life = 60+(rand()&0x7F);
    effect->color = colorForThisSet;
    pfx_alive++; // raise the particle count to include the newest one
  }
}

Before:

drawLine( &ship , makecol(255, 255, 0) );

Add:

// move and draw particles in the same pass
// remember that we may have a TON of particles,
// so it's good to only iterate over the list
// as few times as possible

for(int i=0;ilife > 0) { // move and draw it
    effect->x += effect->xv;
    effect->y += effect->yv;

    circlefill(screenBuffer,
      int(effect->x), int(effect->y),
      int(effect->life>>6), // radius as life/64
      effect->color);
  } else { // remove it from the list
    pfx_alive--; // decrease number of live particles
    if(pfx_alive >= 0) { // providing we had 1+ particle left:
      pfx[i] = pfx[pfx_alive]; // copy last particle onto this one
      i--; // reconsider this particle
    }
  }
}

After:

ship.yv += sin(ship.ang) * thrustPower;

Add:

pfxJet(10,&ship);

That’s it! The code includes a number of strange optimizations – none of which I benchmarked (feel free to inform me if I’ve been silly to cache the array accesses into pointers, etc.). A few examples of minor optimizations shown here are bit shifting for division, bitwise AND as alternative to modulo to limit rand, and end-swapping in an array to avoid gaps without element shifting. Because particles can number in the thousands, particle programming is one of the areas where otherwise outdated or trivial optimizations (that otherwise should be avoided to keep code readable) can make a significant difference.

Modified Source

Here’s an updated example main.cpp file with the above changes made, in case the above insertion hints proved tricky to follow: Asteroids main.cpp with particles.
(Note that this requires core.cpp and core.h from the main starter source.)

(Originally posted as GameDevLessons.com Vol. 9 Sec. 2)



Learn and practice team game development with Gamkedo Club.
Membership worldwide. Professional support. Proven process.




Subscribe by e-mail to receive weekly updates with Gamkedo.Community interviews and YouTube training videos for game developers!



2 Comments

  1. […] Programming Asteroids (Related: Particle Effects Code) […]

  2. […] Vol 9 Int – Particle Effects […]

All contents Copyright ©2017 Chris DeLeon.

Site production by Ryan Burrell.