I am making a projectile system that is currently specified for a dodgeball. The initial throw is enacted through shapecasts to ensure that the throw is consistent and responsive, as well as having a bit of customizability. After the first bounce, it exits from the custom shapecast physics and utilizes Roblox’s physics, as only the visual appeal of the bounce matters.
Prior to the switch, the ball is made invisible on the server, and is cloned and applied force on every client so the bounce is smooth. Since they all enact the same force from the same position, they should logically end up in the same position, regardless of framerate.
The issue is that this not the case, as projectiles tend to end up in different positions across the clients depending on their current framerate, despite undergoing the same forces and collisions. This causes projectiles to end up even more disjointed overtime as other projectiles collide with them.
Here’s 2 screenshots of the same projectiles being thrown across 2 separate clients, one in focus and consistently running at 60fps and the other not in focus and averaging around 15fps.
60fps:
15fps:
All of the projectiles are in entirely different positions, implying that they all enacted different physics.
I’ve tried a handful of things to overcome this issue:
-
Having the bounce also on the server: This is undesired as movement would be clunky on at least one player’s end, regardless of network ownership.
-
Operating the bounce with shapecasts / raycasts: Shapecasts don’t operate properly at low distances, making decay and rolling very difficult to operate without the projectile clipping through surfaces.
-
Enacting force to the server and updating the client projectile’s position every frame: This makes the client projectile appear to be as clunky as if it was on the server.
-
Using bodypositions to naturally move the client projectile to the server: ApplyImpulse and other means of enacting force have a delay on the server, resulting in a weird rubber band effect in the client’s movement. Also, greater velocities result in the movement looking more like a tween.
Is there any other way I can make sure they end up in the same position whilst still making it look appealing?
Here’s the logic behind this for reference:
Server (just the logic after the shapecast disconnects):
self.Object.Position = current_pos
self.Object.Transparency = 1
self.Object.ProximityPrompt.Enabled = false
self.Object.Owner.Changed:Connect(function()
self.Object.Transparency = 0
end)
remotes.Client.BounceProjectile:FireAllClients(self.Object, self.Object.Position, velocity * 8)
Client (Bounce remote event):
client_remotes.BounceProjectile.OnClientEvent:Connect(function(proj : BasePart, pos : Vector3, force : Vector3)
if not proj then return end
local new_proj = proj:Clone()
new_proj.Position = pos
proj.CollisionGroup = "ServerSteadyBall"
new_proj.Transparency = 0
new_proj.ProximityPrompt.Enabled = true
new_proj.Parent = proj.Parent
new_proj:ApplyImpulse(force)
local decay_force = Instance.new("BodyForce")
decay_force.Parent = new_proj
local decay = RunService.Heartbeat:Connect(function()
decay_force.Force = Vector3.new(-new_proj.AssemblyLinearVelocity.X, 0, -new_proj.AssemblyLinearVelocity.Z) * 15
end)
local halt = new_proj:GetPropertyChangedSignal("AssemblyLinearVelocity"):Connect(function()
if Vector3.new(new_proj.AssemblyLinearVelocity.X, 0, new_proj.AssemblyLinearVelocity.Z).Magnitude < 5 then
decay:Disconnect()
if decay_force.Parent then
decay_force:Destroy()
end
end
end)
new_proj.Destroying:Connect(function()
decay:Disconnect()
if decay_force.Parent then
decay_force:Destroy()
end
end)
proj.Owner.Changed:Connect(function(value)
if value then
new_proj:Destroy()
end
end)
new_proj.ProximityPrompt.Triggered:Connect(function(player)
proj.Collect:FireServer()
end)
proj.Destroying:Connect(function()
new_proj:Destroy()
end)
end)
srry for how long this btw, this is just hella complicated