Task 20: Particle Effects
Adding Gravity to the Situation
Putting a Spin on Things
Using Images as Effects
In Task 16 you were introduced to particle effects with the Hocus Pocus example highlighting the use of color. In this task we'll go over the mechanics of creating particle effects and how to implement them in games. Particle effects add fun to any game they are incorporated into. In Fellippe Heitor's game Tic-Tac-Toe Rings there are fun particle effects added when the user clicks on the screen with the mouse before starting a game. Why? No reason other than to add a little fun to the program.
The simplest particle effects use nothing more than controlled pixels. Each pixel contains an X,Y location, X and Y vectors, and speed at a minimum. The following example is a particle effect I use often called sparks. The following code has been included in your .\tutorial\task20\ directory as Sparks.BAS. Hold the space bar down to create sparks on the screen.
Figure 1 - Let the Sparks Fly!
When creating effects such as this there are two components needed: an initiator and maintainer. In the example above the MakeSparks() subroutine is the initiator. Calling MakeSparks() creates new particle elements in the Sparks() dynamic array that are waiting to be acted upon. The UpdateSparks() subroutine then maintains and updates the particle elements sitting in the Sparks() array. The sparks themselves are created using a TYPE identifier:
The variable .life is a countdown timer. With each passing frame .life is decreased by one in effect creating a finite lifetime for the spark to live. The .x and .y variables are the current X and Y coordinate location of each individual spark. The .xdir and .ydir variables control the X and Y vectors of each spark, .speed is used for vector speed, and finally .fade controls the RGB intensity of the spark as it travels through its lifetime.
Line 16 then sets up an empty dynamic array waiting for sparks to be added. When MakeSparks() is called within the main loop a number of sparks, determined by the constant SPARKNUM, are created in the Sparks() array.
The MakeSparks() subroutine first performs some array maintenance in lines 49 through 55. If there are no live sparks found in the Sparks() array the array is reset back to a zero index. This ensures that the array does not get too large and cause an out of memory error.
Line 57 then adds the new number of sparks, determined by the constant SPARKNUM, to the end of the array. Lines 59 through 68 then set up the various properties for each new spark in the array. The newly initiated sparks are now ready to be used when called upon.
To maintain the sparks the UpdateSparks() subroutine needs to be called within the main program loop.
If line 85 of the code detects that the array is currently empty the subroutine is exited immediately. There is no need to scan through the array if it's empty. Line 88 through 118 loops through the entire array looking for any sparks that are still active. Lines 92 and 93 calculate the RGB intensity for the outer pixels. All the pixels are drawn using lines 97 through 106. A center pixel is drawn using the current full intensity, 4 more pixels are drawn using half that intensity, and finally 4 more are drawn using one quarter of the intensity. This gives each spark a hot glow effect. Figure 2 below shows what the pixels look like blown up 800%.
Figure 2 - Sparks Zoomed in 800%
Because the pixels move so fast the user can't tell that each spark is actually a 6x6 grid of pixels with different intensities. They simply blend together to appear as a glowing pixel moving through space. Go ahead and REM out lines 99 through 106 to see what each spark as a single pixel looks like. It's not nearly as clean looking in my opinion.
Line 108 fades the spark out little by little with each frame pass. Lines 112 and 113 updates the spark's position by adding the X and Y vector values to the spark's location. Line 115 slows the pixel down by 10% with each frame pass and finally line 116 decrements the life span of the spark.
This particle effect is about as simple as it gets and can be used as the basis for other more elaborate particle effects.
Adding Gravity to the Situation
Adding simulated gravity to particle effects is a simple matter of tinkering with the Y vector of each particle. By slowly increasing the Y vector value any particles heading upward with a negative Y vector value will eventually turn around and start heading down. Also, by slightly increasing the Y vector speed the particles will appear to speed up as they travel downward simulating a gravitational pull. Keep in mind that the next example simply simulates gravity which in many cases will look "good enough". This keeps the math involved to the bare minimum while still yielding a believable effect.
The following example is the previous example slightly modified to introduce simulated gravity. Any line that has been changed or added has been noted at the right-hand side of the code. This example is saved as GravitySparks.BAS in your .\tutorial\task20\ directory.
The first thing that needs to be done is to set up separate speed variables for the X and Y vectors. This was done in lines 13 and 14 inside of the TYPE definition with the addition of .xspeed and .yspeed. Since the vertical speed of the sparks will need to increase as they travel downward the ability to control the Y vector speed independently from the X vector speed is required.
In line 5 the lifespan of a spark has been increased to 60 frames and in line 23 the _LIMIT has been reduced to 30. This was done to allow you to see the effect more clearly. The sparks will last longer and move a bit slower so you can see the simulated gravity at work.
Lines 67 and 68 in the MakeSparks() subroutine now calculate separate X and Y vector speeds for each spark which is used to update each sparks position in lines 115 and 116.
Lines 119 and 120 is where the calculations for the simulated gravity take place. In line 119 the vertical speed is slightly increased to make each spark appear to move faster downward as the frames progress. Line 120 adds a positive value to the Y vector with each passing frame. This has the effect of turning around any particles moving in an upward to a downward direction during their life span.
Finally, in line 111 the amount of fade has been decreased over time to allow the effect to be more easily seen. Each spark will glow for a longer period of time.
Putting a Spin on Things
Adding rotation to particles can have interesting effects as well. In the following example 32000 particles circle down a drain to create a black hole effect. The following example code is saved as BlackHole.BAS in your .\tutorial\task20\ directory.
Figure 3 - Controlling 32000 Particles
Instead of using PSET to turn pixels on the CIRCLE statement can be used instead to create something different with the same code.
Change the MAXPARTICLES constant in line 3 to equal 500.
REM line 41 to disable it.
Remove the REMs from lines 46 to 48.
Figure 4 below shows the result.
Figure 4 - A Nebula in Space
Adding circular motion to particles can be accomplished using radians. A radian is a unit of measurement for describing the distance around the circumference of a circle. It's a direct relationship between the radius of a circle and its circumference. There are 6.2831852 radians around a circle's circumference or in other words an infinite number of points between 0 and 6.2831852 (2 * Pi) that can be chosen. If for instance you need 60 equally spaced points around a circle (for a clock perhaps) you simply divide 2 * Pi by 60 and that value would be considered one unit out of 60. Figure 5 below visually represents this.
Figure 5 - Calculating Radian Points
Using Images as Effects
The following demonstration program is a simplified version of the Hocus Pocus program shown in Task 16. Instead of displaying pixels the use of cleverly crafted images are used. In this example the images contain circles that get more transparent as their size increases. When displayed on the screen the image is ever increasingly made smaller, giving the illusion of the images fading out over time. The following program is called RomanCandles.BAS and is located in your .\tutorial\task20\ directory. Press the space bar to make roman candle projectiles fly.
Figure 6 - Roman Candles
The MakeProjectiles() subroutine is used to create the seven effect images. Each image is drawn in a unique color using circles that start from the center and work their way out. As the circles get larger their transparency is increased giving them their glowing effect. Granted they are not as elaborately drawn as in Hocus Pocus but the idea remains.
The ShootProjectile() subroutine is used to initiate a particle on the screen. It sets up the characteristics of the particle as well as performing maintenance on the particle array. Finally, UpdateProjectiles() maintains any live projectiles that were previously initiated. With each passing frame the position, speed, and size of the images are manipulated to simulate gravity and fading away.
Also take note that when the main loop is exited by the user pressing the ESC key the images are removed from memory. Don't forget to perform this important cleaning step. Always free your assets from the user's computer before you exit your programs.