I have a question about the best way to handle a large number of 2D colliders. I’m currently building a Tower Defense game, and my game starts to lag significantly when I have a large number of units moving along the path. According to the profiler, the main issue seems to be the “Physics2D.FindNewContacts” part, which is generating the highest CPU load. I’ve already searched online for solutions, but none of them have helped so far.
The settings for the units, based on some forum advice, are as follows:
I'm using a 2D Collider for every unit with a small radius, with no other adjustments.
Rigidbody2D on the units, set to Kinematic, Discrete, and Start Awake.
The Collision Matrix has been adjusted.
The units move forward using the Rigidbody API:
private void FixedUpdate()
{
Vector2 direction = (nexttarget.position - rb.position).normalized;
Vector2 movement = direction * Time.deltaTime * moveSpeed;
rb.MovePosition(rb.position + movement);
}
I understood that if you don’t use the Rigidbody API to move the unit, collision checks are constantly being redone because with each use of Translate, the collider has to be recreated. However, since that shouldn’t be the case for me, what else could be causing the issue?
When I was little, that was any number past 10 or a little older, any number past 100.
How large is large for you? Because that matters. 100 colliders, fine. 1,000 hmmm maybe not. 10,000 definitely not. Unless they’re all sleeping bodies.
Also, the most performance is wasted when you have many (frequent or continuous) contacts. For instance a stack of 100 boxes (2d or 3d) will have a heavy impact on performance, but once the pile has toppled and mostly separated, performance will be fine.
Oh sorry,
By a large number, I mean that at around 2000-3000 units, I only get one frame per second. In this case, the units are constantly touching each other while moving along the path. But I thought that if I set it in the Collision Matrix they don’t interact with each other, it wouldn’t be an issue. The units are only supposed to interact with bullets.
Check if they really aren’t causing the collision callback methods to trigger.
Also, if you have each of these units run their own scripts, specifically collision callbacks (OnCollisionEnter etc) then this will perform badly in general. It’s best to have a central system where you do collision checks for those units. At this scale it matters whether you have a single script that manages all instances vs one script per instance managing itself.
Can you tell me the best way to check if the collision callbacks are being triggered? I tried with the profiler but couldn’t find it. I also tried switching OnCollisionEnter to an ontrigger event and accordingly enabled OnTrigger on the collider, but that didn’t help either. I originally wanted to use this overarching system, where a single script controls all units, for movement, until I realized that this isn’t really the performance issue for now.
Call this from a central script to check each instance, and ideally only once, eg you only need to check if A hits B but not B hitting A. You should be able to find an algorithm for this “mutually exclusive check” by searching. Maybe group objects into smaller areas first to include only the potentially colliding objects.
I’m sorry, I don’t really understand. In your example, it’s more about how I can improve my script, right? I don’t understand why findNewContacts is so computationally intensive if it’s only triggered once when the unit spawns. Or is that not correct? Is there any way to see in the profiler how many collisions are currently happening? Are the settings I posted above generally correct, or is there something wrong with them already?
To test if your units are colliding with each other you can place a Debug.Log message in the OnCollisionEnter2D callback. Also, be sure to set the correct matrix. Somebody recently had issues with their 2D collisions and it turned out they were setting the 3D collision matrix.
I tried using OnCollisionEnter2D on the units with a Debug.Log and it doesn’t get triggered even once. So that doesn’t seem to be the issue. Is there anything else causing findNewContacts to generate so much CPU load? Can someone explain exactly what findNewContacts does? Like, when it’s typically triggered and so on. Is there a way to have the bullets interact with the units without using a collider on the units? Or to have the bullets interact with the units in general?
Like I said, use Physics(2D) Raycast methods. Particularly for hundreds of bullets there should be one system updating each bullet’s flight trajectory while also checking the bullet’s collisions.
In its simplest form, take the projectile’s current position, add its current velocity times deltaTime to get the new position. Then you can raycast from currentPos to newPos to check if there are any intersections and if so, handle them accordingly.
Mm, I’m sorry, but somehow I don’t understand the added value in my case. Physics2D.Raycast also needs colliders on the units to detect them, right?
So how would that help me, other than having a single script handle all bullet collisions instead of each object handling its own collision?
In any case, I was planning to handle the movement of the units and bullets in a central update script, as I heard it’s better to have a somewhat larger update function rather than many smaller ones. Is that correct?
But I don’t understand how that would help me with the findNewContacts problem.
You can make all those colliders triggers and/or only respond to a specific Raycast layer to minimize the risk of undesirable collisions from occuring.
I’m guessing the performance drain is due to actual collision detection for layers that aren’t masked out, yet don’t affect gameplay.
Try disabling all collisions altogether and see if the findNewContacts still performs as bad as it does.
Yes, that’s generally correct and this has benefits even for just a few units, but not necessarily performance. Personally, I prefer this way because it makes me consistently think in terms of “n units” rather than “one unit”. And then, when you have any sort of interaction between two units, it’s far easier and more reliable to do so with a single update method.
One case where this is important is when it matters to know whether two units A and B have yet to be updated or have already been updated. In a central system, you’d typically have a running index and if one unit’s index is lower than the other’s, you know it has already performed its update logic this frame.
Imagine a contact between units A and B where A kills B and it matters whether B has updated prior to dying or is going to still update. At a minimum, this can cause a +1 frame delay of death in some cases, while in others the death happens in the same frame. Often negligible but when it matters, it tends to be crucial to be avoided.
In such cases you “remember” such interactions for a second, post-update loop. Highly preferable particularly for collision resolution. You build a list of all collision pairs, and then process their collisions, and you have a guarantee that all of them have already ran their regular update logic. This is extremely challenging to do with per-instance processing.
I ran a few more tests. First, I moved the movement of the units into a single script, though I still need to check if it’s faster.
Then, I did a test with colliders enabled but everything disabled in the collision matrix. According to the profiler, there are no collisions, yet findNewContacts is still very CPU-intensive. In the second test, I completely deactivated the colliders on the units, and as expected, findNewContacts logically shows 0%.
If I haven’t mentioned it yet, there’s no OnCollisionEnter or similar in the unit scripts; I only use that for the bullets. And in these tests, no bullets were in the scene.
A general recommendation, avoid physics (colliders in particular) anywhere it’s meant to scale to large numbers. There are clever ways to get around it, you just have to find the one that suits your needs. Sometimes it’s unavoidable, but there are ways to mitigate the performance cost (increasing fixed timestep, adjusting collision layers, replacing colliders with raycasts, etc…). I am not sure what your bullets look like, but you should not have colliders on them. Just make them move and raycast to check for hits against the unit colliders on a specific physics layer. Then you can control the raycast frequency to optimize.
I made an ECS grid system for my current game, which would probably be a good fit. Its an adaptation of this tutorial. If your not familiar with ECS it’s a bit of a learning curve, but the general concept is still sound. Everything registers which grid cell it is located in every frame. Bullets that are moving check their current grid cell every frame until they find a valid target to hit. Takes ~2ms for 5000 units shooting thousands of bullets at each other, and doesn’t use a single collider.
FindNewContacts is probably just the collision check process and it will be having to check each unity for collisions with its neighboring units. If you spread the units out more then it’ll not take up quite as much time to process because there’ll fewer neighbors to check against.
You could also try making your units dynamic and moving them with AddForce. Doing this means you won’t need to micromanage them every frame and you could just update their velocity occasionally.
Another possibility is using a particle system for the units with the collision module enabled and shoot them with rigidbody bullets.
Thank you all for your ideas. For now, I couldn’t fix the issue, so I have to think about another solution. Maybe I’ll try out the ECS tutorial @WoodsFiend mentioned.
But I do wonder why this problem occurs so often and yet there seems to be no good solution other than changing the entire system. How do games like Vampire Survivors or other games with hundreds of enemies and hundreds of bullets handle this?
In my case, it even stutters when I completely leave out the bullets. Well, I’ll see what else I can try. But thank you for your help.
I took a quick look at gameplay and I think they might be using a grid system. The fact that enemies kind of follow the bullets after they make contact could be to cover up the inexact hit detection that comes with using grid cells.