eventsluarobloxluauroblox-studio

Roblox LocalScript Triggering Code Multiple Times


I have written a LocalScript (MessageController) for a story-type Roblox game called Police Raid. This script controls the flow of the story by changing the UI messages and triggering events.

-- This script controls the progress of the messages! (It's the GOAT)
local message = script.Parent.Message

-- Wait for character to load
local player = game:GetService("Players").LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()

-- Change message
task.wait(7)
message.Text = "Back there is the panic room. If there's any trouble, click the big red button and get inside. We're just going to hole up in here until someone comes to help us."

-- Change message
task.wait(7)
message.Text = "[static] This is the Robloxian Police Department, Barricaded Supsects Division. We have a warrant for your arrest on counts of [static]..."
message.TextColor3 = BrickColor.Red().Color
script["Heli Sound"]:Play()

-- Change message
task.wait(5)
message.Text = "Uh oh..."
message.TextColor3 = BrickColor.Black().Color

-- Change message
task.wait(2)
message.Text = "If you don't come out and surrender now, we will raid the premises!"
message.TextColor3 = BrickColor.Red().Color

-- Change message
task.wait(3)
message.Text = "Get to the panic room! NOW!!!"
-- Enable panic room
game:GetService("Workspace").Button.Part.ClickDetector.MaxActivationDistance = 32
message.TextColor3 = BrickColor.Black().Color

game:GetService("Workspace").TouchDetector.Touched:Connect(function(otherPart: BasePart)
    -- In the panic room
    task.wait(1)
    script["Explosion"]:Play()
    
    -- Change message
    task.wait(3)
    message.Text = "They're raiding the house... we should be safe in here."
    script["Loud Explosion"]:Play()
    
    -- Change message
    task.wait(2)
    message.Text = "They're in the panic room!"
    message.TextColor3 = BrickColor.Red().Color
    script["Extra Loud Explosion"]:Play()
    
    -- Change message
    task.wait(2)
    message.Text = "I don't think we're as safe as I thought. Grab that rifle from the floor and get to the basement, QUICK!"
    message.TextColor3 = BrickColor.Black().Color
    game:GetService("Workspace")["Panic Door"].Fire.Enabled = true
    game:GetService("Workspace")["Panic Panel"].CanCollide = false
    -- Change message
    game:GetService("Workspace")["Basement Detector"].Touched:Connect(function(otherPart: BasePart)
        task.wait(2)
        message.Text = "Okay... this basement was designed to survive a raid, so we should be okay."

        -- Change message
        task.wait(7)
        script["Extra Loud Explosion"]:Play()
        game:GetService("Workspace")["Basement Wall"].Transparency = 1
        message.Text = "Put your hands up!"
        message.TextColor3 = BrickColor.Red().Color

        -- Change message
        task.wait(2)
        message.Text = "Get down, part of the wall is still up!"
        message.TextColor3 = BrickColor.Black().Color
        local counter = 10
        task.wait(1)

        for i = counter, 0, -1 do
            task.wait(1)
            script.Gunfire:Play()
            -- Damage player

            -- Check if they are in a safe area
            local safezone = game:GetService("Workspace")["Wall Parts"].Safezone
            safezone.Touched:Connect(function() end) -- TouchInterest

            local function CheckIfPlayerIsInArea(Part,Character)
                local touching = Part:GetTouchingParts()
                for i=1,#touching do
                    if touching[i] == Character.HumanoidRootPart then
                        return true
                    end
                end
                return false
            end
            if not CheckIfPlayerIsInArea(safezone, game:GetService("Players").LocalPlayer.Character) then
                --game:GetService("ReplicatedStorage").DamagePlayer:FireServer(10)
                game:GetService("Players").LocalPlayer.Character.Humanoid.Health -= 1
            end
        end

        -- Change message
        message.Text = "The Robloxian Freedom Fighters are here! Get into their helicopter and escape to headquarters!"
        game:GetService("Workspace")["Basement Wall"].CanCollide = false
        local fighters = game:GetService("Workspace")["Freedom Fighters"]
        fighters["RFF-Beta-31"]:MoveTo(Vector3.new(-100.242, 3.003, 39.393))
        fighters["RFF-Alpha-0"]:MoveTo(Vector3.new(-103.259, 3.003, 31.674))
        fighters["RFF-Epsilon-97"]:PivotTo(CFrame.new(-90.501, 9.305, 40.968))
        fighters["RFF-Sigma-69"]:MoveTo(Vector3.new(-98.693, 3.003, 24.884))
        fighters["RFF-Gamma-3"]:MoveTo(Vector3.new(-122.102, 3.003, 25.833))
        fighters["RFF-Foxtrot-11"]:MoveTo(Vector3.new(-96.868, 3.003, 48.219))
        fighters["RFF-Delta-4"]:MoveTo(Vector3.new(-113.647, 3.003, 32.763))
        game:GetService("Workspace")["RFF Heli"]:PivotTo(CFrame.new(-105.629, 3.5, 49.35))
        local ChatService = game:GetService("Chat")
        local talkpart = game:GetService("Workspace")["BSD Commander Parker"].Handle
        do
            ChatService:Chat(talkpart, "It's an ambush!")
        end
        print("Triggered")
        task.wait(1)
        script["Machine Gun Fire"]:Play()
    end)
end)

The problem is, "Triggered" seems to be printed to the console 528 times, and the "Gunfire" sound is played on top of itself very rapidly. Parker the NPC says "It's an ambush!" over and over in chat bubbles above his head. The Freedom Fighters and the RFF Heli are moved so much that they end up disappearing, and the game lags due to so much rapid code execution. I have no idea why the code would execute so much. The part with the problem seems to be:

local counter = 10
        task.wait(1)

        for i = counter, 0, -1 do
            task.wait(1)
            script.Gunfire:Play()
            -- Damage player

            -- Check if they are in a safe area
            local safezone = game:GetService("Workspace")["Wall Parts"].Safezone
            safezone.Touched:Connect(function() end) -- TouchInterest

            local function CheckIfPlayerIsInArea(Part,Character)
                local touching = Part:GetTouchingParts()
                for i=1,#touching do
                    if touching[i] == Character.HumanoidRootPart then
                        return true
                    end
                end
                return false
            end
            if not CheckIfPlayerIsInArea(safezone, game:GetService("Players").LocalPlayer.Character) then
                --game:GetService("ReplicatedStorage").DamagePlayer:FireServer(10)
                game:GetService("Players").LocalPlayer.Character.Humanoid.Health -= 1
            end
        end

        -- Change message
        message.Text = "The Robloxian Freedom Fighters are here! Get into their helicopter and escape to headquarters!"
        game:GetService("Workspace")["Basement Wall"].CanCollide = false
        local fighters = game:GetService("Workspace")["Freedom Fighters"]
        fighters["RFF-Beta-31"]:MoveTo(Vector3.new(-100.242, 3.003, 39.393))
        fighters["RFF-Alpha-0"]:MoveTo(Vector3.new(-103.259, 3.003, 31.674))
        fighters["RFF-Epsilon-97"]:PivotTo(CFrame.new(-90.501, 9.305, 40.968))
        fighters["RFF-Sigma-69"]:MoveTo(Vector3.new(-98.693, 3.003, 24.884))
        fighters["RFF-Gamma-3"]:MoveTo(Vector3.new(-122.102, 3.003, 25.833))
        fighters["RFF-Foxtrot-11"]:MoveTo(Vector3.new(-96.868, 3.003, 48.219))
        fighters["RFF-Delta-4"]:MoveTo(Vector3.new(-113.647, 3.003, 32.763))
        game:GetService("Workspace")["RFF Heli"]:PivotTo(CFrame.new(-105.629, 3.5, 49.35))
        local ChatService = game:GetService("Chat")
        local talkpart = game:GetService("Workspace")["BSD Commander Parker"].Handle
        do
            ChatService:Chat(talkpart, "It's an ambush!")
        end
        print("Triggered")
        task.wait(1)
        script["Machine Gun Fire"]:Play()

Sorry if this is a lot of code or if it's badly formatted.

I have attempted to debug the code by printing the text "Triggered" and have looked around the script for anything that may be causing the infinite looping. I have also tried adding a task.wait(math.huge) to the end of the script to prevent it from executing multiple times (above the end statements), but this did not affect the looping. Interestingly, Machine Gun Fire (a looped sound) does not appear to play more than once. If it's helpful, the LocalScript is parented to a ScreenGui, which is parented to StarterGui.

I am unable to post this question on the Roblox Devforum due to a lack of reputation, since I have been browsing the site without being logged in for a long time.


Solution

  • The issue that you are facing is that you are connecting listeners to events and never disconnecting them, or debouncing events that fire multiple times. A common problem, especially with the Touched event is that it can fire when anything in the Workspace touches it, and also fire multiple times for the same character model. If your hand and foot both touch a Part, the event will fire for both of those parts.

    Aside from that, a lot of your game logic should happen in a Script, that way, when sounds are played or objects are moved, hidden, or destroyed, they should happen for everyone at the same time.

    The only thing that needs to happen in a LocalScript is where you display messages. So let's set up a simple way for the server to push messages to the UI.

    So follow these steps :

    1. Create a RemoteEvent in ReplicatedStorage named DisplayMessage
    2. In your LocalScript, delete everything except for this (don't worry, your code is moving to a Script) :
    -- import services
    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    
    -- find the UI elements
    local lblMessage = script.Parent.Message
    
    -- find the RemoteEvent telling us that the server has a message for us
    local DisplayMessage = ReplicatedStorage.DisplayMessage
    
    -- display the messages when the server sends them to us
    DisplayMessage.OnClientEvent:Connect(function(message : string, color : Color3)
        lblMessage.Text = message
        lblMessage.Color3 = color
    end)
    
    1. Create a ModuleScript in ReplicatedStorage to handle logic for debouncing when a player touches a part. Name it debounceTouch. We'll use this to prevent a user from triggering a Touch event multiple times when their Character Model touches a part.
    local Players = game:GetService("Players")
    
    -- Debounce a function so that it only fires once per player touch event
    local function debounceTouch(part : BasePart, onPlayerTouched : (Player)->(), onPlayerTouchEnded : (Player)->()?)
        -- keep track of which players are touching the part
        local playersTouching : { [Player] : number } = {}
        
        local touchedConnection = part.Touched:Connect(function(otherPart : BasePart)
            local player = Players:GetPlayerFromCharacter(otherPart.Parent)
            if not player then
                return
            end
    
            if not playersTouching[player] then
                -- a player has begun touching!
                onPlayerTouched(player)
                playersTouching[player] = 1
            else
                playersTouching[player] += 1
            end
        end)
        local touchEndedConnection = part.TouchEnded:Connect(function(otherPart : BasePart)
            local player = Players:GetPlayerFromCharacter(otherPart.Parent)
            if not player then
                return
            end
            -- when a player stops touching, decrement the counter so that we can clear the debounce flag
            if playersTouching[player] then
               playersTouching[player] -= 1
               if (playersTouching[player] <= 0) then
                   playersTouching[player] = nil
    
                   -- the player has stopped touching, fire the callback if provided
                   if onPlayerTouchEnded then
                       onPlayerTouchEnded(player)
                   end
               end
            end
        end)
    
        -- return an object that functions like an RbxScriptConnection token so that we can disconnect it when it is no longer relevant
        return {
            Connected = (touchedConnection.Connected or touchEndedConnection.Connected),
            Disconnect = function()
                touchedConnection:Disconnect()
                touchEndedConnection:Disconnect()
            end,
        }
    end
    
    return debounceTouch
    
    1. Create a Script in ServerScriptService, and put this into the contents. This will handle your game logic, and send messages to the players.
    -- import services
    local ChatService = game:GetService("Chat")
    local Players = game:GetService("Players")
    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local Workspace = game:GetService("Workspace")
    
    -- import helper functions
    local debounceTouch = require(ReplicatedStorage.debounceTouch)
    
    
    -- create a helper function to send the messages to all players
    local DisplayMessage = ReplicatedStorage.DisplayMessage
    local function displayMessageWithDelay(delay : number, message : string, color : Color3?)
        task.wait(delay)
        DisplayMessage:FireAllClients(message, color)
    end
    
    -- Wait for any player to load into the game
    if #Players.GetPlayers() == 0 then
        Players.PlayerAdded:Wait()
    end
    
    -- define some values
    local SAFEZONE_DAMAGE_PER_SECOND = 10
    
    
    -- write down all the messages at the top
    local messages = {
        "Back there is the panic room. If there's any trouble, click the big red button and get inside. We're just going to hole up in here until someone comes to help us.",
        "[static] This is the Robloxian Police Department, Barricaded Suspects Division. We have a warrant for your arrest on counts of [static]...",
        "Uh oh...",
        "If you don't come out and surrender now, we will raid the premises!",
        "Get to the panic room! NOW!!!",
        "They're raiding the house... we should be safe in here.",
        "They're in the panic room!",
        "I don't think we're as safe as I thought. Grab that rifle from the floor and get to the basement, QUICK!",
        "Okay... this basement was designed to survive a raid, so we should be okay.",
        "Put your hands up!",
        "Get down, part of the wall is still up!",
        "The Robloxian Freedom Fighters are here! Get into their helicopter and escape to headquarters!",
    }
    
    local colors = {
        black = BrickColor.Black().Color,
        red = BrickColor.Red().Color,
    }
    
    -- load some workspace elements
    local ButtonClickDetector = Workspace.Button.Part.ClickDetector
    local TouchDetector = Workspace.TouchDetector
    local PanicDoor = Workspace["Panic Door"]
    local BasementDetector = Workspace["Basement Detector"]
    local BasementWall = Workspace["Basement Wall"]
    local safezone = Workspace["Wall Parts"].Safezone
    local fighters = Workspace["Freedom Fighters"]
    local helicopter = Workspace["RFF Heli"]
    local talkPart = Workspace["BSD Commander Parker"].Handle
    
    
    
    
    -- start playing the game
    displayMessageWithDelay(7, messages[1])
    displayMessageWithDelay(7, messages[2], colors.red)
    script["Heli Sound"]:Play()
    displayMessageWithDelay(5, messages[3], colors.black)
    displayMessageWithDelay(2, messages[4], colors.red)
    displayMessageWithDelay(3, messages[5])
    
    -- Enable panic room
    ClickDetector.MaxActivationDistance = 32
    
    local touchedConnection
    touchedConnection = TouchDetector.Touched:Connect(function(otherPart: BasePart)
        -- make sure that this only fires if a player touches it
        if not Players:GetPlayerFromCharacter(otherPart.Parent) then
            return
        end
    
        -- disconnect the connection so that it doesn't fire again
        touchedConnection:Disconnect()
    
        -- In the panic room
        task.wait(1)
        script["Explosion"]:Play()
        displayMessageWithDelay(3, messages[6], colors.black)
        script["Loud Explosion"]:Play()
        displayMessageWithDelay(2, messages[7], colors.red)
        script["Extra Loud Explosion"]:Play()
        displayMessageWithDelay(2, messages[8], colors.black)
    
        -- set the door on fire
        PanicDoor.Fire.Enabled = true
        PanicDoor.CanCollide = false
    
        
        local basementTouchedConnection
        basementTouchedConnection = BasementDetector.Touched:Connect(function(otherPart: BasePart)
            -- make sure that this only fires if a player touches it
            if not Players:GetPlayerFromCharacter(otherPart.Parent) then
                return
            end
    
            -- disconnect the connection so it only fires once
            basementTouchedConnection:Disconnect()
     
            -- start playing the next set of messages
            displayMessageWithDelay(2, messages[9])
            displayMessageWithDelay(7, messages[10], colors.red)
    
            -- explode the basement walls
            script["Extra Loud Explosion"]:Play()
            BasementWall.Transparency = 1
            displayMessageWithDelay(2, messages[11], colors.black)
    
            -- create a list of players currently touching the safezone
            local playersInSafezone = {}
            local playerList = Players:GetPlayers()
            for _, player in ipairs(playerList) do
                playersInSafezone[player] = false
            end
            local safezoneTouchConnection = debounceTouch(safezone, function(player)
                -- when they touch the safezone, mark them as safe
                playersInSafezone[player] = true
            end, function(player)
                -- when they stop touching the safezone, mark them as unsafe
                playersInSafezone[player] = false
            end)
    
            task.wait(1)
            for i = 10, 0, -1 do
                task.wait(1)
                script.Gunfire:Play()
    
                -- Check if they are in a safe area
                for player, isSafe in pairs(playersInSafezone) do
                    if not isSafe then
                        -- Damage player
                        player.Character.Humanoid:TakeDamage(SAFEZONE_DAMAGE_PER_SECOND)
                        --ReplicatedStorage.DamagePlayer:FireServer(10)
                    end
                end
            end
    
            -- clean up the safezone events
            safezoneTouchConnection:Disconnect()
            playersInSafezone = nil
    
            -- Change message
            displayMessageWithDelay(0, messages[12])
            BasementWall.CanCollide = false
    
            -- move the fighters into place
            fighters["RFF-Beta-31"]:MoveTo(Vector3.new(-100.242, 3.003, 39.393))
            fighters["RFF-Alpha-0"]:MoveTo(Vector3.new(-103.259, 3.003, 31.674))
            fighters["RFF-Epsilon-97"]:PivotTo(CFrame.new(-90.501, 9.305, 40.968))
            fighters["RFF-Sigma-69"]:MoveTo(Vector3.new(-98.693, 3.003, 24.884))
            fighters["RFF-Gamma-3"]:MoveTo(Vector3.new(-122.102, 3.003, 25.833))
            fighters["RFF-Foxtrot-11"]:MoveTo(Vector3.new(-96.868, 3.003, 48.219))
            fighters["RFF-Delta-4"]:MoveTo(Vector3.new(-113.647, 3.003, 32.763))
            helicopter:PivotTo(CFrame.new(-105.629, 3.5, 49.35))
            
            ChatService:Chat(talkPart, "It's an ambush!")
            print("Triggered")
            task.wait(1)
            script["Machine Gun Fire"]:Play()
        end)
    end)
    
    1. Finally, move all the sounds that were children of your LocalScript over to the Script. This will ensure that the path to the sounds is preserved.

    You'll notice that I've changed how you are damaging the player, and how the safezone works. This way we can quickly check who is touching the safezone every time without needing to ask the engine to do lookups of parts to players.

    The only other thing I really did was move all of the game messages to a list at the top, this wasn't really necessary, but I thought it would make it easier to see the game logic instead of reading through the messages inside the body of the script. This is my preference, and really isn't necessary for the functioning of your code.

    Hopefully this helps.