Hello!
I’ve been working on this tag system for an upcoming project. I’ve got the tag system to finally work, however there seems to be some kind of memory leak that causes more and more server lag for every tag. (See video below). My guess is that it has something to do with the runservice I create on the client to check a spatial hitbox. Not sure how to resolve it or debug it though.
I’ve tried checking memory usage on server and client but I can’t seem to find any outstanding problems. I would really appreciate if someone could give some advice on how to find/get rid of or properly disconnect the runservice perhaps? Thanks!
In the video below you can see how after a couple tags (25-30s in) it starts becoming unstable and the tags take longer and longer to go through until it eventually breaks.
Video link:
This is the local script:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Events = ReplicatedStorage.Events
local UI_Events = ReplicatedStorage.UI_Events
local LocalPlayer = game.Players.LocalPlayer -- Corrected way to get the local player
local character = LocalPlayer.Character or LocalPlayer.CharacterAdded:Wait()
local isTagged = character:WaitForChild("isTagged")
-- Events
local InitiateTagHandlerEvent = Events:WaitForChild("InitiateTagHandlerEvent")
local TagPlayerEvent = Events:WaitForChild("TagPlayerEvent")
local RemoveDynamiteEvent = Events:WaitForChild("RemoveDynamiteEvent")
local DisplayTagUIRemote = UI_Events:WaitForChild("DisplayTagUIRemote")
-- Animation
local humanoid = character:WaitForChild("Humanoid")
local animator = humanoid:WaitForChild("Animator")
local HoldAnimation = Instance.new("Animation")
HoldAnimation.AnimationId = "rbxassetid://126486527881326"
local holdDynamiteAnimationTrack = animator:LoadAnimation(HoldAnimation)
local RunServiceConnection -- Declare for access between functions
-- Function to check the status of the enemy and tag them if they are not tagged
local function checkEnemyStatus(enemy_Character)
local enemyIsTagged = enemy_Character:FindFirstChild("isTagged").Value
local enemyPlayer = game.Players:GetPlayerFromCharacter(enemy_Character)
wait()
if enemyIsTagged == false then
print("Enemy is not tagged")
RunServiceConnection:Disconnect() -- Stop RunService when tagging happens
TagPlayerEvent:FireServer(enemyPlayer) -- Notify server to tag the enemy player
else
print("Enemy is already tagged") -- Runs 60 times per second ish
end
end
-- Function to initiate tagging ability
local function InitiateTagHandler()
local ourHumanoidRootPart = character:WaitForChild("HumanoidRootPart")
local debounce = false
RunServiceConnection = RunService.Heartbeat:Connect(function()
local overlapParams = OverlapParams.new()
local hitContents = workspace:GetPartBoundsInBox(
ourHumanoidRootPart.CFrame * CFrame.new(0, 0, -3), Vector3.new(2.5, 3, 3), overlapParams)
local hitList = {}
for _, object in pairs(hitContents) do
if object.Parent:FindFirstChild("Humanoid") and not table.find(hitList, object.Parent) then
table.insert(hitList, object.Parent) -- Add the player's character to hit list
local enemy_Character = object.Parent
checkEnemyStatus(enemy_Character)
end
end
end)
end
-- Event triggered when the player becomes the tagger
InitiateTagHandlerEvent.OnClientEvent:Connect(function()
InitiateTagHandler()
end)
-- Event handler for when the player's tag status changes
isTagged.Changed:Connect(function()
if isTagged.Value == true then
InitiateTagHandler()
holdDynamiteAnimationTrack:Play() -- Play holding animation
else
if RunServiceConnection then
RunServiceConnection:Disconnect() -- Stop checking
end
holdDynamiteAnimationTrack:Stop()
end
end)
local function DisplayTagUI (status, sendingPlr) -- Sending plr is the player giving the dynamite
if status == "gotTagged" then -- The local player got tagged
print("I got tagged")
local NotificationsScreenGui = LocalPlayer.PlayerGui:FindFirstChild("Notifications")
local NotificationFrame = NotificationsScreenGui.NotificationFrame
local NotificationText = NotificationsScreenGui:FindFirstChild("NotificationFrame").TextLabel
-- Tween the showing of label appearing & disappearing
NotificationText.Text = sendingPlr.Name.." gave you the dynamite!"
NotificationFrame.Visible = true
wait(3)
NotificationFrame.Visible = false
elseif status == "tagged" then -- Local player tagged someone else
local NotificationsScreenGui = LocalPlayer.PlayerGui:FindFirstChild("Notifications")
local NotificationFrame = NotificationsScreenGui.NotificationFrame
local NotificationText = NotificationsScreenGui:FindFirstChild("NotificationFrame").TextLabel
NotificationText.Text = "You passed the dynamite to someone else!"
NotificationFrame.Visible = true
wait(3)
NotificationFrame.Visible = false
end
end
DisplayTagUIRemote.OnClientEvent:Connect(function(status, sendingPlr)
DisplayTagUI(status, sendingPlr) -- Passes the sendingplr and receivingplr
end)
This is the server script:
local ServerStorage = game:GetService("ServerStorage")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerScriptService = game:GetService("ServerScriptService")
local DynamiteModel = ServerStorage.Dynamites:WaitForChild("Default_Dynamite") -- Default dynamite model
-- Services
local CollectionService = game:GetService("CollectionService")
local RunService = game:GetService("RunService")
local Events = ReplicatedStorage.Events
local UI_Events = ReplicatedStorage.UI_Events
-- Events
local InitiateTagHandlerEvent = Events:WaitForChild("InitiateTagHandlerEvent")
local TagPlayerEvent = Events:WaitForChild("TagPlayerEvent")
local RemoveDynamiteEvent = Events:WaitForChild("RemoveDynamiteEvent")
local DisplayTagUIRemote = UI_Events:WaitForChild("DisplayTagUIRemote")
-- Modules
local SkinModule = require(ServerScriptService.Modules.SkinModule)
local DynamiteSkinModule = require(ServerScriptService.Modules.DynamiteSkinModule)
local dynamitePart = game.Workspace:WaitForChild("dynamitePart") -- For testing
local CooldownDuration = 1
local cooldownInfo = {} -- Stores information about the players and their tagged status
-- Remove dynamite when the player is no longer tagged
local function RemoveDynamite(player)
local character = player.Character or player.CharacterAdded:Wait()
local isTagged = character:FindFirstChild("isTagged")
if isTagged.Value == false then
-- Remove anything with a dynamite tag inside it
for _, item in ipairs(character:GetChildren()) do
if CollectionService:HasTag(item, "Dynamite") then
item:Destroy()
end
end
-- Remove highlight
for _, item in ipairs(character:GetChildren()) do
if item:IsA("Highlight") then
item:Destroy()
end
end
end
end
-- Create detonator (First person with the dynamite), give them dynamite + skin + attribute (true)
local function MakePlayerDetonator(player) -- Plr that should be made into detonator
local character = player.Character or player.CharacterAdded:Wait()
local SelectedSkin = "Professor" -- Change this later to their actual selected skin if it doesnt exist then go to default
local SelectedDynamiteSkin = "Default_Dynamite" -- Change this later to their actual selected dynamite skin
local isTagged = character:FindFirstChild("isTagged")
isTagged.Value = true -- Make them "tagged"
local DetonatorInfo = character:GetAttribute("DetonatorInfo")
character:SetAttribute("DetonatorInfo", true) -- Make the player detonator in attribute
InitiateTagHandlerEvent:FireClient(player)
-- Double check that the player owns the skin before giving them it, otherwise give default
SkinModule.ApplySkin(character, SelectedSkin) -- Apply the players selected detonator skin
DynamiteSkinModule.ApplyDynamite(character, SelectedDynamiteSkin) -- Give player their selected dynamite skin
end
-- Temporary touched event to fire MakePlayerDetonator function
dynamitePart.Touched:Connect(function(hit)
if hit.Parent:FindFirstChild("Humanoid") then
local character = hit.Parent
local player = game.Players:GetPlayerFromCharacter(character)
MakePlayerDetonator(player) -- Make the player the detonator with skin etc
wait(3)
end
end)
local function AssignDynamite(player, enemyPlayer)
print(player.Name, "is tagging", enemyPlayer.Name)
local playerCharacter = player.Character or player.CharacterAdded:Wait()
local enemyCharacter = enemyPlayer.Character or enemyPlayer.CharacterAdded:Wait()
enemyCharacter.Humanoid.WalkSpeed = 28
-- Transfer the dynamite
-- Clone the dynamite in storage
RemoveDynamite(player) -- Remove dynamite from the tagging player
local SelectedDynamiteSkin = "Default_Dynamite" -- change this to the players actual selected dynamite skin later
DynamiteSkinModule.ApplyDynamite(enemyCharacter, SelectedDynamiteSkin) -- Give enemyPlayer their selected dynamite skin
-- Add ExplosionVFXPart (used to create explosionVFX)
local rootPart = playerCharacter:FindFirstChild("HumanoidRootPart")
if rootPart then
for _, item in ipairs(rootPart:GetChildren()) do
if item.Name == "ExplosionVFXPart" then
return -- If ExplosionVFXPart exists in the HumanoidRootPart, do nothing
end
end
-- If it doesnt exist then we create it
local ExplosionVFXPart = game:GetService("ReplicatedStorage"):FindFirstChild("ExplosionVFXPart"):Clone()
ExplosionVFXPart.Name = "ExplosionVFXPart"
ExplosionVFXPart.Parent = rootPart
end
end
-- When a player joins
game.Players.PlayerAdded:Connect(function(player)
player.CharacterAdded:Wait()
local character = player.Character or player.CharacterAdded:Wait()
-- Add isTagged value
local isTagged = Instance.new('BoolValue')
isTagged.Name = 'isTagged'
isTagged.Value = false
isTagged.Parent = character
-- Add Detonator Attribute (we use this later to make them detonator and checks etc)
character:SetAttribute("DetonatorInfo", false) -- Set to false as default (not detonator)
-- Add player to tag cooldown table
cooldownInfo[player.UserId] = {
Tagger = player.UserId,
Time = 0
}
end)
-- When the player leaves
game.Players.PlayerRemoving:Connect(function(player)
-- Remove the player from the cooldown table
if cooldownInfo[player.UserId] then
cooldownInfo[player.UserId] = nil -- Remove them from the table
end
end)
-- Fired from client, we do checks then fire AssignDynamite
local function TagPlayer(Tagger, enemyPlayer) -- We have the sending player (tagger) and the player that should be tagged
if enemyPlayer and Tagger ~= enemyPlayer then
local CurrentTime = tick()
local enemyCharacter = enemyPlayer.Character -- Character of the enemy player getting tagged
local enemy_IsTaggedValue = enemyCharacter and enemyCharacter:FindFirstChild("isTagged")
local TaggerCharacter = Tagger.Character -- The taggers charatcer yeyeyeye
local Tagger_IsTaggedValue = TaggerCharacter and TaggerCharacter:FindFirstChild("isTagged")
if not enemyCharacter or not enemy_IsTaggedValue or not Tagger_IsTaggedValue then return end -- if enemy doesnt have character, istagged value and tagger doesnt have istaggedvalue then return end
if cooldownInfo[enemyPlayer.UserId] then -- If the player exist in the table (doesnt mean that they have a cooldown)
local LastTagTime = cooldownInfo[enemyPlayer.UserId].Time -- Gets the last time the player got tagged
if CurrentTime - LastTagTime < CooldownDuration then -- If it has been less than 1 second since last tag then return nothing
return
end
end
if not enemy_IsTaggedValue.Value then -- If the enemy is not tagged
Tagger_IsTaggedValue.Value = false -- We are setting our own tagged status to false
enemy_IsTaggedValue.Value = true -- Making enemy tagged
-- Remove highlight
print(Tagger.Name, "is tagging", enemyPlayer.Name)
AssignDynamite(Tagger, enemyPlayer) -- Fire to Assign them the dynamite
InitiateTagHandlerEvent:FireClient(enemyPlayer) -- Make them able to tag (Initiate their tag ability on client)
cooldownInfo[enemyPlayer.UserId] = { -- Setting new values inside cooldownTable for enemy (This is the new tagger)
Tagger = Tagger.UserId, -- The player that tagged me
Time = CurrentTime -- Last time I got tagged
}
cooldownInfo[Tagger.UserId] = { -- Setting new values for the old tagger inside cooldownTable. (This is the player that is not tagged anymore)
Tagger = enemyPlayer.UserId, -- This is the player that we tagged
Time = CurrentTime -- This is the time we tagged them
}
-- Fire to enemyClient
DisplayTagUIRemote:FireClient(enemyPlayer, "gotTagged", Tagger) -- Fires to enemyPlrs client with the tagger that tagged them (us)
DisplayTagUIRemote:FireClient(Tagger, "tagged", enemyPlayer) -- Fire to our client that we tagged someone else in our UI with the plr we tagged
else
print("Enemy player is already tagged.")
end
end
end
TagPlayerEvent.OnServerEvent:Connect(TagPlayer) -- Passes the player that should be tagged from client