How to Detect Unit Collision

Hello, everyone.

Preface

I’m developing a real-time strategy game, similar to Age of Empires or StarCraft. Currently, units are able to navigate around a map using a custom pathfinding system. Units are placed in a collision group that is set to be non-collidable with itself: in other words, units will pass right through each other when they intersect paths. This was initially a placeholder system until I could create some sort of unit collision behavior, but I think I may actually incorporate it into said system.

Instead of having to recalculate a path every time two vehicles touch, I want to create a collision system similar to that of StarCraft 2, where units are pushed out of the way under the following conditions:

  1. The offending unit is moving. (Moving units do not yield to other units.) And
  2. The offending unit is friendly.

The current issue I am facing is that I do not know if there is an optimal method to detect unit collision.

I currently use the .Touched event to detect collisions, and use a BodyVelocity object within the unit to move it for a short period of time. The .Touched event has proven unreliable, as at times unit collisions will not register. (This has nothing to do with the collision group, as far as I know.)

I searched the DevForum for any threads on this topic, but virtually all of the topics I found focused on players’ character collision, which is not relevant to this game. Units are server-based entities.

I’ve read that raycasting, or even using Region3s are possible alternatives to the .Touched event, but in my research I have gathered more questions than answers. Personally, I would like to use the .Touched event, because I can listen for the event to fire, rather than having to create a loop.

So, what do you developers think? What is the best way to detect collisions on the server in this scenario?

Any help in regards to this issue would be greatly appreciated.

Try write in Touched() code with calling remote-events

The way events work in Roblox is that they check each frame and fire if a certain condition is met. With .Touched events they check if an object is within boundaries of another every frame, and if so, the event is fired for the scripts written by you and I to pick up.

The problem in your particular scenario is that with high-speed projectiles passing through perhaps thin walls or smaller objects in general, Roblox will never notice the projectile “touched” the object. This means the Touched event is never fired.

If you’re dead set on using the Touched event over raycasting, then there are two ways I know of to combat this. The first is increasing the FPS (frames per second), but obviously, that’s in Roblox’s hands. The second way is to elongate/size up projectiles so the Touched event has a higher chance of becoming noticed by Roblox frame-by-frame internal scripts and fired accordingly. Bullets the size of rods won’t do your game aesthetics much good, so the best option is to weld a projectile with a fully transparent enlarged part, and use that same transparent part for the Touched event in your code.

Of course, this means less precision in the hitboxes, but typically the players won’t notice. Use with care. Wish you the best of luck.

1 Like

For detecting collision the easiest thing is just to assume that units are spheres and do a magnitude check. Do some optimizations to not compare against every other unit and you can make it fast.

But it sounds like you’re looking for a solution to collision avoidance, which is sort of a different question.

This question has some good answers: physics - How is RTS Local Avoidance Done? - Game Development Stack Exchange

And it links to this paper, which is probably similar to what starcraft uses: https://gamma.cs.unc.edu/ORCA/publications/ORCA.pdf

tl;dr:

1 Like

Just to clarify: my issue is not with detecting projectile collisions. I am trying to detect collisions with units, which can be anything from infantry to vehicles to buildings. Granted, these entities are still somewhat small (my current tank model is about 2.5 * 2.5 * 3 studs), but they also move much slower than, say, a simulated bullet.

In that case, I would like to ask a few questions:

What other parts do you have in mind that makes you want to check units’ collisions? Other units?
Are units set in Collision Groups to not collide with themselves? If so, that could be the problem.
What sort of behaviour is connected to the Touched event?

If possible, it would be helpful to get an insight into the script you’re utilising.

The preface at the beginning of my original post should answer most of those questions.

Some things I did not mention in the preface:

Apart from units, I also would like units to steer away from blocked areas of the map. Units already steer clear of cliffs, but they may get pushed over them when trying to avoid another unit passing by, so cliffsides would also have to be considered.

From my testing different collision methods, it does not seem that the Collision Group is the problem. Units are set to be collidable, so they still fire the .Touched event; they just don’t have any natural physical behaviors when they intersect.

Units’ models contain a part called “UnitPart,” whose size matches the dimensions of the unit’s model. This part, and this part only, is used when detecting collisions.

Here is the current code I use for unit collision (slightly modified for readability):

local function onTouched(offendingPart)
    if offendingPart.Name == "UnitPart" then
		if CurrentDestination.Value == false then --If the unit being offended is not going anywhere, then:
			--print("Pushing") --DEBUG
			local pushVector = (UnitPart.Position - offendingPart.Position).Unit * COLLISION_FORCE_SPEED
			UnitPart.BodyVelocity.Velocity = UnitPart.BodyVelocity.Velocity + pushVector
			local stillThere = nil
			repeat
				UnitPart.TouchEnded:Wait()
				--print("Touch ended") --DEBUG
				stillThere = table.find(UnitPart:GetTouchingParts(), offendingPart)
				if not stillThere then
					UnitPart.BodyVelocity.Velocity = UnitPart.BodyVelocity.Velocity - pushVector
				else
					--Update the push vector, in case the point of collision has changed
					UnitPart.BodyVelocity.Velocity = UnitPart.BodyVelocity.Velocity - pushVector
					pushVector = (UnitPart.Position - offendingPart.Position).Unit * COLLISION_FORCE_SPEED
					UnitPart.BodyVelocity.Velocity = UnitPart.BodyVelocity.Velocity + pushVector
				end
			until not stillThere
		end
	end
end

If the actual Touched event is misfiring, that could be a reason for serious concern. Maybe I’m missing something, but I’ve read over it a few times and your code seems perfect.

Before we jump into conclusions, I would like to verify how much of the lack of response is down to the Touched event. For debugging purposes only, I suggest you substitute the code modifying BodyVelocities to something beginner-level, like changing the color of the offending part to BrickColor.Red(). If the Touched Event is working fine, the simulation should turn all the moving units red over time.

Apart from units, I also would like units to steer away from blocked areas of the map. Units already steer clear of cliffs, but they may get pushed over them when trying to avoid another unit passing by, so cliffsides would also have to be considered.

I’d just like to bring to your attention on what you should do for this circumstance. Firstly, create invisible walls to barricade the areas the units are not supposed to enter and anchor them. Then, in the Collision Group Editor, create a new Collision Group called “Boundaries”, assign all barricades you made to your Boundaries Group and check all of Boundaries’ boxes (I mean in terms of interacting with other groups, setting true with the PhysicsService API on scripts will do the same).

This way, Roblox physics will completely handle unit barricading for you in runtime. You’ll now also have the power to configure dynamics mid-game through scripts by manipulating these parts’ size, position and parent properties.

Really hope you found this reply helpful!

1 Like

I discovered that the inconsistency with the .Touched event was due to network ownership. Once I manually set the network ownership of all units to the server, collision detection worked much, much better. So for now, I will continue using .Touched.

I do appreciate those who offered their knowledge on the subject. Thank you!

1 Like

Brilliant. I didn’t even think of that.
Hopefully you’ll find the invisible barriers technique of some use though.