Evening.
I have scripted a few guns in the past and they've all had this problem, and I'm thinking it's about time I ask for some help with it.
The gun is set up as one local script and two server scripts. The local script is firing a remoteEvent every time the gun is meant to fire (when the player clicks or every .5 seconds when the player is holding button1 down), with the mouse.hit of the player's mouse. One server scripts is for the beam, and the other is for damage. I will only include the local script and the beam server script here.
I have copied some of the code from developer.roblox.com, as I am too lazy to script it myself and I wouldn't have come up with anything different.
The beam server script creates a ray that extends for 300 studs when the server is fired. It then finds a part on this ray via workspace:FindPartOnRay() and takes the part and the position of the part as variables (part, position). It then creates a beam in the middle of the ray and sizes it to fit the ray's size (0.3, 0.3, distance/2). This beam, however, is lagging behind, which is incredibly frustrating.
The problem is not that the beam is spawning in the right place and the player is moving past it. The problem is that the beam is spawning behind where it should be, or where it would have been had the player fired a bit earlier. AKA the beam is lagging.
The local script:
--VARIABLES local t = script.Parent.Parent local ANIMS = t.ANIMS local p = game.Players.LocalPlayer local event = t:WaitForChild("Event") local mouse = p:GetMouse() local uis = game:GetService("UserInputService") repeat wait() until p.Character repeat wait() until p.Character.Humanoid local c = p.Character local h = c.Humanoid local e = false local patroltoggle = false local debounce = false local w = h.WalkSpeed --ANIMATIONS local idle = ANIMS.IDLE idle = h:LoadAnimation(idle) local patrol = ANIMS.PATROL patrol = h:LoadAnimation(patrol) local firing = ANIMS.FIRING firing = h:LoadAnimation(firing) local animations = {idle, patrol, firing} --FUNCTIONS local function stopAllAnims() for _, anim in pairs(animations) do anim:Stop() end end t.Equipped:Connect(function() e = true w = h.WalkSpeed idle:Play() end) t.Unequipped:Connect(function() e = false stopAllAnims() end) --MAIN FUNCTION uis.InputBegan:Connect(function(input) if e == false then return end if debounce then return end --Firing if uis:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) then patroltoggle = false patrol:Stop() h.WalkSpeed = w firing:Play() debounce = true wait(0.1) while uis:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) do event:FireServer(mouse.hit) wait(0.5) end firing:Stop() debounce = false end --Running if input.KeyCode == Enum.KeyCode.F then if patroltoggle == false then patrol:Play() idle:Stop() h.WalkSpeed = w*2 patroltoggle = true else patrol:Stop() idle:Play() h.WalkSpeed = w patroltoggle = false end end end)
The beam server script:
--VARIABLES local t = script.Parent.Parent local event = t:WaitForChild("Event") --MAIN FUNCTION event.OnServerEvent:Connect(function(p, ...) local tuple = {...} --Creating ray local ray = Ray.new(t.Nozzle.Position, (tuple[1].p - t.Nozzle.Position).unit * 300) local part, position = workspace:FindPartOnRay(ray, p.Character, false, true) --Creating beam local beam = Instance.new("Part", workspace) beam.BrickColor = BrickColor.new("Toothpaste") beam.FormFactor = "Custom" beam.Material = "Neon" beam.Transparency = 0.25 beam.Anchored = true beam.Locked = true beam.CanCollide = false --Positioning and sizing beam local distance = (t.Nozzle.CFrame.p - position).magnitude beam.Size = Vector3.new(0.3, 0.3, distance) beam.CFrame = CFrame.new(t.Nozzle.CFrame.p, position) * CFrame.new(0, 0, -distance / 2) --Deleting the beam game:GetService("Debris"):AddItem(beam, 0.1) end)
If any more information is needed, this article should suffice, however feel free to comment with your questions.
ROBLOX Developer Website, Making a Laser Gun: https://developer.roblox.com/en-us/articles/Making-a-ray-casting-laser-gun-in-Roblox
When you make things that the client directly interacts with such as in your case guns, the most common method of doing so is to split up the special look effects from the real effects.
What I mean by this, is you want to show the beam through the client's script, but do your calculations if it hit anything on the server script. This will ensure that the client doesn't experience any latency or delay in their interactions, but also that there isn't any way they can cheat the system because all your important calculations are on the server.
Here is a very simple example...
Client
local UserInputService = game:GetService("UserInputService") local player = game.Players.LocalPlayer local mouse = player:GetMouse() local Remotes = game.ReplicatedStorage.Remotes local gunFired = Remotes:WaitForChild("gunFired") local onCooldown = false function cooldown() onCooldown = true wait(0.5) onCooldown = false end UserInputService.InputBegan:Connect(function(input, gameEvent) if not gameEvent then if input.UserInputType == Enum.UserInputType.MouseButton1 then if not onCooldown then createRay(mouse.Hit) -- If you need to be very accurate you can use 'tick()' to account for how much time it took for the server to get your request gunFired:FireServer(mouse.Hit, tick()) cooldown() end end end end)
Server
local Remotes = game.ReplicatedStorage.Remotes local gunFired = Remotes:WaitForChild("gunFired") local onCooldown = false function cooldown() onCooldown = true wait(0.5) onCooldown = false end gunFired.OnServerEvent:Connect(function(client, hit, t) if not onCooldown then local dt = tick() - t gunFired:FireAllClients(client, hit, t) processRay(hit, t) -- For this ray you wouldn't show it, it would just be for determining if you actually hit something cooldown() end end)
Other Clients
local UserInputService = game:GetService("UserInputService") local player = game.Players.LocalPlayer local mouse = player:GetMouse() local Remotes = game.ReplicatedStorage.Remotes local gunFired = Remotes:WaitForChild("gunFired") gunFired.OnClientEvent:Connect(function(exclude, hit, t) local dt = tick() - t if player ~= exclude then createRay(hit, t) end end)