Scripting Helpers is winding down operations and is now read-only. More info→
Ad
Log in to vote
0

Gun beam lagging behind, how can I fix this?

Asked by 4 years ago

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

0
Here's the gun, by the way. I didn't make the model, I just scripted it: https://www.roblox.com/games/3438873991/testing User#25069 0 — 4y

1 answer

Log in to vote
2
Answered by 4 years ago
Edited 4 years ago

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)
0
I have read over this maybe 4 times and I still do not get what is different in these scripts compared to mine. Could you elaborate a little bit more? User#25069 0 — 4y
0
Also I don't understand what half of the code is for. User#25069 0 — 4y
0
You are creating the beam on the server, so in your code, you are pressing a button, waiting for the server to receive the remote event, then creating the beam. In my version, you are pressing a button, creating the beam while also sending an event to the server so that the beam is instantaneous and does not have to wait for the server. climethestair 1663 — 4y
Ad

Answer this question