TDK's Particle Tutorial For Beginners
If you are using DB Pro, then you have native support for particles. If however you are using DBC then you don't. This doesn't mean however that you can't have particles in your DBC programs, you just have to code them manually...
But first, just in case you don't know, what exactly are particles?
Well, a particle isn't the easiest of things to explain, so it's probably best to think of a smoke particle. Both clouds and smoke aren't solid objects - they are made up of trillions of tiny particles which move together in a fluid motion.
In DB, each particle is an object and as we obviously can't have that number of objects, we cheat by having a lot fewer, but make them bigger.
To prevent slowdown in DB due to high polygon counts, we use the simplest DB object - the plain - and make it as small as we can to fit the scale of world we are using.
We texture each plain and ghost them (make them semi-transparent) to give that 'fuzzy' look when lots of them are placed close to each other.
Created carefully, you can create acceptable effects for smoke, clouds, dust, steam, water spray, fireworks and so on. You can even get a very nice effect which makes street lights 'glow'.
The key to good particles however is the textures you use. Below is a typical image for a particle. As you can see, it's effectively a coloured sphere on a black background which is bright in the centre and fades to black as it moves outwards.
When you ghost a plain which is textured with this image, the black parts are transparent and it looks just like a soft glowing ball. Put hundreds of them together and/or stretch them and you have smoke or a cloud.
You don't have to stick to traditional particle texture image shapes. You can have square, triangular, star or snowflake shapes too. The important thing is to make the shape gradually fade to the background colour towards the extremities.
Most decent paint programs have an option to do this, but I find that a good effect is the Gaussian Blur in Paintshop Pro.
Simply create a 128x128 canvas with black backround and create a solid white circle (see first image below). Make sure it's positioned in the centre of the image.

Apply the Gaussian blur. The second image uses a blur value of 12 but you can experiment - just make sure that the outermost edges of the blur stay within the image frame or you will see flat edges on your particle.
And that's it. As mentioned before, the initial shape can be whatever you want - this one for example!
Note: You can have texture images bigger than 128x128, but the bigger they are, the bigger the impact on frame rate. No doubt many will say that 128x128 is too big and should be 32x32 or 64x64 pixels max.
What Next?
Once you have your textures, it's simply a case of creating plains, texturing and ghosting them and finally controlling their positions in your program. To be honest, it's only really the control of them that's tricky.
One of the reasons for this is that due to the sheer number of objects we are controlling, each one has a finite 'lifespan', at the end of which the particle is killed off. When dead, the object can be deleted and re-used. Without using this method we wouldn't be able to create nearly enough objects in DB.
And, to keep track of all these object's states and positions we depend heavily on functions and arrays - lots of them, so if you are not up to scratch on arrays or functions, I suggest you go and read my tutorials on both subjects here:
Functions:
http://forum.thegamecreators.com/?m=forum_view&t=96040&b=7
Arrays:
http://forum.thegamecreators.com/?m=forum_view&t=96069&b=7&p=0
Oh yes - another thing to bear in mind is that plains are two-dimensional 3D objects. I know that sounds daft, but plains are only 2D surfaces. They have an X and Y dimension, (width and height), but no depth. In other words, if you look at them from the side, they disappear.
So, your particle code has to make sure that they always face the camera or they won't be seen!
I'll be using the very first image above in the example code snippets, so if you right click on it you can save it to a folder with the name Figb2.jpg for use later...
Emitters:
The last thing to cover before we get to the actual coding is emitters. This is the fancy name given to the point in space which each particle originates. For example, with a 'Volcano' firework (the cone shaped one), the particles originate at the point of the cone.
Creating that particular firework with particles, you would create a cone and set the emitter X,Y,Z position at the apex of the cone.
You can also have multiple emitters - each with a different colour and shaped particle.
Combining different particles can create some interesting and nice effects. But, the thing to remember is that all the particles from a single emitter will be the same and the more emitters you have - the slower your program.
The Code!
In it's simplest form, creating a particle in DB is as follows:
Set Display Mode 800,600,16
Sync On: Sync Rate 0: CLS 0
AutoCam Off
Backdrop On: Color Backdrop 0
Set Ambient Light 100
Load Image "Figb2.jpg",1
Make Object Plain 1,1,1
Texture Object 1,1
Ghost Object On 1
Position Camera 0,5,-10
Point Camera 0,0,0
Do
X# = WrapValue(X# + 3.0)
Z# = WrapValue(Z# + 3.0)
Position Object 1, NewXValue(0,X#,3.0),0.0,NewZValue(0,Z#,3.0)
Set Object To Camera Orientation 1
Sync
Loop
End
I've just added a bit of rotation code in the main do loop for effect.
But that's just a single particle. When we need to control hundreds of particles, we need a control system which uses arrays.
Essentially, we create a particle and use a 'slot' in an array to store all the required information about that particle. All particles have a lifespan, after which they die and the slot is freed up for using again with another particle.
So now it's time to get our hands dirty with some real particle coding, but for this we need a game plan!
We are going to have one emitter and use 100 particles (plain objects). They will have a specific lifespan and when they die, we will re-use them. For this reason, as a minimum, we need to keep track of the following information for every single particle:
* It's number
* It's age (lifespan)
* It's X position
* It's Y position
* It's Z position
* It's X direction (velocity)
* It's Y direction (velocity)
* It's Z direction (velocity)
Later, you can add in other factors in like gravity, friction and so on...
The easiest way to store this information is in a multi-dimensioned array with:
Dim Particle#(100,6)
This allows for up to 100 particles and 7 pieces of information. For example:
Particle#(ParticleNum,0) = the particle's age
Particle#(ParticleNum,1) = the particle's X position
Particle#(ParticleNum,2) = the particle's Y position
Particle#(ParticleNum,3) = the particle's Z position
Particle#(ParticleNum,4) = the particle's X velocity
Particle#(ParticleNum,5) = the particle's Y velocity
Particle#(ParticleNum,6) = the particle's Z velocity
Our function merely checks the value in Particle#(ParticleNum,0) - it's 'age' - and if it's greater than 0 (zero) then the particle is alive. It then deducts one from the value found, calculates the particle's new position and positions it.
If the age is 0 (zero) then it's age is reset, it's X,Y,Z position is reset to that of the emitter and new random velocities for X, Y and Z are created ready for the next particle.
The following example uses a single emitter and particles are emitted in all directions. So let's see some code and then we'll go over the less obvious bits...
Set Display Mode 800,600,16
Sync On: Sync Rate 0: CLS 0
AutoCam Off
Backdrop On: Color Backdrop 0
Set Ambient Light 80
Randomize Timer()
Load Image "Figb2.jpg",1000
Dim Particle#(100,6)
Dim NumParticles(0)
Dim LifeSpan(0)
Dim CurParticle(0)
Dim EmitterX#(0): Dim EmitterY#(0): Dim EmitterZ#(0)
NumParticles(0) = 100
LifeSpan(0) = 100
EmitterX#(0) = 0.0
EmitterY#(0) = 0.0
EmitterZ#(0) = 0.0
For N = 1 To NumParticles(0): Rem Create 100 Particles
Make Object Plain N,1,1
Texture Object N,1000
Ghost Object On N
Next N
Position Camera 0,5,-30
Point Camera 0,0,0
Do
UpdatePSystem()
Sync
Loop
End
function UpdatePSystem()
For T = 1 to NumParticles(0)
If Particle#(T,0) > 0: Rem If particle still alive
Particle#(T,0) = Particle#(T,0) - 1: Rem Knock one unit off it's life
Rem Handle Particle Movement...
Particle#(T,1) = Particle#(T,1) + Particle#(T,4)
Particle#(T,2) = Particle#(T,2) + Particle#(T,5)
Particle#(T,3) = Particle#(T,3) + Particle#(T,6)
Position object T, Particle#(T,1), Particle#(T,2), Particle#(T,3)
Set Object To Camera Orientation T
Else
Particle#(T,0) = Rnd(LifeSpan(0)/2)+LifeSpan(0)/2: Rem Random Life For Particle
Particle#(T,1) = EmitterX#(0): Rem Initial X position
Particle#(T,2) = EmitterY#(0): Rem Initial Y position
Particle#(T,3) = EmitterZ#(0): Rem Initial Z position /100.0
Particle#(T,4) = (Rnd(40)-20) / 100.0: Rem XVelocity
Particle#(T,5) = (Rnd(40)-20) / 100.0: Rem YVelocity
Particle#(T,6) = (Rnd(40)-20) / 100.0: Rem ZVelocity
Show Object T
Endif
Next T
Endfunction
The first lines of interest are in the Dims section. The Particle#(100,6) one is covered above, but those following are arrays which have been used as global variables - hence the (0) on the end of each.
Dimensioning them here allows us to use these arrays as normal type variables globally and access them inside the function.
On the following lines we set NumParticles(0) to 100 - the number of particles we want to use, LifeSpan(0) to 100 so each particle will only last 100 cycles of our loop and EmitterX#(0), EmitterY#(0) and EmitterZ#(0) all to 0.0 - making all our particles originate from 0,0,0 in 3D space.
The next block simply creates 100 plains, textures them with the image loaded earlier and ghosts them to make them transparent.
Finally, we position the camera to get a good view, point it at the emitter at 0,0,0 and drop into the main loop.
The main loop continuously calls the UpdatePSystem() function and Sync's the screen. So what's going on inside the function?...
Well, we simply count in a loop from 1 to NumParticles(0) (100) and check to see if each particle is still alive. If it is, we deduct one as described above.
We then add the X, Y and Z velocities stored in Particle#(T,4), Particle#(T,5) and Particle#(T,6) to the particle's current X, Y and Z positions (stored in Particle#(T,1), Particle#(T,2) and Particle#(T,3)).
We finally position the object at it's new position and make sure that it's facing the camera.
If the particle's age is set to 0 (zero), we re-initialise all the setting so it starts again at the emitter - effectively creating a new particle.
Random values are used for both the lifespan and the X, Y and Z velocities. If the lifespan of all particles were the same, they would all die together and start together. This is handy for an explosion effect, but not what we want here.
Try changing the line:
Particle#(T,0) = Rnd(LifeSpan(0)/2)+LifeSpan(0)/2
to:
Particle#(T,0) = LifeSpan(0)
to see what I mean. As an aside, feel free to change any parts of the example programs to see what effect it has. Experimenting can't do any harm and if it helps you figure out what's going on then all the better...
The velocity values are random because we want the particles to be emitted at lots of different angles. So, Particle#(T,4) is the number of degrees left and right on the X axis. Using Rnd(40)-20 means we get random number between -20 and +20.
Try changing it to Rnd(10)-5 and see how the X axis spread is reduced.
Likewise, changing the YVelocity line to:
Particle#(T,5) = 0.0
results in no spread across the Y axis (up/down) and all the particles spread out as though rolling on a flat table. (Don't forget that the camera is slightly elevated in the program, looking slightly down on the particles).
So, restricting the spread of particles on the X, Y and Z axis you can create effects like rocket thrust - though it would need to take into account gravity and other things, making it much more complicated than what we've covered here.
Here's a simple image to explain what's going on...
As you can see, negative values for the X velocity make the particles head to the left while positive values make them head to the right. A value of 0 (zero) makes them go neither left or right, but (assuming there's a positive Y velocity) will instead head stright up.
Likewise for the Y and Z axis: Positive Y velocities send particles up and negative ones down. Positive Z velocities send particles away from you and negative ones towards you. Using these axis, you can direct particles in any direction you wish.
For example, fireworks send all their particles upwards not downwards and spread out along the X and Z axis. To do this, looking at the above diagram we can see that the Y velocity values must always be positive and the X and Z velocity values need to be both positive and negative.
Some firework code:
Set Display Mode 800,600,16
Sync On: Sync Rate 0: CLS 0
AutoCam Off
Backdrop On: Color Backdrop 0
Set Ambient Light 100
Randomize Timer()
Load Image "Figb2.jpg",1000
Dim Particle#(200,6)
Dim NumParticles(0)
Dim LifeSpan(0)
Dim EmitterX#(0): Dim EmitterY#(0): Dim EmitterZ#(0)
NumParticles(0) = 200
LifeSpan(0) = 50
EmitterX#(0) = 0.0
EmitterY#(0) = 0.0
EmitterZ#(0) = 0.0
For N = 1 To NumParticles(0): Rem Create 100 Particles
Make Object Plain N,.5,.5
Texture Object N,1000
Ghost Object On N
Next N
Position Camera 0,5,-30
Point Camera 0,0,0
Do
UpdatePSystem()
Sync
Loop
End
function UpdatePSystem()
For T = 1 to NumParticles(0)
If Particle#(T,0) > 0: Rem If particle still alive
Particle#(T,0) = Particle#(T,0) - 1: Rem Knock one unit off it's life
Rem Handle Particle Movement...
Particle#(T,1) = Particle#(T,1) + Particle#(T,4)
Particle#(T,2) = Particle#(T,2) + Particle#(T,5)
Particle#(T,3) = Particle#(T,3) + Particle#(T,6)
Position object T, Particle#(T,1), Particle#(T,2), Particle#(T,3)
Set Object To Camera Orientation T
Else
Particle#(T,0) = Rnd(LifeSpan(0)): Rem Random Life For Particle
Particle#(T,1) = EmitterX#(0): Rem Initial X position
Particle#(T,2) = EmitterY#(0): Rem Initial Y position
Particle#(T,3) = EmitterZ#(0): Rem Initial Z position /100.0
Particle#(T,4) = (Rnd(20)-10) / 100.0: Rem XVelocity
Particle#(T,5) = Rnd(40) / 100.0: Rem YVelocity
Particle#(T,6) = (Rnd(20)-10) / 100.0: Rem ZVelocity
Endif
Next T
Endfunction
Notice it's basically the same as the last code snippet - but with a few tweaks in the settings. All we've done really is make the particles a little smaller to look like sparks and directed the particles in an upwards direction like a firework.
You will find that creating most effects use exactly the same code, but with different shaped and coloured particles, along with little tweaks to the movement values.
However, our firework doesn't look quite right yet. What we need is...
Gravity
To simulate gravity, all we need to do is deduct a small amount off each particle's Y position before we position it on the screen.
Set Display Mode 800,600,16
Sync On: Sync Rate 0: CLS 0
AutoCam Off
Backdrop On: Color Backdrop 0
Set Ambient Light 100
Randomize Timer()
Load Image "Figb2.jpg",1000
Dim Particle#(200,6)
Dim NumParticles(0)
Dim LifeSpan(0)
Dim EmitterX#(0): Dim EmitterY#(0): Dim EmitterZ#(0)
Dim Gravity#(0)
NumParticles(0) = 200
LifeSpan(0) = 50
EmitterX#(0) = 0.0
EmitterY#(0) = 0.0
EmitterZ#(0) = 0.0
Gravity#(0) = .01
For N = 1 To NumParticles(0): Rem Create 100 Particles
Make Object Plain N,.5,.5
Texture Object N,1000
Ghost Object On N
Next N
Position Camera 0,5,-30
Point Camera 0,0,0
Do
UpdatePSystem()
Sync
Loop
End
function UpdatePSystem()
For T = 1 to NumParticles(0)
If Particle#(T,0) > 0: Rem If particle still alive
Particle#(T,0) = Particle#(T,0) - 1: Rem Knock one unit off it's life
Rem Handle Particle Movement...
Particle#(T,1) = Particle#(T,1) + Particle#(T,4)
Particle#(T,2) = Particle#(T,2) + Particle#(T,5)
Particle#(T,3) = Particle#(T,3) + Particle#(T,6)
Particle#(T,5) = Particle#(T,5) - Gravity#(0)
Position object T, Particle#(T,1), Particle#(T,2), Particle#(T,3)
Set Object To Camera Orientation T
Else
Particle#(T,0) = Rnd(LifeSpan(0)): Rem Random Life For Particle
Particle#(T,1) = EmitterX#(0): Rem Initial X position
Particle#(T,2) = EmitterY#(0): Rem Initial Y position
Particle#(T,3) = EmitterZ#(0): Rem Initial Z position /100.0
Particle#(T,4) = (Rnd(20)-10) / 100.0: Rem XVelocity
Particle#(T,5) = Rnd(40) / 100.0: Rem YVelocity
Particle#(T,6) = (Rnd(20)-10) / 100.0: Rem ZVelocity
Endif
Next T
Endfunction
And there we have our firework. Try altering the gravity value to see what effect it has on the firework.
Clouds/Smoke
Once again - same program, different settings. This snippet attempts to look like you are flying through clouds, but a similar effect could be used for smoke in a burning building - or steam:
Set Display Mode 800,600,16
Sync On: Sync Rate 0: CLS 0
AutoCam Off
Backdrop On
Set Ambient Light 100
Randomize Timer()
Load Image "Figb2.jpg",1000
Dim Particle#(300,6)
Dim NumParticles(0)
Dim LifeSpan(0)
Dim EmitterX#(0): Dim EmitterY#(0): Dim EmitterZ#(0)
NumParticles(0) = 300
LifeSpan(0) = 1400
EmitterX#(0) = 0.0
EmitterY#(0) = 160.0
EmitterZ#(0) = 0.0
For N = 1 To NumParticles(0): Rem Create 300 Particles
Make Object Plain N,80,30
Texture Object N,1000
Ghost Object On N
Next N
Position Camera 0,180,-60
Do
UpdatePSystem()
Sync
Loop
End
function UpdatePSystem()
For T = 1 to NumParticles(0)
If Particle#(T,0) > 0: Rem If particle still alive
Particle#(T,0) = Particle#(T,0) - 1: Rem Knock one unit off it's life
Rem Handle Particle Movement...
Particle#(T,1) = Particle#(T,1) + Particle#(T,4)
Particle#(T,2) = Particle#(T,2) + Particle#(T,5)
Particle#(T,3) = Particle#(T,3) + Particle#(T,6)
Position object T, Particle#(T,1), Particle#(T,2), Particle#(T,3)
Set Object To Camera Orientation T
Else
Particle#(T,0) = Rnd(LifeSpan(0)): Rem Random Life For Particle
Particle#(T,1) = EmitterX#(0): Rem Initial X position
Particle#(T,2) = EmitterY#(0): Rem Initial Y position
Particle#(T,3) = EmitterZ#(0): Rem Initial Z position /100.0
Particle#(T,4) = (Rnd(80)-40) / 100.0: Rem XVelocity
Particle#(T,5) = Rnd(3) / 100.0: Rem YVelocity
Particle#(T,6) = 0-Rnd(60) / 100.0: Rem ZVelocity
Endif
Next T
Endfunction
Starfield Effect:
Yet another variation on the same theme - the old starfield effect...
Set Display Mode 800,600,16
Sync On: Sync Rate 0: CLS 0
AutoCam Off
Backdrop On: Color Backdrop 0
Set Ambient Light 100
Randomize Timer()
Load Image "Figb2.jpg",1000
Dim Particle#(300,6)
Dim NumParticles(0)
Dim LifeSpan(0)
Dim CurParticle(0)
Dim EmitterX#(0): Dim EmitterY#(0): Dim EmitterZ#(0)
NumParticles(0) = 300
LifeSpan(0) = 100
EmitterX#(0) = 0.0
EmitterY#(0) = 0.0
EmitterZ#(0) = 30.0
For N = 1 To NumParticles(0): Rem Create 100 Particles
Make Object Plain N,.1,.1
Texture Object N,1000
Ghost Object On N
Next N
Position Camera 0,0,0
Do
UpdatePSystem()
Sync
Loop
End
function UpdatePSystem()
For T = 1 to NumParticles(0)
If Particle#(T,0) > 0: Rem If particle still alive
Particle#(T,0) = Particle#(T,0) - 1: Rem Knock one unit off it's life
Rem Handle Particle Movement...
Particle#(T,1) = Particle#(T,1) + Particle#(T,4)
Particle#(T,2) = Particle#(T,2) + Particle#(T,5)
Particle#(T,3) = Particle#(T,3) + Particle#(T,6)
Position object T, Particle#(T,1), Particle#(T,2), Particle#(T,3)
Set Object To Camera Orientation T
Else
Particle#(T,0) = Rnd(LifeSpan(0)/2)+LifeSpan(0)/2: Rem Random Life For Particle
Particle#(T,1) = EmitterX#(0): Rem Initial X position
Particle#(T,2) = EmitterY#(0): Rem Initial Y position
Particle#(T,3) = EmitterZ#(0): Rem Initial Z position /100.0
Particle#(T,4) = (Rnd(40)-20) / 100.0: Rem XVelocity
Particle#(T,5) = (Rnd(40)-20) / 100.0: Rem YVelocity
Particle#(T,6) = 0 - (Rnd(40) / 150.0): Rem ZVelocity
Endif
Next T
Endfunction
Well that's the end of this introduction to particles in DB Classic for beginners. Obviously there's lots more you can do now you know the basics.
If anyone finds this useful and would be interested in a more advanced part 2, then let me know.
There are many more tutorials and example code snippets on TGPF - my game programming forums. Click on the link below - it's free to join and everyone's welcome!
Visit TGPF
TDK_Man