Search Unity

Free Script - Particle Systems in UI Screen Space Overlay

Discussion in 'UGUI & TextMesh Pro' started by glennpow, May 26, 2016.

  1. glennpow

    glennpow

    Joined:
    Jan 30, 2012
    Posts:
    56
    I haven't been too active in the Unity forums, but I am a professional dev who has been working with Unity for several years. I've recently been working with the superb new team at Section Studios in LA, to create a stellar new mobile action-RPG.

    We've been confronting and tackling many interesting issues, trying to create cutting edge effects and gameplay for the mobile platform. My goal is to eventually publish a blog with some of my results, but for now I thought I'd start by posting one particular solution that I've been proud of recently.

    Currently, the only way to get Particle Systems to show up in UI is to create Camera-Space canvases, which can lead to undesirable effects or complications in design. I decided to scrap together a solution that would allow these particles to be rendered as true UI elements (in the UI render order pipeline). The following script is the result of this experiment. It may not be perfect yet (there are still some Unity bugs preventing 100% support for PS features), but it does seem to handle most cases, and is fairly performant.

    INSTRUCTIONS: Simply create a ParticleSystem on an empty RectTransform object in your UI hierarchy, and set it to your UI layer. Then add this script and it should initialize itself with that system and begin reflecting modifications accordingly. NOTE - that it does attempt to hide the actual ParticleSystem particles by using applying a "UI/Particles/Hidden" shader to them. This isn't really necessary, but will prevent them from showing up (in duplicate) in the Scene View. You can either create this shader which should just "discard" and not render anything, or you can comment out those couple lines which apply that shader.

    I'd love to hear any feedback on how to improve this script's support or performance. Again, I hope to start conversations about many of the issues we've been tackling in our endeavors, so I look forward to beginning this dialog. Please comment with any thoughts.
    Thanks!

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3. using System.Collections.Generic;
    4.  
    5. [ExecuteInEditMode]
    6. [RequireComponent(typeof(CanvasRenderer))]
    7. [RequireComponent(typeof(ParticleSystem))]
    8. public class UIParticleSystem : MaskableGraphic {
    9.  
    10.     public Texture particleTexture;
    11.     public Sprite particleSprite;
    12.  
    13.     private Transform _transform;
    14.     private ParticleSystem _particleSystem;
    15.     private ParticleSystem.Particle[] _particles;
    16.     private UIVertex[] _quad = new UIVertex[4];
    17.     private Vector4 _uv = Vector4.zero;
    18.     private ParticleSystem.TextureSheetAnimationModule _textureSheetAnimation;
    19.     private int _textureSheetAnimationFrames;
    20.     private Vector2 _textureSheedAnimationFrameSize;
    21.  
    22.     public override Texture mainTexture {
    23.         get {
    24.             if (particleTexture) {
    25.                 return particleTexture;
    26.             }
    27.  
    28.             if (particleSprite) {
    29.                 return particleSprite.texture;
    30.             }
    31.                
    32.             return null;
    33.         }
    34.     }
    35.  
    36.     protected bool Initialize() {
    37.         // initialize members
    38.         if (_transform == null) {
    39.             _transform = transform;
    40.         }
    41.         if (_particleSystem == null) {
    42.             _particleSystem = GetComponent<ParticleSystem>();
    43.  
    44.             if (_particleSystem == null) {
    45.                 return false;
    46.             }
    47.  
    48.             // automatically set material to UI/Particles/Hidden shader, and get previous texture
    49.             ParticleSystemRenderer renderer = _particleSystem.GetComponent<ParticleSystemRenderer>();
    50.             if (renderer == null) {
    51.                 renderer = _particleSystem.gameObject.AddComponent<ParticleSystemRenderer>();
    52.             }
    53.             Material currentMaterial = renderer.sharedMaterial;
    54.             if (currentMaterial && currentMaterial.HasProperty("_MainTex")) {
    55.                 particleTexture = currentMaterial.mainTexture;
    56.             }
    57.             Material material = new Material(Shader.Find("UI/Particles/Hidden")); // TODO - You should create this discard shader
    58.             if (Application.isPlaying) {
    59.                 renderer.material = material;
    60.             }
    61.             #if UNITY_EDITOR
    62.             else {
    63.                 material.hideFlags = HideFlags.DontSave;
    64.                 renderer.sharedMaterial = material;
    65.             }
    66.             #endif
    67.  
    68.             // automatically set scaling
    69.             _particleSystem.scalingMode = ParticleSystemScalingMode.Hierarchy;
    70.  
    71.             _particles = null;
    72.         }
    73.         if (_particles == null) {
    74.             _particles = new ParticleSystem.Particle[_particleSystem.maxParticles];
    75.         }
    76.  
    77.         // prepare uvs
    78.         if (particleTexture) {
    79.             _uv = new Vector4(0, 0, 1, 1);
    80.         } else if (particleSprite) {
    81.             _uv = UnityEngine.Sprites.DataUtility.GetOuterUV(particleSprite);
    82.         }
    83.  
    84.         // prepare texture sheet animation
    85.         _textureSheetAnimation = _particleSystem.textureSheetAnimation;
    86.         _textureSheetAnimationFrames = 0;
    87.         _textureSheedAnimationFrameSize = Vector2.zero;
    88.         if (_textureSheetAnimation.enabled) {
    89.             _textureSheetAnimationFrames = _textureSheetAnimation.numTilesX * _textureSheetAnimation.numTilesY;
    90.             _textureSheedAnimationFrameSize = new Vector2(1f / _textureSheetAnimation.numTilesX, 1f / _textureSheetAnimation.numTilesY);
    91.         }
    92.  
    93.         return true;
    94.     }
    95.  
    96.     protected override void Awake() {
    97.         base.Awake();
    98.  
    99.         if (!Initialize()) {
    100.             enabled = false;
    101.         }
    102.     }
    103.  
    104.     protected override void OnPopulateMesh(VertexHelper vh) {
    105.         #if UNITY_EDITOR
    106.         if (!Application.isPlaying) {
    107.             if (!Initialize()) {
    108.                 return;
    109.             }
    110.         }
    111.         #endif
    112.  
    113.         // prepare vertices
    114.         vh.Clear();
    115.  
    116.         if (!gameObject.activeInHierarchy) {
    117.             return;
    118.         }
    119.  
    120.         // iterate through current particles
    121.         int count = _particleSystem.GetParticles(_particles);
    122.  
    123.         for (int i = 0; i < count; ++i) {
    124.             ParticleSystem.Particle particle = _particles[i];
    125.  
    126.             // get particle properties
    127.             Vector2 position = (_particleSystem.simulationSpace == ParticleSystemSimulationSpace.Local ? particle.position : _transform.InverseTransformPoint(particle.position));
    128.             float rotation = -particle.rotation * Mathf.Deg2Rad;
    129.             float rotation90 = rotation + Mathf.PI / 2;
    130.             Color32 color = particle.GetCurrentColor(_particleSystem);
    131.             float size = particle.GetCurrentSize(_particleSystem) * 0.5f;
    132.  
    133.             // apply scale
    134.             if (_particleSystem.scalingMode == ParticleSystemScalingMode.Shape) {
    135.                 position /= canvas.scaleFactor;
    136.             }
    137.  
    138.             // apply texture sheet animation
    139.             Vector4 particleUV = _uv;
    140.             if (_textureSheetAnimation.enabled) {
    141.                 float frameProgress = 1 - (particle.lifetime / particle.startLifetime);
    142. //                float frameProgress = textureSheetAnimation.frameOverTime.curveMin.Evaluate(1 - (particle.lifetime / particle.startLifetime)); // TODO - once Unity allows MinMaxCurve reading
    143.                 frameProgress = Mathf.Repeat(frameProgress * _textureSheetAnimation.cycleCount, 1);
    144.                 int frame = 0;
    145.  
    146.                 switch (_textureSheetAnimation.animation) {
    147.  
    148.                 case ParticleSystemAnimationType.WholeSheet:
    149.                     frame = Mathf.FloorToInt(frameProgress * _textureSheetAnimationFrames);
    150.                     break;
    151.  
    152.                 case ParticleSystemAnimationType.SingleRow:
    153.                     frame = Mathf.FloorToInt(frameProgress * _textureSheetAnimation.numTilesX);
    154.  
    155.                     int row = _textureSheetAnimation.rowIndex;
    156. //                    if (textureSheetAnimation.useRandomRow) { // FIXME - is this handled internally by rowIndex?
    157. //                        row = Random.Range(0, textureSheetAnimation.numTilesY, using: particle.randomSeed);
    158. //                    }
    159.                     frame += row * _textureSheetAnimation.numTilesX;
    160.                     break;
    161.  
    162.                 }
    163.  
    164.                 frame %= _textureSheetAnimationFrames;
    165.  
    166.                 particleUV.x = (frame % _textureSheetAnimation.numTilesX) * _textureSheedAnimationFrameSize.x;
    167.                 particleUV.y = Mathf.FloorToInt(frame / _textureSheetAnimation.numTilesX) * _textureSheedAnimationFrameSize.y;
    168.                 particleUV.z = particleUV.x + _textureSheedAnimationFrameSize.x;
    169.                 particleUV.w = particleUV.y + _textureSheedAnimationFrameSize.y;
    170.             }
    171.  
    172.             _quad[0] = UIVertex.simpleVert;
    173.             _quad[0].color = color;
    174.             _quad[0].uv0 = new Vector2(particleUV.x, particleUV.y);
    175.  
    176.             _quad[1] = UIVertex.simpleVert;
    177.             _quad[1].color = color;
    178.             _quad[1].uv0 = new Vector2(particleUV.x, particleUV.w);
    179.  
    180.             _quad[2] = UIVertex.simpleVert;
    181.             _quad[2].color = color;
    182.             _quad[2].uv0 = new Vector2(particleUV.z, particleUV.w);
    183.  
    184.             _quad[3] = UIVertex.simpleVert;
    185.             _quad[3].color = color;
    186.             _quad[3].uv0 = new Vector2(particleUV.z, particleUV.y);
    187.  
    188.             if (rotation == 0) {
    189.                 // no rotation
    190.                 Vector2 corner1 = new Vector2(position.x - size, position.y - size);
    191.                 Vector2 corner2 = new Vector2(position.x + size, position.y + size);
    192.  
    193.                 _quad[0].position = new Vector2(corner1.x, corner1.y);
    194.                 _quad[1].position = new Vector2(corner1.x, corner2.y);
    195.                 _quad[2].position = new Vector2(corner2.x, corner2.y);
    196.                 _quad[3].position = new Vector2(corner2.x, corner1.y);
    197.             } else {
    198.                 // apply rotation
    199.                 Vector2 right = new Vector2(Mathf.Cos(rotation), Mathf.Sin(rotation)) * size;
    200.                 Vector2 up = new Vector2(Mathf.Cos(rotation90), Mathf.Sin(rotation90)) * size;
    201.  
    202.                 _quad[0].position = position - right - up;
    203.                 _quad[1].position = position - right + up;
    204.                 _quad[2].position = position + right + up;
    205.                 _quad[3].position = position + right - up;
    206.             }
    207.  
    208.             vh.AddUIVertexQuad(_quad);
    209.         }
    210.     }
    211.  
    212.     void Update() {
    213.         if (Application.isPlaying) {
    214.             // unscaled animation within UI
    215.             _particleSystem.Simulate(Time.unscaledDeltaTime, false, false);
    216.  
    217.             SetAllDirty();
    218.         }
    219.     }
    220.  
    221.     #if UNITY_EDITOR
    222.     void LateUpdate() {
    223.         if (!Application.isPlaying) {
    224.             SetAllDirty();
    225.         }
    226.     }
    227.     #endif
    228.  
    229. }
    230.  
     
    vozcn, Navid-j, Saeed-Barari and 37 others like this.
  2. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Looks awesome @glennpow Would you be OK with me adding this to UI Extensions project? (link below)
     
    rkvieira, SweatyChair and josiperez like this.
  3. glennpow

    glennpow

    Joined:
    Jan 30, 2012
    Posts:
    56
    @SimonDarksideJ, I was undoubtedly inspired by some of your posts and your work on the UI Extensions. One of my goals was actually to reach out to you personally, for feedback and to see if you might find it useful. You can certainly integrate this script into your library. Please let me know if you devise any improvements.
    And thank you for all the support you've provided over the years!
     
  4. TheValar

    TheValar

    Joined:
    Nov 12, 2012
    Posts:
    760
    I've been running into issues with particle systems + UGUI lately so I'm definitely gonna check this out!!! Thanks a lot!
     
  5. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Worth noting, that this is only supported in Unity 5.3 and upwards (thankfully there's a new UNITY_5_3_OR_NEWER preprocessor directive to make that easier)
    So the UI Extensions project got it's first ever #IF :D

    Added @glennpow for the support, consider it added. Testing tonight for the updated release.

    P.S. you can always just fork the project and add these yourself through a Pull Request :D But I'm just as happy to put them in as well (no barriers to contributing)
     
  6. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    @glennpow can you upload the "ui/particles/hidden" shader you used please?

    OK, hitting problems. Added it to the UI and it shows up in the scene view (with the shader commented out) but it doesn't show up in the Game view.

    Another issue is that it always renders behind other UI elements. E.G. I placed the particle script object as a child to an image and no matter what I did / changed it would always render behind.

    Thoughts?
     
    Last edited: May 28, 2016
  7. glennpow

    glennpow

    Joined:
    Jan 30, 2012
    Posts:
    56
    Well, it's likely only showing up in the scene view because it's showing the default particle system rendering (same as it would without the UIParticleSystem script attached).
    When you attach the UIParticleSystem script it will force the particle system to switch to "Hierarchy" Scaling Mode. This is necessary to render correctly in the UI, but will likely mean that the particles are very very small in the UI view. You will need to scale them up rather large, so that they are at Screen-Pixel Size. The speeds will also have to be scaled up accordingly.

    It still isn't necessary to fix your issue, but the hidden particle shader is below.
    Please let me know if you still have problems.
    Cheers

    Code (CSharp):
    1. Shader "UI/Particles/Hidden"
    2. {
    3.     Properties
    4.     {
    5.     }
    6.     SubShader
    7.     {
    8.         Tags { "Queue"="Geometry" "RenderType"="Opaque" }
    9.         Cull Off Lighting Off ZWrite Off Fog { Mode Off }
    10.         LOD 100
    11.  
    12.         Pass
    13.         {
    14.             CGPROGRAM
    15.             #pragma vertex vert
    16.             #pragma fragment frag
    17.  
    18.             struct v2f
    19.             {
    20.                 float4 vertex : SV_POSITION;
    21.             };
    22.  
    23.             v2f vert ()
    24.             {
    25.                 v2f o;
    26.                 o.vertex = fixed4(0, 0, 0, 0);
    27.                 return o;
    28.             }
    29.            
    30.             fixed4 frag (v2f i) : SV_Target
    31.             {
    32.                 discard;
    33.                 return fixed4(0, 0, 0, 0);
    34.             }
    35.             ENDCG
    36.         }
    37.     }
    38. }
    39.  
     
  8. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    OK, got that working now. For some reason without that shader it doesn't work in runtime, very odd. will have to test more on that.

    Another issue I seem to run in to is that no matter the change to size/ duration or anything to do with the default particles, it doesn't change what is rendered on the ui layer, very odd. Changing the shape seems to work but over all, no changes to the actual particle system take effect. Any ideas?
     
  9. glennpow

    glennpow

    Joined:
    Jan 30, 2012
    Posts:
    56
    Hm, that's strange. I definitely see changes reflected immediately in the UI in my projects.
    Is there anything showing up in the UI at all? Are the particles animating? The SetAllDirty call in the LateUpdate method should be triggering the UI to rebuild the mesh each frame (even when not playing, since there is the ExecuteInEditMode attribute on the script).

    You could send me a sample project, and I could try it out on my side.
     
  10. Deleted User

    Deleted User

    Guest

    Very Good! 1111111111111111.png
     
  11. glennpow

    glennpow

    Joined:
    Jan 30, 2012
    Posts:
    56
    @ds123, Nice!
    Here are a few more shaders that you can create materials from and then set them as the property on the UIParticleSystem component. Then the particles can be drawn additively, etc.
     

    Attached Files:

    Kuptsevych-Yuriy likes this.
  12. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Added those plus some simple scripts to use them for the 1.1 UI Extensions update @glennpow :D
    Finishing touches to ship it now.
     
    glennpow likes this.
  13. ortin

    ortin

    Joined:
    Jan 13, 2013
    Posts:
    221
    Looks cool in theory, thanks for sharing!
    But wouldn't it be vastly inferior in performance to native particles due to canvas batch sorting?
     
  14. glennpow

    glennpow

    Joined:
    Jan 30, 2012
    Posts:
    56
    @ortin, Yes it's absolutely not as performant as native particles, but Unity as of yet offers no clean solution for this particular situation. Note, that if your project can take advantage of using Screen Space Camera or World Space UI, then you should definitely go that route. This UIParticleSystem script is intended solely for situations where you only want a single Screen Space Overlay UI, and want the particles to be rendered in the sorted UI pipeline.
    One other option that I'd considered was to render particles using an extra camera and render texture, and just place that single texture into the UI, but it obviously isn't quite as dynamic.
     
    super77gg likes this.
  15. ortin

    ortin

    Joined:
    Jan 13, 2013
    Posts:
    221
    Yeah, I now insert particles using sorting layers and additional canvases which is PITA to manage. :)
    But I'll give it a try when I have time, may be performance is not that bad if you put it in its own Canvas.
     
  16. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    As the script simply converts the particle stream in to vertex quads, there isn't too much impact. Just the normal overhead to convert it.
     
  17. glennpow

    glennpow

    Joined:
    Jan 30, 2012
    Posts:
    56
    @SimonDarksideJ and all,
    I've updated the script so that particle movement works properly despite the screen resolutions, and to handle the hidden shader application a little cleaner. Note that the scaleMode is automatically set to Local instead of Hierarchy. I learned this is actually the right choice. Any existing UIParticleSystems will need to have their velocities modified to account for the change though.

    Sorry, I haven't branched the UI repo, in order to issue pull requests. I'm somewhat short on time at the moment. I've included the file here for now.
    Hopefully it helps. Cheers!

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3. using System.Collections.Generic;
    4.  
    5. [ExecuteInEditMode]
    6. [RequireComponent(typeof(CanvasRenderer))]
    7. [RequireComponent(typeof(ParticleSystem))]
    8. public class UIParticleSystem : MaskableGraphic {
    9.  
    10.     public Texture particleTexture;
    11.     public Sprite particleSprite;
    12.  
    13.     private Transform _transform;
    14.     private ParticleSystem _particleSystem;
    15.     private ParticleSystem.Particle[] _particles;
    16.     private UIVertex[] _quad = new UIVertex[4];
    17.     private Vector4 _uv = Vector4.zero;
    18.     private ParticleSystem.TextureSheetAnimationModule _textureSheetAnimation;
    19.     private int _textureSheetAnimationFrames;
    20.     private Vector2 _textureSheedAnimationFrameSize;
    21.  
    22.     public override Texture mainTexture {
    23.         get {
    24.             if (particleTexture) {
    25.                 return particleTexture;
    26.             }
    27.  
    28.             if (particleSprite) {
    29.                 return particleSprite.texture;
    30.             }
    31.                
    32.             return null;
    33.         }
    34.     }
    35.  
    36.     protected bool Initialize() {
    37.         // initialize members
    38.         if (_transform == null) {
    39.             _transform = transform;
    40.         }
    41.  
    42.         // prepare particle system
    43.         ParticleSystemRenderer renderer = GetComponent<ParticleSystemRenderer>();
    44.         bool setParticleSystemMaterial = false;
    45.  
    46.         if (_particleSystem == null) {
    47.             _particleSystem = GetComponent<ParticleSystem>();
    48.  
    49.             if (_particleSystem == null) {
    50.                 return false;
    51.             }
    52.  
    53.             // get current particle texture
    54.             if (renderer == null) {
    55.                 renderer = _particleSystem.gameObject.AddComponent<ParticleSystemRenderer>();
    56.             }
    57.             Material currentMaterial = renderer.sharedMaterial;
    58.             if (currentMaterial && currentMaterial.HasProperty("_MainTex")) {
    59.                 particleTexture = currentMaterial.mainTexture;
    60.             }
    61.  
    62.             // automatically set scaling
    63.             _particleSystem.scalingMode = ParticleSystemScalingMode.Local;
    64.  
    65.             _particles = null;
    66.             setParticleSystemMaterial = true;
    67.         } else {
    68.             if (Application.isPlaying) {
    69.                 setParticleSystemMaterial = (renderer.material == null);
    70.             }
    71.             #if UNITY_EDITOR
    72.             else {
    73.                 setParticleSystemMaterial = (renderer.sharedMaterial == null);
    74.             }
    75.             #endif
    76.         }
    77.  
    78.         // automatically set material to UI/Particles/Hidden shader, and get previous texture
    79.         if (setParticleSystemMaterial) {
    80.             Material material = new Material(Shader.Find("UI/Particles/Hidden"));
    81.             if (Application.isPlaying) {
    82.                 renderer.material = material;
    83.             }
    84.             #if UNITY_EDITOR
    85.             else {
    86.                 material.hideFlags = HideFlags.DontSave;
    87.                 renderer.sharedMaterial = material;
    88.             }
    89.             #endif
    90.         }
    91.  
    92.         // prepare particles array
    93.         if (_particles == null) {
    94.             _particles = new ParticleSystem.Particle[_particleSystem.maxParticles];
    95.         }
    96.  
    97.         // prepare uvs
    98.         if (particleTexture) {
    99.             _uv = new Vector4(0, 0, 1, 1);
    100.         } else if (particleSprite) {
    101.             _uv = UnityEngine.Sprites.DataUtility.GetOuterUV(particleSprite);
    102.         }
    103.  
    104.         // prepare texture sheet animation
    105.         _textureSheetAnimation = _particleSystem.textureSheetAnimation;
    106.         _textureSheetAnimationFrames = 0;
    107.         _textureSheedAnimationFrameSize = Vector2.zero;
    108.         if (_textureSheetAnimation.enabled) {
    109.             _textureSheetAnimationFrames = _textureSheetAnimation.numTilesX * _textureSheetAnimation.numTilesY;
    110.             _textureSheedAnimationFrameSize = new Vector2(1f / _textureSheetAnimation.numTilesX, 1f / _textureSheetAnimation.numTilesY);
    111.         }
    112.  
    113.         return true;
    114.     }
    115.  
    116.     protected override void Awake() {
    117.         base.Awake();
    118.  
    119.         if (!Initialize()) {
    120.             enabled = false;
    121.         }
    122.     }
    123.  
    124.     protected override void OnPopulateMesh(VertexHelper vh) {
    125.         #if UNITY_EDITOR
    126.         if (!Application.isPlaying) {
    127.             if (!Initialize()) {
    128.                 return;
    129.             }
    130.         }
    131.         #endif
    132.  
    133.         // prepare vertices
    134.         vh.Clear();
    135.  
    136.         if (!gameObject.activeInHierarchy) {
    137.             return;
    138.         }
    139.  
    140.         // iterate through current particles
    141.         int count = _particleSystem.GetParticles(_particles);
    142.  
    143.         for (int i = 0; i < count; ++i) {
    144.             ParticleSystem.Particle particle = _particles[i];
    145.  
    146.             // get particle properties
    147.             Vector2 position = (_particleSystem.simulationSpace == ParticleSystemSimulationSpace.Local ? particle.position : _transform.InverseTransformPoint(particle.position));
    148.             float rotation = -particle.rotation * Mathf.Deg2Rad;
    149.             float rotation90 = rotation + Mathf.PI / 2;
    150.             Color32 color = particle.GetCurrentColor(_particleSystem);
    151.             float size = particle.GetCurrentSize(_particleSystem) * 0.5f;
    152.  
    153.             // apply scale
    154.             if (_particleSystem.scalingMode == ParticleSystemScalingMode.Shape) {
    155.                 position /= canvas.scaleFactor;
    156.             }
    157.  
    158.             // apply texture sheet animation
    159.             Vector4 particleUV = _uv;
    160.             if (_textureSheetAnimation.enabled) {
    161.                 float frameProgress = 1 - (particle.lifetime / particle.startLifetime);
    162. //                float frameProgress = textureSheetAnimation.frameOverTime.curveMin.Evaluate(1 - (particle.lifetime / particle.startLifetime)); // TODO - once Unity allows MinMaxCurve reading
    163.                 frameProgress = Mathf.Repeat(frameProgress * _textureSheetAnimation.cycleCount, 1);
    164.                 int frame = 0;
    165.  
    166.                 switch (_textureSheetAnimation.animation) {
    167.  
    168.                 case ParticleSystemAnimationType.WholeSheet:
    169.                     frame = Mathf.FloorToInt(frameProgress * _textureSheetAnimationFrames);
    170.                     break;
    171.  
    172.                 case ParticleSystemAnimationType.SingleRow:
    173.                     frame = Mathf.FloorToInt(frameProgress * _textureSheetAnimation.numTilesX);
    174.  
    175.                     int row = _textureSheetAnimation.rowIndex;
    176. //                    if (textureSheetAnimation.useRandomRow) { // FIXME - is this handled internally by rowIndex?
    177. //                        row = Random.Range(0, textureSheetAnimation.numTilesY, using: particle.randomSeed);
    178. //                    }
    179.                     frame += row * _textureSheetAnimation.numTilesX;
    180.                     break;
    181.  
    182.                 }
    183.  
    184.                 frame %= _textureSheetAnimationFrames;
    185.  
    186.                 particleUV.x = (frame % _textureSheetAnimation.numTilesX) * _textureSheedAnimationFrameSize.x;
    187.                 particleUV.y = Mathf.FloorToInt(frame / _textureSheetAnimation.numTilesX) * _textureSheedAnimationFrameSize.y;
    188.                 particleUV.z = particleUV.x + _textureSheedAnimationFrameSize.x;
    189.                 particleUV.w = particleUV.y + _textureSheedAnimationFrameSize.y;
    190.             }
    191.  
    192.             _quad[0] = UIVertex.simpleVert;
    193.             _quad[0].color = color;
    194.             _quad[0].uv0 = new Vector2(particleUV.x, particleUV.y);
    195.  
    196.             _quad[1] = UIVertex.simpleVert;
    197.             _quad[1].color = color;
    198.             _quad[1].uv0 = new Vector2(particleUV.x, particleUV.w);
    199.  
    200.             _quad[2] = UIVertex.simpleVert;
    201.             _quad[2].color = color;
    202.             _quad[2].uv0 = new Vector2(particleUV.z, particleUV.w);
    203.  
    204.             _quad[3] = UIVertex.simpleVert;
    205.             _quad[3].color = color;
    206.             _quad[3].uv0 = new Vector2(particleUV.z, particleUV.y);
    207.  
    208.             if (rotation == 0) {
    209.                 // no rotation
    210.                 Vector2 corner1 = new Vector2(position.x - size, position.y - size);
    211.                 Vector2 corner2 = new Vector2(position.x + size, position.y + size);
    212.  
    213.                 _quad[0].position = new Vector2(corner1.x, corner1.y);
    214.                 _quad[1].position = new Vector2(corner1.x, corner2.y);
    215.                 _quad[2].position = new Vector2(corner2.x, corner2.y);
    216.                 _quad[3].position = new Vector2(corner2.x, corner1.y);
    217.             } else {
    218.                 // apply rotation
    219.                 Vector2 right = new Vector2(Mathf.Cos(rotation), Mathf.Sin(rotation)) * size;
    220.                 Vector2 up = new Vector2(Mathf.Cos(rotation90), Mathf.Sin(rotation90)) * size;
    221.  
    222.                 _quad[0].position = position - right - up;
    223.                 _quad[1].position = position - right + up;
    224.                 _quad[2].position = position + right + up;
    225.                 _quad[3].position = position + right - up;
    226.             }
    227.  
    228.             vh.AddUIVertexQuad(_quad);
    229.         }
    230.     }
    231.  
    232.     void Update() {
    233.         if (Application.isPlaying) {
    234.             // unscaled animation within UI
    235.             _particleSystem.Simulate(Time.unscaledDeltaTime, false, false);
    236.  
    237.             SetAllDirty();
    238.         }
    239.     }
    240.  
    241.     #if UNITY_EDITOR
    242.     void LateUpdate() {
    243.         if (!Application.isPlaying) {
    244.             SetAllDirty();
    245.         }
    246.     }
    247.     #endif
    248.  
    249. }
    250.  
     
    Brockoala, ArmanK11 and dreamer- like this.
  18. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    No worries @glennpow I'll grab this and add it to the latest source (after testing of course :D)
     
  19. Willemvd

    Willemvd

    Joined:
    Mar 19, 2014
    Posts:
    8
    Thanks for this man - helped alot!
     
  20. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Updated script included in the 1.1.1 update. Currently in source and will package with the next release @glennpow
     
  21. super77gg

    super77gg

    Joined:
    Sep 25, 2014
    Posts:
    46
    This is incredible - I've been looking for something like this since the updated UI system was integrated. Thanks for all the hard work and for sharing!
     
    SimonDarksideJ likes this.
  22. Willemvd

    Willemvd

    Joined:
    Mar 19, 2014
    Posts:
    8
    Good day @glennpow !

    Firstly, thank you very much for your excellent work with the UI extensions library - really useful!

    Im having an odd issue with it tho - both in windows and mobile. In the editor (and during runtime) everything works like a charm - but when i build to mobile or windows the built version doesnt show the particles. I can see quads being emitted (but theres no image - its just a quad with nothing in it (uploaded a screenshot so you can see)

    It must be something im doing wrong - any ideas?

    Thanks again and kind regards
     

    Attached Files:

  23. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
  24. Musidian

    Musidian

    Joined:
    Aug 19, 2016
    Posts:
    4
    Same issue with my project.
    Did you fix it or have any ideas?
    Thanks.
     
  25. tswalk

    tswalk

    Joined:
    Jul 27, 2013
    Posts:
    1,109
    sorry for the gif size, still getting use to linux editing... seems to work.


     
  26. TheValar

    TheValar

    Joined:
    Nov 12, 2012
    Posts:
    760
    I was having the same issue with particles not working in my android build. Based on the Logs it looked like
    Code (CSharp):
    1. Material material = new Material(Shader.Find("UI/Particles/Hidden"));
    was causing an error because the shader wasn't being included in the build.

    Solved it by going to Edit > Project Settings > Graphics and adding UI/Partices/Hidden to the list of Always Included Shaders.
     
    Fenikkel, sama-van, Cogniad and 2 others like this.
  27. WaitingWater

    WaitingWater

    Joined:
    Aug 20, 2016
    Posts:
    2
    Thanks for the solution~ : )
     
  28. WaitingWater

    WaitingWater

    Joined:
    Aug 20, 2016
    Posts:
    2
    Thanks for sharing!!!
    While there is a little flaw,now we get the double pass calls :(
    Any ideals to reduce the setpass calls?
    And is it necessary to create a New Material each time?We can use singleton material so that we can use dynamic batching.:)
     
  29. Fdowns

    Fdowns

    Joined:
    Sep 29, 2013
    Posts:
    2
    Hi

    Thanks for the great share.

    I seem to be having issues with sprite sheet animations. The animation sequence works perfectly on the Shuriken particle system until I add the "UI Particle System" script. After adding the script the animation sequence is no longer correct.

    for example, if I use a 3x3 sprite sheet with the following numbers;
    1 2 3
    4 5 6
    7 8 9

    The particle system plays back the following sequence;
    7 8 9
    4 5 6
    1 2 3

    UPDATE:

    After spending hours trying to disassemble and reassemble sprite sheets I realized that the easiest workaround for this problem is to simply flip the sprite sheet vertically using any photo/image editor. Obviously this turns the individual sprites upside down, but for general particle effects this has no noticeable side-effect.

    Regards
     
    Last edited: Sep 6, 2016
  30. ChimeraSW

    ChimeraSW

    Joined:
    Jul 24, 2013
    Posts:
    28
    As always, I found this post only in the middle of writing my own implementation :p

    - Stop doing the whole hidden shader thing; you can set enabled = false on the ParticleSystemRenderer component at runtime which will remove the original particle system from all rendering (but it still runs just fine)

    - Move as much of your per-vertex calculations to a custom vertex shader (especially the rotations). Huge reduction in CPU costs when dealing with lots of particles (this whole problem is CPU bound).

    - GetCurrentColor() is insanely CPU expensive; I'm still trying to figure out why / make it faster :/

    I'm still improving performance on mine (10,000 particles on this old nexus 7 getting 13 fps at the time of this writing). Will try and remember to post back with more suggestions if/when I get it any faster.
     
    wanwanyahoo likes this.
  31. JonAM

    JonAM

    Joined:
    Sep 29, 2016
    Posts:
    1
    Thanks a lot for sharing. This should be part of Unity :)
     
    Honorsoft and afshin_a_1 like this.
  32. kieec

    kieec

    Joined:
    Jan 4, 2013
    Posts:
    4
    Hello~

    Does this UI particle system support stretched billboard particles?
    Thanks!
     
    TuneNX and NeatWolf like this.
  33. BillyMFT

    BillyMFT

    Joined:
    Mar 14, 2013
    Posts:
    178
    This is so awesome. Thanks for sharing it.
     
  34. NeatWolf

    NeatWolf

    Joined:
    Sep 27, 2013
    Posts:
    924
    I'd love to know about this as well.
     
  35. Zarlang

    Zarlang

    Joined:
    Jan 31, 2012
    Posts:
    33
    Hi,

    So, with glennpow's permission (thanks Glenn), I've added a modification of this feature to my asset in the store.

    I've "port" the basic particle shaders to run with the code.

    I'm sharing the shaders, the zip also contains my version of the class as well, it has small modifications and it's updated to Unity 5.5+

    Anyway, wanted to give back something :)
     

    Attached Files:

    Last edited: Feb 6, 2017
  36. gangafinti

    gangafinti

    Joined:
    Jun 13, 2013
    Posts:
    19
    Hi, I am working on a project where I could use this. Is it oke to use comercially? If it is thank you so much. If its not I totally understand to.
     
  37. glennpow

    glennpow

    Joined:
    Jan 30, 2012
    Posts:
    56
    Yes, go ahead. I can't support you if there are issues in a live product, but you can use freely.
     
  38. ninuxw

    ninuxw

    Joined:
    Jul 8, 2014
    Posts:
    26
    This is amazing, thanks!
     
  39. Edan-Smith

    Edan-Smith

    Joined:
    Jan 21, 2015
    Posts:
    27
    Works nicely, but for some reason X and Y rotation doesn't work, any idea?
     
  40. Firreskuggan

    Firreskuggan

    Joined:
    Jan 21, 2017
    Posts:
    1
    No, it doesn't appear so. I'm trying to stretch the particles with that setting but it shows no effect.
    Also, is there anyway you can modify the class so that you can render the particles behind or in front UI elements as desired?
     
  41. aasiq

    aasiq

    Joined:
    Dec 29, 2016
    Posts:
    16
    Particle is working fine in iOS, but the color which I set in start color field becomes lighter in the particle output. Am I doing anything wrong or Is there a workaround for this? @glennpow . Screen Shot 2017-05-25 at 7.13.48 PM.png pariticle.png
     
  42. Toyari

    Toyari

    Joined:
    Oct 28, 2014
    Posts:
    1
  43. KladimirVladne

    KladimirVladne

    Joined:
    Jul 29, 2013
    Posts:
    1
  44. Lulucifer

    Lulucifer

    Joined:
    Jul 8, 2012
    Posts:
    358
    Great work both with this script and Unity-UI -Extensions
     
  45. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Thanks @Zarlang
    I take you too are ok with taking your update in the UI Extensions project?
    Just about to hit "publish" on the projects BIG update.
     
    Zarlang likes this.
  46. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    While merging @Zarlang s updated works (assuming he agrees :D)
    I had to make the following change to fully support Texture Sheet animation
    Code (CSharp):
    1.                 if (textureSheetAnimation.enabled)
    2.                 {
    3. #if UNITY_5_5_OR_NEWER
    4.                     float frameProgress = 1 - (particle.remainingLifetime / particle.startLifetime);
    5.  
    6.                     if (textureSheetAnimation.frameOverTime.curveMin != null)
    7.                     {
    8.                         frameProgress = textureSheetAnimation.frameOverTime.curveMin.Evaluate(1 - (particle.remainingLifetime / particle.startLifetime));
    9.                     }
    10.                     else if (textureSheetAnimation.frameOverTime.curve != null)
    11.                     {
    12.                         frameProgress = textureSheetAnimation.frameOverTime.curve.Evaluate(1 - (particle.remainingLifetime / particle.startLifetime));
    13.                     }
    14.                     else if (textureSheetAnimation.frameOverTime.constant > 0)
    15.                     {
    16.                         frameProgress = textureSheetAnimation.frameOverTime.constant - (particle.remainingLifetime / particle.startLifetime);
    17.                     }
    18. #else
    19.                     float frameProgress = 1 - (particle.lifetime / particle.startLifetime);
    20. #endif
    21.  
    22.                     frameProgress = Mathf.Repeat(frameProgress * textureSheetAnimation.cycleCount, 1);
    23.                     int frame = 0;
    As if you used anything other than random between two curves, the script crashes.

    Only thing that doesn't work atm now, is the Trails and I can't see an easy solution for that as it appears to use it's own renderer.
     
    ArmanK11 and Zarlang like this.
  47. Zarlang

    Zarlang

    Joined:
    Jan 31, 2012
    Posts:
    33
    @SimonDarksideJ sure thing. I've used the extensions in many different projects. It's an honour to contribute to it.
     
  48. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    306
    For me, no matter what I do, my UI will always be rendered in front of my particle system. I tried all the scripts I could find, now I tried yours.

    Any ideas what could be going wrong? Overlay Canvas, Layers are UI on Canvas and PS, I tried to adjust the sort order differently, no change though. Tried different cameras, new sorting layers, nothing seems to work :(
     
  49. pevicentini

    pevicentini

    Joined:
    Jul 14, 2017
    Posts:
    5
    First of all, great Job!. I'm having an issue. In editor, particles render perfectly, but in builds (Tested Web, Android and Native exe/app) not rendering at all. What am i doing wrong?. Thanks in advance!

    EDIT: Foud the solution!, i forgot to use the included shaders for properly rendering. Thanks.
     
    Last edited: Sep 7, 2017
  50. shareming

    shareming

    Joined:
    Oct 28, 2014
    Posts:
    1
    good job !
    and is there a way to remove GC ?