While Bezier curves are a good method for knowing the direction of a projectile, they don’t always make sense. If you’re firing horizontally, it’d act how you’d expect, but if you ever ever fired in a declined angle, it would travel backwards in some fashion.
Projectile motion
Projectile motion is something that’s been well-documented and explored by coders, mathematicians, and physics enthusiasts; so something called SUVAT exists. It’s a collection of equations that allow us to determine properties of a projectile in motion. It’s velocity, it’s distance from the origin, it’s speed, etc.
This is if you want a system that allows for drop. If you’re looking for something that will always fire a projectile linearly, you simply leave out the portion of the equation that deals with drop.
Lots of projectiles
So with a basic understanding of SUVAT under your belt, you’ll want to experiment with getting initially a single projectile that works. Get it running on both your client, and on your server. Theres a couple of different approaches to handling something like this, but the most common way is that each client creates it’s own projectile, and tells the server when it fired, where it fired from, and the direction its traveling. The server then takes this data, and sends it to other clients, who then take that data and make a fake projectile. The original client is then in charge of handling all the collision data; where did it hit, when did it hit, what did it hit?
While we like to think of our players as being nice and trustworthy people, theres always a few bad apples. Hackers can now take these collisions and spoof them, by saying “This projectile has interacted with this object at this point.”. The server then gets the hit confirmation and trusts it and says A-OK. But remember those SUVAT equations we were talking about? The client isn’t the only place they can be checked. A really good method of checking if a collision actually happened is by seeing if the collision occurred along the path of the projectile. If the collision didn’t happen anywhere on it’s path, then it’s likely it never happened at all. If the collision did happen, but the projectile is saying it happened at 0.1 seconds, instead of the 12 seconds it should have taken, then it’s likely that the player is either lagging, or is hacking. This is where something called Server Authority and Sanity Checks come in.
Sanity Checks/Server Authority
This is your hack detection essentially. As all data is being passed through the server, you can analyze it. The main point of a Sanity Check is to say whether or not something actually makes sense. As with the previous mentions, these mean that you can look at the data being given VS the data you should have gotten. It’ll take some tweaking, and will require a bit of understanding for what you’re looking to check, but it will result in a system thats slightly more difficult to manipulate.
Once the server says that it’s a-ok, then it’ll prompt the clients to do whatever collision needed to have happened at any given point. And if the projectile is rejected? Just prompt each client to delete the bullet.
Performance
This is slightly more tricky. There are loads of different performance tips for this. Some people use parts that always exist, but thousands and thousands of units outside of the playing area, because moving a part vs creating a part is more performant. Phantom Forces uses (used? It’s been a while) a UI elements to create them, to give them a very unique appearance.
In terms of collisions, while Roblox’s in-built Raycasting is great, it can dip here and there. It’s often a good idea to break up the detection calls by a few frames, or limit the amount of projectile collision checks you’re doing every frame. Once you start getting projectiles over longer distances and faster, these start getting quite taxing. You could also use Parallel Luau to approach this, but I’ve not yet messed with that.
If you’re looking for something that’s physics-based, SUVAT is the way to go to determine how each projectile should start. If you’re looking for something thats code-based, SUVAT is still the way to go, but will be incredibly useful on both client/server.
Oh and if you do code-based projectiles, try and make sure that all projectiles are moved using BulkMoveTo. It moves all objects that’re referenced within it at once, which is cheaper than updating individual object cframe properties.
Hope that’s useful, and do look into SUVAT! I made a projectile system that I’ve been running with for years and I didn’t realize how close to these equations I was!