Projectile Desynchronization

When you fire a projectile, but it lags behind...

by hotfeet09

Author Avatar

The problem: My projectiles, when fired, behave weirdly due to lag. This ranges from projectiles stuttering in mid-air, to having weird collision detection

The solution: Some clever use of the client and BasePart:SetNetworkOwner()

DISCLAIMER: This tutorial is for solving a common issue with physics-based projectiles, like ones from the classic ROBLOX Slingshot or Paintball Gun. For guns that use raycasting for hit detection, you may want to look into alternative solutions.

First, we need a functioning ranged weapon. This is simple, with some light math involved in its creation. We need to create a handleless Tool, then insert a Script, a LocalScript, and a RemoteFunction with the name "MouseLocation" inside of it. Then, a RemoteEvent should be placed in ReplicatedStorage.

Script:

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local POSITION_OFFSET = 8
local PROJECTILE_SPEED = 85
local SERVER_HITBOX_TRANSPARENCY = 1

local tool = script.Parent
local player = tool.Parent.Parent
local remote = tool.MouseLocation
local event = ReplicatedStorage.RemoteEvent
local head = player.Character.Head

tool.Activated:Connect(function()
    local mouseposition = remote:InvokeClient(player) --gets the mouse from localscript
    local spawnpos = head.Position + ((mouseposition - head.Position).unit * POSITION_OFFSET)
    local velocity = (mouseposition - head.Position).unit * PROJECTILE_SPEED

    local projectile = Instance.new("Part")
    projectile.Size = Vector3.new(1, 1, 1)
    projectile.Shape = Enum.PartType.Ball
    projectile.Position = spawnpos
    projectile.Velocity = velocity
    projectile.Transparency = SERVER_HITBOX_TRANSPARENCY
    projectile.Parent = workspace
    projectile:SetNetworkOwner(nil) --this is the invisible server hitbox projectile

    projectile.Touched:Connect(function() --when server hitbox hits something, it destroys itself and explodes
        local explosion = Instance.new("Explosion")
        explosion.Position = projectile.Position
        explosion.Parent = workspace

        projectile:Destroy()
    end)

    event:FireAllClients(spawnpos, velocity, projectile) --tell the localscript to create a visual copy
end)

LocalScript:

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local player = Players.LocalPlayer
local mouse = player:GetMouse()
local tool = script.Parent
local remotefunc = tool:WaitForChild("MouseLocation")
local event = ReplicatedStorage:WaitForChild("RemoteEvent")
--client doesnt receive these objects instantly unlike server

remotefunc.OnClientInvoke = function()
    return mouse.Hit.p --returns position of mouse to the server
end

event.OnClientEvent:Connect(function(spawnpos, velocity, projectile)
    local visualcopy = projectile:Clone()
    visualcopy.Position = spawnpos
    visualcopy.Velocity = velocity
    visualcopy.Transparency = 0
    visualcopy.Parent = workspace --visual copy of server projectile

    projectile.AncestryChanged:Connect(function()
        visualcopy:Destroy() --when server projectile is destroyed, visual copy destroys itself also
    end)
end)

If set up right, the tool should behave normally with no errors. Remember, the tool needs to be handleless!

Since we've created a visual copy of the projectile on every connected player's client, the projectile should look smooth for everyone. Furthermore, since we set the network owner of the server projectile to "nil", it means that the server has full network ownership over the server's projectile. That means that, instead of the projectile shifting between networks like with automatic network ownership, the projectile instead always sticks with the server's network.

If we didn't set the network ownership of the server projectile, the projectile would be heavily desynchronized, often looking like its exploding sooner than it should, changing the position of the explosion to somewhere far behind the impact point. If we didn't create the visual copies however, the projectile would look very jittery for everyone. Doing this allows you to have accurate physics-based projectiles, but also allow them to look good. For competitive games where precise aim is key, this information is invaluable for any ROBLOX developer.

I hope you've been able to glean something from this article, and hope you have a wonderful rest of your day.