Evolution – adventures with particles and Lua.

Particle systems

What fun. I wanted them in my Asteroids game. Here is the process I went through before I was happy.

Intro

Firstly, let me describe some basics of my game. It is simple enough. Firstly, 99% of game logic is in Lua, not C++. I wanted fine control of the particles from Lua. My game has two lists which it updates and draws, entities and effects. Entities are tested for collision, effects aren’t.

Before writing the particle systems, I had only 2 effect types, text and image. A text effect is used to draw the players score, and other miscellaneous announcements. Image effects are stuff like explosions and other eye candy that don’t affect other objects in the game.

The Origin of the Effect

My first attempt was very simple, and to be fair it worked in small quantities. My first particle effect was a small burst of coloured circles that was spawned when the player picked up an extra life, etc. Simple enough, create 75 image effect instances. Each effect has a position, colour, image but no velocity, only entities have that. So I just had to update the position every frame. I called these sparkles:

function Sparkle.new(x,y,colour)

    colour.a = .5

    local sparkle = ImageEffect.new(Sparkle,'media/sparkle.png')
    sparkle:setPosition(x,y)
    local angle = random_real_range(0,360)
    local speed = random_real_range(sparkle.MIN_SPEED,sparkle.MAX_SPEED)
    self.vx = cosine(angle) * speed
    self.vy = sine(angle) * speed
    sparkle:setColour(colour)
    sparkle.timer = Sparkle.FADE_TIME

    return sparkle
end

function Sparkle:update( dtime )

    self.timer = self.timer - dtime

    if self.timer < 0 then
        self:die()
        return
    end

    self:setColour(self.r,self.g,self.b,(self.timer / self.FADE_TIME) * 0.5)
    local x,y = self:getPosition()
    x = x + self.vx * dtime
    y = y + self.vy * dtime
    self:setPosition(x,y)
end&#91;/sourcecode&#93;
Very simple, but not efficient at all. With more than a few of these on screen, the game slows noticeably. This meant that if I wanted to add additional particle systems I would need to change something.
<h3>Attempt 2</h3>
This time I wanted to only have a single effect control multiple particles. For this, I would need to write some C++. Ever since I started using Lua I've preferred it to C++. I've even written the beginnings of a 2D game in Lua with the most minimal C++ bindings possible, but that is another story.

To add a ParticleEffect class to my C++ game I needed to do a few things. Firstly, write it. Then write some Lua bindings (I prefer to hand write them as a C style interface, rather than use some kind of C++ to Lua generator). A simplified version of the ParticleEffect class looked like this:


class ParticleEffect : public Effect
{
public:
    void setImage( const std::string & );

    void addParticle(const Vector &,const Colour &, float );
    void setParticleOffset( int handle, const Vector & );
    void setParticleColour( int handle, const Colour & );

    int particleCount() const;

private:
    struct Particle {
         Vector offset;
         Colour colour;
         float timetolive;
    };
    std::vector<Particle> particles;
    boost::shared_ptr<Image> image;
}

You can imagine what most of that does. The timetolive field was optional. I made it so that if it started negative a particle never expired. This class, properly bound to Lua, was pretty fast at the expense of being ugly to work with.

function Sparkle.new(sparkleCount,colour)

    colour.a = .5

    local sparkle = ParticleEffect.new(Sparkle,'media/sparkle.png')
    sparkle.sparkles = {}
    sparkle.timer = 0
    sparkle.colour = colour
    for i = 1,sparkleCount do
        sparkle:addParticle(0,0,colour,Sparticle.FADE_TIME)
        local p = {}
        local angle = random_real_range(0,360)
        local speed = random_real_range(sparkle.MIN_SPEED,sparkle.MAX_SPEED)
        p.vx = speed * cosine(angle)
        p.vy = speed * sine(angle)
        table.insert(sparkle.sparkles,i,p)
    end
end

function Sparkle:update(dtime)

    local count = self:particleCount()
    self.timer = self.timer + dtime
    local colour = self.colour
    colour.a = ((self.FADE_TIME - self.timer) / self.FADE_TIME) * 0.5

    for index = 1,count do
        local p = self.sparkles[index]
        self:setParticleOffset(index,p.vx * self.timer,p.vy * self.timer)
        self:setParticleColour(index,colour)
    end
end

The ugliness stemmed (mainly) from having to keep parallel arrays, one in Lua to represent the different speeds, the other data being in C++. However, for other effects that I changed to particles (example: the starfield which had 100 ImageEffects with no updating) it was much faster.

The Final Version

Even though I had this working, I decided to test another method I had thought of. I was unsure if this would slow it down by any amount, but I liked the interface it presented. You must note that my Lua/C++ boundary functions are almost all extremely simple ones. I prefer not to write Lua table manipulation code in C++, I just pass primitives.If I would have to write table manipulation code, I usually write 2 functions, a Lua function that parses the table and hands primitive data to C++.

Anyway, my idea this time was to have the particle system store a Lua table for each particle. That way the Lua side can store data other than the position and colour in the objects. I have a class that works quite neatly with the Lua registry which allows me to hold handles to Lua objects in C++. The idea was, each frame, to read the colour and position from the object the user passes to the particle system. Each particle then must be a table, consisting of 2 sub-tables, position which must have the fields x and y, and colour which must have r,g,b and a.

Instead of calling some kind of update function on each particle, I exposed a function which allows the user to iterate over the particles and update them, should they choose to. Thanks to Lua being such a wonderful language, it supports closures which makes this much nicer than C++. Here is the updated system.

class ParticleEffect : public Effect
{
public:
    void setImage( const std::string & );
    void addParticle( const Lua::Handle & );
    void eachParticle( Lua::Handle & );

private:
    boost::shared_ptr<Image> image;
    typedef std::vector<Lua::Handle> ParticleVector;
    ParticleVector particles;
};

The above allows the Lua code to be much clearer and nicer, in my opinion:

function Sparkle.new(count,colour)

colour.a = .5

local sparkle = ParticleEffect.new(Sparkle,’media/sparkle.png’)

sparkle.timer = 0
sparkle.colour = colour

for i = 1,count do
local p = {position={x=0,y=0}, colour = colour}
local angle = random_real_range(0,360)
local speed = random_real_range(sparkle.MIN_SPEED,sparkle.MAX_SPEED)
p.vx = speed * cosine(angle)
p.vy = speed * sine(angle)
sparkle:addParticle(p)
end

return sparkle
end

function Sparkle:update(dtime)

self.timer = self.timer + dtime
if self.timer < self.FADE_TIME then self:die() return end local colour = self.colour colour.a = ((self.FADE_TIME - self.timer) / self.FADE_TIME) * 0.5 self:eachParticle( function(p) p.position.x = p.position.x + p.vx * dtime p.position.y = p.position.y + p.vy * dtime p.colour = colour end ) end[/sourcecode] The only thing was missing from the previous attempt was the timetolive, allowing particle systems to spawn particles at random. I added a simple rule to remedy: if the function passed to eachParticle() returns any value for a given particle, then that particle would be removed from the list. If the function doesn't return a value the particle is kept.All in all, I am pleased with this improvement. Performance wise the above two are similar, the older version being slightly faster to draw because it doesn't need to touch Lua tables, the second being slightly faster to update because it avoids a bunch of dynamic_casts and other safety checks in the Lua/C++ boundary. Even were the second slightly slower, I would probably stick with it because it is simpler to write. Of course, the major limiting factor for my game is the fact that I use OpenGL in immediate mode. So even though now I can have more effects at one time, I still have to limit the amount. Which is probably a good thing, otherwise all the player would see are particles everywhere...

Advertisements

7 Responses to “Evolution – adventures with particles and Lua.”

  1. An interesting mini-article! I always get all worked up about starting to play with integrating Lua with some of my C/C++ projects whenever I hear about your adventures. One of these days you’re going to have to whip up a little survival guide or something for us folks who are frightened to death by the notion of integrating a scripting language like Lua. 😉

    Congrats on the new development journal, as well. I’ll be eagerly reading up on your progress as development continues. Any idea as to what your next project will be?

    Best of luck!

  2. ‘ello =D

    Sounds like you had a good, meaningful particle implementation session =D

  3. Yes Null, it was quite fun.

    Thanks Steve.

    A survival guide? Perhaps. I think I prefer writing more like the style of this post. It is more post mortem than tutorial, writing what I did and why – and where I went wrong, rather than “this is the one true way” which tutorials often portray. But I will think about it.

    As for my next project, I have a few other things that are in various states of completion. I have a raytracer in progress, and that other game that I briefly alluded to above, which is a platformer written entirely in Lua with minimal bindings to SDL to allow for graphics and input etc.

    I suppose all I can say is “we’ll see” 😉

  4. Also: is there a certain plug-in you installed to get those cool code-boxes, or am I just a few versions behind? 😛

  5. It took ages for me to find out how, but then I did the clever thing and searched for it and found this, and it was a lot easier than I thought. Using <code> didn’t really help at all.

  6. […] to my learning experience dealing with Lua tables through my Asteroids game, I decided to be a bit brave and allow more of […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: