What are some good ways to optimize a 3D project designed to have hundreds of colliding units?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By blushot

Hi, this is my first post here and also my first attempt at creating a game at that scale, so any tips you might have that are not included in the docs will be greatly appreciated.

To give you some context, the game I am working on is mechanically a clone of an old Warcraft 3 map - Castle Fight and it looks a bit like this


Essentially, you have a base and you spawn more and more of different unit types, trying to overwhelm the enemy and destroy their base.

As you can imagine that means having a whole lot of units on the map, and from some playtesting it seems like the game becomes a lot more fun with the more units there are.

The current state of my game handles up to about 220-240 units flawlessly at 30 physics fps. Above that the fps starts to drop very quickly and at about 320 units it’s running at ~3 fps. The profiler shows that it’s the unit._physics_process method that takes up a huge chunk of the frame time with the next one being the physics time. Here’s what that looks like


Both the _phsysics_process and _process functions are the unit’s ones. What is very interesting to me here is that the _process has been called 309 times which is the correct number of units, but for some reason the _physics_process has been called three times that. I am guessing that has something to do with the fact that maybe the _physics_processes of all units take so long that they span multiple frames. Please correct me if I am wrong.

Here’s what a good frame looks like in the profiler at around 230 units

And to show you what the units actually look like and do, here’s what the unit scene looks like

unit scene

where the visibleArea and attackArea are 2 areas which detect enemy units, but they are not called in the _physics_process at all.

And here’s what the _physics_process actually does

func _physics_process(delta):
if path_id != -1:
	var difference = path[path_id] - translation
	move_and_slide(difference.normalized() * $entity.movement_speed, Vector3(0,1,0))
	
	if difference.length() < 1:
		path_id += 1
		
		if path_id == path.size():
			path_id = -1

And then lastly, it seems that the slowdown is directly related to the areas and the units getting stuck next to each other. If I disable the collision between the units, that gives a bit of a performance increase. If I disable the areas collision shapes, that also gives a pretty huge performance increase.

Unfortunately, I need them, so that’s why I am here. I am curious to see if other people have faced the same issues and also how they solved them. Any suggestions for better design ideas will be appreciated.

I am looking to be able to get around 500-600 units maximum in the scene and keep the physics fps around 30.

I am confident I’ll be able to rewrite a lot of my logic in C++ if that would give me a big enough increase. Additionally, another suggestion I saw thrown around is rather than using physics nodes, directly use the physics server, but I wanted to get more opinions before diving into something like that, as that would be quite a time sink.

:bust_in_silhouette: Reply From: klaas

Hi,
do you use spherical collision shapes? Those are least complicated to calculate. This is merely a distance calculation.
Then, if you integrate your own distance based collision and visibility procedure you could split the calculations over several frames. This should keep the frame rate up in exchange for some precission.

Hey, thanks for your reply!

I am using spherical collision shapes for the areas (visibility range and attack range), but I use capsules for the unit’s collision shapes. Most of the units are fairly squat, though, so I can definitely try switching them over to spheres.

Not sure exactly what you are suggesting, though, in terms of implementing my own distance based collision. I can easily see that working for the areas, as I don’t have to worry about collision response in that case, but even then, what’s putting me off is that in order to detect whether units are inside an imaginary area, I’d have to iterate over all units in order to check and I expect that to be slow, though it’s just speculation.

Is that similar to what you suggested?

blushot | 2020-07-27 18:35