Search Unity

What is a better approach to doing projectile trail colliders?

Discussion in '2D' started by G8S, Apr 30, 2021.

  1. G8S

    G8S

    Joined:
    Feb 7, 2017
    Posts:
    18
    I am working on a 2D game where the player and enemies can cast projectiles. Some of these projectiles have a particle system attached to them that leaves a trail behind that disappears after X seconds. This trail is supposed to represent an AOE effect that will apply a debuff to anything that enters it. The projectiles can move in a variety of ways, so straight movement isn't always guaranteed.

    What is an efficient way of building an area that trails behind the projectile that will allow me to trigger an event when an enemy enters it? I essentially want to build an area that is like what the TrailRenderer creates and then use that to trigger OnEnter/OnStay events.

    I have tried a few things that I will outline below, but all of them seem to have their drawbacks/is far too slow and I would love any suggestions on a better path forward.

    Job System that batches up the particles into groups of N and checks if any are inside of the target's box collider
    I created a job system that batches up all of the particle positions and all of the entity positions that they can interact with, and then checks if any particle is inside of any entity's box collider. This works pretty well, but when I pushed it to unreasonable limits, performance dropped quite a bit. Since each enemy checks all particles then when I get to upwards of 60 enemies and thousands of particles, performance takes quite a hit. This is the best approach so far, and I have an idea of how to maybe further optimize it, but I would love any input/ideas.

    Track the trajectory of the projectile and create points that slowly move outward and feed these into the PolygonCollider2D
    I have not fully fleshed this out but: I have a script that creates a point every X seconds (default is 0.1) as the projectile flies through the air. I take note of the direction that the projectile is traveling and the position and store it. I use the direction and calculate perpendicular points that move away from the center point over time. I feed these back into the PolygonCollider2D and it does seem to work, but it always tries to close the polygon and it seems to tank performance when I create 15+ projectiles. Example below:



    I was thinking of trying to implement a 2D convex hull algorithm but if the projectile traveled in a curve the convex hull will include a large area that I do not want to include.

    Any advice or suggestions would be greatly appreciated! Our game has a pretty robust spellcrafting system that will allow for a lot of things to be happening at once so I would like to make this as performant as possible.

    Thank you for your help!
     
    Last edited: Apr 30, 2021
  2. rubcc95

    rubcc95

    Joined:
    Dec 27, 2019
    Posts:
    222
    Ohhh sad you broke your head like this... since you had it implemented by unity from the start :)

    Just go to your particle system, and configure the collision section as you wish, remember to enable "Send Collision Message". Now you only need to attach a new script to the GameObject where the particle system is attached, something like this:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class ParticleCollisionCaller : MonoBehaviour
    4. {
    5.     public ParticleSystem part;
    6.  
    7.     void Awake() => part = GetComponent<ParticleSystem>();
    8.  
    9.     void OnParticleCollision(GameObject other)
    10.     {
    11.         Debug.Log($"Applying debuff to {name}");
    12.     }
    13. }
     
  3. G8S

    G8S

    Joined:
    Feb 7, 2017
    Posts:
    18
    I did try this but unfortunately when it collides with other objects they move according to said collision (i.e. bounce off the object), which is an undesired behavior. Is there some way to get this to work like OnParticleTrigger or something? You are right, this does give me the events that I want but it comes with undesired movement so unfortunately it is not a solution.

    I can enable 'triggers' but then I need to specify what colliders are available, right? Enemies can constantly spawn/be killed so this won't work either.

    Thanks for your help
     
    Last edited: May 3, 2021
    rubcc95 likes this.
  4. rubcc95

    rubcc95

    Joined:
    Dec 27, 2019
    Posts:
    222
    Wow fck this. I thought the answer would be much simpler, but after looking at the documentation for a while, I still can't believe that the unity team didn't make OnParticleTrigger compatible with colliders2D like they did with OnParticleCollision time ago... In order to use OnParticleTrigger seems like you must use 3D physics, so the only one approach I can get for is something like this, but really sucks, hope someone has a better idea.

    Code (CSharp):
    1. public class collisioncontroller : MonoBehaviour
    2. {
    3.     ParticleSystem part;
    4.     ParticleSystem.Particle[] particles;
    5.  
    6.     [SerializeField] LayerMask _triggerMask;
    7.  
    8.     void Awake()
    9.     {
    10.         part = GetComponent<ParticleSystem>();
    11.         particles = new ParticleSystem.Particle[part.main.maxParticles];
    12.     }
    13.  
    14.     void FixedUpdate()
    15.     {
    16.         part.GetParticles(particles);
    17.      
    18.         for (int i = 0; i < particles.Length; i++)
    19.         {
    20.             Collider2D[] triggered = Physics2D.OverlapYourDesiredShapeAll(particles[i].position, ...);
    21.             for(int j = 0; j < triggered.Length; j++)
    22.             {
    23.                 if (triggered[i] == null)
    24.                     break;
    25.  
    26.                 Debug.Log($"Triggered {triggered[j].name}");
    27.             }
    28.         }
    29.     }
    30. }
     
    Last edited: May 3, 2021
    G8S likes this.
  5. G8S

    G8S

    Joined:
    Feb 7, 2017
    Posts:
    18
    Haha yup, I agree, I can't believe that this is not implemented for 2D. My current solution and yours looks very similar. Thanks for putting the time in, I appreciate your efforts! :)

    I have another idea that I have yet to implement but I suppose I will post it just to get it out of my head and in words:
    • Create a grid that overlays over the entire map with cells of size X.
    • When particles are spawned, they will insert themselves into cells in the grid's memory and when they die they will remove themselves from the grid's memory.
    • Enemies should just be able to query the grid with their position and box collider size and the grid would return all particles that fall inside cells that intersect with the enemy.
    There might be a few cases where I explicitly have to check the distance between my collider and the particle to double check if they overlap but I think this will be way faster than my naive approach.
     
  6. rubcc95

    rubcc95

    Joined:
    Dec 27, 2019
    Posts:
    222
    And how you would do it. Using 3d box collider for each cell? Pretty sure it would consume so much resources than the current one.
    Another solution is to not use a Particle System. If theyre not so much particles, it shouldn't affect to the performance, and you can always make a pooling system for your new pseudoParticles (gameObjects) in order to not instantiate them continuosly.
     
    G8S likes this.
  7. G8S

    G8S

    Joined:
    Feb 7, 2017
    Posts:
    18
    The grid would be a 3D Array of a struct that contains something like

    Code (CSharp):
    1. struct DebuffParticle
    2. {
    3.     public Vector2 Position;
    4.     public Debuff Type; // Debuff is an enum
    5. };
    The first dimension is the Y position, second dimension is the X position, and the third dimension is an array of particles found in this [Y][X] coordinate.

    Since I know the size of every cell I could make it so grid[0][0] is at world position (0, 0) and [1][1] is at world position (1, 1) etc.

    Every enemy currently has a Box Collider 2D which defines the size of the enemy, this is used for the raycast controller currently so it is already available. I can use the BoxCollider2D.Size to get the X and Y offsets for

    Code (CSharp):
    1.  
    2. var size = BoxCollider2D.Size;
    3. var ySize = Mathf.CeilToInt(size.y / 2);
    4. var xSize = Mathf.CeilToInt(size.x / 2);
    5. var debuffs = 0;
    6. for(int y = -ySize; y <= ySize; y++)
    7. {
    8.     for(int x = -xSize; x <= xSize; x++)
    9.     {
    10.         var targetCell = grid[(int)EnemyPosition.y + y][(int)EnemyPosition.x + x];
    11.         for(int i = 0; i < targetCell.Length; i++)
    12.         {
    13.             // accumulate the particle debuff types using bitwise OR to save space
    14.             // (might have to rethink if we get a lot of debuff types)
    15.             debuffs |= targetCell[i].Type;
    16.         }
    17.     }
    18. }
    19.  
    I think that this would only loop over the needed particles? I would need to come up with a clever way to move the particles around from cell to cell as the particles move around, though.
     
    rubcc95 likes this.
  8. rubcc95

    rubcc95

    Joined:
    Dec 27, 2019
    Posts:
    222
    Ye, but how the targetCell know what debbuff should apply, it needs to check triggers with the particles... Or you're missing something... or it's me whos missing it xD
     
  9. G8S

    G8S

    Joined:
    Feb 7, 2017
    Posts:
    18
    Haha yeah this is the bad part I think, I would need to get the ParticleSystem.particles and loop over them to keep the grid updating, wouldn't I. That could get pretty hefty. That is not good.

    I think you are right and I do not want to use particles for this system. I already can draw an area around the curve of the projectile, maybe I just use that to fill the grid as it flies through the air and then the grid would just count down the life of every debuff in the active cells and remove them when they run out? If I need a volume that spreads I can have the effect leak into adjacent cells over time, etc.
     
  10. rubcc95

    rubcc95

    Joined:
    Dec 27, 2019
    Posts:
    222
    Yee, thats what i mean. It would be as heavy as my 1st code, but just with an extra grid.

    Sincerely anything with means an extra grid I think is not the best solution, just check directly the enemies (whatever with overlap solution, or just with classic trigger solution if you don't use at end particlesystem). Enemies will be probably much less than the grid cells count, so it will be cheaper and more accurate
     
  11. G8S

    G8S

    Joined:
    Feb 7, 2017
    Posts:
    18
    I did originally think that creating a collider that followed behind the projectile was the way but my projectiles movement can be pretty curved so a box collider that stretches won't work unfortunately, and when I create a PolygonCollider that is dynamic (see vid in first post) it seems to tank performance when we get just a couple projectiles. I am not sure how I can efficiently create a collider that would represent the area that I want to be covered with poison or w/e.

    I do agree that this all-encompassing grid approach is not as performant as I would like. If I don't have a grid or some sort of data structure that keeps track of where our projectile has touched then I am not sure how I could efficiently query to see what the enemy/player is touching.

    I am now thinking that this is a good approach:
    • Have a manager that keeps a dictionary of active cells.
    • We do not create any cells unless we know a projectile has touched them or that they have been forced to activate by some other effect (psn dissipating, etc)
    • The manager loops through all active cells and ticks their age, when they reach their max life they remove themselves
    I now have 2 solutions for having entities interact with the system:
    1. When we create a cell we can move a collider to that position which will have an OnTrigger event which will trigger the manager to evaluate what debuffs are applied, etc
    2. Enemies will send their (X, Y) coordinates to the manager and the manager can do quick maths to calculate if any active tiles are intersecting with the enemies. Maybe I even do an overlap solution like you mentioned, I can investigate and see which is faster
    I think that this is as performant as I am gonna get. What do you think?