Search Unity

Bullet hell without performance hit?

Discussion in 'Scripting' started by serbusfish, Oct 12, 2018.

  1. serbusfish

    serbusfish

    Joined:
    Dec 27, 2016
    Posts:
    247
    I am wondering how to create bullet hell style bullet patterns without tanking performance? I find that once I have 15 - 20 projectiles on screen performance can drop below 60fps and as they are being enabled I even experience several large stutters (they are in a pool, not being instantiated).

    Each projectile has a rigidbody, sphere collider, and a script that deals damage to the player in the event of a hit. I'm wondering if its to do with how my scripts interact with one another. Here is what I have in the projectile script:

    Code (csharp):
    1.  
    2. {
    3.  
    4. public static PlayerHealth playerHealth;
    5.  
    6. Start()
    7.  
    8.    {
    9.  
    10. if(playerHealth == null)
    11.  
    12.         {
    13.                 playerHealth = FindObjectsOfType<PlayerHealth>();
    14.         }
    15.  
    16.    }
    17. }
    18.  
    Is this the best way to make my scripts interact? Could this cause issues with performance?
     
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    To figure out what's being slow, use the Profiler. If more than 20 projectiles are tanking the framerate, there's probably something very obvious going on.
     
    hippocoder likes this.
  3. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Use deep profile first and foremost.

    You shouldn't use rigidbodies for projectiles ever.
    Another trick is to avoid methods OnTriggerX / OnCollisionX.

    They're absurdly slow, as they're interop.
    (As an extra, they as well allocate memory when using contact data from native side)

    Solution / Algo:
    1. Make a player that casts e.g. OverlapSphere, or OverlapBox (depending on the simplified shape for the player).
    2. Detect if any projectile got hit by the query.
    3. Get projectile component from collider.
    4. Grab damage value from the projectile instance.
    5. Apply the damage to the player.

    Casting against single layer from only one object yields excelent performance results.
    Getting component is fast when not performed that often (<1000/sec), so it's cost is neglegible even on mobile.

    You can as well make it so it checks e.g. once in a 0.05 instead of default fixed update of 0.02 via coroutines.
    Extra optimization -> Disable collision between everything and "Projectile" layer in Physics collision matrix.

    Find is okay, if it's done only once among projectiles.
    (Though aproach described above is far superior)
     
    Last edited: Oct 12, 2018
    scarface117 likes this.
  4. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,556
    You'll find that most games that do this don't really model individual bullets like you do, but simply define a volume where they animate a single objects that looks like many bullets. If another objects intersects with the volume, a certain percentage (usually based on overlap) is then used to determine the damage. This reduces draw calls and call-backs drastically, and can even simplify game mechanics (after taking care of the overhead of having to program the logic for a bullet swarm).
     
  5. serbusfish

    serbusfish

    Joined:
    Dec 27, 2016
    Posts:
    247
    OK thank you guys, first things first, here are 3 examples from the profiler:







    @VergilUa I never knew projectiles shouldn't use rigidbodies, in fact way back when I was learning to use Unity in the space shooter tutorial we are instructed to do just that with our laser bolts. How can I move the projectiles without rigidbodies? But I will look into your alternate hit detection solution, thanks. I dont know if i'll be able to figure it out as i'm still quite new to coding but i'll give it a shot.

    @csofranz Thanks pal that is something else i'll consider.
     
  6. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Doing the trasform repositioning on your own. That's what rigidbody's do in the end.
     
  7. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    The third profiler frame is easy to understand, if you've been around Unity. StackTraceUtility is used by Unity to show the stack trace that's a part of Debug.Log messages. Debug.Log is very, very slow. Don't leave it around in your code, it's going to wreck performance. It's useful for figuring out what's going on in code during development, but after you're done with something, you should delete those.

    The first one means that you've got a big cost when deactivating objects. Maybe you're doing something very expensive in OnDisable? Try turning on the deep profiler.

    Number two is way harder, I have no clue why Camera.render should spike like that. I'd check if it appears in the deep profiler, and then expand the hierarchy a bunch.


    Note that this is why it's very important to profile - @VergilUa is telling you to ditch rigidbodies for performance, but nothing rigidbody-related is showing up as costly in the profiler.
    Don't get me wrong, bullet-hell shooters should probably not use physics for the reasons laid out in this thread, but physics is not causing your current performance problems.
     
    xVergilx and DaDonik like this.
  8. DaDonik

    DaDonik

    Joined:
    Jun 17, 2013
    Posts:
    258
    To add to what @Baste said, you should also check what is generating those 34kb of garbage each frame. It's generally a good idea to prevent generating any garbage at all, which would then get rid of those 19ms you see in GC.Collect from time to time.
     
  9. serbusfish

    serbusfish

    Joined:
    Dec 27, 2016
    Posts:
    247
    OK thanks, I have removed the debug.log entries, and as far as OnDisable goes I call this anytime a projectile, or particle effect etc is used, these things come from a pool so are enabled then disabled after 1.5 seconds so they can be reused. I will look at the deep profiler to see if I can figure out what's going on with the camera renderer.
     
  10. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    375
    Enable and disable are very slow. Just move the bullets very far away when you don't need them.
     
  11. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    No, they model all the bullet in bullet hell games in recent times. It is trivial to have a few hundred things moving around... assuming it's not some silly scripting language or game maker.

    Heck, I was doing bullet hell games in 16 bit from 90s onward. It's not how many things, it's how much code you're executing for those things and how efficient it is. I was doing 128 on the Amiga without it dropping a frame (although this was a hack, and I only needed 32 actual bullets) so I'm pretty sure it's whatever the OP is doing badly.

    Profile it:

    If rendering is the bottleneck, use particlesystem or dynamic batching or instantiating or mesh vert array + shader.
    If cpu is the bottleneck, use ECS or packed structs.
     
    PraetorBlue likes this.
  12. newjerseyrunner

    newjerseyrunner

    Joined:
    Jul 20, 2017
    Posts:
    966
    What? Why on earth would this be the case? I’ve operated under the assumption that The Unity development team knows how to properly use preprocessor directives and Debug.Anything completely compiles away for release.
     
  13. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Firstly you assumed something. That's wrong in any kind of development, and always has been. Because people want different things.

    Secondly, it's documented:
    https://docs.unity3d.com/Manual/BestPracticeUnderstandingPerformanceInUnity7.html
     
  14. scarface117

    scarface117

    Joined:
    Nov 9, 2019
    Posts:
    16
    Quick question for this.
    in a case where I'd like the bullets to also interact with the environment.
    e.g bullet hits a wall, and I need to destroy it and possibly leave a mark in the wall.

    What do you think would be the best approach?

    First thing that comes to mind is to:
    1) Have a list containing all the environment object data I need for collision detection i.e a List of Tuple<radius,vector> for a sphere colider.
    2) Do a query for each of this environment objects to see if any bullet has hit them in the current frame.
    3) Do necesary logic, in the case of hitting a wall, would be something like calling a 'Destroy' method on the bullet object, and adding a sprite of a bullet mark to the wall.

    Although what gives me concern on this idea is that the overhead will depend on how many elements in the environment we have (we could have n environment objects), and what worries me is that each of this environment object will be iterating trough all the active bullets.

    Thanks for your answer earlier btw, it was really helpful.
     
  15. newjerseyrunner

    newjerseyrunner

    Joined:
    Jul 20, 2017
    Posts:
    966
    I would create a component called something like “BulletImactScript” and have your bullet do all the work. Don’t you have to raycast the bullet anyway to know when it hits a wall? You can put environment items in a seperate layer than the walls and change your raycast to look for both. When your bullet hit something, check the later to know if it’s a wall and if not, then get the bulletimpact component(s) and run a method on them.
     
  16. Emolk

    Emolk

    Joined:
    Feb 11, 2014
    Posts:
    241
    Huh? For what reasons?
     
  17. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Depending on numbers, moving lots of rigidbodies really stresses the physics system.
    Which drains performance a lot.

    By a lot I mean thousands at the same time.

    This happens rather happens because of attached colliders to them has to be still processed by the physics system.
    Also, reliable collision detection is only possible while setting collision detection to continious, which may lead to even further performance degradation.

    And if not set to - it may lead to hard to track bugs, in case of really fast moving projectiles.

    One exception I could think of is "grenade" type of projectiles.
    Which are ment to interact with environment on physics laws which will probably have minimal amount of instances in the scene anyways.
     
    Last edited: Feb 23, 2020
  18. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Just use physics queries for that while moving projectile. No need to re-invent the wheel.

    Make sure you're casting on the proper layermask and your collision matrix set accordingly.
    That will minimize the performance impact.

    Also, please don't necro threads.
    This one is from 2018, create your own one if you've got a different specific question.
     
    scarface117 likes this.
  19. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,113
    performance reasons. that's like the shortest path to 10 fps. every bullet having a collider, trying to hit other colliders, being checked in every frame. now multiply this tenfold, hundredfold. bizarre. and completely excessive.
     
    xVergilx likes this.
  20. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    This is what the physics engine does. It's really good at it, and has been fine-tuned by a lot of competent people at NVidia for years to be bug-free and fast. You're probably not going to write something better unless you do a very restricted version.
     
  21. nitrox945

    nitrox945

    Joined:
    Jan 13, 2019
    Posts:
    4
    Hello @Baste I hope you don't mind me asking you a question. If I wanted to make a 2D bullet hell game, would you recommend going with xVergilx's solution?

    I understand that the player must cast a sphere to check for colliders, but then do I need to add colliders to the bullets themselves? Or should they also be calling OverlapSphere?

    Thank you very much :)
     
    Last edited: Feb 8, 2021
  22. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Use raycasts on projectiles if they're small enough, that way it would be faster.
    Otherwise attaching collider to actual player is fine, just make sure to exclude player from queries when player should not be hit.

    Note, if you don't need 3d physics -> use 2d physics.
    Also, Collider2D is created / destroyed when enabled / disabled internally, which is way slower than it should be. So lots of colliders (thousands) might not be a good idea for mobile.

    Otherwise both approaches valid.
     
    Last edited: Feb 8, 2021
  23. nitrox945

    nitrox945

    Joined:
    Jan 13, 2019
    Posts:
    4
    This all makes so much more sense now. My player will have a collider2D while my projectiles will call a variant of OverlapCircle. Your posts in this thread have been very helpful to me. Thank you very much @xVergilx
     
    xVergilx likes this.