So I went the way to create my own Sprite Script Emitter.
As I am still newbie in Unity and specially in Physics I’m placing the script here for review and for any improvements from other users.
How to use the script:
- Create an empty GameObject in the scene.
- Drag the script into the new GameObject.
- Add the sprite(s) that you wish to be used as particles in the “sprites” parameter of the script.
And that is all.

To test it via the inspector at runtime press the “emit” check-box, or by code call emitParticles()
Play with the different settings such as Force and Sparse-Direction to alter the collapse/explosion effect.
The particles scale is adjusted via the Transform Scale of the GameObject
Facts about the script:
-This script is not a replacement for the ParticlesSystem, neither tries to emulate the same functionality.
-Particles are emitted all at the same time as this is what I needed for my scenario.
-Collision is handled at layer level.
-As I said I’m still a newbie in Unity, so probably there are better ways to do it and with better performance. The script works fine for what I need it for, but if anyone can see a way to improve it or add functionality then please submit your changes.
The script:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class SpriteParticlesEmitter : MonoBehaviour
{
private class Particle
{
private GameObject _gameObject;
private float _lifespan;
private float _intialLifespan;
private Vector3 _position = new Vector3();
private Vector3 _scale = new Vector3();
private Vector2 _force = new Vector2();
public Particle(GameObject value)
{
this._gameObject = value;
this._gameObject.SetActive(false);
}
public bool isActive()
{
return this._gameObject.activeSelf;
}
public void setActive(bool value)
{
this._gameObject.SetActive(value);
this.setScale(this._scale);
}
public void setPosition(Vector3 value)
{
this._position.Set(value.x, value.y, value.z);
this._gameObject.transform.position = this._position;
}
public float lifespan
{
get { return this._lifespan; }
set
{
this._lifespan = value;
this._intialLifespan = value;
}
}
public void setScale(Vector3 value)
{
this._scale.Set(value.x, value.y, value.z);
this._gameObject.transform.localScale = this._scale;
}
public void dissipate()
{
this._lifespan -= Time.deltaTime;
if (this._lifespan < this._intialLifespan / 3.0f)
{
this._gameObject.transform.localScale = Vector3.Lerp(Vector3.zero,
this._scale,
this._lifespan / this._intialLifespan);
}
}
public void setForce(float forceX, float forceY)
{
this._force.Set(forceX, forceY);
this._gameObject.rigidbody2D.AddForce(this._force, ForceMode2D.Impulse);
}
public void setVelocity(Vector2 value)
{
this._gameObject.rigidbody2D.velocity = value;
}
public void setGravityScale(int value)
{
this._gameObject.rigidbody2D.gravityScale = value;
this._gameObject.rigidbody2D.drag = Random.Range(0.0f, 3.0f);
}
public void canCollide(bool value)
{
this._gameObject.collider2D.enabled = value;
}
public void destroy()
{
Destroy(this._gameObject);
}
}
public bool emit = false;
public bool loop = false;
public bool dissipate = false;
[Range(0, 50)]
public int particleAmount = 10;
[Range(0, 10)]
public float particleLifespan = 3f;
[Range(0, 10)]
public int force = 5;
public Vector2 sparseDirection;
[Range(1, 4)]
public int gravityScale = 1;
[Range(0, 1)]
public float bounciness = 0;
[Range(0, 1)]
public float friction = 1.0f;
public LayerMask collideWith = Physics2D.AllLayers;
public Sprite[] sprites;
private List<Particle> _particlesPool;
private int _enabledPooledParticles;
private int _lifeParticles = 0;
private LayerMask _collidesWith;
private Vector3 _spriteScale;
private PhysicsMaterial2D _particlesMaterial;
/// <summary>
/// Awake this instance
/// </summary>
public void Awake()
{
if (this.sprites == null || this.sprites.Length == 0)
{
Debug.LogError("Failed. Script has no sprites.", this.gameObject);
this.enabled = false;
return;
}
this._spriteScale = new Vector3(-1.0f, -1.0f, -1.0f);
this._particlesMaterial = new PhysicsMaterial2D(this.name + "_material");
this._particlesMaterial.bounciness = this.bounciness;
this._particlesMaterial.friction = this.friction;
this.setupParticlesPool();
this.adjustCollisionLayers();
}
/// <summary>
/// Update this instance
/// </summary>
void Update()
{
if (this.particleAmount != this._enabledPooledParticles)
{
this.setupParticlesPool();
}
if (this._collidesWith != this.collideWith)
{
this.adjustCollisionLayers();
}
if ((this.emit == true) || (this.loop == true && this._lifeParticles <= 0))
{
this.emit = false;
StopCoroutine("updateParticles");
this._lifeParticles = this.particleAmount;
StartCoroutine("updateParticles", true);
}
if (this._spriteScale != this.transform.localScale ||
this._particlesMaterial.bounciness != this.bounciness ||
this._particlesMaterial.friction != this.friction)
{
for (int index = 0; index < this.particleAmount; index++)
{
this._particlesMaterial.bounciness = this.bounciness;
this._particlesMaterial.friction = this.friction;
this._spriteScale = this.transform.localScale;
this._particlesPool[index].setScale(this._spriteScale);
}
}
}
/// <summary>
/// Executes a particles emission
/// </summary>
public void emitParticles()
{
this.emit = false;
StopCoroutine("updateParticles");
this._lifeParticles = this.particleAmount;
StartCoroutine("updateParticles", true);
}
/// <summary>
/// Update all the active pooled particles
/// </summary>
/// <param name="emitParticles">True to emit the particles</param>
private void updateParticles(bool emitParticles)
{
StopCoroutine("watchParticlesLifespan");
for (int index = 0; index < this.particleAmount; index++)
{
Particle particle = this._particlesPool[index];
particle.setActive(true);
particle.setScale(this._spriteScale);
particle.lifespan = Random.Range(this.particleLifespan, this.particleLifespan + 2);
particle.setPosition(this.transform.position);
particle.setGravityScale(Random.Range(this.gravityScale, this.gravityScale + 1));
particle.setVelocity(Vector2.zero);
float forceToApply = Random.Range(this.force, this.force + 1.0f);
float forceX;
float forceY;
if (this.sparseDirection.x == 0)
{
forceX = Random.Range(-forceToApply, forceToApply);
}
else
{
forceX = Random.Range(this.sparseDirection.x, this.sparseDirection.x * forceToApply);
}
if (this.sparseDirection.y == 0)
{
forceY = Random.Range(-forceToApply, forceToApply);
}
else
{
forceY = Random.Range(this.sparseDirection.y, this.sparseDirection.y * forceToApply);
}
particle.setForce(forceX, forceY);
particle.canCollide(this.collideWith.value != 0);
particle.lifespan = Random.Range(this.particleLifespan, this.particleLifespan + 1.0f);
}
if (emitParticles == true && this._lifeParticles > 0)
{
StartCoroutine("watchParticlesLifespan");
}
}
/// <summary>
/// Create a new single Particle
/// </summary>
/// <param name="name">Particle name</param>
/// <returns>New created particle</returns>
private Particle createNewParticle(string name)
{
GameObject item = new GameObject(name);
item.transform.parent = this.transform;
item.layer = this.gameObject.layer;
item.AddComponent<SpriteRenderer>();
SpriteRenderer spriteRenderer = (SpriteRenderer)item.renderer;
int randomSpriteIndex = Random.Range(0, this.sprites.Length);
spriteRenderer.sprite = this.sprites[randomSpriteIndex];
item.AddComponent<Rigidbody2D>();
// Using EdgeCollider2D, because it doesn't
// collide with others of the same type
item.AddComponent<EdgeCollider2D>();
((EdgeCollider2D)item.collider2D).sharedMaterial = this._particlesMaterial;
// Give some shape/volume to the edge collider
float sizeFactorX = spriteRenderer.sprite.bounds.extents.x;
float sizeFactorY = spriteRenderer.sprite.bounds.extents.y;
((EdgeCollider2D)item.collider2D).points = new Vector2[]
{
new Vector2(0.0f, -sizeFactorY),
new Vector2(0.0f, sizeFactorY),
new Vector2(sizeFactorX, 0.0f),
new Vector2(-sizeFactorX, 0.0f)
};
Particle particle = new Particle(item);
return particle;
}
/// <summary>
/// Setups the particles pool. Note that the pool does not shrink
/// </summary>
private void setupParticlesPool()
{
if (this._particlesPool == null)
{
this._particlesPool = new List<Particle>();
}
StopCoroutine("updateParticles");
StopCoroutine("watchParticlesLifespan");
int totalParticles = Mathf.Max(this.particleAmount, this._particlesPool.Count);
for (int index = 0; index < totalParticles; index++)
{
if (index == this._particlesPool.Count)
{
Particle particle = this.createNewParticle(this.name + "_particle_" + index);
this._particlesPool.Add(particle);
}
this._particlesPool[index].setActive(false);
}
this._enabledPooledParticles = this.particleAmount;
this._lifeParticles = 0;
}
/// <summary>
/// Watch each particle lifespan
/// </summary>
/// <returns>IEnumerator as this method is to be called as a coroutine</returns>
IEnumerator watchParticlesLifespan()
{
while (this._lifeParticles > 0)
{
for (int index = 0; index < this._enabledPooledParticles; index++)
{
Particle particle = this._particlesPool[index];
if (particle.isActive() == true)
{
if (dissipate == true)
{
particle.dissipate();
}
else
{
particle.lifespan -= Time.deltaTime;
}
if (particle.lifespan <= 0)
{
particle.lifespan = 0;
particle.setActive(false);
this._lifeParticles--;
}
}
}
yield return 0; // Wait one frame
}
}
/// <summary>
/// Adjust the collision layers against which the particles can collide
/// </summary>
private void adjustCollisionLayers()
{
this._collidesWith = this.collideWith;
int objectLayerId = this.gameObject.layer;
for (int layerId = 0; layerId < 32; layerId++)
{
bool ignoreLayer = !(this.collideWith == (this.collideWith | (1 << layerId)));
Physics2D.IgnoreLayerCollision(objectLayerId, layerId, ignoreLayer);
}
}
/// <summary>
/// Destroy the emitter
/// </summary>
public void destroy()
{
this.loop = false;
this.emit = false;
StopCoroutine("updateParticles");
StopCoroutine("watchParticlesLifespan");
if (this._particlesPool != null && this._particlesPool.Count > 0)
{
foreach (Particle particle in this._particlesPool)
{
particle.destroy();
}
}
Destroy(this.gameObject);
}
}