Building a particle system

Before we start coding, it will be helpful to see exactly what it is we are trying to achieve. Take a look at the following screenshot:

Building a particle system

This is a screenshot of the particle effect on a plain background. We will use the effect in our game.

The way we achieve the effect is as follows:

  1. Spawn 1,000 dots (particles), one on top of the other, at a chosen pixel position.
  2. In each frame of the game, move each of the 1,000 particles outward at a predetermined, but random, speed and angle.
  3. Repeat step two for two seconds and then make the particles disappear.

We will use a VertexArray to draw all the dots and the primitive type of Point to represent each particle visually. Furthermore, we will inherit from Drawable so that our particle system can take care of drawing itself.

Coding the Particle class

The Particle class will be a simple class that represents just one of the 1,000 particles. Let's get coding.

Coding Particle.h

Right-click Header Files in the Solution Explorer and select Add | New Item.... In the Add New Item window, highlight (by left-clicking) Header File (.h) and then in the Name field, type Particle.h. Finally, click the Add button. We are now ready to code the header file for the Particle class.

Add the following code to the Particle.h file:

#pragma once 
#include <SFML/Graphics.hpp> 
 
using namespace sf; 
 
class Particle 
{ 
private: 
   Vector2f m_Position; 
   Vector2f m_Velocity; 
 
public: 
   Particle(Vector2f direction); 
 
   void update(float dt); 
 
   void setPosition(Vector2f position); 
 
   Vector2f getPosition(); 
}; 

In the preceding code, we have two Vector2f objects. One will represent the horizontal and vertical coordinates of the particle and the other will represent the horizontal and vertical speed.

Note

When you have a rate of change (speed) in more than one direction, the combined values also define a direction. This is called velocity; hence, the Vector2f is called m_Velocity.

We also have a number of public functions. First is the constructor. It takes a Vector2f, which will be used to let it know what direction/velocity this particle will have. This implies that the system, not the particle itself, will be choosing the velocity.

Next is the update function, which takes the time the previous frame has taken. We will use this to move the particle by precisely the correct amount.

The final two functions, setPosition and getPosition, are used to move the particle into position and find out its position, respectively.

All these functions will make complete sense when we code them.

Coding the Particle.cpp file

Right-click Source Files in the Solution Explorer and select Add | New Item.... In the Add New Item window, highlight (by left-clicking) C++ File (.cpp) and then in the Name field, type Particle.cpp. Finally, click the Add button. We are now ready to code the .cpp file for the Particle class.

Add the following code to Particle.cpp:

#include "stdafx.h" 
#include "Particle.h" 
 
Particle::Particle(Vector2f direction) 
{ 
 
   // Determine the direction 
   //m_Velocity = direction; 
   m_Velocity.x = direction.x; 
   m_Velocity.y = direction.y; 
} 
 
void Particle::update(float dtAsSeconds) 
{ 
   // Move the particle 
   m_Position += m_Velocity * dtAsSeconds; 
} 
 
void Particle::setPosition(Vector2f position) 
{ 
   m_Position = position; 
 
} 
 
Vector2f Particle::getPosition() 
{ 
   return m_Position; 
} 

All these functions use concepts we have seen before. The constructor sets up the m_Velocity.x and m_Velocity.y values using the passed-in Vector2f object.

The update function moves the horizontal and vertical positions of the particle by multiplying m_Velocity by the elapsed time (dtAsSeconds). Notice that to achieve this, we simply add the two Vector2f objects together. There is no need to perform calculations for both the x and y members separately.

The setPosition function, as previously explained, initializes the m_Position object with the passed-in values. The getPosition function returns m_Position to the calling code.

We now have a fully functioning Particle class. Next, we will code a ParticleSystem class to spawn and control the particles.

Coding the ParticleSystem class

The ParticleSystem class does most of the work for our particle effects. It is this class that we will create an instance of in the Engine class.

Coding ParticleSystem.h

Right-click Header Files in the Solution Explorer and select Add | New Item.... In the Add New Item window, highlight (by left-clicking) Header File (.h) and then in the Name field, type ParticleSystem.h. Finally, click the Add button. We are now ready to code the header file for the ParticleSystem class.

Add the code for the ParticleSystem class to ParticleSystem.h:

#pragma once 
#include <SFML/Graphics.hpp> 
#include "Particle.h" 
 
using namespace sf; 
using namespace std; 
 
class ParticleSystem : public Drawable 
{ 
private: 
 
   vector<Particle> m_Particles; 
   VertexArray m_Vertices; 
   float m_Duration; 
   bool m_IsRunning = false; 
 
 
public: 
 
   virtual void draw(RenderTarget& target, RenderStates states) const; 
       
   void init(int count); 
 
   void emitParticles(Vector2f position); 
 
   void update(float elapsed); 
 
   bool running(); 
 
}; 

Let's go through this a bit at a time. Firstly, notice that we are inheriting from Drawable. This is what will enable us to pass our ParticleSystem instance to m_Window.draw, because ParticleSystem is a Drawable.

There is a vector named m_Particles, of type Particle. This vector will hold each and every instance of Particle. Next we have a VertexArray called m_Vertices. This will be used to draw all the particles in the form of a whole bunch of Point primitives.

The m_Duration, float variable is how long each effect will last. We will initialize it in the constructor function.

The Boolean  m_IsRunning variable will be used to indicate whether the particle system is currently in use or not.

Next, in the public section, we have the pure virtual function, draw, that we will soon implement to handle what happens when we pass our instance of ParticleSystem to m_Window.draw.

The init function will prepare the VertexArray and the vector. It will also initialize all the Particle objects (held by the vector) with their velocities and initial positions.

The update function will loop through each and every Particle instance in the vector and call their individual update functions.

The running function provides access to the m_IsRunning variable so that the game engine can query whether or not the ParticleSystem is currently in use.

Let's code the function definitions to see what goes on inside ParticleSystem.

Coding the ParticleSystem.cpp file

Right-click Source Files in the Solution Explorer and select Add | New Item.... In the Add New Item window, highlight (by left-clicking) C++ File ( .cpp ) and then in the Name field, type ParticleSystem.cpp. Finally, click the Add button. We are now ready to code the .cpp file for the ParticleSystem class.

We will split this file into five sections to code and discuss it better. Add the first section of code as shown here:

#include "stdafx.h" 
#include <SFML/Graphics.hpp> 
#include "ParticleSystem.h" 
 
using namespace sf; 
using namespace std; 
 
void ParticleSystem::init(int numParticles) 
{ 
   m_Vertices.setPrimitiveType(Points); 
   m_Vertices.resize(numParticles); 
 
   // Create the particles 
 
   for (int i = 0; i < numParticles; i++) 
   { 
      srand(time(0) + i); 
      float angle = (rand() % 360) * 3.14f / 180.f; 
      float speed = (rand() % 600) + 600.f; 
 
      Vector2f direction; 
 
      direction = Vector2f(cos(angle) * speed, 
         sin(angle) * speed); 
 
      m_Particles.push_back(Particle(direction)); 
 
   } 
 
} 

After the necessary includes, we have the definition of the init function. We call setPrimitiveType with Points as the argument so that m_VertexArray knows what types of primitive it will be dealing with. We resize m_Vertices with numParticles, which was passed in to the init function when it was called.

The for loop creates random values for speed and angle. It then uses trigonometric functions to convert those values into a vector, which is stored in the Vector2f, direction.

Tip

If you want to know more about how the trigonometric functions (cossin, and tan) convert angles and speeds into a vector, you can take a look at this article series: http://gamecodeschool.com/essentials/calculating-heading-in-2d-games-using-trigonometric-functions-part-1/

The last thing that happens in the for loop (and the init function) is that the vector is passed in to the Particle constructor. The new Particle instance is stored in m_Particles using the push_back function. Therefore, a call to init with a value of 1000 would mean we have one thousand instances of Particle, with random velocity, stashed away in m_Particles just waiting to blow!

Next, add the update function to ParticleSysytem.cpp:

void ParticleSystem::update(float dt) 
{ 
   m_Duration -= dt; 
   vector<Particle>::iterator i; 
   int currentVertex = 0; 
 
   for (i = m_Particles.begin(); i != m_Particles.end(); i++) 
   { 
      // Move the particle 
      (*i).update(dt); 
 
      // Update the vertex array 
      m_Vertices[currentVertex].position = (*i).getPosition(); 
 
      // Move to the next vertex 
      currentVertex++; 
   } 
 
   if (m_Duration < 0) 
   { 
      m_IsRunning = false; 
   } 
 
} 

The update function is simpler than it looks at first glance. First of all, m_Duration is reduced by the passed-in time, dt. This is so we know when the two seconds have elapsed. A vector iterator, i, is declared for use with m_Particles.

The for loop goes through each of the Particle instances in m_Particles. For each and every one it calls its update function and passes in dt. Each particle will update its position. After the particle has updated itself, the appropriate vertex in m_Vertices is updated by using the particle's getPosition function. At the end of each pass through, the for loop currentVertex is incremented, ready for the next vertex.

After the for loop has completed, if(m_Duration < 0) checks whether it is time to switch off the effect. If two seconds have elapsed, m_IsRunning is set to false.

Next, add the emitParticles function:

void ParticleSystem::emitParticles(Vector2f startPosition) 
{ 
   m_IsRunning = true; 
   m_Duration = 2; 
 
   vector<Particle>::iterator i; 
   int currentVertex = 0; 
 
   for (i = m_Particles.begin(); i != m_Particles.end(); i++) 
   { 
      m_Vertices[currentVertex].color = Color::Yellow; 
      (*i).setPosition(startPosition); 
 
      currentVertex++; 
   } 
 
} 

This is the function we will call to start the particle system running. So, predictably, we set m_IsRunning to true and m_Duration to 2. We declare an iterator, i, to iterate through all the Particle objects in m_Particles, and then we do so in a for loop.

Inside the for loop, we set each particle in the vertex array to yellow and set each position to startPosition, which was passed in as a parameter. Remember that each particle starts life in exactly the same position, but they are each assigned a different velocity.

Next, add the pure virtual draw function definition:

void ParticleSystem::draw(RenderTarget& target, RenderStates states) const 
{ 
   target.draw(m_Vertices, states); 
} 

In the previous code, we simply use target to call draw, passing in m_Vertices and states. This is exactly as we discussed when talking about Drawable earlier in the chapter, except we pass in our VertexArray, which holds 1,000 point primitives instead of the hypothetical spaceship Sprite.

Finally, add the running function:

bool ParticleSystem::running() 
{ 
   return m_IsRunning; 
} 

The running function is a simple getter function that returns the value of m_IsRunning. We will see where this is useful to determine the current state of the particle system.

Using ParticleSystem

To put our particle system to work is very straightforward, especially because we inherited from Drawable.

Adding a ParticleSystem object to the Engine class

Open Engine.h and add a ParticleSystem object, as shown in the following highlighted code:

#pragma once 
#include <SFML/Graphics.hpp> 
#include "TextureHolder.h" 
#include "Thomas.h" 
#include "Bob.h" 
#include "LevelManager.h" 
#include "SoundManager.h" 
#include "HUD.h" 
#include "ParticleSystem.h" 
 
using namespace sf; 
 
class Engine 
{ 
private: 
   // The texture holder 
   TextureHolder th; 
 
   // create a particle system
   ParticleSystem m_PS; 
 
   // Thomas and his friend, Bob 
   Thomas m_Thomas; 
   Bob m_Bob; 

Next, initialize the system.

Initializing ParticleSystem

Open the Engine.cpp file and add the short highlighted code right at the end of the Engine constructor:

Engine::Engine() 
{ 
   // Get the screen resolution and create an SFML window and View 
   Vector2f resolution; 
   resolution.x = VideoMode::getDesktopMode().width; 
   resolution.y = VideoMode::getDesktopMode().height; 
 
   m_Window.create(VideoMode(resolution.x, resolution.y), 
      "Thomas was late", 
      Style::Fullscreen); 
 
   // Initialize the full screen view 
   m_MainView.setSize(resolution); 
   m_HudView.reset( 
      FloatRect(0, 0, resolution.x, resolution.y)); 
 
   // Inititialize the split-screen Views 
   m_LeftView.setViewport( 
      FloatRect(0.001f, 0.001f, 0.498f, 0.998f)); 
 
   m_RightView.setViewport( 
      FloatRect(0.5f, 0.001f, 0.499f, 0.998f)); 
 
   m_BGLeftView.setViewport( 
      FloatRect(0.001f, 0.001f, 0.498f, 0.998f)); 
 
   m_BGRightView.setViewport( 
      FloatRect(0.5f, 0.001f, 0.499f, 0.998f)); 
 
   // Can this graphics card use shaders? 
   if (!sf::Shader::isAvailable()) 
   { 
      // Time to get a new PC 
      m_Window.close(); 
   } 
 
   m_BackgroundTexture = TextureHolder::GetTexture( 
      "graphics/background.png"); 
 
   // Associate the sprite with the texture 
   m_BackgroundSprite.setTexture(m_BackgroundTexture); 
 
   // Load the texture for the background vertex array 
   m_TextureTiles = TextureHolder::GetTexture( 
      "graphics/tiles_sheet.png"); 
 
   // Initialize the particle system
   m_PS.init(1000); 
 
}// End Engine constructor 

The VertexArray and the vector of Particle instances are ready for action.

Updating the particle system in each frame

Open the Update.cpp file and add the following highlighted code. It can go right at the end of the update function:

   // Update the HUD every m_TargetFramesPerHUDUpdate frames 
   if (m_FramesSinceLastHUDUpdate > m_TargetFramesPerHUDUpdate) 
   { 
      // Update game HUD text 
      stringstream ssTime; 
      stringstream ssLevel; 
 
      // Update the time text 
      ssTime << (int)m_TimeRemaining; 
      m_Hud.setTime(ssTime.str()); 
 
      // Update the level text 
      ssLevel << "Level:" << m_LM.getCurrentLevel(); 
      m_Hud.setLevel(ssLevel.str()); 
 
      m_FramesSinceLastHUDUpdate = 0; 
   } 
 
   // Update the particles
   if (m_PS.running())
   {
     m_PS.update(dtAsSeconds);
   } 
 
}// End of update function 

All that is needed in the previous code is the call to update. Notice that it is wrapped in a check to make sure the system is currently running. If it isn't running, there is no point updating it.

Starting the particle system

Open the DetectCollisions.cpp file, which has the detectCollisions function in it. We left a comment in it when we originally coded it back in Chapter 15Building Playable Levels and Collision Detection.

Identify the correct place from the context and add the highlighted code, as shown:

// Is character colliding with a regular block 
if (m_ArrayLevel[y][x] == 1) 
{ 
 
   if (character.getRight().intersects(block)) 
   { 
      character.stopRight(block.left); 
   } 
   else if (character.getLeft().intersects(block)) 
   { 
      character.stopLeft(block.left); 
   } 
 
 
   if (character.getFeet().intersects(block)) 
   { 
      character.stopFalling(block.top); 
   } 
   else if (character.getHead().intersects(block)) 
   { 
      character.stopJump(); 
   } 
} 
 
// More collision detection here once  
// we have learned about particle effects 
 
// Has the character's feet touched fire or water?
// If so, start a particle effect
// Make sure this is the first time we have detected this
// by seeing if an effect is already running
if (!m_PS.running())
{
   if (m_ArrayLevel[y][x] == 2 || m_ArrayLevel[y][x] == 3)
   {
     if (character.getFeet().intersects(block))
     {
        // position and start the particle system
        m_PS.emitParticles(character.getCenter());
     }
   }
} 
 
// Has the character reached the goal? 
if (m_ArrayLevel[y][x] == 4) 
{ 
   // Character has reached the goal 
   reachedGoal = true; 
}  

First the code checks if the particle system is already running. If it isn't, it checks if the current tile being checked is either a water or a fire tile. If either is the case, it checks whether the character's feet are in contact. When each of these if statements is true, the particle system is started by calling the emitParticles function and passing in the location of the center of the character as the coordinates to start the effect.

Drawing the particle system

This is the best bit. See how easy it is to draw the ParticleSystem. We pass our instance directly to the m_Window.draw function after checking that the particle system is actually running.

Open the Draw.cpp file and add the highlighted code in all the places shown in the following code:

void Engine::draw() 
{ 
   // Rub out the last frame 
   m_Window.clear(Color::White); 
 
   if (!m_SplitScreen) 
   { 
      // Switch to background view 
      m_Window.setView(m_BGMainView); 
      // Draw the background 
      m_Window.draw(m_BackgroundSprite); 
      // Switch to m_MainView 
      m_Window.setView(m_MainView);     
 
      // Draw the Level 
      m_Window.draw(m_VALevel, &m_TextureTiles); 
 
      // Draw thomas 
      m_Window.draw(m_Thomas.getSprite()); 
 
      // Draw thomas 
      m_Window.draw(m_Bob.getSprite()); 
 
      // Draw the particle system
      if (m_PS.running())
      {
         m_Window.draw(m_PS);
      } 
   } 
   else 
   { 
      // Split-screen view is active 
 
      // First draw Thomas' side of the screen 
 
      // Switch to background view 
      m_Window.setView(m_BGLeftView); 
      // Draw the background 
      m_Window.draw(m_BackgroundSprite); 
      // Switch to m_LeftView 
      m_Window.setView(m_LeftView); 
 
      // Draw the Level 
      m_Window.draw(m_VALevel, &m_TextureTiles); 
          
      // Draw thomas 
      m_Window.draw(m_Bob.getSprite()); 
 
      // Draw thomas 
      m_Window.draw(m_Thomas.getSprite()); 
 
      // Draw the particle system
      if (m_PS.running())
      {
         m_Window.draw(m_PS);
      } 
       
      // Now draw Bob's side of the screen 
 
      // Switch to background view 
      m_Window.setView(m_BGRightView); 
      // Draw the background 
      m_Window.draw(m_BackgroundSprite); 
      // Switch to m_RightView 
      m_Window.setView(m_RightView); 
 
      // Draw the Level 
      m_Window.draw(m_VALevel, &m_TextureTiles); 
 
      // Draw thomas 
      m_Window.draw(m_Thomas.getSprite()); 
 
      // Draw bob 
      m_Window.draw(m_Bob.getSprite()); 
 
      // Draw the particle system
      if (m_PS.running())
      {
         m_Window.draw(m_PS);
      }           
   } 
    
   // Draw the HUD 
   // Switch to m_HudView 
   m_Window.setView(m_HudView); 
   m_Window.draw(m_Hud.getLevel()); 
   m_Window.draw(m_Hud.getTime()); 
   if (!m_Playing) 
   { 
      m_Window.draw(m_Hud.getMessage()); 
   } 
    
    
   // Show everything we have just drawn 
   m_Window.display(); 
} 

Notice in the previous code that we have to draw the particle system in all of the left, right, and full-screen code blocks.

Run the game and move one of the character's feet over the edge of a fire tile. Notice the particle system burst into life:

Drawing the particle system

Now for something else that is new.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.70.244