Search Unity

Particle Playground

Discussion in 'Assets and Asset Store' started by save, Dec 4, 2013.

  1. joihelgi

    joihelgi

    Joined:
    Jan 21, 2014
    Posts:
    19
    @save
    Is it possible to spawn particles from basic shape without texture or mesh like Shape in Unity Particle System, where I can use circle with radius and arc and spawn from the edge ?

    I would use source scatter but the spherical linear will not do because i need it in 2d and spawned from edge (Circle).
     
    Last edited: May 22, 2015
  2. joihelgi

    joihelgi

    Joined:
    Jan 21, 2014
    Posts:
    19
    @save
    No need, I extended your code and made a Circular and CircularLinear modes for Source Scatter and Initial Global and Local Velocity, the outcome is very cool (at least for 2D) :rolleyes:

    I hope you don't mind me changing your code, I can send you the changes if you want.

    It could be Torus instead of Circle, then it would be 3D. But now I only need it 2D so circle dose the trick ;)

    Quick preview:



     
    Last edited: May 23, 2015
    Constantinos likes this.
  3. jalapen0

    jalapen0

    Joined:
    Feb 20, 2013
    Posts:
    136

    Hrmmm no thoughts on my questions?
     
  4. Binary42

    Binary42

    Joined:
    Aug 15, 2013
    Posts:
    207
    Hoi, is it possible to spawn mesh particles on another mesh ( State or World Object) so that the mesh particles are perpendicular to the surface? Think of the holobot example, instead of billboard particles, having spikes that point away from the surface.
     
  5. save

    save

    Joined:
    Nov 21, 2008
    Posts:
    744
    There's no example of an electricity beam, but you can animate the UVs in the same manor as within the Shuriken.
    You can use PP's event system to detect particle collisions, have a look in the Event Listener scene found in Examples/Example Scenes/Interactive/ for a basic example. There's also an example called GlobalEventListeners.cs found in Examples/Example Scripts/Simple Scripts/ which hooks up to the global event delegates, which might be a good approach if you have several particle systems sending events in your scene.

    In case you're not already familiar with the Events section:


    Great! Yes Unity 4.6.5 seems to be running ok now with iOS 64-bit + multithreading, same thing goes for Unity 5.

    Currently Playground is using Time.deltaTime and Time.timeScale during runtime. Bypassing this will be available in an update soon, where you can choose the time method to use. If you need this right now you can use this solution: http://pastebin.com/5i0BZbVV (will affect all systems globally without any flexibility)

    Very nice! Feel free to share if you like. :) I'm hoping to soon extend this part in terms of Source shapes and velocity methods, it is a highly requested feature. It's stuff like this that makes me think a git repo for the Playground source would make sense so we can have more amazing things more quickly.

    Hi jalapen0! Truly sorry, your question fell outside my limited attention span at the moment. :)
    It is a daunting task indeed. You would need to combine several particle systems and possibly use a vertex lit shader to really make it pop. Using only particles might not be sufficient though, I would recommend looking into this shader to make the base of the rolling mushroom cloud, both for improving the visual effect and resource efficiency.

    If you decide to go with only particles, here's some suggestions:
    • Have a look at Forces > Force Annihilation > Transition Back To Source. By setting the Animation Curve you can make particles transition back to its source position with high level of control to make explode-implode behaviors.
    • Manipulators would be to your great benefit in controlling the behavior, where you could retarget particles over time.
    • Try using Particle Settings > Lifetime > Lifetime Sorting: Linear/Reversed. This enables you to have controlled linear emission patterns over a Source, the Overflow Offset, Particle Filter in Manipulators and Color Method: Particle Array. The linear particle array methods can be really useful when creating something that needs this level of control.
    • Separate the events of the explosion into different particle systems.
    A lot will be depending on the timings. Here is a (really) rough example preset to show the structure: http://polyfied.com/development/unity/playground/Playground_Nuke.unitypackage

    Looks like this:


    Sorry not natively in Playground, it would require either another particle system or having the spikes as GameObjects controlled by particles.
     
    chelnok and jalapen0 like this.
  6. BoraG

    BoraG

    Joined:
    Dec 17, 2014
    Posts:
    9
    Hi,

    I've been using Particle Playground for standard particle systems in my games in Unity but I wonder if I can use it for implementing fluid mechanics like Cocuy plugin in this link https://www.assetstore.unity3d.com/en/#!/content/33564.

    If I can, can you direct me for a starting point, are there any tutorials regarding to this ?

    I've sent a mail to Polyfied support about this too by the way.

    Thank you
     
  7. Binary42

    Binary42

    Joined:
    Aug 15, 2013
    Posts:
    207
    Uh ... that sounds even better than just having meshes. Can you give a pointer how to
    control GameObjects by particles?

    And btw: beautiful code! :)
     
  8. jalapen0

    jalapen0

    Joined:
    Feb 20, 2013
    Posts:
    136
    Wow, what a reply, thank you so much for the effort you put into that. Everything looks great and once I get a free cycle, will be looking into all of it.
     
  9. QFGlenn

    QFGlenn

    Joined:
    Feb 21, 2013
    Posts:
    39
    I want to also say I hope this asset gets playmaker actions. I would even pay extra for them. It's the only thing keeping me from buying it.
     
  10. save

    save

    Joined:
    Nov 21, 2008
    Posts:
    744
    Hi Bora,
    It’s an interesting question which I unfortunately don’t have a good answer to as I’ve never looked into Cocuy.
    I’m yet unsure if Cocuy can read particle positions (it would be the standard Shuriken particles which Playground is based upon), otherwise it seems that it can detect GameObjects or colliders within the scene in realtime, perhaps you may have luck with using PlaygroundFollow.cs found in Examples/Example Scripts/Simple Scripts/ which tags along the package. PlaygroundFollow.cs will make objects follow your particles, where you then could let those interact with Cocuy. You can also have a look at my answer to Binary42 below for an optional approach.

    If your GameObjects simply are a mesh filter and a renderer which contains your spikes you would be a lot better off performance-wise using a single mesh where you reposition vertices immediately onto Vector3s (without having the need for particles involved). Separate meshes becomes an intriguing bottleneck very quickly!

    However, based upon your question, to control GameObjects with particles you could either use the PlaygroundFollow.cs which tags along the package or a simplified example without the death/birth safe pool:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using ParticlePlayground;
    4.  
    5. public class PlaygroundTransformFollow : MonoBehaviour {
    6.  
    7.     public PlaygroundParticlesC particles;
    8.     public Transform followerReference;
    9.     Transform[] followers;
    10.     Vector3 zero = Vector3.zero;
    11.  
    12.     void Start () {
    13.         followers = new Transform[particles.particleCount];
    14.         for (int i = 0; i<particles.particleCount; i++) {
    15.             followers[i] = ((Transform)Instantiate (followerReference)).transform;
    16.             followers[i].parent = transform;
    17.         }
    18.         followerReference.gameObject.SetActive (false);
    19.     }
    20.  
    21.     void Update () {
    22.  
    23.         // Return if lengths doesn't match
    24.         if (followers.Length != particles.particleCount)
    25.             return;
    26.  
    27.         for (int i = 0; i<followers.Length; i++) {
    28.  
    29.             // Position each follower
    30.             followers[i].position = particles.particleCache[i].position;
    31.  
    32.             // As an example rotate each follower based on velocity
    33.             if (particles.playgroundCache.velocity[i] != zero)
    34.                 followers[i].rotation = Quaternion.LookRotation(particles.playgroundCache.velocity[i]);
    35.         }
    36.     }
    37. }
    38.  
    Not recommended for spikes in this case. :)

    This is on the todo where I'm hoping to have at least the basics in a first version. The PlayMaker actions support will be a free extra pack. Apart from that you can actually use Playground with PlayMaker already, just by dragging the Inspector component (PlaygroundParticlesC) into the PlayMaker FSM, where all members of the particle system will be exposed. A bit cumbersome and overwhelming though as you'll get a list of everything.
     
    Binary42 likes this.
  11. hoesterey

    hoesterey

    Joined:
    Mar 19, 2010
    Posts:
    659
    Sorry if this has been answered but I'm not quite clear on it.

    Could I ask what the efficient way to create sub emitters is with particle playground? Goal is to have smoke trails coming from chunks of metal.

    Thanks!
     
  12. BortStudios

    BortStudios

    Joined:
    May 1, 2012
    Posts:
    48
    In terms of best coding practices using this, how should I handle something like dust being kicked up every time a bullet hits the ground? Should I have one dust particle system that I just script to instantiate at the contact point, or should I instantiate an instance of it each time a bullet hits?
     
  13. QFGlenn

    QFGlenn

    Joined:
    Feb 21, 2013
    Posts:
    39
    Bought it, tried using playmaker to edit.....the options were there, but something is messed up. I'm trying to make realistic underwater bubbles, and so I was trying to create a big initial surge and then back off. So I try to edit both max particles and emission rate. I see them change in runtime along with the playmaker script but no visual change.


    [EDIT] As a workaround I also tried to use playmaker to turn a particle system off after 3 seconds. Playmaker's activate game object action doesn't turn the particle system off like normal...

    [EDIT 2] I realize that particle playground can control these things, I just prefer playmaker so I can time my states and actions more easily.

    [EDIT 3] Not to pile on because I really liek this asset, but when I use a burst of bubbles that doesn't loop, and I press play they all stack in the same spot and move up. So all the bubbles look like a wisp or a fireball.... how do I fix this?
     
    Last edited: Jun 5, 2015
  14. save

    save

    Joined:
    Nov 21, 2008
    Posts:
    744
    The most efficient way is to make use of the Event system if your chunks of metal are particles, there you can make another particle system trigger emission based on your settings. Have a look at the example scene Event Targets found in Examples/Example Scenes/ where one particle system trigger smoke trails from another. You can also have a look at the Events video guide to get some basic info on how things connect.

    If your chunks of metal are GameObjects, it will be more efficient to hook everyone of those up using the Source: Transform. There you can connect as many Transforms as you'd like to make the effect originate from just one particle system. In case you want to work with this approach 'procedurally' during runtime, here's a helper/example class:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using ParticlePlayground;
    4.  
    5. public class AddRemoveSourceTransforms : MonoBehaviour {
    6.  
    7.     public Transform[] sourceTransforms;            // Example list of transforms to use as Source
    8.     PlaygroundParticlesC particles;                    // Cached particle system component
    9.  
    10.     void Start ()
    11.     {
    12.         particles = GetComponent<PlaygroundParticlesC>();
    13.         particles.source = SOURCEC.Transform;
    14.  
    15.         // Example usage
    16.         if (sourceTransforms.Length > 0)
    17.         {
    18.             // Clear out the first in case this is the particle system's own transform
    19.             RemoveTransform(particles.particleSystemTransform);
    20.  
    21.             // Add your transforms
    22.             AddTransforms(sourceTransforms);
    23.         }
    24.     }
    25.  
    26.     /// <summary>
    27.     /// Adds a built-in array of transforms as Source to the particle system's Source: Transform list.
    28.     /// </summary>
    29.     public void AddTransforms (Transform[] transformsToAdd)
    30.     {
    31.         for (int i = 0; i<transformsToAdd.Length; i++)
    32.             AddTransform (transformsToAdd[i]);
    33.     }
    34.  
    35.     /// <summary>
    36.     /// Adds a transform as Source to the particle system's Source: Transform list.
    37.     /// </summary>
    38.     public void AddTransform (Transform transformToAdd)
    39.     {
    40.         // Create a new Playground Transform (a multithreading friendly Transform wrapper)
    41.         PlaygroundTransformC sourceTransform = new PlaygroundTransformC();
    42.         sourceTransform.transform = transformToAdd;
    43.  
    44.         // Assign the transform as a source
    45.         particles.sourceTransforms.Add (sourceTransform);
    46.     }
    47.  
    48.     /// <summary>
    49.     /// Removes a specific transform from the Source: Transform list.
    50.     /// </summary>
    51.     public void RemoveTransform (Transform transformToRemove)
    52.     {
    53.         // Iterate through the list of Playground Transforms
    54.         for (int i = 0; i<particles.sourceTransforms.Count; i++)
    55.         {
    56.             if (particles.sourceTransforms[i].transform == transformToRemove)
    57.             {
    58.                 RemoveTransform (i);
    59.                 break;
    60.             }
    61.         }
    62.     }
    63.  
    64.     /// <summary>
    65.     /// Removes a specific transform at index position in the Source: Transform list.
    66.     /// </summary>
    67.     public void RemoveTransform (int index)
    68.     {
    69.         particles.sourceTransforms.RemoveAt (index);
    70.     }
    71. }
    72.  
    There's also a video guide for the Sources if you want to get to know them a bit better.

    If you potentially have a lot of that effect going on within the scene then the best approach is to use Source: Script. As long as you have a position and a normal as in-data for the emission things should look good. Here's an example how to emit using a raycast:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using ParticlePlayground;
    4.  
    5. public class EmitOnRaycastHit : MonoBehaviour {
    6.  
    7.     PlaygroundParticlesC particles;
    8.    
    9.     void Start ()
    10.     {
    11.         particles = GetComponent<PlaygroundParticlesC>();
    12.     }
    13.    
    14.     void Update ()
    15.     {
    16.         if (Input.GetMouseButton (0))
    17.         {
    18.             Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    19.             RaycastHit hit;
    20.             if (Physics.Raycast (ray, out hit))
    21.             {
    22.                 particles.Emit (
    23.                     hit.point,
    24.                     hit.normal
    25.                 );
    26.             }
    27.         }
    28.     }
    29. }
    30.  
    You can also find an example scene called Ink in Examples/Example Scenes/Interactive/ which uses this method.

    If your bullets are other particles, then you could make use of the Event system, have a look at the example scene Event Listener found in Examples/Example Scenes/Interactive/ (also see first answer in this post for more info).

    Thanks for getting back with details, I think I grasp the issue better after reading through the edits. Try using a Lifetime Sorting: Custom (Particle Settings > Lifetime) and set the Animation Curve to falloff from X:0, Y:1 down to X:1, Y:0. This will create more particles birthing in the beginning to then thin out.


    To detach the lifetime cycle from the emission pattern you can use Lifetime Emission to compress the birth cycle and make particle live longer than the actual emission pattern. Whenever you change a setting connected to the lifetime cycle the particle system needs to recalculate timings and recache, this is why you don't see any effect while transitioning values. If you want to transition such thing as visual particle count, you would need to use the Particle Mask, as changing particle count during runtime would also need to initiate a reboot and caching due to the current reused particle structure.

    In relation to your third edit, it sounds like you need some more Initial Velocity (Forces > Initial Velocity > Initial Local Velocity) to your particles to spread them out. Try using Method: Spherical with a min to max range that fits your scene. Then you could try adding Turbulence where the Perlin noise algorithm is often suitable for underwater behaviors.

    My best tip is to backtrack what is currently happening in the PlayMaker FSM to also make sure that you don't use a method mentioned previously which forces the particle system to restart.

    Here's an example preset of a bubble burst which hopefully lead towards the right direction. It behaves like this:
     
    overthere likes this.
  15. redred77

    redred77

    Joined:
    May 31, 2013
    Posts:
    23
    I want to know the compatibility for Unity 4.0
    I know that it doesn't support officially but this asset started quite early versions and seems like some of them would work.

    Are all features would not work? Some of them?
    Or can you provide me older project that running under Unity 4.0?

    I am about to buy this asset but want to check the compatibility.
     
  16. J_P_

    J_P_

    Joined:
    Jan 9, 2010
    Posts:
    1,027
    Just got the asset and I'm wondering how I'd get this movement in 3D. Source scatter can get them to spawn along a sphere's edge, but I can't get their initial velocity to point toward the center. Spherical method seems to be random direction.
     
  17. J_P_

    J_P_

    Joined:
    Jan 9, 2010
    Posts:
    1,027
    Seems buggy. Not sure if I'm doing something wrong. I set up a particle system for a non-looping effect (on trigger, I want to play the particle effect once). For a while, I'd uncheck emit/loop but on play, they'd be checked again. Now that seems to stick, so it starts off correctly (off)... but I can't get it to fire. At runtime, if I check the emit box in the inspector it acts as expected (it emits based on the settings I set up in inspector), but PlaygroundParticlesC.Emit() doesn't actually emit anything even though it changes the box to checked. Also tried Emit(int) specifying the same number in inspector, but no particles come out.

    edit: ok, took a look at the source code and Emit(true) is what I needed.

    Unrelated, but another bug I found was I'd occasionally get index out of range error while adjusting settings for the particles and the particles would break -- I'd have to duplicate that object and delete the old one to continue working..
     
    Last edited: Jun 10, 2015
  18. mb28

    mb28

    Joined:
    Oct 7, 2012
    Posts:
    27
    I'm running into a slight problem and not sure if I'm completely missing something here or not. I have a bunch of particles with a gravitational attractor in the shape of a sphere.

    I set the strength of this to 0, so all my particles just float around which is great. So I create a snapshot.
    Next, I set strength to 10 and all the particles move towards the point which is also great. I create a second snapshot.

    When I click between the two different snapshots, the change I've made shows up correctly in the inspector, but the particles don't do what the snapshot indicates in the scene both when Unity and playing and when I'm just in the editor

    I'd like to be able to move through multiple snapshots to create an effect. Is this possible or am I missing the intent of snapshots? Or maybe they don't work quite right? As a fall back, I can script this all out but would much rather move through snapshots if possible.

    Let me know. Thanks!
     
  19. mb28

    mb28

    Joined:
    Oct 7, 2012
    Posts:
    27
    I think I found my own answer. Looks like I needed to click "load" to load the state. Everything is in order. :)
     
  20. SomerenV

    SomerenV

    Joined:
    Dec 20, 2011
    Posts:
    83
    I've used this asset for a while when I was over at a friend and while it has a lot of really cool features I do miss one thing: the collisions of the Shuriken particles.

    I've got a windshield and snow is falling on it. With Shuriken, with bounce on 0, gravity on 1 and dampen on 0.8 the snow slides off the windshield very nicely. I tried to replicate that with Particle Playground 2 but couldn't get it right. The particles don't stick precisely to the windshield and they don't slide down. If I fiddle with dampen and the collider the particles eventually fall through the windshield. I would use Shuriken but with Particle Playground I can let a mesh interact with the particles (repelling/attracting).

    So what I want: a tilted surface with particles sliding off with precise collisions.

    If I can replicate what I made with Shuriken than you've got yourself a new customer :)
     
  21. GodJammit

    GodJammit

    Joined:
    Feb 10, 2014
    Posts:
    32
    Considering a purchase, does Particle Playground have any remedies to Shuriken's Z-billboarding problem? That is, when the camera rotates on the z-axis, so do all the particles. Been a bit of a bane for my project.
     
  22. DougVFX

    DougVFX

    Joined:
    Nov 24, 2013
    Posts:
    2
    I just started using particle playground and it's great. My question is, coming from scripting with shuriken what is the equivalent to the Play() function using particle playgrounds api? I can only seem to find info on spawning particles from script that involve spawning from scratch. I just want to play the effect upon instantiate.
     
  23. DougVFX

    DougVFX

    Joined:
    Nov 24, 2013
    Posts:
    2
    It looks like I need to wait a frame after instantiation and then call Emit()?
     
  24. save

    save

    Joined:
    Nov 21, 2008
    Posts:
    744
    Hey JTown,
    Sorry for the late reply, great you got it working! Do you know which setting gave you index out of range exceptions and could it be reproduced?

    Great! Yes this is the intention of Snapshots, there's an example scene called Snapshots to get to know the method a bit better through scripting. They do lack a global to local space conversion natively and some more intelligent transitioning which should come in an update soon. In case you want to transition the global to local space simulation there's a script called SnapshotToLocalPosition.cs found in the Simple Scripts folder.

    The reason why PP acts this way is due to optimization, particles will only check for collisions in their own direction using a raycast. This means that they won't be affected by other moving colliders or naturally inherit velocity from them, such feature would be amazing though.
    What's new since 2.26 is the collision cache and "Sticky" particles, you could base any further particle logic using that to make particles react much more confined to the windshield. For example you could iterate the sticky positions to make particles slide downwards with high level of control on an object with high velocity.

    As stated in the release notes:
    More on the Collision Cache can be found here.

    Playground uses the Shuriken component to render particles, but you could use vertical billboard or a quad mesh to make them stop rotating on their Z axis.

    You will need to wait for the particle system to become ready as the initialization is done asynchronously on another thread. For example when emitting particles through script you can use (as stated in the FAQ):

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using ParticlePlayground;
    4.  
    5. public class WaitBeforeReady : MonoBehaviour {
    6.  
    7.     public PlaygroundParticlesC particles;
    8.  
    9.     IEnumerator Start () {
    10.  
    11.         // Wait for particle system to become ready...
    12.         while (!particles.IsReady())
    13.             yield return null;
    14.  
    15.         // ...then emit!
    16.         particles.Emit ();
    17.     }
    18. }
    To enable emission on a system that isn't set to Source: Script you can call Emit(true). Was it that thing you were after?
     
  25. CaptainMurphy

    CaptainMurphy

    Joined:
    Jul 15, 2014
    Posts:
    746
    I have been digging through documentation and the examples and for some reason I still don't grasp the concept of the emit in relation to what we are trying to do with it. We have ships with cannons on the sides and we are trying to call the same particle on each cannon for the fire portion, that means there will be a lot of the particles going at once. We created the effect in PP2 in a separate project, exported it as a unitypackage, then imported it into our current project. At this point it is just confusing as to how we can call that particle through code at the position of each cannon muzzle during the fire portion. The attempts we made using the Emit() method on a collection of the particles end up losing the velocity that we had set inside the PP2 editor in the other project. If we drag and drop the prefab, they have the correct velocity. Is there an example that I am just missing somewhere? We are trying to make this as efficient as possible and it looks like we are just not understanding what it is we need to do. The documentation has been great for firing off 1 particle, but in our case it just goes crazy.

    Edit:
    Scratch all of that. I just built a pooler that handles the emission of the particles in a parent object and was able to get it working.
     
    Last edited: Jun 25, 2015
  26. RecursiveRuby

    RecursiveRuby

    Joined:
    Jun 5, 2013
    Posts:
    163
    Hey I'm not sure if this has been asked before or if it is included in the documentation somewhere but how could I change the emission rate to be based off distance rather than time like in Shuriken? I've looked through the documentation but I can't find anything relating to it. I was thinking maybe control it with a script but I just wanted to check if this would be the proper way of doing things. I literally just bought PP 2 days ago and am still coming to grips with it.
    Appreciate it!
     
  27. RecursiveRuby

    RecursiveRuby

    Joined:
    Jun 5, 2013
    Posts:
    163
    Hey think I figured it out just found the emit on translation script
     
  28. save

    save

    Joined:
    Nov 21, 2008
    Posts:
    744
    Great! Yes there's some usable gems in the Simple Scripts folder, more coming up soon. :)
     
  29. ShinKazuo

    ShinKazuo

    Joined:
    Oct 28, 2014
    Posts:
    12
    Wow I loved this effects. How hard is the process to do that? I'm not very good at extending code...
     
  30. QFGlenn

    QFGlenn

    Joined:
    Feb 21, 2013
    Posts:
    39
    Particle shadows are great for building clouds, but there isn't any volumetric light scattering. Naturally clouds absorb light towards the top and bleed into darker towards the bottom. Any chance for future development into volumetric light scattering?
     
  31. hopeful

    hopeful

    Joined:
    Nov 20, 2013
    Posts:
    5,684
    I think what you may be looking for is more of a shader solution, and I'm not sure there is one out there. You can look into this, but there are some limitations.
     
  32. Vibs_appit

    Vibs_appit

    Joined:
    Feb 16, 2014
    Posts:
    53
    Hi save,
    I bought the PP2 a while ago and have been playing quite a bit with it for our new game. It's been fantastic! I ran into a problem yesterday while trying to generate a healing effect (it's a moba game) for my character by using a spline ring. The issue is that I have my playgroundparticle as a child of the character object. It does work well, but as it is a healing effect, I need to move the character. I tried switching the simulation space to local and that works if I move the spline, but it moves weirdly while I move the character object itself which is the parent of the spline. Any thoughts on how I might be able to move the particle system with the character?
     
  33. save

    save

    Joined:
    Nov 21, 2008
    Posts:
    744
    What @hopeful said. :) There's currently no plans on extending Playground with more advanced shaders, but I hope that will be the case later down the road.

    Hey Vibs,
    When using local space try unparenting the Playground Spline from the particle system. In case you are instantiating the player or the particle effect then try something like this script on the Playground Spline:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class UnparentOnStart : MonoBehaviour {
    4.     void Start () {
    5.         transform.parent = null;
    6.         transform.position = Vector3.zero;
    7.         transform.rotation = Quaternion.identity;
    8.     }
    9. }
    10.  
     
  34. save

    save

    Joined:
    Nov 21, 2008
    Posts:
    744
    Here's something I hope to be able to release soon! (very Inspector-unpolished still)

     
    chelnok and hopeful like this.
  35. ZJP

    ZJP

    Joined:
    Jan 22, 2010
    Posts:
    2,649
    Very nice. As usual. :cool:
     
  36. jalapen0

    jalapen0

    Joined:
    Feb 20, 2013
    Posts:
    136
    hello, any information on using playground splines for something other than particles? I was hoping to use it to stretch a mesh to create a river. I can't figure out how to attach the mesh properly. Thanks!
     
  37. SpaceRay

    SpaceRay

    Joined:
    Feb 26, 2014
    Posts:
    455
    WOW!! I have seen this update and is really amazing and awesome effect shown here in this image preview, and is exactly something that I want to make myself with an alien enemy.

    I have an alien enemy mesh that I want that when I fire my weapon at him and on impact it will disintegrate the alien mesh into LOTS of multiple light emitting particles and then fly away with turbulence effect, so as shown perfectly on the image above

    I have made the destruction script already so when the enemy is being hit by my weapon, it will instantiate a prefab death effect, but do not know how to make this death prefab using this Particle Playground

    I suposse and think that I have to convert first the alien mesh to particles as seen at first on the beginning of the image

    Please, is there any possible estimated date when this will be available

    Can be done in another way with what is already inside the available version without waiting for this update?

    Thanks very much for any help and I love your asset
     
    Last edited: Jul 22, 2015
  38. MikeTon

    MikeTon

    Joined:
    Jan 10, 2013
    Posts:
    57
    I'm really digging how Particle Playground allows you to replace particles with actual GameObjects. And I'm thinking about using Particle Playground to spawn my simpler enemies.

    I'm using the PlaygroundFollow.cs script and it's working great, except for this:

    screen-capture-2.jpg

    I'm getting this error, why I try to spawn a persistent version of my enemy GameObject with OnParticleDidDie (PlaygroundEventParticleparticle) { ... }

    It's an error I'm not familiar with. Or understand. Is there any advice you can offer. Or thoughts on how suitable Particle Playground would be for spawning enemies?

    Thank you for your time and attention.

    -Mike
     
  39. MikeTon

    MikeTon

    Joined:
    Jan 10, 2013
    Posts:
    57
    PpGround_enemySpawn.gif

    Here is an animated gif of the effect I'm trying to get. When the particle effect is done, I'd like to replace the spheres with a simple enemy game object; essentially using Particle Playground as a spawning source. But are getting the error in the previous post. Hope that helps.

    -Mike
     
  40. unitywlp

    unitywlp

    Joined:
    Jan 3, 2014
    Posts:
    146
    Hi i want to create one particle moving and make trail renderer to follow the particle until it death but i was not able to do that i dont understand the particle follower script i am using blox plugin to read api and convert that to blox but unfortunately i was not able to read the api in correct manner.
    can you please make bare minimum script with explanation what does what due to event system it was even more confusing.
    i will create a particle and i need trail renderer to follow the particle.
    explanation with out using events will be helpful
    Thanks
     
  41. MikeTon

    MikeTon

    Joined:
    Jan 10, 2013
    Posts:
    57
    screen-capture-2.jpg

    Have a look at the Follow Particle scene, it has a trail object following the particles set up. And is pretty easy to deconstruct.

    -Mike
     
  42. unitywlp

    unitywlp

    Joined:
    Jan 3, 2014
    Posts:
    146
    HI
    i was not good programmer so i dont really understand script and how it works so if you dont mine can you explain
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using ParticlePlayground;
    5.  
    6. public class PlaygroundFollow : MonoBehaviour {
    7.  
    8.     /// <summary>
    9.     /// Reference to the particle system.
    10.     /// </summary>
    11.     public PlaygroundParticlesC particles;
    12.     /// <summary>
    13.     /// Reference to an existing GameObject. This will be cloned to be used on every particle.
    14.     /// </summary>
    15.     public GameObject referenceObject;
    16.     /// <summary>
    17.     /// The lifetime of the followers. Set 0 to follow during each particle's individual lifetime.
    18.     /// </summary>
    19.     public float followerLifetime = 0;
    20.  
    21.     TrailRenderer referenceTrailRenderer;
    22.     /// <summary>
    23.     /// If the follower has a Trail Renderer component, this sets trail time once the follower is active again.
    24.     /// </summary>
    25.     float trailTime = 0;
    26.     /// <summary>
    27.     /// The size of the cache. Set 0 to automatically set the needed amount.
    28.     /// </summary>
    29.     public int cacheSize = 0;
    30.     /// <summary>
    31.     /// The list of active followers.
    32.     /// </summary>
    33.     List<PlaygroundFollower> followers = new List<PlaygroundFollower>();
    34.     /// <summary>
    35.     /// As Playground is running in a multithreaded environment we need a queue for instantiation (which cannot be called from a different thread).
    36.     /// </summary>
    37.     List<PlaygroundFollower> waitingFollowers = new List<PlaygroundFollower>();
    38.     PlaygroundFollower[] referenceObjectsCache;
    39.     PlaygroundFollower[] queue = new PlaygroundFollower[0];
    40.     int cacheIndex = 0;
    41.  
    42.     PlaygroundEventC birthEvent;
    43.     PlaygroundEventC deathEvent;
    44.     Transform followerParent;
    45.  
    46.     void Awake () {
    47.  
    48.         // Create and setup the birth event
    49.         birthEvent = PlaygroundC.CreateEvent(particles);
    50.         birthEvent.broadcastType = EVENTBROADCASTC.EventListeners;
    51.         birthEvent.eventType = EVENTTYPEC.Birth;
    52.  
    53.         // Create and setup the death event
    54.         deathEvent = PlaygroundC.CreateEvent(particles);
    55.         deathEvent.broadcastType = EVENTBROADCASTC.EventListeners;
    56.         deathEvent.eventType = EVENTTYPEC.Death;
    57.  
    58.         // Hook up the event listeners to the delegates
    59.         birthEvent.particleEvent += OnParticleDidBirth;
    60.         deathEvent.particleEvent += OnParticleDidDie;
    61.  
    62.         // Setup the followers
    63.         followerParent = new GameObject("Followers").transform;
    64.         followerParent.parent = transform;
    65.         referenceTrailRenderer = referenceObject.GetComponent<TrailRenderer>();
    66.         if (referenceTrailRenderer!=null)
    67.             trailTime = referenceTrailRenderer.time;
    68.  
    69.         int extra = followerLifetime<=0?
    70.             Mathf.CeilToInt(Mathf.Abs (particles.lifetime-trailTime)+(trailTime-particles.lifetime))+2 :
    71.                 Mathf.CeilToInt(Mathf.Abs (particles.lifetime-followerLifetime)+(followerLifetime-particles.lifetime))+2 ;
    72.         if (particles.lifetime<=1f) extra++;
    73.         referenceObjectsCache = new PlaygroundFollower[cacheSize>0? cacheSize : particles.particleCount+Mathf.CeilToInt(particles.particleCount*extra)];
    74.         for (int i = 0; i<referenceObjectsCache.Length; i++) {
    75.             GameObject clone = (GameObject)Instantiate(referenceObject);
    76.             referenceObjectsCache[i] = new PlaygroundFollower(clone.transform, clone, clone.GetComponent<TrailRenderer>(), 0, 0);
    77.             referenceObjectsCache[i].transform.parent = followerParent;
    78.             if (referenceObjectsCache[i].trailRenderer!=null)
    79.                 referenceObjectsCache[i].trailRenderer.time = 0;
    80.             referenceObjectsCache[i].gameObject.SetActive(false);
    81.         }
    82.     }
    83.  
    84.     /// <summary>
    85.     /// Event listener for particle birth.
    86.     /// </summary>
    87.     /// <param name="particle">Particle.</param>
    88.     void OnParticleDidBirth (PlaygroundEventParticle particle) {
    89.         waitingFollowers.Add (new PlaygroundFollower(null, null, null, followerLifetime<=0? particle.totalLifetime+trailTime : followerLifetime, particle.particleId));
    90.     }
    91.  
    92.     /// <summary>
    93.     /// Event listener for particle death.
    94.     /// </summary>
    95.     /// <param name="particle">Particle.</param>
    96.     void OnParticleDidDie (PlaygroundEventParticle particle) {
    97.         int followerId = GetFollowerWithId(particle.particleId);
    98.         if (followerId<0) return;
    99.         followers[followerId].enabled = false;
    100.     }
    101.  
    102.     /// <summary>
    103.     /// Gets the follower which has the passed in particle identifier.
    104.     /// </summary>
    105.     /// <returns>The follower with particle identifier.</returns>
    106.     /// <param name="particleId">Particle identifier.</param>
    107.     int GetFollowerWithId (int particleId) {
    108.         float lowestLife = 999f;
    109.         int returnIndex = -1;
    110.         for (int i = 0; i<followers.Count; i++)
    111.             if (followers[i].particleId==particleId && followers[i].lifetime<lowestLife)
    112.                 returnIndex = i;
    113.         return returnIndex;
    114.     }
    115.  
    116.     void Update () {
    117.         if (waitingFollowers.Count>0) {
    118.             queue = waitingFollowers.ToArray();
    119.         }
    120.     }
    121.     void LateUpdate () {
    122.         UpdateFollowers();
    123.     }
    124.  
    125.     void UpdateFollowers () {
    126.  
    127.         // Follow, lifetime, remove
    128.         for (int i = 0; i<followers.Count; i++) {
    129.  
    130.             // Follow particle
    131.             if (followers[i].enabled)
    132.                 followers[i].transform.position = particles.particleCache[followers[i].particleId].position;
    133.            
    134.             // Subtract lifetime
    135.             followers[i].lifetime -= Time.deltaTime;
    136.  
    137.             // Remove if no lifetime left
    138.             if (followers[i].lifetime<=0) {
    139.                 RemoveFollower(i);
    140.                 continue;
    141.             }
    142.         }
    143.  
    144.         // Add any waiting followers to the live follower list. The waiting list may change during iteration!
    145.         if (queue.Length>0) {
    146.             if (queue.Length!=waitingFollowers.Count) return;
    147.             int inQueueThisFrame = waitingFollowers.Count;
    148.  
    149.             foreach (PlaygroundFollower wFollower in queue) {
    150.                 AddFollower (wFollower, followers.Count-1);
    151.             }
    152.             if (inQueueThisFrame==waitingFollowers.Count)
    153.                 waitingFollowers = new List<PlaygroundFollower>();
    154.             else waitingFollowers.RemoveRange (0, inQueueThisFrame-1);
    155.             queue = new PlaygroundFollower[0];
    156.         }
    157.     }
    158.  
    159.     void AddFollower (PlaygroundFollower follower, int i) {
    160.         if (follower==null) return;
    161.         followers.Add (follower.Clone());
    162.         followers[followers.Count-1].enabled = true;
    163.         followers[followers.Count-1].gameObject = referenceObjectsCache[cacheIndex].gameObject;
    164.         followers[followers.Count-1].gameObject.SetActive(true);
    165.         followers[followers.Count-1].transform = referenceObjectsCache[cacheIndex].transform;
    166.         followers[followers.Count-1].trailRenderer = referenceObjectsCache[cacheIndex].trailRenderer;
    167.         followers[followers.Count-1].particleId = follower.particleId;
    168.         followers[followers.Count-1].transform.position = particles.playgroundCache.position[followers[followers.Count-1].particleId];
    169.         if (followers[followers.Count-1].trailRenderer!=null)
    170.             followers[followers.Count-1].trailRenderer.time = trailTime;
    171.         NextCacheIndex();
    172.     }
    173.  
    174.     void RemoveFollower (int i) {
    175.         followers[i].enabled = false;
    176.         if (followers[i].trailRenderer!=null)
    177.             followers[i].trailRenderer.time = 0;
    178.         followers[i].gameObject.SetActive(false);
    179.         followers.RemoveAt(i);
    180.     }
    181.  
    182.     void NextCacheIndex () {
    183.         cacheIndex = (cacheIndex+1)%referenceObjectsCache.Length;
    184.     }
    185. }
    186.  
    187. /// <summary>
    188. /// Playground follower class.
    189. /// </summary>
    190. public class PlaygroundFollower {
    191.     public bool enabled = true;
    192.     public float lifetime;
    193.     public Transform transform;
    194.     public GameObject gameObject;
    195.     public TrailRenderer trailRenderer;
    196.     public int particleId;
    197.  
    198.     /// <summary>
    199.     /// Initializes a new instance of the <see cref="PlaygroundFollower"/> class.
    200.     /// </summary>
    201.     /// <param name="setTransform">Transform to reposition.</param>
    202.     /// <param name="setLifetime">Start lifetifetime.</param>
    203.     /// <param name="setParticleId">Particle identifier to follow.</param>
    204.     public PlaygroundFollower (Transform setTransform, GameObject setGameObject, TrailRenderer setTrailRenderer, float setLifetime, int setParticleId) {
    205.         transform = setTransform;
    206.         gameObject = setGameObject;
    207.         trailRenderer = setTrailRenderer;
    208.         lifetime = setLifetime;
    209.         particleId = setParticleId;
    210.     }
    211.  
    212.     /// <summary>
    213.     /// Clones this instance.
    214.     /// </summary>
    215.     public PlaygroundFollower Clone () {
    216.         return new PlaygroundFollower (transform, gameObject, trailRenderer, lifetime, particleId);
    217.     }
    218. }


    i have doubt in this

    Code (CSharp):
    1. followerParent = new GameObject("Followers").transform;
    2.         followerParent.parent = transform;
    3.         referenceTrailRenderer = referenceObject.GetComponent<TrailRenderer>();
    4.         if (referenceTrailRenderer!=null)
    5.             trailTime = referenceTrailRenderer.time;


    and in this
    logic behind the follower

    Code (CSharp):
    1. int extra = followerLifetime<=0?
    2.             Mathf.CeilToInt(Mathf.Abs (particles.lifetime-trailTime)+(trailTime-particles.lifetime))+2 :
    3.                 Mathf.CeilToInt(Mathf.Abs (particles.lifetime-followerLifetime)+(followerLifetime-particles.lifetime))+2 ;
    4.         if (particles.lifetime<=1f) extra++;
    5.         referenceObjectsCache = new PlaygroundFollower[cacheSize>0? cacheSize : particles.particleCount+Mathf.CeilToInt(particles.particleCount*extra)];
    6.         for (int i = 0; i<referenceObjectsCache.Length; i++) {
    7.             GameObject clone = (GameObject)Instantiate(referenceObject);
    8.             referenceObjectsCache[i] = new PlaygroundFollower(clone.transform, clone, clone.GetComponent<TrailRenderer>(), 0, 0);
    9.             referenceObjectsCache[i].transform.parent = followerParent;
    10.             if (referenceObjectsCache[i].trailRenderer!=null)
    11.                 referenceObjectsCache[i].trailRenderer.time = 0;
    12.             referenceObjectsCache[i].gameObject.SetActive(false);
    13.         }
    14.     }
    15.  
    if you can explain it will be help full
     
  43. chelnok

    chelnok

    Joined:
    Jul 2, 2012
    Posts:
    680
    Nice! While you're working with new features, would you like to add vector field? Just like perlin you already got there, but b/w texture. With manipulators it's possible to do quite a lot, but with vector field things would be easier, faster and more precise.
     
  44. save

    save

    Joined:
    Nov 21, 2008
    Posts:
    744
    Hi @jalapen0! You can use Playground Splines for any type of data, so using it to for example construct a mesh along one could be done like this:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. namespace PlaygroundSplines {
    5.     /// <summary>
    6.     /// The PlaygroundSplineMesh class lets you create a mesh from a Playground Spline.
    7.     /// </summary>
    8.     [ExecuteInEditMode()]
    9.     public class PlaygroundSplineMesh : MonoBehaviour {
    10.  
    11.         public PlaygroundSpline spline;
    12.         [Range(2,1000)]
    13.         public int points = 100;
    14.         [Range(.01f, 100f)]
    15.         public float width = 1f;
    16.  
    17.         int prevPoints;
    18.         float prevWidth;
    19.  
    20.         void OnEnable ()
    21.         {
    22.             if (GetComponent<Renderer>() == null)
    23.                 gameObject.AddComponent<MeshRenderer>();
    24.             if (spline == null)
    25.                 spline = GetComponent<PlaygroundSpline>();
    26.             if (spline != null)
    27.                 BuildSplineMesh(spline, points, width);
    28.             prevPoints = points;
    29.             prevWidth = width;
    30.         }
    31.  
    32.         void Update ()
    33.         {
    34.             if (prevPoints!=points || prevWidth!=width)
    35.             {
    36.                 if (spline != null)
    37.                     BuildSplineMesh(spline, points, width);
    38.                 prevPoints = points;
    39.                 prevWidth = width;
    40.             }
    41.         }
    42.        
    43.         public void BuildSplineMesh (PlaygroundSpline spline, int points, float width)
    44.         {
    45.             if (points<2)
    46.                 points = 2;
    47.             int totalVertices = points*2;
    48.             MeshFilter _mf = GetComponent<MeshFilter>()!=null? GetComponent<MeshFilter>() : gameObject.AddComponent<MeshFilter>();
    49.             Mesh _m = new Mesh();
    50.             Vector3[] verts = new Vector3[totalVertices];
    51.             Vector2[] uvs = new Vector2[totalVertices];
    52.             int[] tris = new int[(points-1)*6];
    53.  
    54.             // Construct the mesh
    55.             for (int i = 0; i<points; i++)
    56.             {
    57.                 // Create a normalized time value
    58.                 float t = (i*1f)/(points-1);
    59.                 float tNext = ((i*1f)+1)/(points-1);
    60.                 if (t>=1)
    61.                     t = .9999f;
    62.                 if (tNext>=1)
    63.                     tNext = .99999f;
    64.  
    65.                 // Get the current and next position from the spline on time
    66.                 Vector3 currentPosition = spline.GetPoint (t);
    67.                 Vector3 nextPosition = spline.GetPoint (tNext);
    68.  
    69.                 // Raycast down to determine up direction (especially practical for roads / rivers)
    70.                 RaycastHit hit;
    71.                 Vector3 up = Vector3.up;
    72.                 if (Physics.Raycast (currentPosition, Vector3.down, out hit))
    73.                     up = hit.normal;
    74.  
    75.                 // Create two width point references based on current and next position
    76.                 Vector3 dir = (Vector3.Cross(up, nextPosition - currentPosition)).normalized;
    77.                 Vector3 lPoint = currentPosition + dir * (width/2);
    78.                 Vector3 rPoint = currentPosition - dir * (width/2);
    79.  
    80.                 // Draw debug
    81.                 Debug.DrawLine(lPoint, rPoint);
    82.  
    83.                 verts[i*2] = lPoint;
    84.                 verts[(i*2)+1] = rPoint;
    85.                 uvs[i*2] = new Vector2(t,0);
    86.                 uvs[(i*2)+1] = new Vector2(t,1f);
    87.  
    88.                 if (i>0)
    89.                 {
    90.                     int triIndex = (i-1)*6;
    91.                     int vertIndex = i*2;
    92.  
    93.                     tris[triIndex] = vertIndex-2;
    94.                     tris[triIndex+1] = vertIndex-1;
    95.                     tris[triIndex+2] = vertIndex;
    96.                     tris[triIndex+3] = vertIndex;
    97.                     tris[triIndex+4] = vertIndex-1;
    98.                     tris[triIndex+5] = vertIndex+1;
    99.                 }
    100.             }
    101.  
    102.             // Assign the data to the mesh
    103.             _m.vertices = verts;
    104.             _m.uv = uvs;
    105.             _m.triangles = tris;
    106.             _m.RecalculateNormals();
    107.  
    108.             // Assign the mesh to the MeshFilter
    109.             _mf.mesh = _m;
    110.         }
    111.     }
    112. }
    Looks like this:


    Hi! You can do this by creating a particle system that is using Source: Skinned World Object and enable it once you want to vaporize the mesh. The Sources video guide might be of help when setting it up. There's no color information yet for skinned meshes, but that is coming. :)

    Hey! The reason why you're getting the error is because transform.position can only be get and set from the main thread. PP is multithreaded and is calculated async from the rest of MonoBehaviour, this make the event delegates execute on a different thread hence why you can't get the position. Playground uses a transform wrapper internally (PlaygroundTransform) which updates on the main thread to tackle this scenario.

    Here's an updated version of PlaygroundFollow.cs which will send events on the main thread:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. namespace ParticlePlayground {
    6.     public class PlaygroundFollow : MonoBehaviour {
    7.  
    8.         /// <summary>
    9.         /// Reference to the particle system.
    10.         /// </summary>
    11.         public PlaygroundParticlesC particles;
    12.         /// <summary>
    13.         /// Reference to an existing GameObject. This will be cloned to be used on every particle.
    14.         /// </summary>
    15.         public GameObject referenceObject;
    16.         /// <summary>
    17.         /// The lifetime of the followers. Set 0 to follow during each particle's individual lifetime.
    18.         /// </summary>
    19.         public float followerLifetime = 0;
    20.         /// <summary>
    21.         /// The size of the cache. Set 0 to automatically set the needed amount.
    22.         /// </summary>
    23.         public int cacheSize = 0;
    24.  
    25.         /// <summary>
    26.         /// Determines if the Playground Followers should broadcast to any event listeners.
    27.         /// </summary>
    28.         public bool sendEvents = false;
    29.         public event OnPlaygroundFollower followerEventBirth;
    30.         public event OnPlaygroundFollower followerEventDeath;
    31.  
    32.         /// <summary>
    33.         /// The reference to the trail renderer (if existing)
    34.         /// </summary>
    35.         TrailRenderer referenceTrailRenderer;
    36.         /// <summary>
    37.         /// If the follower has a Trail Renderer component, this sets trail time once the follower is active again.
    38.         /// </summary>
    39.         float trailTime = 0;
    40.         /// <summary>
    41.         /// The list of active followers.
    42.         /// </summary>
    43.         List<PlaygroundFollower> followers = new List<PlaygroundFollower>();
    44.         /// <summary>
    45.         /// As Playground is running in a multithreaded environment we need a queue for instantiation (which cannot be called from a different thread).
    46.         /// </summary>
    47.         List<PlaygroundFollower> waitingFollowers = new List<PlaygroundFollower>();
    48.         PlaygroundFollower[] referenceObjectsCache;
    49.         PlaygroundFollower[] queue = new PlaygroundFollower[0];
    50.         int cacheIndex = 0;
    51.  
    52.         PlaygroundEventC birthEvent;
    53.         PlaygroundEventC deathEvent;
    54.         Transform followerParent;
    55.  
    56.         void Awake () {
    57.  
    58.             // Create and setup the birth event
    59.             birthEvent = PlaygroundC.CreateEvent(particles);
    60.             birthEvent.broadcastType = EVENTBROADCASTC.EventListeners;
    61.             birthEvent.eventType = EVENTTYPEC.Birth;
    62.  
    63.             // Create and setup the death event
    64.             deathEvent = PlaygroundC.CreateEvent(particles);
    65.             deathEvent.broadcastType = EVENTBROADCASTC.EventListeners;
    66.             deathEvent.eventType = EVENTTYPEC.Death;
    67.  
    68.             // Hook up the event listeners to the delegates
    69.             birthEvent.particleEvent += OnParticleDidBirth;
    70.             deathEvent.particleEvent += OnParticleDidDie;
    71.  
    72.             // Create a parent for all followers (for Hierarchy convenience)
    73.             followerParent = new GameObject("Followers").transform;
    74.             followerParent.parent = transform;
    75.  
    76.             // Get the trail renderer (if available) and its time
    77.             referenceTrailRenderer = referenceObject.GetComponent<TrailRenderer>();
    78.             if (referenceTrailRenderer!=null)
    79.                 trailTime = referenceTrailRenderer.time;
    80.  
    81.             // Set an extra amount of followers if required (a trail's time will exceed a particle's)
    82.             int extra = followerLifetime<=0?
    83.                 Mathf.CeilToInt(Mathf.Abs (particles.lifetime-trailTime)+(trailTime-particles.lifetime))+2 :
    84.                     Mathf.CeilToInt(Mathf.Abs (particles.lifetime-followerLifetime)+(followerLifetime-particles.lifetime))+2 ;
    85.             if (particles.lifetime<=1f) extra++;
    86.  
    87.             // Create the follower cache (this will be iterated through and reused whenever a particle rebirths)
    88.             referenceObjectsCache = new PlaygroundFollower[cacheSize>0? cacheSize : particles.particleCount+Mathf.CeilToInt(particles.particleCount*extra)];
    89.             for (int i = 0; i<referenceObjectsCache.Length; i++) {
    90.                 GameObject clone = (GameObject)Instantiate(referenceObject);
    91.                 referenceObjectsCache[i] = new PlaygroundFollower(clone.transform, clone, clone.GetComponent<TrailRenderer>(), 0, 0);
    92.                 referenceObjectsCache[i].transform.parent = followerParent;
    93.                 if (referenceObjectsCache[i].trailRenderer!=null)
    94.                     referenceObjectsCache[i].trailRenderer.time = 0;
    95.                 referenceObjectsCache[i].gameObject.SetActive(false);
    96.             }
    97.         }
    98.  
    99.         /// <summary>
    100.         /// Event listener for particle birth.
    101.         /// </summary>
    102.         /// <param name="particle">Particle.</param>
    103.         void OnParticleDidBirth (PlaygroundEventParticle particle) {
    104.             waitingFollowers.Add (new PlaygroundFollower(null, null, null, followerLifetime<=0? particle.totalLifetime+trailTime : followerLifetime, particle.particleId));
    105.         }
    106.  
    107.         /// <summary>
    108.         /// Event listener for particle death.
    109.         /// </summary>
    110.         /// <param name="particle">Particle.</param>
    111.         void OnParticleDidDie (PlaygroundEventParticle particle) {
    112.             int followerId = GetFollowerWithId(particle.particleId);
    113.             if (followerId<0) return;
    114.             followers[followerId].enabled = false;
    115.         }
    116.  
    117.         /// <summary>
    118.         /// Gets the follower which has the passed in particle identifier.
    119.         /// </summary>
    120.         /// <returns>The follower with particle identifier.</returns>
    121.         /// <param name="particleId">Particle identifier.</param>
    122.         int GetFollowerWithId (int particleId) {
    123.             float lowestLife = 999f;
    124.             int returnIndex = -1;
    125.             for (int i = 0; i<followers.Count; i++)
    126.                 if (followers[i].particleId==particleId && followers[i].lifetime<lowestLife)
    127.                     returnIndex = i;
    128.             return returnIndex;
    129.         }
    130.  
    131.         void Update () {
    132.             if (waitingFollowers.Count>0) {
    133.                 queue = waitingFollowers.ToArray();
    134.             }
    135.         }
    136.         void LateUpdate () {
    137.             UpdateFollowers();
    138.         }
    139.  
    140.         void UpdateFollowers () {
    141.  
    142.             // Follow, lifetime, remove
    143.             for (int i = 0; i<followers.Count; i++) {
    144.  
    145.                 // Follow particle
    146.                 if (followers[i].enabled)
    147.                     followers[i].transform.position = particles.particleCache[followers[i].particleId].position;
    148.            
    149.                 // Subtract lifetime
    150.                 followers[i].lifetime -= Time.deltaTime;
    151.  
    152.                 // Remove if no lifetime left
    153.                 if (followers[i].lifetime<=0) {
    154.                     RemoveFollower(i);
    155.                     continue;
    156.                 }
    157.             }
    158.  
    159.             // Add any waiting followers to the live follower list. The waiting list may change during iteration!
    160.             if (queue.Length>0) {
    161.                 if (queue.Length!=waitingFollowers.Count) return;
    162.                 int inQueueThisFrame = waitingFollowers.Count;
    163.  
    164.                 foreach (PlaygroundFollower wFollower in queue) {
    165.                     AddFollower (wFollower, followers.Count-1);
    166.                 }
    167.                 if (inQueueThisFrame==waitingFollowers.Count)
    168.                     waitingFollowers = new List<PlaygroundFollower>();
    169.                 else waitingFollowers.RemoveRange (0, inQueueThisFrame-1);
    170.                 queue = new PlaygroundFollower[0];
    171.             }
    172.         }
    173.  
    174.         void AddFollower (PlaygroundFollower follower, int i) {
    175.             if (follower==null) return;
    176.             followers.Add (follower.Clone());
    177.             followers[followers.Count-1].enabled = true;
    178.             followers[followers.Count-1].gameObject = referenceObjectsCache[cacheIndex].gameObject;
    179.             followers[followers.Count-1].gameObject.SetActive(true);
    180.             followers[followers.Count-1].transform = referenceObjectsCache[cacheIndex].transform;
    181.             followers[followers.Count-1].trailRenderer = referenceObjectsCache[cacheIndex].trailRenderer;
    182.             followers[followers.Count-1].particleId = follower.particleId;
    183.             followers[followers.Count-1].transform.position = particles.playgroundCache.position[followers[followers.Count-1].particleId];
    184.             if (followers[followers.Count-1].trailRenderer!=null)
    185.                 followers[followers.Count-1].trailRenderer.time = trailTime;
    186.             if (sendEvents && followerEventBirth!=null)
    187.                 followerEventBirth(followers[followers.Count-1]);
    188.             NextCacheIndex();
    189.         }
    190.  
    191.         void RemoveFollower (int i) {
    192.             if (sendEvents && followerEventDeath!=null)
    193.                 followerEventDeath(followers[i]);
    194.             followers[i].enabled = false;
    195.             if (followers[i].trailRenderer!=null)
    196.                 followers[i].trailRenderer.time = 0;
    197.             followers[i].gameObject.SetActive(false);
    198.             followers.RemoveAt(i);
    199.         }
    200.  
    201.         void NextCacheIndex () {
    202.             cacheIndex = (cacheIndex+1)%referenceObjectsCache.Length;
    203.         }
    204.     }
    205.  
    206.     /// <summary>
    207.     /// Playground follower class.
    208.     /// </summary>
    209.     public class PlaygroundFollower {
    210.         public bool enabled = true;
    211.         public float lifetime;
    212.         public Transform transform;
    213.         public GameObject gameObject;
    214.         public TrailRenderer trailRenderer;
    215.         public int particleId;
    216.  
    217.         /// <summary>
    218.         /// Initializes a new instance of the <see cref="PlaygroundFollower"/> class.
    219.         /// </summary>
    220.         /// <param name="setTransform">Transform to reposition.</param>
    221.         /// <param name="setLifetime">Start lifetifetime.</param>
    222.         /// <param name="setParticleId">Particle identifier to follow.</param>
    223.         public PlaygroundFollower (Transform setTransform, GameObject setGameObject, TrailRenderer setTrailRenderer, float setLifetime, int setParticleId) {
    224.             transform = setTransform;
    225.             gameObject = setGameObject;
    226.             trailRenderer = setTrailRenderer;
    227.             lifetime = setLifetime;
    228.             particleId = setParticleId;
    229.         }
    230.  
    231.         /// <summary>
    232.         /// Clones this instance.
    233.         /// </summary>
    234.         public PlaygroundFollower Clone () {
    235.             return new PlaygroundFollower (transform, gameObject, trailRenderer, lifetime, particleId);
    236.         }
    237.     }
    238.  
    239.     /// <summary>
    240.     /// Event delegate for sending a PlaygroundFollower to any event listeners.
    241.     /// </summary>
    242.     public delegate void OnPlaygroundFollower(PlaygroundFollower follower);
    243. }
    Here's an example of usage:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using ParticlePlayground;
    4.  
    5. public class DoSomethingAtFollowerEvent : MonoBehaviour {
    6.  
    7.     public PlaygroundFollow followScript;
    8.  
    9.     void OnEnable ()
    10.     {
    11.         if (followScript != null)
    12.         {
    13.             followScript.sendEvents = true;
    14.             followScript.followerEventBirth += OnFollowerBirth;
    15.             followScript.followerEventDeath += OnFollowerDeath;
    16.         }
    17.     }
    18.  
    19.     void OnDisable ()
    20.     {
    21.         if (followScript != null)
    22.         {
    23.             followScript.followerEventBirth -= OnFollowerBirth;
    24.             followScript.followerEventDeath -= OnFollowerDeath;
    25.         }
    26.     }
    27.  
    28.     void OnFollowerBirth (PlaygroundFollower follower)
    29.     {
    30.         Debug.Log ("Follower "+follower.gameObject.name+" is now tracking the particle id: "+follower.particleId);
    31.     }
    32.  
    33.     void OnFollowerDeath (PlaygroundFollower follower)
    34.     {
    35.         Debug.Log ("Follower "+follower.gameObject.name+" died at position "+follower.transform.position);
    36.     }
    37. }
    38.  
    Another option is to set your particle system to update on the main thread through Advanced > Misc > Particle Thread Method: No Threads. But that is not preferable if you have loads of particles/followers. :)

    Let me know how it works out!

    Hi! The event system is using event delegates to broadcast data from particles, this is covered in the manual (page 27) and in Unity's tutorials. This can let us know when a particle birth, dies, collides etc. in a preferable performant environment (as there may be a lot of simulated particles that requires tracking of some sort). The most straightforward way without using events would be to just get the position from the particle immediately through the particle cache, then you could add your own logic for when a particle is dead. Here's a much simpler version of the follower script without events:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using ParticlePlayground;
    4.  
    5. public class PlaygroundFollowSimple : MonoBehaviour {
    6.  
    7.     public PlaygroundParticlesC particles;
    8.     public Transform followerReference;
    9.     public bool rotateTowardsDirection = false;
    10.     private Transform[] _followers;
    11.  
    12.     void Start () {
    13.         _followers = new Transform[particles.particleCount];
    14.         for (int i = 0; i<particles.particleCount; i++) {
    15.             _followers[i] = ((Transform)Instantiate (followerReference)).transform;
    16.             _followers[i].parent = transform;
    17.         }
    18.         followerReference.gameObject.SetActive (false);
    19.     }
    20.  
    21.     void Update () {
    22.  
    23.         // Return if lengths doesn't match
    24.         if (_followers.Length != particles.particleCount)
    25.             return;
    26.  
    27.         for (int i = 0; i<_followers.Length; i++) {
    28.  
    29.             // Position each follower
    30.             _followers[i].position = particles.particleCache[i].position;
    31.  
    32.             // Rotate each follower
    33.             if (rotateTowardsDirection && particles.playgroundCache.velocity[i] != Vector3.zero)
    34.                 _followers[i].rotation = Quaternion.LookRotation(particles.playgroundCache.velocity[i]);
    35.         }
    36.     }
    37. }
    38.  
    Regarding your questions about the follower script;
    The followers are cached up and then populated by the amount necessary for reuse. As a trail has lifetime that is exceeding a particle's lifetime a larger amount of follower cache is needed than the actual amount of particles. When a particle dies it will detach from its current follower, when it births later it will queue up with another waiting follower.

    Some comments added:
    Code (CSharp):
    1.         // Create a parent for all followers (for Hierarchy convenience)
    2.         followerParent = new GameObject("Followers").transform;
    3.         followerParent.parent = transform;
    4.  
    5.         // Get the trail renderer (if available) and its time
    6.         referenceTrailRenderer = referenceObject.GetComponent<TrailRenderer>();
    7.         if (referenceTrailRenderer!=null)
    8.             trailTime = referenceTrailRenderer.time;
    9.  
    10.         // Set an extra amount of followers if required (a trail's time will exceed a particle's)
    11.         int extra = followerLifetime<=0?
    12.             Mathf.CeilToInt(Mathf.Abs (particles.lifetime-trailTime)+(trailTime-particles.lifetime))+2 :
    13.                 Mathf.CeilToInt(Mathf.Abs (particles.lifetime-followerLifetime)+(followerLifetime-particles.lifetime))+2 ;
    14.         if (particles.lifetime<=1f) extra++;
    15.  
    16.         // Create the follower cache (this will be iterated through and reused whenever a particle rebirths)
    17.         referenceObjectsCache = new PlaygroundFollower[cacheSize>0? cacheSize : particles.particleCount+Mathf.CeilToInt(particles.particleCount*extra)];
    18.         for (int i = 0; i<referenceObjectsCache.Length; i++) {
    19.             GameObject clone = (GameObject)Instantiate(referenceObject);
    20.             referenceObjectsCache[i] = new PlaygroundFollower(clone.transform, clone, clone.GetComponent<TrailRenderer>(), 0, 0);
    21.             referenceObjectsCache[i].transform.parent = followerParent;
    22.             if (referenceObjectsCache[i].trailRenderer!=null)
    23.                 referenceObjectsCache[i].trailRenderer.time = 0;
    24.             referenceObjectsCache[i].gameObject.SetActive(false);
    25.         }
    Agreed! This is in the pipeline, I don't have a clear ETA yet though. :)
     
    Last edited: Jul 24, 2015
    chelnok and MikeTon like this.
  45. save

    save

    Joined:
    Nov 21, 2008
    Posts:
    744
    This is starting to become something rather detached from particles.. :) However since there might be more interested in using the Playground Spline for other things, here's an updated example for rivers (supports looping and perlin noise):



    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. namespace PlaygroundSplines {
    5.     /// <summary>
    6.     /// The PlaygroundSplineMesh class lets you create a mesh from a Playground Spline.
    7.     /// </summary>
    8.     [ExecuteInEditMode()]
    9.     public class PlaygroundSplineMesh : MonoBehaviour {
    10.  
    11.         public PlaygroundSpline spline;
    12.         [Range(2,1000)]
    13.         public int points = 100;
    14.         [Range(.01f, 100f)]
    15.         public float width = 1f;
    16.         public bool noise = false;
    17.         [Range(.01f, 100f)]
    18.         public float noiseStrength = 1f;
    19.         public Vector2 noiseScale = new Vector2(1f,1f);
    20.  
    21.         int prevPoints;
    22.         float prevWidth;
    23.         bool prevNoise;
    24.         float prevNoiseStrength;
    25.         Vector2 prevNoiseScale;
    26.  
    27.         void OnEnable ()
    28.         {
    29.             if (GetComponent<Renderer>() == null)
    30.                 gameObject.AddComponent<MeshRenderer>();
    31.             if (spline == null)
    32.                 spline = GetComponent<PlaygroundSpline>();
    33.             if (spline != null)
    34.                 BuildSplineMesh(spline, points, width);
    35.             SetVals();
    36.         }
    37.  
    38.         void Update ()
    39.         {
    40.             if (prevPoints!=points || prevWidth!=width || prevNoise!=noise || noise && (prevNoiseScale!=noiseScale || prevNoiseStrength!=noiseStrength))
    41.             {
    42.                 if (spline != null)
    43.                     BuildSplineMesh(spline, points, width);
    44.                 SetVals ();
    45.             }
    46.         }
    47.  
    48.         void SetVals ()
    49.         {
    50.             prevPoints = points;
    51.             prevWidth = width;
    52.             prevNoise = noise;
    53.             prevNoiseStrength = noiseStrength;
    54.             prevNoiseScale = noiseScale;
    55.         }
    56.        
    57.         public void BuildSplineMesh (PlaygroundSpline spline, int points, float width)
    58.         {
    59.             if (points<2)
    60.                 points = 2;
    61.             int totalVertices = points*2;
    62.             MeshFilter _mf = GetComponent<MeshFilter>()!=null? GetComponent<MeshFilter>() : gameObject.AddComponent<MeshFilter>();
    63.             Mesh _m = new Mesh();
    64.             Vector3[] verts = new Vector3[totalVertices];
    65.             Vector2[] uvs = new Vector2[totalVertices];
    66.             int[] tris = new int[(points-1)*6];
    67.  
    68.             // Construct the mesh
    69.             for (int i = 0; i<points; i++)
    70.             {
    71.                 // Create a normalized time value
    72.                 float t = (i*1f)/(points-1);
    73.                 float tNext = ((i*1f)+1)/(points-1);
    74.                 if (t>=1 && !spline.Loop)
    75.                     t = .9999f;
    76.                 if (tNext>=1 && !spline.Loop)
    77.                     tNext = .99999f;
    78.  
    79.                 // Get the current and next position from the spline on time
    80.                 Vector3 currentPosition = spline.GetPoint (t);
    81.                 Vector3 nextPosition = spline.GetPoint (tNext);
    82.  
    83.                 // Raycast down to determine up direction (especially practical for roads / rivers)
    84.                 RaycastHit hit;
    85.                 Vector3 up = Vector3.up;
    86.                 if (Physics.Raycast (currentPosition, Vector3.down, out hit))
    87.                     up = hit.normal;
    88.  
    89.                 // Calculate noise (if enabled)
    90.                 float noiseAmountL = noise? Mathf.PerlinNoise((t%1f)*noiseScale.x, 0)*noiseStrength : 0;
    91.                 float noiseAmountR = noise? Mathf.PerlinNoise((t%1f)*noiseScale.y, 0)*noiseStrength : 0;
    92.  
    93.                 // Create two width point references based on current and next position
    94.                 Vector3 dir = (Vector3.Cross(up, nextPosition - currentPosition)).normalized;
    95.                 Vector3 lPoint = currentPosition + dir * ((width/2)+noiseAmountL);
    96.                 Vector3 rPoint = currentPosition - dir * ((width/2)+noiseAmountR);
    97.  
    98.                 // Draw debug
    99.                 Debug.DrawLine(lPoint, rPoint);
    100.  
    101.                 verts[i*2] = lPoint;
    102.                 verts[(i*2)+1] = rPoint;
    103.                 uvs[i*2] = new Vector2(t,0);
    104.                 uvs[(i*2)+1] = new Vector2(t,1f);
    105.  
    106.                 if (i>0)
    107.                 {
    108.                     int triIndex = (i-1)*6;
    109.                     int vertIndex = i*2;
    110.  
    111.                     tris[triIndex] = vertIndex-2;
    112.                     tris[triIndex+1] = vertIndex-1;
    113.                     tris[triIndex+2] = vertIndex;
    114.                     tris[triIndex+3] = vertIndex;
    115.                     tris[triIndex+4] = vertIndex-1;
    116.                     tris[triIndex+5] = vertIndex+1;
    117.                 }
    118.             }
    119.  
    120.             // Assign the data to the mesh
    121.             _m.vertices = verts;
    122.             _m.uv = uvs;
    123.             _m.triangles = tris;
    124.             _m.RecalculateNormals();
    125.  
    126.             // Assign the mesh to the MeshFilter
    127.             _mf.mesh = _m;
    128.         }
    129.     }
    130. }
     
    Arkade and chelnok like this.
  46. MikeTon

    MikeTon

    Joined:
    Jan 10, 2013
    Posts:
    57
    Thanks for the in depth explanation. You rock! And I will give this a try
     
  47. save

    save

    Joined:
    Nov 21, 2008
    Posts:
    744
    For the love of all mobile devs, Playground is about to get its own managed thread pool which improves GC allocations.


    There's quite a lot other nice features soon about to go into beta, if you're interested in testing send your email in a pm!
     
    MikeTon and hopeful like this.
  48. Arkade

    Arkade

    Joined:
    Oct 11, 2012
    Posts:
    655
    Hi
    Bought PP ages ago and only just started using last night. Great time waste so far (kind'a good / kind'a bad IYKWIM ;) )

    Ran into 2 odd little bugs while working on a player death/reincarnate effect:
    1. I instantiated a Preset (Smokey Turbulence)
    2. Used with SkinnedWorldObject for a while (the player)
    3. Created a prefab of it (by dragging into Project area)
    4. worked with it a bit more
    5. decided I wanted to use WorldObject (not skinned)
    6. Switched to WorldObject in the GUI, assigned
    7. Disabled (not destroyed) the player object (it's still there, just not enabled)
    8. Ran. Result:
      NullReferenceException: Object reference not set to an instance of an object
      at ParticlePlayground.SkinnedWorldObject.Initialize () [0x00044] in D:\Users\User\Dev\Unity3D\ParticlePlaygrondExperiments\Assets\Standard Assets\Particle Playground\Scripts\PlaygroundC.cs:3104

      at ParticlePlayground.PlaygroundParticlesC.Start () [0x000c7] in D:\Users\User\Dev\Unity3D\ParticlePlaygrondExperiments\Assets\Standard Assets\Particle Playground\Scripts\PlaygroundParticlesC.cs:6290

      (Filename: Assets/Standard Assets/Particle Playground/Scripts/PlaygroundC.cs Line: 3104)
    9. Switched back to SkinnedWorldObject, selected the object field, pressed Delete (see it blank)
    10. Returned to WorldObject.
    11. Ran. Result: Same error!?
    12. Switched back to SkinnedWorldObject, see the previously-deleted field has a value again?!
    So 2 potential bugs:
    1. serialization of deletion of SkinnedWorldObject reference is somehow broken.
    2. An inactive SkinnedMesh causes a NPE (without, at least, better error -- I understand error might be only useful recourse.)

    I've not investigated in depth but, assuming (1) isn't a commonly seen error, I wonder if the prefab stuff is interfering with serialization?

    I worked-around (2) (which also works-around (1)) by modifying PlaygroundParticlesC.cs:6289: if (skinnedWorldObject!=null && skinnedWorldObject.transform!=null && skinnedWorldObject.gameObject.activeInHierarchy) // added the last clause

    Hope that is enough to help & fix,
    Thanks, Rupert.
     
  49. Async0x42

    Async0x42

    Joined:
    Mar 3, 2015
    Posts:
    104
    Hey, I was tracking down an issue with my Scene getting 'touched' each time it was run, causing it to show in git as modified. It looks like Particle Playground serializes variables such as:

    Code (CSharp):
    1. isReadyForThreadedCalculations
    2. localTime
    3. localDeltaTime
    4. simulationStarted
    Maybe some others that I haven't noticed being saved in the scene during play. I've made them NonSerialized, and everything seems to work properly. If there's no coding reason for it, could this get changed so fields that aren't needed, aren't serialized to the scene in future versions so that the scene isn't modified each time it's run?

    Of course, if it does affect anything, let me know, thanks for any help!
     
  50. save

    save

    Joined:
    Nov 21, 2008
    Posts:
    744
    Thanks a lot for the detailed description, a very specific but annoying bug. :) This will be fixed in the upcoming version!

    Yes go ahead and NonSerialize them. This is corrected and will follow along the next update. These values were never meant to serialize and is causing some distress for repository projects, especially when it comes to scene merging. Sorry about that!
     
    Async0x42 and Arkade like this.