Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Is there any way to render a ParticleSystem -immediately- ? [Solved]

Discussion in 'General Graphics' started by Loius, Apr 19, 2018.

  1. Loius

    Loius

    Joined:
    Aug 16, 2012
    Posts:
    546
    I'm building a shooter, and some projectiles start with particle systems attached to them (set to emit over distance). This happens when the projectile has special effects like Fire or Poison. The projectile is created during Unity's Update step.

    When the player fires a projectile, it is required that the projectile move one frame immediately, so that it is visible emerging from the weapon. If I do not move the projectile forward by one frame, then projectiles appear to lag one frame behind when the player fired them. I do this by using SendMessage on all objects in the projectile - Update, LateUpdate, RenderNow(0) - this renders everything that isn't a ParticleSystem, but particles aren't visible for the first frame of movement.

    Is there some way to force a particle system to render or otherwise "trick" particles into appearing in the indicated space immediately? I can't have them be delayed by one frame, it has to be rendered immediately. If there's no way to do this with particles, is there some kind of replacement effect I could use that will render immediately?

    Here's a screenshot of the frame immediately following me firing a very fast projectile (the issue happens on everything, but this is the one where it's most noticeable):

    upload_2018-4-19_10-51-7.png
     
    DragonCoder likes this.
  2. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    7,850
    Yes, this should be possible.
    The problem is not that the particle system is not rendering but that no particles have been emitted yet because you are using emit by distance and the system has not moved yet. You can work around this by using Simulate on the particle system to force an update and create some artificial movement. Spawn the particle system at the gun barrel, call Simulate, move the system to the position you want and then call Simulate again, it should now generate particles between the 2 points. Make sure to call Play at the end so the system continues to emit as Simulate will Pause the system.
    Alternatively, you could also manually emit some particles and position them yourself.
     
    Loius and richardkettlewell like this.
  3. Loius

    Loius

    Joined:
    Aug 16, 2012
    Posts:
    546
    Thanks for the prompt reply - that looks promising, but I wasn't able to get it to behave. It looks like the system is just simulating two frames at the end point (using the Rate Over Time emission and not the Rate Over Distance emission), rather than running from the spawn point to the end point.

    upload_2018-4-19_13-22-28.png

    This code occurs during Unity's Update, immediately after the projectile has spawned and attached particles to itself.

    Code (CSharp):
    1.         // P = the projectile
    2.         // Order = list of all transforms in the projectile & its descendants
    3.  
    4.         var systems = p.GetComponentsInChildren<ParticleSystem>();
    5.         Debug.Log("Simulating " + systems.Length + " systems @ " + p.transform.position);
    6.         foreach (var ps in systems) {
    7.             ps.Simulate(Time.deltaTime);
    8.             ps.Play(); // tried with and without this line
    9.         }
    10.  
    11.         // FIXME: Start may not have run by this point, but calling it twice is bad
    12.         // foreach (var el in order) if (el) el.SendMessage("Start", SendMessageOptions.DontRequireReceiver);
    13.         // Provide a custom step for objects that use Soco's update (e.g. constantmotion) or which need to update before other projectile components
    14.         foreach (var el in order) if (el) el.SendMessage("GH_InitialProjectileUpdate", SendMessageOptions.DontRequireReceiver);
    15.         // Enforce a Unity update to drive e.g. SmoothTrails and motion components
    16.         foreach (var el in order) if (el) el.SendMessage("Update", SendMessageOptions.DontRequireReceiver);
    17.         // Enforce a late-update as well (for complex motion components like Helix)
    18.         foreach (var el in order) if (el) el.SendMessage("LateUpdate", SendMessageOptions.DontRequireReceiver);
    19.  
    20.         Debug.Log("Second particle simulation @ " + p.transform.position);
    21.         foreach (var ps in systems) {
    22.             ps.Simulate(Time.deltaTime);
    23.             ps.Play();
    24.         }
    25.  
    26.         // Forcibly render the object for e.g. trails
    27.         foreach (var el in order) if (el) el.SendMessage("RenderNow", 0, SendMessageOptions.DontRequireReceiver);
    28.  
    29.         UnityEditor.EditorApplication.isPaused = true;
    30.  
    I'll try manually emitting the particles. This one just looked easier so I was hopeful :)
     
  4. Loius

    Loius

    Joined:
    Aug 16, 2012
    Posts:
    546
    Manual emission did the trick!

    upload_2018-4-19_13-53-12.png

    It needs some straightening up (for some reason it did 100 particles per unit instead of 10), but it's doing what I need. :D


    Code (CSharp):
    1.         // P = the projectile
    2.         // Order = list of all transforms in the projectile & its descendants
    3.  
    4.         var systems = p.GetComponentsInChildren<ParticleSystem>();
    5.         var from = p.transform.position;
    6.  
    7.         // FIXME: Start may not have run by this point, but calling it twice is bad
    8.         // foreach (var el in order) if (el) el.SendMessage("Start", SendMessageOptions.DontRequireReceiver);
    9.         // Provide a custom step for objects that use Soco's update (e.g. constantmotion) or which need to update before other projectile components
    10.         foreach (var el in order) if (el) el.SendMessage("GH_InitialProjectileUpdate", SendMessageOptions.DontRequireReceiver);
    11.         // Enforce a Unity update to drive e.g. SmoothTrails and motion components
    12.         foreach (var el in order) if (el) el.SendMessage("Update", SendMessageOptions.DontRequireReceiver);
    13.         // Enforce a late-update as well (for complex motion components like Helix)
    14.         foreach (var el in order) if (el) el.SendMessage("LateUpdate", SendMessageOptions.DontRequireReceiver);
    15.  
    16.         Debug.Log("Particle emission from " + from + " to " + p.transform.position);
    17.         foreach (var ps in systems) if (ps) SimParticles(from, p.transform.position, ps);
    18.  
    19.         // Forcibly render the object for e.g. trails
    20.         foreach (var el in order) if (el) el.SendMessage("RenderNow", 0, SendMessageOptions.DontRequireReceiver);
    21.  
    22.         UnityEditor.EditorApplication.isPaused = true;
    23.  
    24.         /**/
    25.        
    26.         return p;
    27.     }
    28.  
    29.     void SimParticles(Vector3 from, Vector3 to, ParticleSystem ps) {
    30.         var dif = to - from;
    31.         var dist = dif.magnitude;
    32.  
    33.         var perDist = ps.emission.rateOverDistanceMultiplier * ps.emission.rateOverDistance.constant;
    34.         var pCount = Mathf.RoundToInt(dist * perDist);
    35.         Debug.Log("Emitting " + perDist + " particles per distance (total:" + pCount + ") on " + ps, ps);
    36.  
    37.         ps.Emit(pCount);
    38.  
    39.         var particles = new ParticleSystem.Particle[ps.main.maxParticles];
    40.         var pExist = ps.GetParticles(particles);
    41.         if (pExist != pCount) {
    42.             Debug.LogWarning("Unexpected - particle count != desired count");
    43.         }
    44.  
    45.         float pExistF = pExist;
    46.         for (int i = pExist - 1; i >= 0; i--) {
    47.             // position = from + a ratio of the travel vector "dif"
    48.             particles[i].position = from + dif * (i / pExistF);
    49.         }
    50.  
    51.         ps.SetParticles(particles, pExist);
    52.     }
    53.  
     
    karl_jones likes this.