Welcome, Guest! Login | Register

Particle Engine tutorial part 2 [Print this Article]
Posted by: Persuter
Date posted: Feb 14 2003
User Rating: N/A
Number of views: 10643
Number of comments: 1
Description: Part 2 in 5 part series
Part 3
Part 1

(If you haven't already read the first part of this tutorial, now would be a good time to do so.)

In the last tutorial, we created a particle system class that manipulated a linked list of particle structures. However, we didn't create anything to handle the particle system objects themselves. In this tutorial, we will create a manager class that will handle all the particle systems, including creating them, updating them, and removing them when necessary.

Don't worry too much, though: most of the stuff we'll cover in this tutorial will be much the same as in the last tutorial. The only new concept we'll cover is an often-used coding pattern: the Singleton. More on that later, though. First, let's dive right into the particle system manager class.

First off, the manager class must be able to create and store particle systems in an easily-accessible form, and be able to update them all at once each frame. If you're thinking this sounds a lot like what a particle system has to do to particles, you're correct. We'll keep the particle systems in a linked list just like we did the particles. Again, this is all stuff we can and will change later, so we want to make sure we follow good coding principles, making it easy to come back and change it later.

 CODE (C++) 

class ParticleSystemManager
{
public:

    void AddSystem( ParticleSystem* );

    void ClearSystems( void );

    void UpdateSystems( void );

    void BeforeUpdating( void );
    void AfterUpdating( void );

private:

    ParticleSystem* m_pHeadSystem;

};


Some things to note. First off, you'll note we put ParticleSystem* in AddSystem, rather than ParticleSystem (even though we put particle rather than particle* in the AddParticle function). This is for two reasons. One, because memory management issues are not so critical here as they were inside the particle system, and thus we can let the scruffy riff-raff who will be using our particle system handle it a bit more. The other reason is that, unlike the particle system, we won't be making a bunch of copies of a particle. That is, we may want to create five hundred particles that are all alike in sprite, colour, size, velocity, and so forth in a snow storm, but very rarely will we want to create many particle systems at the same time. Also, and this is rather more important, ParticleSystem is an abstract base class, and thus we can only pass pointers to it (which are really pointers to derived classes).

Also, you may well note that we have no DeleteSystem, but only ClearSystems, which will clear all the systems from memory. This is for a very good reason: good coding style demands that we shouldn't let there be multiple ways to destroy a system. If you want to destroy a system, get TestSystem to return false. A guiding principle of coding is that when the question is posed: "What is causing X to fail?", you should be able to say, "I don't know, but I know it's somewhere in Y." where Y is a nice, small area. If we allowed systems to be removed from the manager anywhere in the code, that means that if we later noticed a problem with removing some system, there might be fifty DeleteSystems spread all over the code, any one of which might be the problem. We let ClearSystems stick around for matters of convenience: all particle systems will need to be cleared at the end of the map, and it's rather wasteful to put a check in every TestSystem. Note that this doesn't break the principle we just mentioned: if we only use ClearSystems at the end of a map, there should only be one invocation of it, and thus only one place that could be failing.

Finally, we'll call BeforeUpdating before updating the systems, and AfterUpdating afterwards. It'll become clear why we need this and why we can't just put it in UpdateSystems.

We start off with AddSystem, there isn't anything here you haven't seen before:

 CODE (C++) 

void ParticleSystemManager::AddSystem( ParticleSystem* system )
{
    system->m_pNextSystem = m_pHeadSystem;
    m_pHeadSystem = system;
}


You'll note that we're calling m_pNextSystem. If you're searching the first part of the tutorial in vain for m_pNextSystem, don't worry; it's not there. We'll change ParticleSystem now:

 CODE (C++) 
class ParticleSystem
{
public:

    BOOL DrawSystem( void );

    virtual BOOL TestSystem( void ) = 0;
    virtual void UpdateSystem( void ) = 0;

    virtual BOOL TestParticle( particle* ) = 0;
    virtual void UpdateParticle( particle* ) = 0;
    virtual void DrawParticle( particle* ) = 0;

    BOOL AddParticle( particle );

    ParticleSystem* m_pNextSystem;

private:

    void TrashParticle( particle* );

    particle* m_pHeadParticle;

// Again, lots to add

};


Better coding style would be to have Get and Set functions for m_pNextSystem, or to put it in protected and declare ParticleSystemManager as a friend; however, we're going to skip that in the interest of brevity.

OK, now that that's over with, we whip up ClearSystems:

 CODE (C++) 
void ParticleSystemManager::ClearSystems( void )
{
    ParticleSystem* system = m_pHeadSystem;
    ParticleSystem* deadsystem = NULL;

    while( system )
    {
        deadsystem = system;
        system = system->m_pNextSystem;
        delete deadsystem;
    }

    m_pHeadSystem = NULL;
   
}


Now we do UpdateSystems, which is a bit more complicated than the other two:

 CODE (C++) 
void ParticleSystemManager::UpdateSystems( void )
{
    BeforeUpdating();

    ParticleSystem* system = m_pHeadSystem;
    ParticleSystem* lastsystem = NULL;
    ParticleSystem* deadsystem = NULL;

    while( system )
    {
        if( !system->DrawSystem() )
        {
            if( lastsystem )
                lastsystem->m_pNextSystem = system->m_pNextSystem;
            else
                m_pHeadSystem = system->m_pNextSystem;

            deadsystem = system;
            system = system->m_pNextSystem;
            delete deadsystem;
   
        }
        else
            system = system->m_pNextSystem;
    }

    AfterUpdating();
}


Still pretty simple. The only sticky part is if DrawSystem returns false, which means we need to remove the system from the list. However, that's handled pretty much the same way as DrawSystem handles dead particles.

Finally we create BeforeUpdating and AfterUpdating. These are very Half-Life-specific, and they should be. You'll have to change these if you ever use the system in another game.

 CODE (C++) 
void ParticleSystemManager::BeforeUpdating( void )
{
    gEngfuncs.pTriAPI->RenderMode(kRenderTransAdd);
}

void ParticleSystemManager::AfterUpdating( void )
{
    gEngfuncs.pTriAPI->RenderMode(kRenderNormal);
}


All these two functions do is change TriAPI so that we can render transparent particles. If we were following strict good coding principles, we'd like to put these in DrawParticle (after all, we might want to change render modes between systems, or even particles). However, we can't afford to do two mode changes every time we draw a particle. It's still good coding to put these in here, as long as we remember to change them when we move the system. If we really wanted to be good, we'd make these virtual functions, and derive HalfLifeParticleSystemManager, OpenGLParticleSystemManager, and so forth, changing BeforeUpdating and AfterUpdating every time, and if you really have a great need for millions of different particle system managers, by all means, do that. However, we're not going to do that, because we want to make this a Singleton, which I'll explain now.

Oftentimes in programming we wish there to be only one copy of a class to handle everything. Here, we want there to be only one particle system manager. Of course, we could just say "Well, I just won't make more than one." There are two problems with that. First, a central article of programming is to assume everyone in the world is ten times dumber than you are, so coders who are trying to use your system might start creating managers willy-nilly. Also, if you make an object, it's global by definition, and global objects are, in general, not good coding style.

So, to fix this problem, we're going to use a Singleton pattern. First of all, you have to be familiar with the static keyword. The static keyword basically defines a variable or a function inside a class as part of the class, rather than the objects of that class. They can be called directly from the class, via ParticleSystemManager::MyStaticFunction(), rather than having to create a specific object.

That having been said, let's see how we'll change this. In some sense, the most obvious way to not allow other people to create objects is to make the constructor private. But then how do we create one? We start by altering the class definition:

 CODE (C++) 

class ParticleSystemManager {

public:

    ~ParticleSystemManager( void )
    {
        ClearSystems();
    }

    static ParticleSystemManager* GetManager( void );

    void AddSystem( ParticleSystem* );

    void ClearSystems( void );

    void UpdateSystems( void );

    void BeforeUpdating( void );
    void AfterUpdating( void );

private:

    ParticleSystemManager( void )
    {
        m_pHeadSystem = NULL;
    }

    ParticleSystem* m_pHeadSystem;

};


OK, so we've made the constructor private. (I also made the destructor, just because we need it at some point, and it seems natural to put it in now.) And we've got a GetManager function. How then do we create the manager? Like this:

 CODE (C++) 
ParticleSystemManager* ParticleSystemManager::GetManager( void )
{
    static ParticleSystemManager* manager = NULL;

    if( !manager )
        manager = new ParticleSystemManager();

    return manager;
}


There we go, as simple as that. You'll note that this has a nice side effect too: If we never use the particle system manager, it is never created, thus saving us a little memory. Now, every time we want to use the manager, we can simply type in ParticleSystemManager::GetManager(), and immediately we get the object which is guaranteed to be the only particle system manager in existence. Now, unless you really like typing, you'll also add in this simple global function declaration beneath the particle system manager definition:

 CODE (C++) 
ParticleSystemManager* GetManager( void );


Then, in your cpp file, add:
 CODE (C++) 
ParticleSystemManager* GetManager( void )
{
    return ParticleSystemManager::GetManager();
}


There we go, now we can just type in GetManager() and get the object. You could also do a macro style thing, like:

 CODE (C++) 
#define MANAGER (ParticleSystemManager::GetManager())


Now you'd simply use MANAGER, e.g.:
 CODE (C++) 
MANAGER->AddSystem( new MyParticleSystem( NULL ) );


Whichever way seems best to you is perfectly acceptable, they're both basically the same. I personally like functions, so I use GetManager(), but the macro would work just as well.

Finally, you may be asking yourself, "But wait a minute. All this code is great, but where do my particle system functions get called?" Well, it's quite simple, my friend. You simply go to HUD_DrawTransparentTriangles in tri.cpp, and put in:
 CODE (C++) 
GetManager()->UpdateSystems();

(You'll have to include the manager header file.) Now you can add systems anywhere you want in the code and they will get updated at every pass through DrawTransparentTriangles.

Now we have a working particle engine in the code, and in fact, at this point you have enough code to go out there and start coding. However, unless you're really ambitious, you'll want to check out the third part of this tutorial for explanations on what goes where, and an example particle system. Also, we haven't even come close to finishing off the particle structure (for example, there's no way to define what it actually looks like), so we'll be seeing a lot of changes in the next tutorial. Hurry back!

Part 3
Part 1

Rate This Article
This article has not yet been rated.

You have to register to rate this article.
User Comments Showing comments 1-1

Posted By: darkPhoenix on Feb 11 2010 at 05:47:42
I had a bit of fun with Parts 1 and 2 of this tutorial, trying to get all the correct #include files #included so it would compile. Apart from that, the biggest hiccup I found was that you've used 'BOOL' here, but 'bool' in Part 3, which gave me a compile error (C2555). Once I changed everything back to 'bool' the error went away! :-)


You must register to post a comment. If you have already registered, you must login.

Latest Articles
3rd person View in Multiplayer
Half-Life 2 | Coding | Client Side Tutorials
How to enable it in HL2DM

By: cct | Nov 13 2006

Making a Camera
Half-Life 2 | Level Design
This camera is good for when you join a map, it gives you a view of the map before you join a team

By: slackiller | Mar 05 2006

Making a camera , Part 2
Half-Life 2 | Level Design
these cameras are working monitors that turn on when a button is pushed.

By: slackiller | Mar 04 2006

Storing weapons on ladder
Half-Life 2 | Coding | Snippets
like Raven Sheild or BF2

By: British_Bomber | Dec 24 2005

Implementation of a string lookup table
Half-Life 2 | Coding | Snippets
A string lookup table is a set of functions that is used to convert strings to pre-defined values

By: deathz0rz | Nov 13 2005


Latest Comments
knock knock
General | News
By: omega | Dec 22 2016
 
knock knock
General | News
By: MIFUNE | Oct 10 2015
 
New HL HUD Message System
Half-Life | Coding | Shared Tutorials
By: chbrules | Dec 31 2011
 
knock knock
General | News
By: Whistler | Nov 05 2011
 
Particle Engine tutorial part 4
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 18 2010
 
Particle Engine tutorial part 2
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 11 2010
 
Particle Engine tutorial part 3
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 11 2010
 
Game Movement Series #2: Analog Jumping and Floating
Half-Life 2 | Coding | Shared Tutorials
By: mars3554 | Oct 26 2009
 
Particle Engine tutorial part 5
Half-Life | Coding | Client Side Tutorials
By: Deadpool | Aug 02 2009
 
Particle Engine tutorial part 5
Half-Life | Coding | Client Side Tutorials
By: Persuter | Aug 02 2009
 

Site Info
297 Approved Articless
8 Pending Articles
3940 Registered Members
0 People Online (9 guests)
About - Credits - Contact Us

Wavelength version: 3.0.0.9
Valid XHTML 1.0! Valid CSS!