Keeping projectile movement synced both on the server and client?

I have a test weapon that fires a projectile that travels at 1 stud per second, but I keep having problems keeping the projectile synced with both the client and the server.

setting the network owner to the server causesa delay before the projectile even fires.
setting the network owner to the player keeps the projectile smooth when the player sees it, but the delay is now on the server.

I did a test to confirm this:

local firepart = script.Parent
local projspeed = 1


function fire(plr,cfr,part)
	spawn(function()
	
	local mag = (firepart.Position - cfr.p).Magnitude
	local proj = game.ReplicatedStorage.Assets.TestProjectile:Clone()
	proj.Parent = game.Workspace
	proj.CFrame = CFrame.new(firepart.Position,cfr.p)
	proj.BodyVelocity.Velocity = proj.CFrame.LookVector * projspeed
	proj:SetNetworkOwner(plr)
	
	print(mag)
	wait(mag)
proj:Destroy()
	end)
end

magnitude is the amount of time the script waits before deleting the projectile, and since the projectile is traveling at 1 stud per second, 10 magnitude means 10 seconds before the projectile is deleted. by the time the projectile reaches cfr.p, the projectile is gone.
when watching from the server the projectile is deleted a bit early indicating that there is a delay on the server rather than the client.

is there a way to keep projectiles smooth on both the client and the server?

1 Like

By definition, if you’re replicating data over a network, there will be a delay with a minimum of the ping time.

Therefore, the trick would be to minimize actual network replication, right?

This doesn’t work in all scenarios, but I bet you can find a way to adapt this technique to whatever your situation may be:

For sake of simple example, say we are trying to move a part from point A to point B at a constant velocity.

The way I would do this is send a remote to all clients containing the two points, along with the timestamp that it was spawned.
Then, each client moves the part each Heartbeat, checking the time passed since the timestamp and updating position accordingly.

try not to use spawn(), since it does not immediately schedule the code you are trying to run, so there will be a delay.

Instead, use coroutine.wrap().

Next, don’t rely too much on Roblox BodyVelocity to work exactly how you want - BodyVelocity does not directly set the velocity of the part it’s in, but instead applies a force to the object. This means that the object will gradually speed up until it reaches the desired velocity.
A better way to delete the projectile is to check every heartbeat how far away the part is from cfr.P
If the part is within a threshold value, then you should remove it. This better accounts for all the variance in the velocity.

Finally, anyone who doesn’t hold network ownership will always see a laggy projectile, as they are receiving replicated cframe information from the client.

there seems to be a lot of complexity just to keep everyone syncronized.
my main concern mostly regards the impact, I know a lot of people have this same issue whether they set the network owner or not as there will always be a visible hop or studder regarding projectiles and what they hit.

1 Like

Yes, this is a very difficult issue to completely solve.
My approach is to replicate a projectile on each of the clients, so that each one has their own local copy. Then, I do client-sided hit detection for non-essential things like impact sounds or particles, so that the projectiles still feel responsive. However, at the same time, I do the real hit detection on the server to make sure hit detections are agreed upon by all clients.

There’s a few small tricks you can do to make this better - namely, you can add some form of delay to the animations before the projectile is created, which gives some buffer time for replication to occur.
A trick I really like to use is to slow down projectiles on the client that created them, and speed them up on the clients receiving the replicated projectiles. If you do your math right, you can get projectiles to roughly sync up to the server hit detection.

5 Likes

just a thought, but what if I set the network ownership to the player. the projectile will be using a touched function.

normally if the projectile hits something while owned by the player the acual hit on the server “lags” and the projectile continues to fly through the part until the server catches up and deletes it.

but…what if I take the localscript firing the remote event and have it setup its own touch function? the localscript will delete the projectile upon hitting something, but because the server ignores this change the server will continue to process the projectile as normal.

theoretically, the visual problem on the client gets fixed and the performance on the server also remains smooth.

would this work?

Since you are setting network ownership to the player, the projectile might still appear choppy.

However, it would work exactly as you just said - since any client changes don’t replicate to the server, then the projectile will still exist on the server (and on other clients).

well this is all really good information, ill see what I can do for the time being. Im seeing if I can get my theory going but Im keeping my fingers crossed.