Projectile collisions and client visualizations

Hello, I’m planning to make a ton of projectiles for my game, and I want to make sure that my foundations are sturdy before going any further.

I plan on using body/constraint movers, mainly BodyVelocity for linearly moving projectiles (e.g. rockets that explode upon hitting a surface) and ApplyImpulse for projectiles with random trajectories (e.g. grenades that roll or bounce upon hitting a surface). Some projectiles can collide with each other or influence other projectile’s trajectories, and I’m hoping that the Roblox physics engine just works for that.

I’m not using FastCast because the shape and size of the projectile matters (also, these projectiles are not bullets). A large sphere projectile would not trigger a collision event if a small portion near its edge contacted something with FastCast (even though it should); instead, the collision event would only trigger if a raycast from the sphere’s center hit something.

That being said, what is the best, most reliable and performant way to detect a projectile collision? I’m currently using Touched and GetPartsInPart (done every Heartbeat), and this works decently but I’m worried about its performance.

Secondly, what’s the best way for clients to visualize the projectiles? All projectiles are created and handled server-side currently, and there are some clear delays on the client (i.e. there’s a noticeable delay between when the projectile is spawned and visible and when it starts moving). I could fire all clients to create and move their own projectile but what do I do about randomness (e.g. what if a grenade rolls to the left server-side but rolls to the right on the client)? I guess a solution would be to have clients spawn the projectiles and have them take ownership, but then I’d have to worry about exploit prevention.

Touched already works quite nicely.

One thing you may try is making a RemoteEvent that will summon the same bullet on client-side, and make the original transparent. Of course, replace code in client-side bullet (on Touched event) to just simply :Destroy() the projectile.

A very bad idea to use BodyVelocity, since it is deprecated. Use LinearVelocity instead.

Doing the client->server-> client remote event with change in network ownership will result in a stutter for the projectile for the person that started the cycle, and will show much laggier to other players. Do not recommend it unless you know how to combat/nullify this. I’d like to know how as well for my own game, but it is easier said than done, using remote events or even 3rd party network tools like Red.

I’ve discovered that manually setting the projectile’s CFrame on the server every Heartbeat creates a relatively smooth projectile, removing the delay between the client and server when the projectile is spawned. However, this interferes with the physics engine and creates some choppy, messy looking projectiles if they happen to collide with a wall and start rolling on the ground; stopping the CFrame Heartbeat updates completely pauses the projectile for about half a second before the physics engine takes full control, which also looks really bad. The choppiness also applies to fast projectiles.

1 Like

I actually do something similar to this for my ball hitbox. I have it changing the Cframe and orientation(ik its redundant, i jsut want the hitbox striaghtly oriented) , and run that in its own thread. Running together with a client hitbox lag fix that goes forward or back depending on their ping, ive had the best collision detection after 3 months.

image
Like This:

The ball itself connects with the player at the right time, but it lags behind its hitbox just enough to “seem” faster to the player

Shapecast (more specifically workspace:Spherecast) exists, just code your own projectile framework from scratch utilizing them. Relying on physics and bodymovers for projectiles comes with its own set of problems, the biggest ones being physics throttling affecting the speed of the projectiles, latency issues, and/or network ownership exploits.

Projectiles that will constantly interact with the physical world should remain physics-based. Only ballistic projectiles where the only force is gravity should be simulated via procedural shapecasting.

And just to back up my own mouth, here’s my own projectile framework that I’ve reposted for the 100th time on the forum.

1 Like

I’ve tried Touched, GetPartsInPart and Raycast and have most reliable results doing a raycast every render step. My projectiles are small and fairly fast moving so both Touched and GetPartsInPart did not fire reliably or accurately enough to yield the result I wanted.
I found raycasting every render step was the only way I could accurately detect the precise hit position of a projectile reliably every time it was thrown.
You can judge the result for yourself in Monkey Poop, the ultimate monkey poop throwing experience.

Sorry for my late replies; I’ve been very busy lately.

Thanks to your answers, I have a pretty good idea for what I’m going to do for projectiles that get deleted when they contact something (I have not started on implementation): have the client and server simulate the projectile’s motion using manual position updates and using a combination of spherecasting and raycasting to detect collisions on both sides, then have some sort of server-side validation for when the client registers a hit (or ignore the client if the server somehow registers the hit first). I don’t know how well this would replicate to other clients, though.

However, I still don’t really know what to do with projectiles that interact with the workspace. These are the projectiles that I’m only using :ApplyImpulse on and letting Roblox physics deal with everything else.

I’m not so worried about their collision detection; I’ll probably just use Touched (or shapecasting if I find that works better). Letting the server and only the server spawn the projectile and deal with its hit detection would be perfectly accurate, but this would look really bad for the clients. For the clients, the projectile’s visual position would always lag a few frames behind its position on the server (and once again, there would be that delay between when the projectile is spawned completely motionless and starts moving).

If the client and server each create a projectile, the distance between the client and server’s projectiles is not completely absurd but significant enough so that exploiters can easily manipulate the server into accepting their hit detection because of the server’s high tolerance. For example, when the projectiles hit a wall, the client projectile can bounce off to the left but the server projectile can bounce to the right at differing velocities.

Projectiles colliding with one another and thus influencing each others’ motion in one way or another would be an absolute nightmare with this, not to mention client replication. That being said, is there any compromise between having accurate, exploiter-proof hit detection and having projectile motions smoothly and accurately replicate to all clients?

You are not supposed to have a collision system on client projectiles. Just :Destroy() and thats it. I don’t understand this sentence.

If you’re referring to the projectiles that interact with workspace, having the client and server each create and simulate their own projectile but with only the server controlling its projectile’s hit detection would create major visual discrepancies.

Here’s a few videos to demonstrate what I’m talking about; the projectile is a ball that explodes upon contacting a character with a humanoid:

1: The red ball is the client created projectile (has its own collision detection and deletes itself when it collides), and the blue ball is the server created projectile (notice the lag compared to the client projectile). Things look consistent only when the player is not moving.

If hit detection were purely based on the server, then the projectile fired by the client would be inaccurate compared to the server’s when the client is moving, and this would create those visual discrepancies of projectiles destroying themselves when they visually shouldn’t or not destroying themselves when they visually should.

2: Here, I’ve hidden the server ball and added a RemoteEvent fired from the server to destroy the client projectile when the server projectile detects a collision. I’ve also removed the client projectile’s hit detection.

Sometimes, the client projectile clearly hits the humanoid but keeps on moving. Additionally, when the server projectile also hits the humanoid, the client projectile continues moving for a noticeable amount of time until the client picks up the RemoteEvent and deletes it.

That’s why I feel having the client take part in hit detection is important, but remember the first video (side note: the discrepancies get worse at higher velocities but most especially when projectiles start colliding with each other). There is no way for the server to validate client hit detection correctly if the server projectile is, for many cases, decently far away from the client projectile. That would require a high amount of tolerance when doing distance checks, which exploiters can easily manipulate.

You can sync client position and server position by sending a tick/os clock through the remotes

That can sow you the delay between the remotes, thus acts like a delta time

Then you can cframe the position of the projectile with some math calculations

Thanks for introducing the newer types of casting of casting to me. I started with FindPartOnRay a long time back and thought Raycast was the latest greatest.

It looks like you’re shooting a lot of projectiles, is that just cosmetic and only a single ray is determining a hit then you switch it to using a spherecast to more accurately reflect the visual?

It’s procedurally simulated. On each frame, the new position of the projectile is calculated and then casted to for hit registration. Yes, this means doing thousands of raycasts/spherecasts per FRAME, but it’s multithreaded (Parallel Luau) so the overall performance is still acceptable. In fact, the biggest bottleneck comes from CFraming the projectiles themselves, which usually accounts for around 80% of the lag!

1 Like

Sounds like parallel luau is something for me to look into as well. I assume it’s more complex than just starting new threads? Since you seem to know it well is there a resource you recommend for getting started?

Do you have an example of this by chance?

For example:
Your projectile travels 10 studs per second
When firing the remote, send the workspace:getservertimenow() to the client and you should get the ping delay.

Then move the projectile accordingly (if delay is 2 seconds you should move 20 studs)

2 Likes