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.

Upvotes: 0

Views: 245

Answers (1)

Kylaaa
Kylaaa

Reputation: 7188

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.

Upvotes: 3

Related Questions