Laggy bullets after i made them spawn on the server instead of the client

for some reason i cant attach a video but i tested creating bullets with remote events and they are quite laggy.

Cool! Code?


local function ReturnDecimal(range)
	return Random.new():NextNumber(-range, range)
end

local function Lerp(a, b, t)
	return a + (b - a) * t
end

game.ReplicatedStorage.RemoteEvents.CreateBullet.OnServerEvent:Connect(function(Player, Properties)
	local Bullet = Instance.new("Part")

	for k, v in pairs(Properties) do
		if typeof(v) == "table" then
			for l, q in pairs(v) do
				Bullet:SetAttribute(k.. "_".. l, q)
			end
		elseif typeof(v) ~= "function" and typeof(v) ~= "Instance" and k ~= "Speed" and k ~= "Direction" and k ~= "Origin" and k ~= "Lifetime" then
			Bullet:SetAttribute(k, v)
		end
	end

	Bullet:SetAttribute("ID", tostring(math.random(1, 10^8)))
	Bullet:SetAttribute("TimesRicocheted", 0)
	Bullet:SetAttribute("EnemiesHit", 0)
	Bullet:SetAttribute("BulletVelocity", Vector3.new(Properties.Direction.X + ReturnDecimal(Bullet:GetAttribute("MaxInaccuracy")), Properties.Direction.Y + ReturnDecimal(Bullet:GetAttribute("MaxInaccuracy")), Properties.Direction.Z + ReturnDecimal(Bullet:GetAttribute("MaxInaccuracy"))) * Properties.Speed)
	Bullet.Name = (Properties.Name and true or "Bullet").. Bullet:GetAttribute("ID")
	Bullet.Size = Properties.Size or Vector3.new(0.75, 0.75, 0.75)
	Bullet.Color = Properties.Color or Color3.new(0, 0, 0)
	Bullet.Material = Properties.Material or Enum.Material.SmoothPlastic
	Bullet.CFrame = Properties.Origin
	Bullet.CollisionGroup = "Extra"
	Bullet:AddTag("Bullet")
	Bullet:AddTag(Properties.ExtraType)
	Bullet.CastShadow = false
	Bullet.AssemblyLinearVelocity = Bullet:GetAttribute("BulletVelocity")
	Bullet.Parent = workspace

	local BulletHitByHitscanEvent = Instance.new("BindableEvent")
	BulletHitByHitscanEvent.Name = "BulletHitByHitscanEvent"
	BulletHitByHitscanEvent.Parent = Bullet

	local BulletHitByBulletEvent = Instance.new("BindableEvent")
	BulletHitByBulletEvent.Name = "BulletHitByBulletEvent"
	BulletHitByBulletEvent.Parent = Bullet

	local BulletShooter = Instance.new("ObjectValue")
	BulletShooter.Parent = Bullet
	BulletShooter.Value = Properties.Shooter
	BulletShooter.Name = "Shooter"

	BulletHitByBulletEvent.Event:Connect(function()
		Properties.BulletHitByBulletFunction(Bullet, Properties)
	end)
	BulletHitByHitscanEvent.Event:Connect(function()
		Properties.BulletHitByHitscanFunction(Bullet, Properties)
	end)

	local RaycastParameters = RaycastParams.new()
	RaycastParameters.RespectCanCollide = true
	RaycastParameters.FilterDescendantsInstances = {Bullet.Shooter.Value}
	
	local Loop = game:GetService("RunService").Heartbeat:Connect(function()
		local Blockcast = workspace:Blockcast(Bullet.CFrame, Bullet.Size, Bullet.AssemblyLinearVelocity.Unit * 4)
		Movement(Blockcast, Bullet, Player)
		Damage(Blockcast, Bullet)
	end)

	Bullet.Destroying:Connect(function()
		local EffectProperties = {
			EffectType = Bullet:GetAttribute("EffectProperties_EffectType"),
			OutColor = Bullet:GetAttribute("EffectProperties_OutColor"),
			InColor = Bullet:GetAttribute("EffectProperties_InColor"),
			Size = Bullet:GetAttribute("EffectProperties_Size"),
			Damage = Bullet:GetAttribute("EffectProperties_Damage"),
			Position = Bullet.Position,
		}
		game.ReplicatedStorage.RemoteEvents.MakeEffect:Fire(EffectProperties)
		Loop:Disconnect()
	end)

	return Bullet
end)

local function SelectTarget(ObjectToSearch, Bullet, Shooter)
	local NearestObject = nil
	for i, v in pairs(game.CollectionService:GetTagged(ObjectToSearch)) do
		local RaycastParameters = RaycastParams.new()
		RaycastParameters.RespectCanCollide = true

		if NearestObject == nil or (v:IsA("Part") and (v.Position - Bullet.Position).Magnitude < (NearestObject.Position - Bullet.Position).Magnitude) or
			(v:IsA("Model") and v ~= Shooter and (v.PrimaryPart.Position - Bullet.Position).Magnitude < (NearestObject.Position - Bullet.Position).Magnitude) then
			NearestObject = v
		end
	end
	return NearestObject
end

local function HandleRicocheting(RicochetRay, Bullet)
	if RicochetRay and RicochetRay.Instance and not RicochetRay.Instance:HasTag("EffectPart") then
		if not RicochetRay.Instance.Parent:FindFirstChildOfClass("Humanoid") and Bullet:GetAttribute("TimesRicocheted") < Bullet:GetAttribute("Ricochet_MaxRicochets") and not Bullet:HasTag("HeldByMagnet") then
			Bullet:SetAttribute("TimesRicocheted", Bullet:GetAttribute("TimesRicocheted") + 1)        
			if Bullet:GetAttribute("Ricochet_RicochetType") == "Perpendicular" then
				Bullet.AssemblyLinearVelocity = (RicochetRay.Normal - (-2 * RicochetRay.Normal)).Unit * Bullet.AssemblyLinearVelocity.Magnitude / 1.05
				Bullet:SetAttribute("BulletVelocity", (RicochetRay.Normal - (-2 * RicochetRay.Normal)).Unit * Bullet.AssemblyLinearVelocity.Magnitude)
			elseif Bullet:GetAttribute("Ricochet_RicochetType") == "Normal" then
				Bullet.AssemblyLinearVelocity = (Bullet.AssemblyLinearVelocity - (2 * Bullet.AssemblyLinearVelocity:Dot(RicochetRay.Normal) * RicochetRay.Normal)) / 1.05
				Bullet:SetAttribute("BulletVelocity", Bullet:GetAttribute("BulletVelocity") - (2 * Bullet:GetAttribute("BulletVelocity"):Dot(RicochetRay.Normal) * RicochetRay.Normal))
			end
		end
	end
end

local function HandleMagnetism(Bullet)
	local NearestMagnet = SelectTarget("Magnet", Bullet, Bullet.Shooter.Value)
	if (NearestMagnet.Position - Bullet.Position).Magnitude < Bullet:GetAttribute("Magnetic_MagneticRange") then
		NearestMagnet.Destroying:Once(function() Bullet:RemoveTag("HeldByMagnet") end)
		if Bullet:GetAttribute("Magnetic_MagneticType") == "Swarm" then
			Bullet:SetAttribute("BulletVelocity", Lerp(Bullet:GetAttribute("BulletVelocity"), (NearestMagnet.Position - Bullet.Position).Unit * Bullet:GetAttribute("BulletVelocity").Magnitude, Bullet:GetAttribute("Magnetic_MagneticStrength")))
			Bullet.AssemblyLinearVelocity = Lerp(Bullet.AssemblyLinearVelocity, (NearestMagnet.Position - Bullet.Position).Unit * Bullet:GetAttribute("BulletVelocity").Magnitude, Bullet:GetAttribute("Magnetic_MagneticStrength"))
			if not Bullet:HasTag("HeldByMagnet") then Bullet:AddTag("HeldByMagnet") end
		elseif Bullet:GetAttribute("Magnetic_MagneticType") == "Circular" then
			Bullet:SetAttribute("Angle", Bullet:GetAttribute("Angle") + (math.clamp(Bullet:GetAttribute("BulletVelocity").Magnitude / 900, 0, math.huge)))
			local NewX = NearestMagnet.Position.X + (7 / Bullet:GetAttribute("Magnetic_MagneticStrength")) * math.cos(Bullet:GetAttribute("Angle"))
			local NewZ = NearestMagnet.Position.Z + (7 / Bullet:GetAttribute("Magnetic_MagneticStrength")) * math.sin(Bullet:GetAttribute("Angle"))
			Bullet:SetAttribute("BulletVelocity", (Vector3.new(NewX, NearestMagnet.Position.Y, NewZ) - Bullet.Position).Unit * Bullet:GetAttribute("BulletVelocity").Magnitude / Bullet:GetAttribute("Magnetic_MagneticStrength") / 3)
			Bullet.AssemblyLinearVelocity = (Vector3.new(NewX, NearestMagnet.Position.Y, NewZ) - Bullet.Position).Unit * Bullet:GetAttribute("BulletVelocity").Magnitude / Bullet:GetAttribute("Magnetic_MagneticStrength") / 3
			if not Bullet:HasTag("HeldByMagnet") then Bullet:AddTag("HeldByMagnet") end
		end
	end
end

local function HandleHoming(Bullet, Player)
	local Shooter = Bullet.Shooter.Value
	local NearestEnemy = (Shooter ~= Player.Character and Shooter.AttackTarget.Value == Player.Character and SelectTarget("Player", Bullet, Shooter)) or SelectTarget("Enemy", Bullet, Shooter)
	if not Bullet:HasTag("HeldByMagnet") and NearestEnemy and NearestEnemy ~= Shooter and NearestEnemy.Humanoid.Health > 0 then
		if Shooter ~= Player.Character and NearestEnemy ~= Shooter.AttackTarget.Value then return end
		if (NearestEnemy.PrimaryPart.Position - Bullet.Position).Magnitude < Bullet:GetAttribute("Homing_HomingRange") then
			Bullet.AssemblyLinearVelocity = Lerp(Bullet.AssemblyLinearVelocity, Vector3.new((NearestEnemy.PrimaryPart.Position - Bullet.Position).Unit.X * Bullet:GetAttribute("BulletVelocity").Magnitude, Bullet.AssemblyLinearVelocity.Y, (NearestEnemy.PrimaryPart.Position - Bullet.Position).Unit.Z * Bullet:GetAttribute("BulletVelocity").Magnitude), Bullet:GetAttribute("Homing_HomingStrength"))			
			Bullet:SetAttribute("BulletVelocity", Lerp(Bullet:GetAttribute("BulletVelocity"), (NearestEnemy.PrimaryPart.Position - Bullet.Position).Unit * Bullet:GetAttribute("BulletVelocity").Magnitude, Bullet:GetAttribute("Homing_HomingStrength")))
		end
	end
end

function Movement(RicochetRay, Bullet, Player)
	HandleRicocheting(RicochetRay, Bullet)
	HandleMagnetism(Bullet)
	HandleHoming(Bullet, Player)

	Bullet.CFrame = CFrame.lookAt(Bullet.Position, Bullet.Position + Bullet.AssemblyLinearVelocity)

	if Bullet:GetAttribute("FollowsGravity") == false then
		Bullet.AssemblyLinearVelocity = Bullet:GetAttribute("BulletVelocity")
	end
end

function Drill(CharacterCollisionRay, Bullet)
	Bullet.Anchored = true
	for i = 1, Bullet:GetAttribute("ContinuousDamage_AmountOfLoops") do
		task.wait(Bullet:GetAttribute("ContinuousDamage_WaitPerLoop"))
		CharacterCollisionRay.Instance.Parent.Humanoid:TakeDamage(Bullet:GetAttribute("Damage") * ((Bullet:GetAttribute("LocationalDamage") == true and CharacterCollisionRay.Instance:GetAttribute("WeakpointMultiplier") ~= nil) and CharacterCollisionRay.Instance:GetAttribute("WeakpointMultiplier") or 1))
	end
	Bullet.Anchored = false
end

function Damage(CharacterCollisionRay, Bullet, Playert)	
	if CharacterCollisionRay and CharacterCollisionRay.Instance and not CharacterCollisionRay.Instance:HasTag("EffectPart") then
		if CharacterCollisionRay.Instance:HasTag("Bullet") then
			CharacterCollisionRay.Instance.BulletHitByBulletEvent:Fire(Bullet)
		else
			if CharacterCollisionRay.Instance.Parent == Bullet.Shooter.Value then return end

			if CharacterCollisionRay.Instance.Parent:FindFirstChild("Humanoid") and CharacterCollisionRay.Instance.Parent:FindFirstChild("Humanoid").Health > 0 then
				local HitHumanoid = CharacterCollisionRay.Instance.Parent:FindFirstChild("Humanoid")

				if Bullet:GetAttribute("EnemiesHit") and Bullet:GetAttribute("EnemiesHit") < Bullet:GetAttribute("MaxEnemiesToHit") and not HitHumanoid:HasTag("BeenHit".. Bullet:GetAttribute("ID")) then
					Bullet:SetAttribute("EnemiesHit", Bullet:GetAttribute("EnemiesHit") + 1)
					HitHumanoid:AddTag("BeenHit".. Bullet:GetAttribute("ID")) 
					HitHumanoid:TakeDamage(Bullet:GetAttribute("Damage") * ((Bullet:GetAttribute("LocationalDamage") == true and CharacterCollisionRay.Instance:GetAttribute("WeakpointMultiplier") ~= nil) and CharacterCollisionRay.Instance:GetAttribute("WeakpointMultiplier") or 1))

					task.delay(HitHumanoid.Parent:GetExtentsSize().Magnitude / 10, function() HitHumanoid:RemoveTag("BeenHit".. Bullet:GetAttribute("ID")) end)

					Drill(CharacterCollisionRay, Bullet)

					if Bullet:GetAttribute("FollowsGravity") == true and Bullet:GetAttribute("MaxEnemiesToHit") > 1 and HitHumanoid.Health > 0 and Bullet:GetAttribute("ContinuousDamage_AmountOfTimes") > 0 then
						Bullet.AssemblyLinearVelocity = Vector3.yAxis * Bullet:GetAttribute("").Magnitude / 2
						Bullet:SetAttribute("TimesRicocheted", Bullet:GetAttribute("Ricochet_MaxRicochets"))
					else print("B") Bullet:Destroy() end

					if Bullet:GetAttribute("EnemiesHit") >= Bullet:GetAttribute("MaxEnemiesToHit") then print("C") Bullet:Destroy() end
				end
			elseif not (game.CollectionService:HasTag(CharacterCollisionRay.Instance, "Bullet") or CharacterCollisionRay.Instance.Name == "Bullet") and not Bullet:HasTag("HeldByMagnet") and not CharacterCollisionRay.Instance.Parent:IsA("Accessory") then
				Bullet:Destroy()
			end
		end
	end
end

i’ve just had a rough scroll through the code, have you tried setting the network ownership of the part to the player

local Bullet = Instance.new("Part")

	for k, v in pairs(Properties) do
		if typeof(v) == "table" then
			for l, q in pairs(v) do
				Bullet:SetAttribute(k.. "_".. l, q)
			end
		elseif typeof(v) ~= "function" and typeof(v) ~= "Instance" and k ~= "Speed" and k ~= "Direction" and k ~= "Origin" and k ~= "Lifetime" then
			Bullet:SetAttribute(k, v)
		end
	end

	Bullet:SetAttribute("ID", tostring(math.random(1, 10^8)))
	Bullet:SetAttribute("TimesRicocheted", 0)
	Bullet:SetAttribute("EnemiesHit", 0)
	Bullet:SetAttribute("BulletVelocity", Vector3.new(Properties.Direction.X + ReturnDecimal(Bullet:GetAttribute("MaxInaccuracy")), Properties.Direction.Y + ReturnDecimal(Bullet:GetAttribute("MaxInaccuracy")), Properties.Direction.Z + ReturnDecimal(Bullet:GetAttribute("MaxInaccuracy"))) * Properties.Speed)
	Bullet.Name = (Properties.Name and true or "Bullet").. Bullet:GetAttribute("ID")
	Bullet.Size = Properties.Size or Vector3.new(0.75, 0.75, 0.75)
	Bullet.Color = Properties.Color or Color3.new(0, 0, 0)
	Bullet.Material = Properties.Material or Enum.Material.SmoothPlastic
	Bullet.CFrame = Properties.Origin
	Bullet.CollisionGroup = "Extra"
	Bullet:AddTag("Bullet")
	Bullet:AddTag(Properties.ExtraType)
	Bullet.CastShadow = false
	Bullet.AssemblyLinearVelocity = Bullet:GetAttribute("BulletVelocity")
	Bullet.Parent = workspace
    Bullet:SetNetworkOwner(Player) -- here

it should make it a bit smoother.

even if you set network ownership, the bullet will still lag for other clients

usually games make a fake bullet on each client and then make the invisible bullet hitbox on the server

thats what im trying to do now, but it keeps on saying “trying to index nil with destroying”

local RemoteEvents = game.ReplicatedStorage.RemoteEvents

RemoteEvents.ReplicateBullet.OnClientEvent:Connect(function(Bullet : Part)
	local ClientBullet = Instance.new("Part")
	print(Bullet)
	
	local Loop = game["Run Service"].Heartbeat:Connect(function()
		if ClientBullet and Bullet then
			ClientBullet.Transparency = 0
			ClientBullet.Color = Bullet.Color
			ClientBullet.Material = Bullet.Material
			ClientBullet.CanCollide = false
			ClientBullet.Size = Bullet.Size
			ClientBullet.Reflectance = Bullet.Reflectance
			ClientBullet.CFrame = Bullet.CFrame
		end
	end)
	
	Bullet.Destroying:Connect(function()
		ClientBullet:Destroy()
		Loop:Disconnect()
	end)
end)

I tried to cleam up your code a little and improve debuggability if you dont mind:

local RunService = game:GetService("RunService")

local RemoteEvents:RemoteEvent = game.ReplicatedStorage.RemoteEvents

local ClientBullet_Model = Instance.new("Part")
ClientBullet_Model.Transparency = 0
ClientBullet_Model.Color = Color3.new(1, 0.776471, 0.258824)
ClientBullet_Model.Material = Enum.Material.Metal
ClientBullet_Model.CanCollide = false
ClientBullet_Model.Anchored = true
ClientBullet_Model.Size = vector.create(1,0.25,1)
ClientBullet_Model.Reflectance = 1

local function ClientBullet(Bullet:Part):()
	local ClientBullet:typeof(ClientBullet_Model) = Instance.fromExisting(ClientBullet_Model)
	print((typeof(Bullet)=="Instance" and Bullet.ClassName) or typeof(Bullet))

	local Loop:RBXScriptConnection = RunService.Heartbeat:Connect(function():()
		if ClientBullet and Bullet then
			ClientBullet.CFrame = Bullet.CFrame
		end
	end)

	Bullet.Destroying:Connect(function()
		ClientBullet:Destroy()
		Loop:Disconnect()
	end)
end


RemoteEvents.ReplicateBullet.OnClientEvent:Connect(ClientBullet)

Althrough you should change alghoritm complitelly becouse it makes no sense has too many anonimous functions that don’t do anything
YOu should complitelly rewrite it and instead use Beams for bullets

you can use beams for arcade shooters but if you want real projectiles you can use fastcast or implement the bullets yourself with raycasthitbox

The client event doesnt work because you cant send parts over a remote event
Its also not a good idea to send all of the properties of the bullet

You should make a bullet template in replicated storage, and then send the name of which bullet template to use, then the client script clones that template

There is no point in making visuals on server code. Visuals are usually on client, because they are more responsive and way faster than server. I recommend you move your visual to client, while keeping your hit scan logic in the server.