Question about client replication for projectiles

So I have spent the day learning about making projectiles smooth with good hit detection. What I have taken away is that a client will fire the server like so :

local UIS = game:GetService("UserInputService")

UIS.InputBegan:Connect(function(key,chat)
	
	if not chat and key.KeyCode == Enum.KeyCode.T then
		
		game.ReplicatedStorage.ShootEvent:FireServer()
		
	end
	
end)

After the server has received the call it will create a loop or RunService (Not sure which is better) that continuously creates new points along the projectiles trajectory and then sends a signal to the clients, while also sending the projectiles current position , something similar to this :

game.ReplicatedStorage.ShootEvent.OnServerEvent:Connect(function(plr)
	
	local char = plr.Character
	
	local startPos = char.HumanoidRootPart.Position
	local Direction = char.HumanoidRootPart.CFrame.LookVector
	local currentPosition = startPos
	
	local count = 0
	local speed = 0.1
	
	while true do
		
		--Code that moves the projectile
		
		currentPosition = startPos + Direction * count
		
		count += speed
		
		game.ReplicatedStorage.Effects:FireAllClients(currentPosition)
		
		task.wait()
	end
	
end)

The client will then receive the second call and clone a unique projectile for each client :

local clone = game.ReplicatedStorage.Part:Clone()
clone.Parent = game.Workspace

game.ReplicatedStorage.Effects.OnClientEvent:Connect(function(position)
	
	clone.Position = position
	
	--Code here that does hit detectection for the each client to see if the projectile hits them on their screen?
	
end)

Anyway that is what I think I have gathered but I have come here to be corrected if I am wrong because I want to do my projectiles correctly without any issues with optimisation or delay. Obviously this method has higher input delay than others but it is a good tradeoff if it works. Excuse the sloppy code as it was just a quick type up for this thread.

2 Likes

There’s probably a better way.

  • Send a remote to the server requesting a projectile. The server sets the network ownership of the projectile to the client. The server performs a raycast to check if it will hit anything.
  • The client manages everything to do with the projectile (effects, physics, etc.)
1 Like

Won’t this make it so if you are lagging you will get hit when you are out of the way of the projectile as well as make the projectiles movement and speed accessible to cheaters. Just asking because I am not 100% sure.

You are doing a lot of unnecessary work here.
Instead of manually replicating the position of your projectile inside a while-loop (You should ideally use RunService for loops like this anyway) you could achieve the same while sending much less data, but just sending the data necessary to reproduce the projectile on the client.

You can achieve this by taking the code that moves the projectile, and placing it on the client instead. Then when you wish to replicate the projectile to all clients, you simply fire the remote with information regarding the start, end, and speed of your projectile.

1 Like

Oh I see, I changed it. I have one last question. Where do you advise I run the hit detection? On the client who shot the projectile or the client getting hit by it?

Good question!
The answer varies by preference, but my answer would be neither!

Allowing any client to run hit-detection opens your game to a bunch of vulnerabilties.
Remember; anything your client can do, and exploiter can manipulate!

If you give the attacking client control, an exploiter could hit everyone regardless of where they are aiming.

If you give the attacked player control, an exploiter could ensure they would never be hit by any attacks.

Instead you should do the hit-detection on the server! The particular details can be bothersome, as there are many approaches to this, each with their pros and cons.

For slow-moving projectiles, I would recommend deciding on a “tick-rate” and using that, every so often conduct a shapecast such as a Spherecast to see if the projectile hit anyone.

For fast-moving projectiles, I would use a single Raycast when the projectile is fired.

However this is something you will have to consider yourself, the ideal choice heavily depends on what game you are making.

1 Like

Damn, I was attempting to use a method like this to avoid hit detection on the server to to save network. The things I want to achieve are :
-Smooth and non jittery projectiles
-Projectiles that will have minimal effect on performace.
-Projectiles with a fair balance between the two clinets so that the one does not get hit when they dodge and the one who shoots does not miss when it clearly hits their opponent on their screen.

Building the projectile visuals on the client is still a very good idea!
The server absolutely does not need to be moving around a potentially detailed model just because a player fired a projectile, when in reality, it would only be using its position.

What I am trying to say is that you should still replicate the projectile to the client like you were attempting. I just don’t recommend doing the same with hitboxes.

As for consistency, I recommend you favor low-ping players. It’s unfortunately impossible to make hitboxes appear perfect for both parties, so it is better (in my opinion) to cater to players with low ping, as this will (hopefully) be the majority of your playerbase.

1 Like

Did not go into development expecting it to be this painful when it comes to decisions like these. Thanks for the advice though, it was very helpful. So to sum it up I should have the visuals of the projectile on the client, the hit detection on the server and I have one more question if you don’t mind. I am going to incorporate a deflection system into the game. So the player will be able to redirect an incoming projectile which will change its speed and direction. How should I go about doing that?

You got it right with the visuals and hitbox.
As for the deflection you mention, there are many approaches to this. The immediate solution that comes to mind is to give each projectile a UUID and using that to allow the server to fire a separate remote which redirects the projectile for all clients when fired.

One way to achieve this would be to instead of creating a new projectile when the remote you have is fired, check if there already exists one with the given UUID, and just change it’s position accordingly.

2 Likes

I am not sure what a UUID is exactly. Is it simply a identifier for different projectiles I assume?
Also one more question is should I use Unreliable Remote events for the client version of the projectile as it is faster? Oh and sorry for being annoying at this point but since the server will be handling hit detection, should I send a tick value back to the clients to making the starting position start more in front of the attacker to make the position of the projectile more accurate or is this unnecessary?

UUID is short for Universal Unique Identifier. It’s a generic term for an identifier that is universally unique. People typically generate them by just generating a string long enough that you will statistically never get a duplicate. Roblox does have a built-in function for this.

I wouldn’t personally use Unreliable Remote Events for this. You seem to misunderstand their purpose. They aren’t faster, but rather, they allow for your game to run faster, because if you game ever falls behind on handling network requests, any unreliable remote event that hasn’t been handled will just be dropped. Essentially, they are unreliable as the name suggests. They should be used for networking that doesn’t hold much importance. Using them for projectiles could result in the projectile not appearing at all for some players.

As for compensating for the by passing the timestamp. You could do this, but I don’t believe it would be necessary. If implemented correctly it could improve the experience for your players, but it would be a decent amount of work, for little payoff.

1 Like

Alright, I found the timestamp compensation relatively easy except for the fact that task.wait and runservice do not have an exact value so it is difficult to determine the amount of frames that have passed on different scripts as I will be using either one. I found doing frame = timepassed/0.085 to be the most accurate way to turn time into frames and move the projectile by speed * frames but if you have a more accurate way please share. I will mark you as solution for your help. Thanks for you time. :grin:

Be careful with relying on a fixed framerate for your projectiles, as some players use thirdparty FPS unlockers. And rumors are that Roblox are also working on their own FPS unlockers. Meaning framerate might not stay as consistent in the future!

1 Like

So do I use task.wait() in a while loop with a set number in brackets?

I recommend you use Stepped on the server, and RenderStepped on the client. Both of these are RBXScriptSignals, and will pass the delta time since it was last triggered.

1 Like

Thanks, won’t bother you anymore. You are a life saver!

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.