This tech blog will show both the GML and DnD™ methods for setting up and using particle systems, and at the end of the blog, you can find links to two downloadable test projects so you can see what we'll be discussing in the proper context. With that said, let's get started with the fundamentals...
WHAT IS A PARTICLE?
A particle is a graphic resource that has certain fixed properties which are defined by a particle system. These properties cannot be manipulated directly for individual particles, but are changed through the code that is used to define the system that a type of particle belongs to. Particles are very useful for creating beautiful and flashy effects (or subtle and discreet ones!) in a game without the CPU overhead required when using instances.
Fire effect made with particles, courtesy of Martin Crownover
WHAT IS A PARTICLE SYSTEM?
Think of a particle system like a container that we will use to hold our particles ready for use. We use code to define a series of visual aspects for our particle, and then we place it in the "container" so that we can take it out and use it whenever and wherever we need later.
HOW DO I SET ONE UP?
Before setting up a particle system it's important to note that most of a particle systems code is only ever called once in a game, usually in some type of controller object in the first room or level (this is not always the case, but it is a general rule of thumb). This is because a particle system, once created, stays in memory and is ready to be used at any time, so if you create it more than once it can quickly increase the memory usage and eventually cause serious lag or even crashing the game. So, in this tech blog, we will make a global particle system that can be used by any object at any time.
PART I - CREATING A PARTICLE SYSTEM
To start with, we'll need a controller object for the particle system and then in that, we need to define the system properties and give it a name so that we can use it later. This particle controller object would be placed in the first room of your game and the following code should be placed in the Game Start event. Since this is going to be a global system that any object can use we will set it up like this:
Notice that the particle system is given a layer. In the code above we are assigning it to the layer that the instance running the code is on, but you could as easily give it to any layer present in the room, that the controller instance will be placed in. It's good practice to create a layer specifically for the particle system and then place your controller instance on that (or assign the layer when you create the system). Note that we also flag the system as persistent. This is because we want it to persist across all rooms without having to recreate it again. If you set this to false, then trying to access the system using the global variable will cause an error.
That's the system created then, but what about the particles? We have to define them too or the system is useless and does nothing! To create a particle, you have to define its general properties. These are like object properties, but they only apply in a general way to individual particles. What this means is that if you give the particles a minimum movement speed of 1 and a maximum movement speed of 2, any particle created by the system will have a random speed between 1 and 2 pixels per step, and that overall they will have an average speed of 1.5. Let's name and add our first particle to the system (the following code would go in the Room Start event of the same object we used to create the particle system):
global.Particle1 = part_type_create();
Before continuing, note that the DnD™ action permits you to enable or disable additive blending for the particle when it is drawn to the screen. We'll explain what enabling this means a bit further along in this article.
We have a particle type created, but we still have to define its general properties (ie: how it looks, its speed, rotation, alpha etc...). There are a number of different functions that can be used to define a particle effect, which we'll list and explain below, before showing a snippet of code that has them all together, as they should be used:
part_type_shape(ind, shape)- Sets the shape of the particle type to any of the constants above (default is
part_type_size(ind, size_min, size_max, size_incr, size_wiggle)- Sets the size parameters for the particle type. You specify the minimum starting size, the maximum starting size, the size increase in each step (use a negative number for a decrease in size).
part_type_scale(ind, xscale, yscale)- Sets the horizontal and vertical scale. This factor is multiplied with the size. It is particularly useful when you need to scale differently in x- and y-direction.
part_type_color3(ind, colour1, colour2, colour3)- Indicates 3 colours to be used for the particle, and it will fade from one to the other over the particle lifetime.
part_type_alpha3(ind, alpha1, alpha2, alpha3)- Sets three alpha transparency parameters (0-1) for the particle type, and the particle will merge between them over its lifetime.
part_type_speed(ind, speed_min, speed_max, speed_incr, speed_wiggle)- Sets the speed properties for the particle type. You specify a minimal and maximal speed. A random value between the given bounds is chosen when the particle is created. You can indicate a speed increase in each step. Use a negative number to slow the particle down (the speed will never become smaller than 0).
part_type_direction(ind, dir_min, dir_max, dir_incr, dir_wiggle)- Sets the direction properties for the particle type. Again, you specify a range of directions (in counterclockwise degrees; 0 indicates a motion to the right). For example, to let the particle move in a random direction choose 0 and 360 as values. You can specify an increase in direction for each step.
part_type_orientation(ind, ang_min, ang_max, ang_incr, ang_wiggle, ang_relative)- Sets the orientation angle properties for the particle type. You specify the minimum angle, the maximum angle, the increase in each step and the amount of wiggling in angle. You can also indicate whether the given angle should be relative (1) to the current direction of motion or absolute (0). E.g. by setting all values to 0 but ang_relative to 1, the particle orientation will precisely follow the path of the particle.
part_type_blend(ind, additive)- Sets whether to use additive blending (1) or normal blending (0) for the particle type.
part_type_life(ind, life_min, life_max)- Sets the lifetime bounds for the particle type.
Before you look at the code being used, let's go over a couple of these properties in more detail...
When you see wiggle as an argument for a particle function, it means that if you place a number between 1 and 20 the particle will "wiggle" or fluctuate between the min and max values for the code (with 1 being a slow wiggle and 20 being very fast). So a particle speed minimum of 2 and maximum of 5 with a wiggle of 20 will oscillate very quickly between the min/max speeds for the lifetime of each particle.
And what about the lifetime? This is the length of time in game steps that a particle will exist for. So, a lifetime of 30 min and 30 max will have the particle existing for exactly 30 steps, but a lifetime of 20 min and 60 max will have each particle exists for a random number of steps between 20 and 60.
Finally, it's worth noting the blend option (if you are using DnD™, particle blending is enabled or disabled when you create the particle type, as explained above). With this you set the particle to have additive blending (enabled) or normal blending (disabled). Having additive blending means that a particles brightness will be added to whatever is behind it on the screen - sprites, backgrounds or even other particles - and so it gives a saturated glowing look, which is very useful for fire and magic effects (for example), but would be best left off for something like smoke. Experiment to see the differences when creating your own particles, but keep in mind that additive blending will normally increase the texture swaps being done to render the game, which in turn can cause lag if you have a great number of them, so you do not want to abuse this effect too much.
Now to define our global particle using the code detailed above we'd add something like this (again, in the Room Start event of the controller, where we defined the particle system)...
part_type_size(global.Particle1, 0.01, 0.05, 0, 0.5);
part_type_color3(global.Particle1, c_aqua, c_lime, c_red);
part_type_alpha3(global.Particle1, 0.5, 1, 0);
part_type_speed(global.Particle1, 2, 5, -0.10, 0);
part_type_direction(global.Particle1, 0, 359, 0, 20);
part_type_life(global.Particle1, 30, 60);
So that's it! We have now defined our particles and they are ready to be used? How do we do that? Read on...
PART II(A) - CREATING PARTICLES DIRECTLY
There are a couple of ways to create particles, and each has its pros and cons. You can use emitters to burst or stream particles, or you can create particles directly at a point in the room. Which one you use really depends on what you are trying to achieve and the effect you are wanting to create, but we'll start with the easiest of the two, which is creating particles directly without the use of an emitter. For that we'd do the following, in any event where we want to make particles appear (step event, a collision event, a mouse pressed event, etc...):
part_particles_create(global.P_System, mouse_x, mouse_y, global.Particle1, 50);
That short code above will create 50 particles of Particle1 from our global system at the position of the mouse cursor (you can see this being used in the Global Mouse Down event of the examples linked at the end of this article). The great thing about that line of code is that it can be used anywhere without any fuss. For example, if you have a rocket, then you could place code like this in the step event and have smoke particles coming every step, or add it into a collision event and have a damage effect burst when the player object is hit.
You can also use this to create particles over an area by changing the x/y coords randomly, for example:
var _mx = mouse_x - 50 + irandom(100);
var _my = mouse_y - 50 + irandom(100);
part_particles_create(global.P_System, _mx, _my, global.Particle1, 5);
The above code will create 250 particles at a random position within a 100px square area around the mouse cursor position.
PART II(B) - CREATING PARTICLES WITH EMITTERS
Since we've explained the easy way to create particles, let's now go the slightly more complex - but versatile - way, which is to use emitters. Emitters are another part of the particle system that has to be defined before being used, so we'll make a global emitter the same as we did for the system and the particle type. We also have to decide whether to have a static (non-moving) emitter or not and whether we are going to burst or stream the particles, as well as decide what area and what kind of distribution we are going to have the emitter use.
You may be asking what all that means, so let's explain... A static emitter is one that you can define once and forget about as it will not be moving anywhere for the duration of the game, ie: think of a log fire - it doesn't move, it just emits flames and so is static, but a fireball would require a dynamic emitter as it will move across the screen. As for bursting or streaming, a burst is a one-off explosion of particles, whereas a stream is just that - a constant stream of particles every game step. For area and distribution, with emitters you can define an area for emitting particles (which can be a rectangle, ellipse, diamond or line) as well as the distribution curve (gaussian, inverse gaussian, or linear).
The following images illustrate the different types of shape available:
And the distribution curves are illustrated here:
Here is a set of example codes that define two emitters. One will be static and stream particles over the area of the whole room, while the other will follow the mouse and burst every 30 steps from a small ellipse... The first code block would go in the Game Start Event along with the rest of the code for defining the system:
global.Emitter1 = part_emitter_create(global.P_System);
global.Emitter2 = part_emitter_create(global.P_System);
part_emitter_region(global.P_System, global.Emitter1, 0, room_width, 0, room_height, ps_shape_rectangle, ps_distr_invgaussian);
part_emitter_region(global.P_System, global.Emitter2, mouse_x - 50, mouse_x + 50, mouse_y - 25, mouse_y + 25, ps_shape_ellipse, ps_distr_gaussian);
part_emitter_stream(global.P_System, global.Emitter1, global.Particle1, 10);
alarm = room_speed;
The first emitter will now constantly stream particles every step, and the second emitter has been setup, but does nothing. Since we want it to be a dynamic emitter and move and burst particles every second we need to add an Alarm event with the following:
part_emitter_region(global.P_System, global.Emitter2, mouse_x - 50, mouse_x + 50, mouse_y - 25, mouse_y + 25, ps_shape_ellipse, ps_distr_gaussian);
part_emitter_burst(global.P_System, global.Emitter2, global.Particle1, 10);
alarm = room_speed;
Here we change the position of the emitter based on the current mouse cursor position, and then we burst some particles before resetting the alarm. Note that you can call the emitter region functions at any time and so can use them to create some complex effects by tweening values for their area and position, and all particles created after each change will follow the new settings.
But what if you want several objects to emit particles at the same time? A global emitter can only be in one place at a time, so you would need to create local emitters in each object. These emitters will still use the global particle system and any particles that are within it, but they can be different shapes and sizes and move with the object, making them ideal for rockets or bullets or anything that moves. The code you would use is exactly the same as above, but without the
global. before the emitter names (if you drag room1 in the example file to the top of the room resources above room0 and run the game, you can see examples of this).
That's the creation of particles dealt with but there is one more thing that's very important... Cleaning up when you are finished with them.
PART III - CLEANING UP
As mentioned at the start, once created, a particle system (and its particles, emitters etc...) are stored in memory for instant use. But what happens when you restart the game? Or if your player dies and starts the room again? Well, if you don't manage the code right you get a memory leak. This is when something starts eating up memory because it has been created and then dereferenced (ie: the variable that stored it no longer exists, so the game no longer has access to it), and this will cause lag or even block the computer causing your game to crash. It is a pretty common problem with first-time users of particle systems! So, how do you avoid this? Well, GameMaker Studio 2 has a couple of functions to delete particle systems and their emitters as well as particles from memory when not in use and with a little bit of planning when using the following code, you can remove this potential problem.
The first thing you have to do is decide where you are going to create the system and how you will use it. You could create it in the Game Start event of an object that is in your first room (like a menu or title screen), but this means that if you restart the game code using the
game_restart() function, it will be recreated and cause a memory leak. To avoid that you would need to have something like this in the Game End event of your object:
This will remove the defined particle, the emitters and then the system from memory before restarting the game again. If you have it in a controller object that is placed in every game room, then you could have the create event that defines the particle system and other parts, then place the destroy codes in the room end event. Just remember that after thinking about where would be the most suitable place to create the system, think about where would be the most suitable place to destroy the system too! If you check out the file that goes with this tutorial you can see what I mean by looking at the room end and destroy events of the objects used.
Now you know the basics about particles, particle systems and emitters, so you can now add them into your game! But before rushing off please note that as I said at the start, particles are less CPU hungry than objects, but they are not the solution to everything as they will cause lag if you have thousands of them at a time. The best thing to do is experiment and use them carefully to "flesh out" visually a game and give it a bit more eye-candy without overdoing it.
Before finishing this article, it should be pointed out that a particle system can be a chore to set up and test, so we can't recommend enough that you use one of the many particle designing tools that are available. For example, the excellent Geon FX for visually creating and editing particle effects, or Sparks which is a particle animation tool that will create sprites from particle effects (useful for HTML5 and other occasions where particles cannot be used).