How To: Create a Particle Emitter |
This section explains how to create a particle emitter (an effector that creates particles).
Objects that create new particles are usually called particle emitters. In DigitalRune Particles, emitters can be implemented as ParticleEffectors that call the method ParticleSystemAddParticles(Int32) to create particles.
This article shows how to implement a stream emitter that creates particles at a configurable rate. (Please note: DigitalRune Particles contains a StreamEmitter that provides the same functionality.)
First, we create a new class that inherits from ParticleEffector. Particle effectors can be added to a particle system, and they are called whenever the particle system is initialized or updated. Here is the empty class and the namespaces that we will need:
using System; using System.Collections.Generic; using DigitalRune.Particles; namespace MyNamespace { public class MyStreamEmitter : ParticleEffector { // TODO: Add code. } }
To allow users to configure the emission rate, we have two options:
We can offer both options by adding these properties to the stream emitter:
[ParticleParameter(ParticleParameterUsage.In, Optional = true)] public string EmissionRateParameter { get; set; } public float DefaultEmissionRate { get; set; } public MyStreamEmitter() { DefaultEmissionRate = 10; }
If EmissionRateParameter is set, the stream emitter should search for a uniform particle parameter with this name and use the value of this parameter as the emission rate. Using particle parameters is very flexible because particle parameters can be animated using other particle effectors or the animation system in DigitalRune Animation.
If the EmissionRateParameter is not set, the value of the property DefaultEmissionRate (with a default value of 10) is used as the emission rate. Regular properties are easier to use than particle parameters - but offer less flexibility.
The ParticleParameterAttribute provides meta-information for particle editors and validation. In this example, the EmissionRateParameter is an optional input parameter.
Particle effectors should be cloneable. The following two methods must be overridden to support cloning: CreateInstanceCore must return a new instance of the particle emitter and CloneCore must copy all important properties:
protected override ParticleEffector CreateInstanceCore() { return new MyStreamEmitter(); } protected override void CloneCore(ParticleEffector source) { base.CloneCore(source); var sourceTyped = (MyStreamEmitter)source; EmissionRateParameter = sourceTyped.EmissionRateParameter; DefaultEmissionRate = sourceTyped.DefaultEmissionRate; }
The base class ParticleEffector provides several virtual methods, which are automatically called by the particle system. Derived classes can override these methods to change particle parameters, manipulate the particle system, or emit new particles.
The particle effector can query the required parameters and cache the references in OnRequeryParameters. This method is called when the particle system is updated for the first time, and then every time the particle parameters change and need to be requeried.
OnInitialize is called after OnRequeryParameters when the particle system is updated for the first time and whenever the particle system is reset. In this method the internal state of the particle effector should be reset.
OnUninitialize is called when the particle effector is removed from a particle system. In this method all resources and references to external objects should be released.
The stream emitter in our example will use two private fields that require initialization and clean-up: The first field stores a reference to the emission rate parameter, and the second field remembers how many particles need to be emitted in the next time step:
private IParticleParameter<float> _emissionRateParameter; private float _leftoverParticles; protected override void OnRequeryParameters() { _emissionRateParameter = ParticleSystem.Parameters.Get<float>(EmissionRateParameter); } protected override void OnInitialize() { _leftoverParticles = 0; } protected override void OnUninitialize() { _emissionRateParameter = null; }
The base class ParticleEffector has several virtual methods that are called when the particle system is updated. One of these method is OnBeginUpdate. This method is called when the particle system begins with the update. The stream emitter can override the method and call ParticleSystemAddParticles(Int32) to create ("emit") new particles in each frame.
protected override void OnBeginUpdate(TimeSpan deltaTime) { float dt = (float)deltaTime.TotalSeconds; float emissionRate = (_emissionRateParameter != null) ? _emissionRateParameter.DefaultValue : DefaultEmissionRate; float numberOfParticles = emissionRate * dt + _leftoverParticles; ParticleSystem.AddParticles((int)numberOfParticles, this); // The decimal fraction of numberOfParticles is truncated, so not the whole // deltaTime is really used. We store the unused fraction for the next frame. _leftoverParticles = numberOfParticles - (float)Math.Floor(numberOfParticles); }
Here is the full source code of the stream emitter.
using System; using System.Collections.Generic; using DigitalRune.Particles; namespace MyNamespace { public class MyStreamEmitter : ParticleEffector { private IParticleParameter<float> _emissionRateParameter; private float _leftoverParticles; [ParticleParameter(ParticleParameterUsage.In, Optional = true)] public string EmissionRateParameter { get; set; } public float DefaultEmissionRate { get; set; } public MyStreamEmitter() { DefaultEmissionRate = 10; } protected override ParticleEffector CreateInstanceCore() { return new MyStreamEmitter(); } protected override void CloneCore(ParticleEffector source) { base.CloneCore(source); var sourceTyped = (MyStreamEmitter)source; EmissionRateParameter = sourceTyped.EmissionRateParameter; DefaultEmissionRate = sourceTyped.DefaultEmissionRate; } protected override void OnRequeryParameters() { _emissionRateParameter = ParticleSystem.Parameters.Get<float>(EmissionRateParameter); } protected override void OnInitialize() { _leftoverParticles = 0; } protected override void OnUninitialize() { _emissionRateParameter = null; } protected override void OnBeginUpdate(TimeSpan deltaTime) { float dt = (float)deltaTime.TotalSeconds; float emissionRate = (_emissionRateParameter != null) ? _emissionRateParameter.DefaultValue : DefaultEmissionRate; float numberOfParticles = emissionRate * dt + _leftoverParticles; ParticleSystem.AddParticles((int)numberOfParticles, this); // The decimal fraction of numberOfParticles is truncated, so not the whole // deltaTime is really used. We store the unused fraction for the next frame. _leftoverParticles = numberOfParticles - (float)Math.Floor(numberOfParticles); } } }