(For more resources related to this topic, see here.)
Introducing particle effects
Particle effects are the decorative flourishes used in games to represent dynamic and complex phenomena, such as fire, smoke, and rain. To create a particle effect, it requires three elements: a System, Emitters, and the Particles themselves.
Understanding particle systems
Particle systems are the universe in which the particles and emitters live. Much like the universe, we cannot define the size but we can define a point of origin which all emitters and particles will be placed relative to. We can also have multiple particle systems in existence at any given time, which can be set to draw the particles at different depths. While we can have as many particle systems as we want, it is best to have as few as possible in order to prevent possible memory leaks. The reason for this is that once a particle system is created, it will remain in existence forever unless it is manually destroyed. Destroying the instance that spawned it or changing rooms will not remove the system, so make sure it is removed when it is no longer needed. By destroying a particle system, it will remove all the emitters and particles in that system along with it.
Utilizing particle emitters
Particle emitters are defined areas within a system from which particles will spawn. There are two types of emitters to choose from: Burst emitters that spawn particles a single time, and Stream emitters that spew particles continuously over time. We can define the size and shape of the region in space for each emitter, as well as how the particles should be distributed within the region.
When defining the region in space, there are four Shape options: DIAMOND, ELLIPSE, LINE, and RECTANGLE. An example of each can be seen in the preceding diagram, all using exactly the same dimensions, amount of particles, and distribution. While there is no functional difference between using any one of these shapes, the effect itself can benefit from a properly chosen shape. For example, only a LINE can make an effect appear to be angled 30 degrees.
The distribution of the particles can also affect how the particles are expelled from the emitter. As can be seen in the preceding diagram, there are three different distributions. LINEAR will spawn particles with an equal random distribution throughout the emitter region. GAUSSIAN will spawn particles more towards the center of the region. INVGAUSSIAN is the inverse of GAUSSIAN, wherein the particles will spawn closer to the edges of the emitter.
Particles are the graphic resources that are spawned from the emitters. There are two types of particles that can be created: Shapes and Sprites. Shapes are the collection of 64 x 64 pixel sprites that comes built-in with GameMaker: Studio for use as particles. The shapes, as seen in the next diagram, are suitable for the majority of the most common effects, such as fireworks and flames. When wanting to create something more specialized for a game, we can use any Sprite in the Resource tree.
There are a lot of things we can do with particles by adjusting the many attributes available. We can define ranges for how long it lives, the color it should be, and how it moves. We can even spawn more particles at the point of death for each particle. There are, however, some things that we cannot do. In order to keep the graphics processing costs low, there is no ability to manipulate individual particles within an effect. Also, particles cannot interact with objects in any way, so there is no way to know if a particle has collided with an instance in the world. If we need this kind of control, we need to build objects instead.
Designing the look of a particle event is generally a trial and error process that can take a very long time. To speed things up, try using one of the many particle effect generators available on the Internet, such as Particle Designer 2.5 by Alert Games found here: http://alertgames.net/index.php?page=s/pd2.
Adding particle effects to the game
We are going to build a few different particle effects to demonstrate the various ways effects can be implemented in a game, and to look into some of the issues that might arise. To keep things straightforward, all of the effects we create will be a part of a single, global particle system. We will use both types of emitters, and utilize both shape and sprite-based particles. We will start with a Dust Cloud that will be seen anytime a Pillar is broken or destroyed. We will then add a system to create a unique shrapnel effect for each Pillar type. Finally, we will create some fire and smoke effects for the TNT explosion to demonstrate moving emitters.
Creating a Dust Cloud
The first effect we are going to create is a simple Dust Cloud. It will burst outwards upon the destruction of each Pillar and dissolve away over time. As this effect will be used in every level of the game, we will make all of its elements global, so they only need to be declared once.
Open the Tower Toppling project we were previously working on if it is not already open.
We need to make sure that WebGL is enabled when we build the game. Navigate to Resources | Change Global Game Settings and click on the HTML5 tab.
On the left-hand side, click on the tab for Graphics. As seen in the following screenshot, there are three options under WebGL in Options. If WebGL is Disabled, the game will not be able to use the GPU and all browsers will suffer from any potential lag. If WebGL is Required, any browser that does not have this capability will be prevented from running the game. The final option is Auto-Detect which will use WebGL if the browser supports it, but will allow all browsers to play the game no matter what. Select Auto-Detect and then click on OK.
Now that we have WebGL activated we can build our effects. We will start by defining our particle system as a global variable by creating a new script called scr_Global_Particles.
system = part_system_create();
The first effect we are going to make is the Dust Cloud which will be attached to the Pillars. For this we only need a single emitter which we will move to the appropriate position when it is needed. Create a global variable for the emitter and add it to the particle system with the following code at the end of the script:
dustEmitter = part_emitter_create(system);
For this particle, we are going to use one of the built-in shapes, pt_shape_explosion, which looks like a little thick cloud of dust. Add the following code to the end of the script:
particle_Dust = part_type_create();
Once again we have made this a global variable, so that we have to create this Dust Cloud particle only once. We have declared only the shape attribute of this particle at this time. We will add more to this later once we can see what the effect looks like in the game.
We need to initialize the particle system with the other global variables. Reopen scr_Global_GameStart and call the particles script.
With everything initialized, we can now create a new script, scr_Particles_DustCloud, which we can use to set the region of the emitter and have it activate a burst of particles.
part_emitter_region(system, dustEmitter, x-16, x+16, y-16, y+16,
part_emitter_burst(system, dustEmitter, particle_Dust, 10);
We start by defining a small area for the emitter based on the position of instance that calls this script. The region itself will be circular with a Gaussian distribution so that the particles shoot out from the center. We then activate a single burst of 10 dust particles from the emitter.
All we need to do now is execute this script from the destruction of a Pillar. Reopen scr_Pillar_Destroyand insert the following line of code on the line before the instance is destroyed:
We need to add this effect to the breaking of the Pillars as well. Reopen scr_ Pillar_BreakApart and insert the same code in the same spot.
Save the game and then play it. When the glass Pillars are destroyed, we should see thick white clouds appearing as shown in the following screenshot:
The particles are boring and static at this point, because we have not told the particles to do anything other than to look like the shape of a cloud. Let's fix this by adding some attributes to the particle. Reopen scr_Global_Particles and add the following code at the end of the script:
part_type_life(particle_Dust, 15, 30);
part_type_direction(particle_Dust, 0, 360, 0, 0);
part_type_speed(particle_Dust, 1, 2, 0, 0);
part_type_size(particle_Dust, 0.2, 0.5, 0.01, 0);
part_type_alpha2(particle_Dust, 1, 0);
The first attribute we add is how long we want the particle to live for, which is a range between 15 and 30 steps, or at the speed of our rooms, a half to a whole second. Next, we want the particles to explode outwards, so we set the angle and add some velocity. Both functions that we are using have similar parameters. The first value is the particle type for which this is to be applied. The next two parameters are the minimum and maximum values from which a number will be randomly chosen. The fourth parameter sets an incremental value every step. Finally, the last parameter is a wiggle value that will randomly be applied throughout the particle's lifetime. For the Dust Cloud, we are setting the direction to be in any angle and the speed is fairly slow, ranging only a few pixels per step. We also want to change the size of the particles and their transparency, so that the dust appears to dissipate.
Save the game and run it again. This time the effect appears much more natural, with the clouds exploding outwards, growing slightly larger, and fading out. It should look something like the next screenshot. The Dust Cloud is now complete.
Adding in Shrapnel
The Dust Cloud effect helps the Pillar destruction appear more believable, but it lacks the bigger chunks of material one would expect to see. We want some Shrapnel of various shapes and sizes to explode outwards for each of the different types of Pillars. We will start with the Glass particles.
Create a new Sprite, spr_Particle_Glass, and with Remove Background checked, load Chapter 8/Sprites/Particle_Glass.gif.mhanaje This sprite is not meant to be animated, though it does have several frames within it. Each frame represents a different shape of particle that will be randomly chosen when the particle is spawned.
We will want the particles to rotate as they move outwards, so we need to center the origin. Click on OK.
Reopen scr_Global_Particles and initialize the Glass particle at the end of the script.
particle_Glass = part_type_create();
part_type_sprite(particle_Glass, spr_Particle_Glass, false, false,
Once we have created the global variable and the particle, we set the particle type to be a Sprite. When assigning Sprites there are a few extra parameters beyond which resources should be used. The third and fourth parameters are for whether it should be animated, and if so, should the animation stretch for the duration of the particle's life. In our case we are not using animation, so it has been set to false. The last parameter is for whether we want it to choose a random subimage of the Sprite, which is what we do want it to do.
We also need to add some attributes to this particle for life and movement. Add the following code at the end of the script:
part_type_life(particle_Glass, 10, 20);
part_type_direction(particle_Glass, 0, 360, 0, 0);
part_type_speed(particle_Glass, 4, 6, 0, 0);
part_type_orientation(particle_Glass, 0, 360, 20, 4, false);
When compared with the Dust Cloud, this particle will have a shorter lifespan but will move at a much higher velocity. This will make this effect more intense while keeping the general area small. We have also added some rotational movement through part_type_orientation. The particles can be set to any angle and will rotate 20 degrees per frame with a variance of up to four degrees. This will give us a nice variety in the spin of each particle. There is one additional parameter for orientation, which is whether the angle should be relative to its movement. We have set it to false as we just want the particles to spin freely.
To test this effect out, open up scr_Particles_DustCloud and insert a burst emitter before the Dust Cloud is emitted, so that the Glass particles appear behind the other effect.
part_emitter_burst(system, dustEmitter, particle_Glass, 8);
Save the game and then play it. When the Pillars break apart, there should be shards of Glass exploding out along with the Dust Cloud. The effect should look something like the following screenshot:
Next we need to create Shrapnel for the Wood and Steel particles. Create new Sprites for spr_Particle_Wood and spr_Particle_Steel with the supplied images in Chapter 8/Sprites/ in the same manner as we did for Glass.
As these particles are global, we cannot just swap the Sprite out dynamically. We need to create new particles for each type. In scr_Global_Particles, add particles for both Wood and Steel with the same attributes as Glass.
Currently the effect is set to Always create Glass particles, something we do not want to do. To fix this we are going to add a variable, myParticle, to each of the different Pillars to allow us to spawn the appropriate particle. Open scr_Pillar_Glass_Create and add the following code at the end of the script:
myParticle = particle_Glass;
Repeat the last step for Wood and Steel with the appropriate particle assigned.
In order to have the proper particle spawn, all we need to do is reopen scr_Particles_DustCloud and change the variable particle_Glass to myParticle as in the following code:
part_emitter_burst(system, dustEmitter, myParticle, 8);
Save the game and play the game until you can destroy all the three types of Pillars to see the effect. It should look something similar to the following screenshot, where each Pillar spawns its own Shrapnel:
Making the TNT explosion
When the TNT explodes, it shoots out some TNT Fragments which are currently bland looking Sprites. We want the Fragments to be on fire as they streak across the scene. We also want a cloud of smoke to rise up from the explosion to indicate that the explosion we see is actually on fire. This is going to cause some complications. In order to make something appear to be on fire, it will need to change color, say from white to yellow to orange. As we have already mentioned, due to the fact that WebGL is not supported by all browsers, we cannot utilize any of the functions that allow us to blend colors together. This means that we need to work around this issue. The solution is to use several particles instead of one.
We will start by creating some custom colors so that we can achieve the look of fire and smoke that we want. Open scr_Global_Colors and add the following colors:
orange = make_color_rgb(255, 72, 12);
fireWhite = make_color_rgb(255, 252, 206);
smokeBlack = make_color_rgb(24, 6, 0);
We already have a nice yellow color, so we add an orange, a slightly yellow tinted white, and a partially orange black color.
In order to achieve the fake blending effect we will need to spawn one particle type, and upon its death, have it spawn the next particle type. For this to work properly, we need to construct the creation of the particles in the opposite order that they will be seen. In this case, we need to start by building the smoke particle. In scr_Global_Particles add a new particle for the smoke with the following attributes:
particle_Smoke = part_type_create();
part_type_life(particle_Smoke, 30, 50);
part_type_direction(particle_Smoke, 80, 100, 0, 0);
part_type_speed(particle_Smoke, 2, 4, 0, 0);
part_type_size(particle_Smoke, 0.6, 0.8, 0.05, 0);
part_type_alpha2(particle_Smoke, 0.5, 0);
part_type_gravity(particle_Smoke, 0.4, 90);
We start by adding the particle and using the built-in smoke shape. We want the smoke to linger for a while, so we set its life to range between a minimum of a second to almost two full seconds. We then set the direction and speed to be more or less upwards so that the smoke rises. Next, we set the size and have it grow over time. With the alpha values, we don't want the smoke to be completely opaque, so we set it to start at half transparent and fade away over time. Next, we are using part_type_color1 which allows us to tint the particle without affecting the performance very much. Finally, we apply some gravity to the particles so that any angled particles float slowly upwards.
The smoke is the final step of our effect and it will be spawned from an orange flame that precedes it.
particle_FireOrange = part_type_create();
part_type_life(particle_FireOrange, 4, 6);
part_type_direction(particle_FireOrange, 70, 110, 0, 0);
part_type_speed(particle_FireOrange, 3, 5, 0, 0);
part_type_size(particle_FireOrange, 0.5, 0.6, 0.01, 0);
part_type_alpha2(particle_FireOrange, 0.75, 0.5);
part_type_gravity(particle_FireOrange, 0.2, 90);
part_type_death(particle_FireOrange, 1, particle_Smoke);
Once again we set up the particle using the built-in smoke shape, this time with a much shorter lifespan. The general direction is still mainly upwards, though there is more spread than the smoke. These particles are slightly smaller, tinted orange and will be partially transparent for its entire life. We have added a little bit of upward gravity, as this particle is in between fire and smoke. Finally, we are using a function that will spawn a single particle of smoke upon the death of each orange particle.
The next particle in the chain for this effect is a yellow particle. This time we are going to use the FLARE shape, which will give a better appearance of fire. It will also be a bit smaller, live slightly longer than the orange particle, and move faster, spreading in all directions. We will not add any transparency to this particle so that it appears to burn bright.
particle_FireYellow = part_type_create();
part_type_life(particle_FireYellow, 6, 12);
part_type_direction(particle_FireYellow, 0, 360, 0, 0);
part_type_speed(particle_FireYellow, 4, 6, 0, 0);
part_type_size(particle_FireYellow, 0.4, 0.6, 0.01, 0);
part_type_death(particle_FireYellow, 1, particle_FireOrange);
We have only one more particle to create this effect for, which is the hottest and brightest white particle. Its construction is the same as the yellow particle, except it is smaller and faster.
particle_FireWhite = part_type_create();
part_type_life(particle_FireWhite, 2, 10);
part_type_direction(particle_FireWhite, 0, 360, 0, 0);
part_type_speed(particle_FireWhite, 6, 8, 0, 0);
part_type_size(particle_FireWhite, 0.3, 0.5, 0.01, 0);
part_type_death(particle_FireWhite, 1, particle_FireYellow);
We now have all the particles we need for this particle effect; we just need to add an emitter to spawn them. This time we are going to use a stream emitter, so that the fire continuously flows out of each Fragment. Since the Fragments are moving, we will need to have a unique emitter for each Fragment we create. This means it cannot be a global emitter, but rather a local one. Open scr_TNT_Fragment_Create and add the following code at the end of the script:
myEmitter = part_emitter_create(system);
part_emitter_region(system, myEmitter, x-5, x+5, y-5, y+5, ps_
part_emitter_stream(system, myEmitter, particle_FireWhite, 5);
We create an emitter with a fairly small area for spawning with balanced distribution. At every step, the emitter will create five new Fire particles as long as the emitter exists.
The emitter is now created at the same time as the Fragment, but we need the emitter to move along with it. Open scr_TNT_Fragment_Step and add the following code:
part_emitter_region(system, myEmitter, x-5, x+5, y-5, y+5, ps_
As already mentioned we need to destroy the emitter, otherwise it will never stop streaming particles. For this we will need to open obj_TNT_Fragment and add a destroy event with a new Script, scr_TNT_Fragment_Destroy, which removes the emitter attached.
This function will remove the emitter from the system without removing any of the particles that had been spawned.
One last thing we need to do is to uncheck the Visible checkbox, as we don't want to see the Fragment sprite, but just the particles.
Save the game and detonate the TNT. Instead of just seeing a few Fragments, there are now streaks of fire jetting out of the explosion that turn into dark clouds of smoke that float up. It should look something like the following screenshot:
Cleaning up the particles
At this point, we have built a good variety of effects using various particles and emitters. The effects have added a lot of polish to the game, but there is a flaw with the particles. If the player decides to restart the room or go to the SHOP immediately after the explosion has occurred, the emitters will not be destroyed. This means that they will continue to spawn particles forever, and we will lose all references to those emitters. The game will end up looking like the following screenshot:
The first thing we need to do is to destroy the emitters when we leave the room. Luckily, we have already written a script that does exactly this. Open obj_TNT_Fragment and add a Room End event and attach scr_TNT_Fragment_Destroy to it.
Even if we destroy the emitters before changing rooms, any particles remaining in the game will still appear in the next room, if only briefly. What we need to do is clear all the particles from the system. While this might sound like it could be a lot of work, it is actually quite simple. As Overlord is in every level, but not in any other room, we can use it to clean up the scene. Open obj_Overlord, add a Room End event and attach a new Script, scr_Overlord_RoomEnd, with the following line of code: part_particles_clear(system);
This function will remove any particle that exists within the system, but will not remove the particle type from memory. It is important that we do not destroy the particle type, as we would not be able to use a particle again if its type no longer exists.
Save the game, explode some TNT, and restart the room immediately. You should no longer see any particles in the scene.
In this article, we were provided with the details to add some spit and polish to the game to really make it shine. We delved into the world of particles and created a variety of effects that add impact to the TNT and Pillar destruction.
Resources for Article :
- HTML5: Generic Containers [Article]
- HTML5 Presentations - creating our initial presentation [Article]
- Deploying HTML5 Applications with GNOME [Article]