-
Notifications
You must be signed in to change notification settings - Fork 3
Particle Pursuit
The explosion of particles that seek a point is probably the coolest effect in the game and was also one of the trickiest to figure out.
RELEVANT CODE AT BOTTOM
Regular Emission and Explosion Emission are different
ParticleSystems
to allow manual control of just Explosion
Particles. There is a SendParticlesTo
method that
Emits the particles and starts the controlling process in Update
. The controlling process uses
GetParticles to gain acess to individual particles , applies Reynolds's Pursuit Algorithm to each of them, and then uses
SetParticles to apply the changes. When particles get to close to the target, they're set to stop moving and disappear.
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.
See: Unity ParticleSystem
The regular emission is as simple as calling Play and Stop on the ParticleSystemwhen 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
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.
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().
See: Unity ParticleSystem.Particle, Unity ParticleSystem.main
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
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
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
and the stillTarget while the second MovingObject version sets moving = false
and the movingTarget.
They both set sending = true
and emit particles part.Emit(minNum * particleMultiplier);
sentParticles += minNum * particleMultiplier;
is also increased in order to account for a second explosion occurring while existing particles are still seeking
See: Unity ParticleSystem.Emit()
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 allows the particles to pursue the future position of a moving target and seek the current position of a still target
part.GetParticles(particles)
is then used to get the particle data and return the number of active particles which are then looped over. part.particleCount
would have also worked for getting the number of particles but doesn't give access to the individual particles.
See: Unity ParticleSystem.GetParticles()
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;
See: Reynolds Algorithms
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. This effectivly removes the particle from the system.
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
See: Unity ParticleSystem.SetParticles()
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
Check out a Free Demo of the game on my personal website. (Updated Frequently)