Projectile Lag Fix?

Hello,
I’ve made a script that creates a projectile. This project will lose all of it’s velocity, turn invisible (to simulate being destroyed), emit particles, and then destroy itself. There are a few problems though:

  1. When it hits something, it takes a second before it actually registers it being hit, leading to the explosion happening a bit after hitting the object.

  2. Another strange thing is that the collision also registers a bit late or early randomly. This was also happening before I added collisions when the bullet just gets destroyed it would happen earlier or later every time.

  3. More of a minor thing, but every time I start the game, the output says “Multiple StyleLinks under RobloxGui may result in undefined behavior”

The code is:
(Ignore the disorganized naming convention for the particles, this is more of a test game.)

game.ReplicatedStorage.RemoteEvent.OnServerEvent:Connect(function(player,mouseposition)
	local particleemitter = game.Workspace.ParticleEmitter:Clone()
	local particleemitter2 = game.Workspace.ParticleEmitter2:Clone()
	local particleemitter3 = game.Workspace.Lines:Clone()
	local particleemitter5 = game.Workspace.ParticleEmitterGlow:Clone()
	local four = game.Workspace.ParticleEmitterRing:Clone()
	local bullet = Instance.new("Part")
	local attachment = Instance.new("Attachment")
	local cantouch = true

	attachment.Parent = bullet
	bullet.Shape = Enum.PartType.Ball
	bullet.Size = Vector3.new(1,1,1)
	bullet.Parent = workspace
	bullet.CFrame = player.Character.HumanoidRootPart.CFrame
	bullet:SetNetworkOwner(player)
	local velocity = Instance.new("LinearVelocity")
	velocity.Attachment0 = attachment
	velocity.Parent = bullet
	velocity.MaxForce = 99999999999
	velocity.VectorVelocity = (mouseposition - bullet.Position).Unit * 100
	bullet.CanCollide = false
	bullet.Material = Enum.Material.SmoothPlastic
	particleemitter.Parent = attachment
	particleemitter2.Parent = attachment
	particleemitter5.Parent = attachment
	four.Parent = attachment
	particleemitter3.Parent = attachment
	bullet.Touched:Connect(function(yes)
		if cantouch == true and not (yes.Parent == player.Character) and not (yes.Parent.Parent == player.Character) then 
			cantouch = false
			print(yes)
			particleemitter2:Emit(10)
			velocity.VectorVelocity = Vector3.new(0,0,0)
			bullet.Transparency = 1 
			task.wait(0.05)
			particleemitter5:Emit(25)
			particleemitter3:Emit(25)
			particleemitter:Emit(50)
			four:Emit(1)
			task.wait(2)
			bullet:Destroy()
		end
	end)
end)

Here is a video of the problem:

1 Like

Making projectiles that use velocity like this will always pose this issue: replication lag makes the projectile not have precise hit detection.
You can look into 2 alternatives:

  1. Keep velocity based projectiles, but they are handled client side so the hit reaction is instant. You will also get the added benefit of the projectile feeling more responsive since it will appear right when the player uses the ability/item that makes a projectile.
  2. Use raycasting. There is a community resource module that already does this called FastCast, but the foundation isn’t complex and you can set it up yourself.

What’s the point of using velocity based projectiles if the max force for the LinearVelocity is 999999999? At that point, just manually update the CFrame or tween it. Still, I would recommend you go with a community projectile module like FastCast (also mentioned by @Giftdenz) which includes features such as custom bullets and custom hit effects.

I’ve done so much research on projectiles and holy hell they’re hard to make feel good.

If I remember correctly, this is happening because of network ownership being the player. The player is actually simulating the touch, and then sends a signal to the server. However in the time it takes for that signal to reach to the server and back, your projectile has already moved past the final hit position.

The easy fix would be to set the Network owner to nil (the server.) However this causes a visible stutter. To fix this, you could create two. A fake projectile for show which is set to the player, and the real projectile Network owned by the server which is invisible, and detects hits. Then, when the real projectile detects a hit, delete both.

Because then that’s better than CFrame or tweening, if ur using max force like that, you don’t need to manually update the CFrame or Tween it, the physics already handle that.

1 Like

This happens because Roblox .Touched is not suitable for fast touch detections, thats why you Raycast if you want to have fast projectiles

Your fix would be, send CFrame of your Mouse position in world, and on server raycast to the direction from player

I find .Touched for projectiles to be extremely unreliable since there seems to be a random latency to it. Instead, I like to loop GetPartsBoundsInBox using the position and size of my projectile while it is travelling on every tick for consistent and accurate collision detection. Here’s an example in practice:

task.spawn(function()
	local clone = grenade:Clone()
	clone.Position = position
	clone.Velocity = velocity
	clone.Parent = workspace
	clone:SetNetworkOwner(player)

	while task.wait() do
		local params = OverlapParams.new()
		params.FilterDescendantsInstances = player.Character:GetChildren()
		params.FilterType = Enum.RaycastFilterType.Exclude
				
		local position = clone.CFrame
		local size = clone.Size
		local parts = workspace:GetPartBoundsInBox(position,size,params)
		if #parts > 0 then break end
	end

	local cloneEffect = explosionEffect:Clone()
	cloneEffect.Position = clone.Position
	cloneEffect.Parent = workspace
end)