Cannonballs lag before being launched

What is the issue?
I scripted a simple cannon but it seems like every time I shoot, the cannonballs get stuck for a couple of seconds before getting launched, How can I fix this ?


Here’s the script:

script.Parent.fire.OnServerEvent:Connect(function()
	local seat = script.Parent.Canon.Value.VehicleSeat
	local cannon = seat.Parent.barrel

	local ball = Instance.new("Part")
	ball.Shape = Enum.PartType.Ball
	ball.Massless = true
	ball.Parent = workspace
	ball.CanCollide = false
	ball.Size = Vector3.new(2,2,2)
	ball.Reflectance = 1
	local direction = seat.Parent.hitbox.CFrame.LookVector
	local cannonCFrame = cannon.CFrame * CFrame.new(0, 0, -5)
	ball.CFrame = cannon.CFrame + direction * 5
	ball.AssemblyLinearVelocity = direction * 500
end)

This is due to replication overhead, you’re simulating the cannon ball on the server. Which means it will be simulated with a latency of at least PING * 2 on the client.

2 Likes

What the client script its prob client issue

you could just mask it off by actually putting the cannonball deeper, so its not visible that it snaps

I wouldn’t recommend actually using this, but this is an example of what may help, try it out and see if you achieve your desired effect.

local function FireCannon(player)
	local seat = script.Parent.Canon.Value.VehicleSeat
	local cannon = seat.Parent.barrel

	local ball = Instance.new("Part")
	ball.Shape = Enum.PartType.Ball
	ball.Massless = true
	ball.Parent = workspace
	ball.CanCollide = false
	ball.Size = Vector3.new(2,2,2)
	ball.Reflectance = 1
	ball:SetNetworkOwner(player)
	local direction = seat.Parent.hitbox.CFrame.LookVector
	local cannonCFrame = cannon.CFrame * CFrame.new(0, 0, -5)
	ball.CFrame = cannon.CFrame + direction * 5
	ball.AssemblyLinearVelocity = direction * 500
end
script.Parent.fire.OnServerEvent:Connect(FireCannon)

I speculate what you’re seeing is replication, as this script is most likely running on the server. Replication tends to make things look “laggy”.

By setting the network ownership to the client, the ball’s physics are simulated on the client, which could either eliminate that look, or make it drastically worse. So I recommend you play with it a bit, as this is just meant to be a small push in another direction.

1 Like

I don’t think this would eliminate the “freezing” the OP is talking about. The cannon ball will always freeze as long as it’s spawned on the server. An actual solution would be to animate the cannon ball on the client, and perform the projectile simulation on the server.

yep, client-side visual + server-side hitbox is the way to go. spawn the ball locally the moment the player fires so it feels instant, let the server handle the actual damage separately. no amount of SetNetworkOwner will fix the initial spawn delay since replication always has at least one round trip

local function spawnVisualBall(origin, direction)
    local ball = Instance.new("Part")
    ball.Shape = Enum.PartType.Ball
    ball.Size = Vector3.new(2,2,2)
    ball.Reflectance = 1
    ball.CanCollide = false
    ball.Massless = true
    ball.CFrame = CFrame.new(origin)
    ball.AssemblyLinearVelocity = direction * 500
    ball.Parent = workspace
    game:GetService("Debris"):AddItem(ball, 5)
end

fireEvent:FireServer()
spawnVisualBall(cannon.CFrame.Position + direction * 5, direction)

You can set velocity before parenting. A flash helps hide this too. One approach with this I like is just going with it and using a delayed transparency. Ball/Shell starts out transparent. This called just before the shot happens.

task.delay(0.05, function() --whatever looks best timed out.. 0.33 on my tanks.
	ball.Transparency = 0
end)

How I have fixed this is by having a seperate client and server bullet.

When fired, the gun shoots a client-side uncolidable bullet and then fires the server-side function which makes the real server sided bullet. An identifier is also passed to the bullet. Then a seperate piece of client-side code listens for any part made and if the identifier matches, it hides the server-side bullet.

You can also have an event fire the client to destroy the client-side bullet when the server-side bullet collides/is destroyed. If wanted.

This is complicated and most likely over-engineered but It works very well.

Explanation

I discovered this when I realized that an explosive bullet fired exploded before visually hitting the ground. The time in between when it actually explodes and when it should visually hit the ground is the same as the time in between the bullet spawning in and beginning to move. This is due to weird replication artifacts where the bullet explodes at the same time for the client and the server but the client visual is behind where it is in actuality (server). I used a shot prediction script to get the time to target and destroyed the client-side bullet using a timer, so the event to destroy a client side bullet is untested by me.

This is a way to make @Axe3z’s “client-side visual + server-side hitbox” work. Another method would be to fire the server when the bullet interacts with something (to say, damage a player) but writing

event.OnServerEvent:Connect(function(Player,Hit)
    if Hit.Parent:FindFirstChild("Humanoid") then
        Hit.Parent.Humanoid.Health -= 10
    end
end)

is not a good idea as it can easily be spoofed by hackers.

Good luck, and godspeed.

This seems to have fixed the cannonballs, thanks!

1 Like

I know this thread is closed, but you should definitely know that giving network ownership of the cannon ball to the player (who is firing) makes it so that the server has to trust the client that the ball wasn’t manipulated, which is against one of the core principles of scripting: never trust the client

I strongly urge you to (re)consider the other solutions we provided.

1 Like