Handling thousands of projectiles with high performance?

Hello!

I want to create a bullet hell type of game, where players will have weapons that can create tons of projectiles per second (like 50+) and servers would have up to around 15 players. Enemies would also shoot out tons of projectiles.

I’m aiming to make this game have as high performance as it possibly can. Some things I know I’ll probably do:

  • All projectiles and effects created on the client
  • Using a part/object pool to not have to instance a new projectile every time

One thing I’m stuck on is handling hit detection. Ideally I would use raycasts every frame on the server, but I imagine 2500 raycasts or so per frame is too intense. So it seems to make sense for each client to handle raycasts for their own projectiles and tell the server when each one hits something, though I’m unsure if I would be able to reasonably have sanity checks on the server as I feel it would be hard to detect a projectile not going where it should be when there are so many, and I also feel as if the server would still lag if it had to verify each projectile’s hit each time.

In addition to this, are physics based projectiles or cframe projectiles better for performance?

So is there any possible solution or way I can reasonably have high performance with a ton of projectiles, or do I have to reduce projectiles?

Thanks!

3 Likes

I dont see a problem honestly also firing events would cause some latency issues I’m guessing. my game can handle 180 bullets per frame, tested with three people shooting a bullet each frames at 60fps. And the bullets disappear after 5 seconds.

Now I am casting a ray EACH frame like fastcast except im using my own module because fastcast isn’t as optimized as I was looking for. In 5 seconds thats 600 raycasts per FRAME, with no LAG at ALL. If you want to test what im talking about here, join me. ALSO the bullets created on the server and the client. BOTH of them raycast client for visuals and server for hit detection, server doesn;t have a model.

One thind I reccomond is as you said only raycast on the server don’t make proejctiles with a model on the server. Only the client ones should me making models.

Also for object pooling eh you would need a lot of instances pooled now if you got smart and instead of destorying a bullet and re-using a bullet that would help. I don’t know if thats what object pooling is already that tho lol

6 Likes

I’m doing upwards of 500 raycasts per second and it really isn’t causing much performance issues, it can go upwards of a few thousand in game. I instance the on the client:

Each ray has detection code next to it, so technically this is creating 200 rays a second with sanity checks, stat changes, and CFraming. Everything else(animations, bullets, sounds, and anything aesthetic related) is done on the client.

I have a very bad computer(running at 1.6GHz), recording a video in studio(studio uses more CPU than in-game), and still running fine. I also have background programs that I am using.

1 Like

Yah, games should be able to handle a lot of raycasts I can already do 1k raycasts per frame on a stress test I did with my game with my projectile class, with absolutly no lag, but avoid print statements as much as possible, print statements are expensive to do

Also would you mind telling me what you use, like do you use hearbeat, renderstepped, stepped ect.

1 Like

I just checked out the game, it looks fantastic! I’m surprised how many projectiles/raycasts can actually be handled, and thats per frame on the server!

Are you destroying the projectiles on the client when they hit something with the raycasts, then having separate raycasts on the server that determine whether it actually hit something? (I think that’s what you said)

For object pooling I’d plan on making a set amount (maybe 100) projectiles, and instancing new ones each time there were none left in the pool.

Otherwise, seems awesome, thanks!

1 Like

Alright when the projectile is hit its removed completely, and yes the server raycasts for hit detection with no model while the client raycasts with a model for visuals. When the bullet on the server is hit the server bullet gets desotryed. Same with the client bullet, when the client bullet is hit the client bullet gets destoryed.

The bullets also get destoryed when they move 400 studs server or client. Server would just be removing the bullet from the table ect cause it has no model.

The client Never Communicates to the server after the bullet is created. The server does hit detection in it’s own to prevent exploiters, before this I have had systems which made the client fire if the projectile was hit, not only was it hard to make it was also spamming the server with a lot of requests, causing latency issues. The server and the client and the nonshooter clients manage the bullets by themselves.

My bullets aren’t hitscan the server doesn’t do a ray one time for each bullet. Instead, it does a ray for each nextposition the bullet goes to until it is destroyed. this is because people can move during the projectile running so for hit detection I use multiple rays until the bullet gets destroyed.

Each time the bullet moves a raycast is made, wheater on the client or server, except the server one has no physcial representation. So thats a lot of ray because for each bullet the server might do 5 to 6 raycasts depending on the velocity and the distance the bullet gets destoryed.

Some things I also do is disconnecting the hearbeat loop when theres no projectiles running and run it when theres atleast 1 projectile (optimization). Basically what I do is make a bullet on the client tell server to make it, and then the server pings all the other clients which arent the shooter to make a bullet to which is used for visuals like the shooter one.

For ping compesasation I add the amount of elapsedtime from when the client created the bullet to when the server recieved the message. And raycast there to see if the bullet is already hit, if not then I just start it from the position it should be on that elapsedTime. Same for lag compesation on the other clients which didn’t fire the gun but need to display it except they get compesated from when the client shot the bullet to when they got the message

My bullets run on a equation something like this: Distance = ElapsedTime * 3dVelocityRepresntation that’s why there always are where theyre supposed to be regardless of the framerate being low.

Object pooling is a bad idea I tried it before and it wasn’t the best solution for me. If you really want to try it I reccomond reusing the object pool.

If you have any other questions you can ask me.

Incase you were wondering it took me about a half a month to think of this system, I experiemnted with many systems and this one seemed the best!

Im suspecting your using instant projectiles and not projectiles which take time as a consideration, so the server doesn’t cast a ray for EACH bullet every frame until it’s destoryed, which is about 30 raycasts per frames for each bullet if the bullet takes 400 distance to travel and speed of 400 without hitting anything. My projectiles do take time as a consideration so you will be able to handle even more then mine, since you will not have to cast 30 rays for each bullet if the bullet isn’t hit

If this is the case you can handle way more projectiles then mine using a basis of my system except of a raycast for the projectile every frame instead since it’s a instant projectile you can do 1 raycast per bullet

Nice I did a stress test and I can handle 400 bulets a second, at 40 to 45 fps on my good computer, don’t know about the other one tho.

4 Likes

My game “Blox Royale: Battlegrounds” gets around alot of this by doing all the visuals on the client. This is a good model to go by for every game, always do your visuals on the client.

For example, you can choose to render bullets on the client only if the player is close enough or has a certain graphics setting on.

2 Likes

Yah I forgot to mention that, magnitude checks for rendering. Do you have any other tips Im still looking to improve my projectile class

2 Likes

Alot of them have been said above, use object pooling. Use fastcast, it works and its easy.

If you do choose to use fastcast, make it so it only updates the position of the bullet to the client every 3 frames instead of every frame and then tweens the position on the client to make it not look horrible and jumpy, this will also reduce lag due to it not moving every frame.

2 Likes

Awesome!

I do have a few follow up questions if you wouldn’t mind answering:

Do you fire a remote for each projectile every frame when the user is clicking and holding, or are you only sending a remote to tell the server when the client is holding down their mouse and creating projectiles on the client/raycasting on the server every frame while they are? I’d assume firing a remote every frame for each projectile would not be too great.

I’m actually developing projectiles that are more slow moving and would be raycasting like you are every frame for the next position they are in. Are you using physics to simulate the arc of your projectiles or CFraming them?

Also, what does the script activity look like for your script on the server/client creating the projectiles? Is it above 3%?

And finally, should I create a new connection to Heartbeat for each projectile, or only one connection that moves all projectiles currently created? I read somewhere that looping over all the created projectiles in like a table and updating their values is better for performance than creating a whole new loop per projectile.

Thanks for your indepth answers, they are very helpful! I have so much stuff I want to ask but I’ll leave it at that for now and get to experimenting more on my own

Only one hearbeat connection, I am cframing my projectiles, using a bullet drop equation (which simulates an ark), firing a remote event every frame didn’t cause any issues. But I used a diferent way because it might be more performant with a lot of bullets.

What I do when a bullet is created store it in a table in the client. The client bullet immediatly starts. Every 0.1 seconds the client sends this table to the server and make this table emtpy afterword. The server creates all the projectiles, each table has a shootingcframe, startingcframe ect. This requires proper lag compesation techniques tho since the max delay is 0.1 second plus the ping, and I have that in place so this works correctly.

Basically something like a que system, the clients script activity on the shoothandler script stays at 5 to 15 percent when bullet travels at full distance.

However if the bullets get desotryed in midway such as hitting walls since the client will not have to do a lot of raycasts making the script take less then 15 percent. The least I got was 2 percent when I was 20 studs away from a wall. Also the less speed your bullets have the less script activity they will have since longer raycasts are more laggy.

The server suprisingly stays at less at 7 percent. The client script could be optimized by not raycasting and instead when the ray is hit on the server tell the client to delete that bullet, by giving each bullet an unique id. However, it woudn’t provide immediate visual feedback if the client ping is high.

On your game since you have a bunch of bullets I would go with this method since you have a lot you don’t need immediate feedback since some of the bullets before will hit to. In your game I would go with the client not raycasting and only moving but destorying that bullet when the servers raycast is hit, however this only looks good if you have proper lag compesation, you probbaly should since your game will have way more bullets then mine.

1 Like

Thank you again for all your help! I’ll keep all this advice in mind!