Skip to content

Particle Pursuit

Dane Sherman edited this page Dec 11, 2018 · 10 revisions

The explosion of particles that seek a point is probably the coolest effect in the game and was also one of the trickiest to implement.

RELEVANT CODE AT BOTTOM

Two different particle systems

The first thing to note is that the player actually has two different particle effects. One for regular emission and another for the explosions. Splitting up the two particle behaviors became necessary when I needed to control the explosion without affecting the regular emission as well as give the explosion particles a greater speed and altered noise function. Both particle systems are simulated in World Space.

Regular

The regular emission is as simple as calling Play and Stop on the ParticleSystem when the mouse is being clicked. These particles are emitted from a circle at a random angle and collide with the player; this causes the subtle effect of the player pushing particles out of the way. The particles also fade in and out slightly and have a small noise function. All this is controlled in the inspector through the ParticleSystem Component

Explosion

Identical to the Regular Emission but with a greater initial speed and a much stronger noise function. Emission and movement are controlled almost entirely through a script.

How to Explode

Fields related to the particle system

ParticleSystem part;Reference to the ParticleSystem Component

ParticleSystem.Particle[] particles; Array of all the particles being controlled. Assigned to an empty array with length part.main.maxParticles in Awake().

int sentParticles Keeps track of how many particles still need to reach the target

bool sending; True when the particles are being sent to the target

Fields related to the target

Vector2 target current world space position of the target

bool moving True when the target is a moving target

Transform stillTarget Target Transform when moving == false

MovingObject movingTarget (Contains move velocity information) Target MovingObject when moving == true

Send Particles To

There are two versions of the SendParticlesTo method:

void SendParticlesTo(Transform target, int minNum) and void SendParticlesTo(MovingObject target, int minNum)

The first Transform version sets moving = false the stillTarget while the second MovingObject version sets moving = false and the movingTarget.

They both set sending = true, increase sentParticles += minNum * particleMultiplier;, and emit particles part.Emit(minNum * particleMultiplier);

Update

Setup

After checking if sending == true it makes sure the particle system is playing if (part.isPaused) { part.Play(); }

the Vector2 target is set by either the stillTarget.transform.position or by `movingTarget.transform.position + (Vector3)movingTarget.MoveVelocity' This allow the particles to pursue the future position of a moving target

part.GetParticles(particles) is then used to get the particle data and return the number of active particles which are then looped over

Reynold's Seek and Pursuit

for each individual ParticleSystem.Particle particle = particles[i]; a move vector from it's current particle.position to the target is calculated.

That move vector is then used to calculate a new particle.velocity += ((moveVector.normalized * particleSpeed) - particle.velocity) * Time.deltaTime;

when the particle is close enough to the target it's particle.remainingLifetime = 0; and particle.velocity = Vector3.zero;. sent particles is also decremented to keep track of how many particles have arrived

Final steps

In order to apply the changes to the individual particle it must be assigned back into particles[i] = particle;

One final check is made it see if sentParticles <= 0, in which case sending = false; and part.Stop(); just in case

To apply changes to the actual particle system part.SetParticles(particles, numParticles); and the new particle velocities are set

Relevant Code

Note: Use of access modifiers was due to external and inheriting classes. Some irrelevant code was also left out.

[SerializeField] private float particleSpeed;
private const int particleMultiplier = 3;
private const float overshoot = 0.75f;
private const float slowRadius = 6;

protected ParticleSystem part; //ref to this objects particle system
protected ParticleSystemRenderer partRend;
protected ParticleSystem.Particle[] particles; //array of particles being controlled 

protected int sentParticles;
protected bool sending; //true when particles arde being sent to a location

protected Vector2 target;
private bool moving = false;
protected Transform stillTarget;
private MovingObject movingTarget;

protected virtual void Awake() 
{
     part = GetComponent<ParticleSystem>();
     particles = new ParticleSystem.Particle[part.main.maxParticles];
     part.Stop();
}

/// <summary>
/// Send Particles to a specified world positions
/// </summary>
/// <param name="target">target transform</param>
/// <param name="minNum">minimum number of particles</param>
public void SendParticlesTo(Transform target, int minNum)
{
     stillTarget = target;
     moving = false;

     //emit additional particles 
     part.Emit(minNum * particleMultiplier);
     sentParticles += minNum * particleMultiplier;

     //start sending particles to point
     sending = true;
}

/// <summary>
/// Send Particles to a specified world positions
/// </summary>
/// <param name="targets">target moving object</param>
/// <param name="minNum">minimum number of particles</param>
public void SendParticlesTo(MovingObject target, int minNum)
{
     movingTarget = target;
     moving = true;

     //emit additional particles 
     part.Emit(minNum * particleMultiplier);
     sentParticles += minNum * particleMultiplier;

     //start sending particles to point
     sending = true;
}

protected void MoveParticles()
{
     if (sending)
     {
          if (part.isPaused) { part.Play(); }

          //loop through all particles
          int numParticles = part.GetParticles(particles); 
          target = moving ? (movingTarget.transform.position + (Vector3)movingTarget.MoveVelocity) : stillTarget.position;
          for (int i = 0; i < numParticles; i++)
          {
               ParticleSystem.Particle particle = particles[i];
               //particle.remainingLifetime += Time.deltaTime; //keep particle alive
               Vector3 moveVector = ((Vector3)target - particle.position);
               moveVector += moveVector.normalized * overshoot;

               particle.velocity += ((moveVector.normalized * particleSpeed) - particle.velocity) * Time.deltaTime;
               particle.velocity *= Mathf.Clamp((moveVector.magnitude + slowRadius) / 10 , slowRadius * 0.1f , 1);

               if (moveVector.magnitude - overshoot < 1f)
               {
                    particle.remainingLifetime = 0;
                    particle.velocity = Vector3.zero;
                    sentParticles -= 1;
               }
               particles[i] = particle; //set the particle's data back into particles array
                               
          }

          if (sentParticles <= 0 || numParticles <= 0)
          {
               sentParticles = 0;
               sending = false;
               part.Stop();
          }
          part.SetParticles(particles, numParticles); //apply changes to particle system
     }
}

The final verison of this script can be found Here

Flower Check out a Free Demo of the game on my personal website. (Updated Frequently)

Clone this wiki locally