• 2D
  • Instanced bodies with collision shapes tanking my fps?

Hi everyone

I've made a bunch of instances of a character in my game, complete with multiple collision shapes (for selecting, attack area, hit area etc) and have noticed that my fps drops considerably with just 8 or so on screen - to literally unplayable levels.

The same thing happens with instances of a building, also with collision shapes, though less of them and perhaps less code. Disabling them improves fps so I'm guessing it has something to do with processing too many bodies through 'get overlapping bodies' in the physics_process function.

Could anyone perhaps help with this issue or confirm that 'get overlapping bodies' is really this demanding? For reference, my CPU usage is around 25% when testing.

Thanks

I think your guess is probably right, but you should share the code your concerned about and ppl here can make better suggestions. The problem probably isn't "processing too many bodies" but instead processing those bodies too often. Or maybe something else. You might want to be more specific about when you want to get overlapping bodies such as when an object is placed or when a new object enters. There are signals for object entered and exited that come in very handy. Very complicated collision shapes might also be making things worse. I use collision shapes in 3d alot.

In fact even in the physics process function I call get overlapping bodies for this game you can see at the beginning of this video:

You can see what goes on there: there's some work going on but nothing huge. This is in 3d, too.

func _physics_process(_delta):
	if on:
		pushOverlappingBodies()

func pushOverlappingBodies():
	var bodiesOnBelt = get_overlapping_bodies()
	for body in bodiesOnBelt:
		if body is RigidObject:
			var force = pushDir - body.get_linear_velocity()
			if body.has_method("add_Conveyor_Force"):
				body.add_Conveyor_Force(force, speed)

Each belt is calling this every physics call. This might not be useful cause I know you're in 2D, but I'd guess in 2D you'd be able to uh... push even more units! Also... glad I shared this code made me realize there's some extra lines.

You might be right about processing them too often. They are supposed to be like units in an RTS game, checking for nearby enemy units within range.

Here is one of my scripts:

extends Area2D

func _physics_process(delta):
	var bodies = get_overlapping_bodies()
	for body in bodies:
		if body.is_in_group("Unit"):
			if not get_parent().exhausted == true:
				if body.tribe == "Romans":
					if get_parent().get_parent().get_parent().Romans == "Hostile":
						if get_parent().stance == "Aggressive":
							if not get_parent().action == "Attacking":
								if not get_parent().action == "Attack_Target":
									if not get_parent().action == "Walking":
										if not get_parent().action == "Walking_To_Pos":
											if not get_parent().action == "Walking_To_House":
												_target()
									
func _target():
	var bodies = get_overlapping_bodies()
	var closest = bodies[0]
	for body in bodies:
		if body.is_in_group("Unit"):
			if not get_parent().exhausted == true:
				if body.tribe == "Romans":
					if get_parent().get_parent().get_parent().Romans == "Hostile":
						if body.global_position.distance_to(get_parent().global_position) < 		 closest.global_position.distance_to(get_parent().global_position):
							closest = body
							closest.sighted = 1
							get_parent().pos = closest.global_position
							get_parent().action = "Walking_To_Attack"
							get_parent().walking_to_attack = 1
							get_parent().target = closest
						else:
							closest = body
							closest.sighted = 1
							get_parent().pos = closest.global_position
							get_parent().action = "Walking_To_Attack"
							get_parent().walking_to_attack = 1
							get_parent().target = closest

I feel ashamed to admit I’ve never programmed RTS units, but I see a few things. First of all, _target() calls get_overlapping_bodies() even though you had already found those bodies in process. You could just send those from process to the target() function. Also remember get_parent() is still a function, so it’s probably better to save it and reuse the parent node var p = get_parent() than to just keep calling get_parent(). That should offer a speed boost.

Actually, to be honest, I don’t think this needs to be done in the process func. This is how I’d do it for performance:

RTS units have a line of sight, right? Well I’m sure they also have a separate radius in which they’ll aggro an enemy, but for this lets just assume it’s the same. All you need is to give the unit his/her area2d, then attach the body_entered() signal to script. Something like this:

var target :  UnitClass

func body_entered(body):
	if body is Unit:
		If body.distanceToSelf() < target.distanceToSelf() and self.notBusy:
			If body.allied:
				Print(“what’s up, bro!”)
			else:
				print(“grrr”)
				target = body
			# react to target in other function presumably. 

If the enemy enters the unit’s LOS, do stuff, else chillax. In process I’d have it so that once per frame the unit checks for closer threats or whatever other situations to look for.

@Freeman_Reigns also to put your code in that styled box thing you do three ~ signs above and below the code

In other words, I think the logic should be like this: A pregnant woman holding a rock hard baguette with sphagetti drenching her blouse busts down the door demanding you return the True Blood DVD’s that your father in law loaned to you two four years ago when things were going well in New York. She enters your zone. What do you do? Charge!!!!

But I think your logic is like this: Roughly sixty times per second, you look at her and reevaluate not only your attack directive but also how you’ll get there, whether or not she’s Roman, whether or not your parents are exhausted, checking her hostility indicators and so on.

At most you might want to reevaluate... once she gets halfway to you or right when she gets within attack range.

Body enter/exit would help as mentioned but

var bodies = get_overlapping_bodies()
for body in bodies:
if body.is_in_group("Unit"):
if not get_parent().exhausted == true:
if body.tribe == "Romans":
if get_parent().get_parent().get_parent().Romans == "Hostile":
if get_parent().stance == "Aggressive":
if not get_parent().action == "Attacking":
if not get_parent().action == "Attack_Target":
if not get_parent().action == "Walking":
if not get_parent().action == "Walking_To_Pos":
if not get_parent().action == "Walking_To_House":> 

if you have a set of states, collect the value of action in a local variable to save over calling get parent. action over and over and use a match (switch) case or elif statements. only one of those strings are viable and you are testing all of them.

state management is going to weigh on your game i think and you should investigate state management more. Look into state machines.

Definitely don't use get_parent().get_parent() multiple times at 60 fps. While it is not a super expensive call by itself, it is a function call that has to travel the tree, so may be one cause of the slowdown. Save all the nodes you want as onready vars at the top (or in _ready(). That way you don't have to find them every frame.

Also, get overlapping bodies is not supposed to be used like this, certainly not every frame. Use signals to detect when an object overlaps, and simply handle that one object (rather than checking every single object 60 times a second). You can connect the signal area_entered to do this. This will be way more optimized and less code actually. And it doesn't have to run every frame.

Finally, the nested if statements are a bad design. If you have more than 2 or 3 levels deep, it probably means your logic is faulty. I would investigate FSM (finite state machines) as one way to clean up this code.

Thanks for all the help everyone. Regarding using body/area entered instead of get overlapping bodies, my only problem is it only runs once when a body enters the unit's line of sight. If I make the unit move to a point manually, it needs to then return to attacking once idle and if there is still an enemy within sight.

Edit: Ok I've found a workaround by having a variable act as a switch that comes on and off briefly when the unit becomes idle, causing it to do a check for enemies with get overlapping bodies. Not sure if it's the best way but it works.

@Freeman_Reigns I hope you post about this game more, I (just realized today) really enjoy thinking about units in an RTS game. =)