
Starting with Unity 5.3, you have full scripting access to the particle system’s modules. We noticed these new scripting features can be a bit confusing. Why do we use structs in an unconventional manner?
In this blog post, we will answer some of your questions, take a little peek behind the scenes and explain some of our plans to make the process more intuitive in the future.
Accessing Modules
An example
Here is a typical example in which we modify the rate property from the Emission module.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
using UnityEngine; public class AccessingAParticleSystemModule : MonoBehaviour { // Use this for initialization void Start () { // Get the emission module. var forceModule = GetComponent<ParticleSystem>().forceOverLifetime; // Enable it and set a value forceModule.enabled = true; forceModule.x = new ParticleSystem.MinMaxCurve(15.0f); } } |
To anyone familiar with .NET, you may notice that we grab the struct and set its value, but never assign it back to the particle system. How can the particle system ever know about this change, what kind of magic is this!?
It’s just an interface
Particle System modules in Unity are entirely contained within the C++ side of the engine. When a call is made to a particle system or one of its modules, it simply calls down to the C++ side.
Internally, particle system modules are properties of a particle system. They are not independent and we never swap the owners or share them between systems. So whilst it’s possible in script to grab a module and pass it around in script, that module will always belong to the same system.
To help clarify this, let’s take a look at the previous example in detail.
When the system receives a request for the emission module, the engine creates a new EmissionModule struct and passes the owning particle system as its one and only parameter. We do this because the particle system is required in order to access the modules properties.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public sealed class ParticleSystem : Component { ..... public EmissionModule emission { get { return new EmissionModule(this); } } .... } public partial struct EmissionModule { private ParticleSystem m_ParticleSystem; // Direct access to the particle system that owns this module. EmissionModule(ParticleSystem particleSystem) { m_ParticleSystem = particleSystem; } public MinMaxCurve rate { set { // In here we call down to the c++ side and perform what amounts to this: m_ParticleSystem->GetEmissionModule()->SetRate(value); } } ...... } |
When we set the rate, the variable m_ParticleSystem is used to access the module and set it directly. Therefore we never need to reassign the module to the particle system, because it is always part of it and any changes are applied immediately. So all a module does is call into the system that owns it, the module struct itself is just an interface into the particle systems internals.
Internally, the modules store their respective properties and they also hold state based information, which is why it is not possible for modules to be shared or assigned to different particle systems.
If you do want to copy properties from one system to another, it is advised that you only copy the relevant values and not the entire class, as this results in less data being passed between the C++ side and the C# side.
The MinMaxCurve
A significant number of module properties are driven by our MinMaxCurve class. It is used to describe a change of a value with time. There are 4 possible modes supported; here is a brief guide on how to use each when scripting.
Constant
By far the simplest mode, constant just uses a single constant value. This value will not change over time. If you wanted to drive a property via script, then setting the scalar would be one way to do this.
In script, we would access the constant like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
using UnityEngine; public class MinMaxCurveConstantMode : MonoBehaviour { ParticleSystem myParticleSystem; ParticleSystem.EmissionModule emissionModule; void Start() { // Get the system and the emission module. myParticleSystem = GetComponent<ParticleSystem>(); emissionModule = myParticleSystem.emission; GetValue(); SetValue(); } void GetValue() { print("The constant value is " + emissionModule.rate.constantMax); } void SetValue() { emissionModule.rate = new ParticleSystem.MinMaxCurve(10.0f); } } |
Random between two constants
This mode generates a random value between two constants. Internally, we store the two constants as a key in the min and max curves respectively. We get our value by performing a lerp between the 2 values using a normalised random parameter as our lerp amount.This means that we are almost doing the same amount of work as the random between two curves mode.
This is how we would access the two constants of a module:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
using UnityEngine; public class ParticleModuleExamples : MonoBehaviour { ParticleSystem myParticleSystem; ParticleSystem.EmissionModule emissionModule; void Start() { // Get the system and the emission module. myParticleSystem = GetComponent<ParticleSystem>(); emissionModule = myParticleSystem.emission; GetRandomConstantValues(); SetRandomConstantValues(); } void GetRandomConstantValues() { print(string.Format("The constant values are: min {0} max {1}.", emissionModule.rate.constantMin, emissionModule.rate.constantMax)); } void SetRandomConstantValues() { // Assign the curve to the emission module emissionModule.rate =new ParticleSystem.MinMaxCurve(0.0f, 1.0f); } } |
Curve
In this mode, the value of the property will change based on querying a curve. When using curves from the MinMaxCurve in script there are some caveats.
Firstly, if you try to read the curve property when it is in one of the Curve modes, then the you’ll get the following error message: “Reading particle curves from script is unsupported unless they are in constant mode”.
Due to the way the curves are compressed in the engine, it is not possible to get the MinMaxCurve unless it is using one of the 2 constant modes.We know this isn’t great, read on our plans to improve it. The reason for this is that internally, we don’t actually just store an AnimationCurve, but select one of two paths. If the curve is simple (no more than 3 keys with one at each end), then we use an optimized polynomial curve, which provides improved performance. We then fallback to using the standard non-optimized curve if it is more complex. In the Inspector, an unoptimized curve will have a small icon at the bottom right which will offer to optimize the curve for you.
An optimized curve
An unoptimized curve
While it may not be possible to get the curve from a module in script, it is possible to work around this by storing your own curve and applying this to the module when required, like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
using UnityEngine; public class MinMaxCurveCurveMode : MonoBehaviour { ParticleSystem myParticleSystem; ParticleSystem.EmissionModule emissionModule; // We can "scale" the curve with this value. It gets multiplied by the curve. public float scalar = 1.0f; AnimationCurve ourCurve; void Start() { // Get the system and the emission module. myParticleSystem = GetComponent<ParticleSystem>(); emissionModule = myParticleSystem.emission; // A simple linear curve. ourCurve = new AnimationCurve(); ourCurve.AddKey(0.0f, 0.0f); ourCurve.AddKey(1.0f, 1.0f); // Apply the curve emissionModule.rate = new ParticleSystem.MinMaxCurve(scalar, ourCurve); // In 5 seconds we will modify the curve. Invoke("ModifyCurve", 5.0f); } void ModifyCurve() { // Add a key to the current curve. ourCurve.AddKey(0.5f, 0.0f); // Apply the changed curve emissionModule.rate = new ParticleSystem.MinMaxCurve(scalar, ourCurve); } } |
Random between 2 curves.
This mode generates random values from in between the min and a max curves, using time to determine where on the x axis to sample. The shaded area represents the potential values. This mode is similar to the curve mode in that it is not possible to access the curves from script and that we also use optimized polynomial curves(when possible). In order to benefit from this, both curves must be optimizable, that is contain no more than 3 keys and have one at each end. Like in curve mode, it is possible to tell if the curves are optimized by examining the bottom right of the editor window.
This example is very similar to the curve, however, we now also set the minimum curve as well.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
using UnityEngine; public class MinMaxCurveRandom2CurvesMode : MonoBehaviour { ParticleSystem myParticleSystem; ParticleSystem.EmissionModule emissionModule; AnimationCurve ourCurveMin; AnimationCurve ourCurveMax; // We can "scale" the curves with this value. It gets multiplied by the curves. public float scalar = 1.0f; void Start() { // Get the system and the emission module. myParticleSystem = GetComponent<ParticleSystem>(); emissionModule = myParticleSystem.emission; // A horizontal straight line at value 1 ourCurveMin = new AnimationCurve(); ourCurveMin.AddKey(0.0f, 1.0f); ourCurveMin.AddKey(1.0f, 1.0f); // A horizontal straight line at value 0.5 ourCurveMax = new AnimationCurve(); ourCurveMax.AddKey(0.0f, 0.5f); ourCurveMax.AddKey(1.0f, 0.5f); // Apply the curves emissionModule.rate = new ParticleSystem.MinMaxCurve(scalar, ourCurveMin, ourCurveMax); // In 5 seconds we will modify the curve. Invoke("ModifyCurve", 5.0f); } void ModifyCurve() { // Create a "pinch" point. ourCurveMin.AddKey(0.5f, 0.7f); ourCurveMax.AddKey(0.5f, 0.6f); // Apply the changed curve emissionModule.rate = new ParticleSystem.MinMaxCurve(scalar, ourCurveMin, ourCurveMax); } } |
Performance
We did some simple performance comparisons to see how these different modes compare. These samples were taken before our recent SIMD optimizations, which should provide a significant performance boost. In our specific test scene, we got these results:
Easing the pain
Reading curves from the MinMaxCurve class
We know that you really ought to be able to read particle system curves from script, regardless of what mode they are in. We are actively working on removing this limitation, so you can read/modify all those lovely curves in your scripts. Its also currently not possible to query the curve to check if the mode is using a curve, without an error being thrown. That’s also going to be fixed!
Changing modules from structs to classes
We are currently prototyping a change to move all the structs to classes. Functionally they will behave the same, however by using a reference type, it should be clearer that the module belongs to a system. It will also allow for setting/getting values without having to hold a temporary variable. However, this will mean we allocate them once in the constructor, which will generate garbage, but only at initialization time.
For example:
1 2 3 4 5 6 |
var em = ps.emission; em.enabled = true; // Could be written as ps.emission.enabled = true; |
Finally
We hope this post has been of some use to you, please head over to this forum post to grab the examples and comment/discuss.
We will also add this information over to our documentation.
Imi
五月 4, 2016 8:37 上午Have you guys thought about extracting some useful classes to be used in other non-particle related cases?
I could imagine a lot of places, e.g. in the AI, where I would need “a random value between two curves”. And benefiting from optimized 3-key curves never sounds wrong… not all animation curves are imported from 3DMax with 50+ keyframes ;)
Karl Jones
五月 5, 2016 12:34 上午Hi.
We have added an Evaluate function in 5.4 to the MinMaxCurve – http://docs.unity3d.com/540/Documentation/ScriptReference/ParticleSystem.MinMaxCurve.Evaluate.html
You can now use it although it uses a c# side evaluation so no polynomial curves, it will just be like calling Evaluate on a standard AnimationCurve when you use the curve modes.
For it to be useful as a generic component it would need to have inspector support and possibly add the poly curves in. That’s not currently on the near term roadmap for us but certainly something we will consider in the future.
Fugogugo
五月 1, 2016 11:54 上午any plan to support particle positioning in between Unity UI?
Karl Jones
五月 3, 2016 1:31 下午There have been discussions on this. We have proposed changes for the UI system in the future to allow for this but nothing concrete at the moment.
David
四月 28, 2016 10:18 下午Not sure if this is the place to ask, but in the event that MinMaxCurve becomes modifiable from script, will they be usable in custom inspectors for non-particle related things?
Karl Jones
四月 28, 2016 10:37 下午Hi David.
We have added an Evaluate function in 5.4 to the MinMaxCurve – http://docs.unity3d.com/540/Documentation/ScriptReference/ParticleSystem.MinMaxCurve.Evaluate.html
You can now use it although it uses a c# side evaluation so no polynomial curves, it will just be like calling Evaluate on a standard AnimationCurve when you use the curve modes.
The main problem is that it does not currently appear in the inspector or serialize itself so we would need to sort that out for it to be useful. I’ll add the suggestion to our list of features so we can discuss it in the future.
Thanks!
sathya g
四月 25, 2016 11:33 上午the blog is very interesting and will be much useful for us. thank you for sharing the blog with us. please keep on updating.
deeksha
四月 25, 2016 8:46 上午this content is really useful and you have done a tremendous work ,it is really awesome and it is good too.
Alesk
四月 22, 2016 8:14 下午Any plans about the shader side ?
Would it be possible to add a special case where a custom geometry shader could be used to display particles ? So the mesh generated by shuriken could only contains a vertex for each particle (+ all interesting data to be used in the shader)
And in the current state, do you think passing particles data (age, lifetime, size, etc…) to the shader to be something doable ?
Karl Jones
四月 22, 2016 9:43 下午We have a vertex stream module in the works that may do some of those features but i’m not too sure of the exact details at the moment.
Richard Kettlewell
四月 25, 2016 12:27 下午I can provide a bit more information on that.
In 5.5, we are hoping to introduce a Vertex Streams feature to the Renderer Module. You will be able to choose exactly what gets sent to your shader (positions, colors, velocities, rotations, lifetime information, etc). This should allow you to do a wide variety of custom shader-based effects.
Additionally, you will be able to set custom particle data from script, and pass that to your shaders too. So, to give an example, you could tag particles with a custom property to represent the “health” of each particle, and then use that in your script logic, or in your shader, or both.
You’ll need to be comfortable writing your own shaders in Unity, to be able to make the most of this feature, but in its basic form, it will finally allow you to properly use the Standard Shader to create normal mapped particles, by including the Tangent Stream.
WEN
四月 27, 2016 10:51 上午After Unity 5.2 . we can’t see stable version on Android !?
Anton Hand
四月 22, 2016 6:37 下午Hi Karl,
These new changes are welcome, but would it be possible to _please_ have someone fill out the documentation with stub example usages for the Shuriken-related classes/structs/etc. on the Script Reference?
Karl Jones
四月 22, 2016 6:45 下午Funny you should mention it I have been doing that today :)
Kutlas
四月 22, 2016 10:01 上午We need Standard Unity Video Player (Android,IOS,Windows,….) ;
Jonney Shih
四月 22, 2016 12:01 上午Those API features are indeed well overdue, thanks.
But we need better UI and UX with Shuriken, and maybe a buit-in previewer.
Karl Jones
四月 22, 2016 12:23 上午What do you have in mind by better UI/UX? You can preview the system in the editor, how do you see a built in previewer working?
Marcin Szymanski
四月 23, 2016 6:18 下午I think being able to preview a particle system without placing it in the scene, how you can preview a prefab or texture by clicking it in your Assets list. That would be pretty cool.
Karl Jones
四月 24, 2016 11:57 下午Interesting. I have added it to out feature requests list so we can consider it in the future when we next review it.
Jeff Johnson
四月 21, 2016 10:55 下午Thanks for the post. The struct mechanism was a little confusing to me at first as well. Will the classes in a future version simply be references to a private member variable then? I assume so from your post, but just wanted to make sure it wasn’t allocating a wrapper every time the property was accessed.
Karl Jones
四月 22, 2016 12:21 上午Yes it will only be allocated once when the system is created and then just accessed. So no wrapping each time it’s accessed.
rea
四月 20, 2016 6:55 下午Since the post is about particle,
is there any plan to support custom module addition in shuriken?
Karl Jones
四月 20, 2016 7:49 下午Its been discussed but nothing concrete at the moment. If you have an idea about it or how it should work you can post it on feedback https://feedback.unity3d.com/
There is not much regarding custom modules so its certainly worth doing it.
Zi
四月 20, 2016 6:28 下午Why have I been seeing GetComponent() in a lot of Unity articles lately? Is this a new version of the method I have not seen, which has no parameters or generic parameters? Thanks!
Zi
四月 20, 2016 6:31 下午Never mind. I realized it was because C# can infer the generic parameter type. Sacrifices a little clarity for brevity in this case, I suppose. =)
Nevermind
四月 20, 2016 6:36 下午It’s actually plain old generic
GetComponent
, but since angle brackets have a special meaning in HTML this gets rendered incorrectly. And apparently no one ever proofreads these posts, so there.Karl Jones
四月 20, 2016 6:41 下午Looks like the brackets were removed when i made some changes. Its fixed now.
Marc
四月 20, 2016 5:15 下午Hi ! I like this article, this clarifies a lot of things with Particle System Scripting.
I downloaded the new Unity 5.4 beta and it was said that there are new ways to deal with Particle System events in the code, but I can’t found the documentation related to this.
Can you tell me where ?
Thank you !
Richard Kettlewell
四月 20, 2016 5:27 下午Hi Marc, do you mean the new Trigger Module?
The documentation for it is here: http://docs.unity3d.com/540/Documentation/Manual/PartSysTriggersModule.html
Marc
四月 20, 2016 5:35 下午Yes that’s it !
Thank you ! :D
Peter
四月 20, 2016 4:02 下午I wonder why “Random between two Constants” isn’t faster than “Random between two Curves”. I also wonder why “Random between two Constants” is so much more expensive than “Constant”. Finding a random point between two constants doesn’t sound like an expensive feature.
Karl Jones
四月 20, 2016 4:09 下午Its because we store the 2 constant values as keys in the min max curve. This means the values need to be normalised as well. Doing a random between 2 constants is almost the same process as a random between 2 curves. We have discussed this approach a little internally and may try and optimize it in the future.
Imi
五月 4, 2016 8:44 上午That begs the question: Why do you store 2 constant values as “key in the min max curve” then? Clearly, you need more floats than two to store any kind of curve, so there should be space enough ruling out any answer like “we want to conserve memory”.
Afraid of too many code branches? Then why don’t you sort your different curve types to avoid code branches between evaluations?