Calling Physics2D.BoxCast thousands of times per frame causes frame drop (many units in simple RTS)

Hi everyone. I am developing a 2D pixelart mobile game which contains a sandbox mode where the user is able to create huge battles. Right now each unit is typically calling Physics2D.BoxCast each frame to check if they hit an Enemy using the “Enemy” LayerMask. This has deemed very performance heavy, especially on mobile devices (which the game is intended for). Is there any smart way to approach this? I have read something about using the Jobs System, but i’m just wondering if anyone had any experience with this or any alternative way of handling line of sight and choosing a target other than using BoxCast. Thanks!

Illustration of a scenario where the user has many units at once

Profiler data
The data is from the profiler where there are 1200 active units (1200 BoxCast calls per frame) to check if they see any enemy units (line of sight pretty much)

BoxCast code being executed each frame
```csharp
**private RaycastHit2D GetRaycastHit2D()
{
// Adds an offset of half the size of the box to ensure that the box only casts forward
Vector2 origin = transform.position + new Vector3(Distance * 0.5f * Direction.x, transform.position.y, transform.position.z);
// X size is just distance, y size is 20 (just arbitrary big number) to ensure that the range will always catch everything its supposed to do on the y axis
Vector2 size = new Vector2(Distance, 20);
RaycastHit2D hit = Physics2D.BoxCast(origin, size, 0, Direction, 0, LayerMask);

if (hit)
{
    CurrentTarget = hit.transform.GetComponentInParent<Health>();
    _lastKnownTargetPosition = hit.transform.position;
}

return hit;

}**
```

Unity - Scripting API: RaycastCommand (unity3d.com)
use this with unity jobs,

or spread them out over more frames with a manager writing the results somewhere. thousands of raycasts is bound to hurt any computer, and no ones’ eyes see every frame so enemies not updating constantly isn’t going to be obvious at all.

Hi Poorshank. I have looked into using the BoxcastCommand like you mentioned. I have also thought about spreading them out with a manager, but how exactly would that happen? Should i randomly run every soldiers BoxCast every N frames, because if it is not random then wouldn’t all the BoxCast be called on for example the 10th frame and then slow everything down every 10th frame?

I dont particullary need a BoxCast and was thinking about having a manager that for looped through all the soldiers and then give them a random enemy who is within their range, but wouldn’t this scale very badly because of nested for loop?

What i really need is a way to check all enemy units in the scene (easy since i can just add them to a list), then determine for each soldier which units are within the soldiers range and then select a random enemy from that list, that the soldier can target, but i can’t come up with any more performant way then using BoxCast at the moment other than the BoxCastCommand with Unity Jobs (which i have to look into)

the manager method i’m describing would go something like this.

do your raycasts every frame, but instead of doing 1000 or so, do the number you specify. (eg 10)
add to a counter you define for each troop, and loop the list or array with the counter. (counter++)
do whatever needs done afterwards

this will prevent the game from having to do everything at once, and will still be pretty seamless. the stutters you mentioned wouldn’t occur as the game is going through the troops at a consistent pace the whole time.

hope this helps :slight_smile:

This makes a lot of sense. So if i had lets say 30 frames, and 300 soldiers and i would end up going through “all the soldiers” after about 1 second. Would you just loop through the soldiers from 0 to n (so that index 0 is alwasy the first that is looped and index n is the last?) (I know this wouldn’t technically matter after the first complete iteration)

You wouldn’t use physics for this at all, honestly.

You would have a blackboard (programming pattern, look it up) of information, particularly one that already knows about every enemy that is present, and can provide an API for finding a unit’s nearest enemy, etc. When you spawn a unit it gets added to the blackboard, and removed when destroyed, etc.

The bigger the scale you want, the more you need to be data oriented.

for massive game like that, especially for mobile, you’ll need to roll your own collision check, like, use grid system and ‘check collision’ by checking if a grid is occupied or not, and roll your own ‘linecast’ by doing the grid check in a line, maybe using bressenham algorithm or something.

So no default unity physics in the game? I have actually fixed this quite easily, by just selecting the unit that is closest on the x axis, but now im running into the problem with moving a large amount of colliders. The Step of Physics2D ends up taking a lot of time, but if i were to remove colliders on all Players i would need to refactor a lot of the project since it is used to see if an explosion should kill them, if they should enter this house etc.

Colliders don’t move in 2D physics, Rigidbody2D move and colliders are attached. You should always ensure you’re using its API to move and never modify the Transform.

I know. I’m already using Rigidbody2D.MovePosition when moving my rigidbodies. If i have 2000 units moving at the same time Physics2D.Simulate is taking up (30.9 %, 33.62 ms) which is mainly Step, UpdateTransform etc. I have no idea how i should optimize this further other than not using colliders at all (or using the Jobs system i guess?)

Another major performance hitter is the FixedUpdate.ScriptRunBehaviourFixedUpdate which is (20%, 21.78 ms). Could this eventually be fixed by using only one FixedUpdate on a manager and let it control all the units Rigidbody.MovePosition?

Nothing you can do in a job there but that is a lot of units and by the looks of it, a lot of overlapping colliders. Presumably then these are Kinematic. Hard to tell you how to optimize it when you have 2000 units moving all with their own physics components etc.

Yes, that’s the FixedUpdate callbacks called by the PlayerLoop (not physics). Reducing those and moving all your units in one go should improve the situation.

Alright so no easy way of optimizing having thousands of rigidbodies with colliders on moving. Yes they are all kinematic and they have a BoxCollider as a child (which acts as their proximity collider)

I was thinking of removing the colliders and rigibodies, but without those they won’t be able to check when the enter a house (OnTriggerEnter) or walk on a mine (bomb) - (OnTriggerEnter) or when an explosion occurs close to the soldiers and the explosion calls (OverlapCircleAll).

I guess there is really nothing to do except for looking at ECS or making a population cap (the user can have a maximum of 1000 units at a time etc).

Also as you mentioned with a lot of overlapping colliders. Is there any way to avoid them overlapping. Like changing their z position value etc? The only reason i am using colliders is for the things mentioned above.

Also i have set so Players do not collide with Players in the Layermask Collision Matrix, but it is still a problem when they overlap or what?

Not what I meant at all. Just don’t use physics/raycasts for these position queries. Have that separate using something like a blackboard pattern.

Alright i understand. I think i have solved the position problem, but now the other problem is just moving so many kinematic rigidbodies (of type 2D) using MovePosition. The Step is taking too many ms per frame. Do you know any way of optimizing this. I have already set so the player will not collide with other players in the Layer Collision Matrix. Is there anything i can do differently?

Did you miss Melv mentioning this?

No i read it, but the Physics2D.Simulate is still the main bottleneck and i was wondering if there was any way to migitate that without not using the Physics system. Like a smart way of setting colliders so they dont overlap (i dont know if changing their z position would do anything or setting everything to trigger?)