Reduce lag from grenade script

I am creating a wave-based game and I have a grenade ability. At about 75% of the way through the game, my frame rate drops from a consistent 60 FPS to 35-45 FPS for the remainder of play. If I complete the game without using the grenade ability at all, this never seems to happen, so I am pretty confident it is the cause.

I have a local script for the input (removed some parts that don’t apply):

local player = game.Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local Timestamp1 = tick()
local UserInputService = game:GetService("UserInputService")

local animation2 = Instance.new("Animation")
animation2.AnimationId = "http://www.roblox.com/asset/?id=7148111065"
local animationTrack2 = character:WaitForChild("Humanoid"):LoadAnimation(animation2)

UserInputService.InputBegan:Connect(function(Input, IsTyping)
	if not IsTyping then
		if Input.KeyCode == Enum.KeyCode.Q then
			if character.Class.Value == "Pistol" then
				if (tick() - Timestamp1) < (character.Q.Value) then return end --Q is an int value inside of character
				animationTrack2:Play()
				Timestamp1 = tick()
				wait(0.2)
				game.ReplicatedStorage.RemoteEvents.Grenade:FireServer()			
			end
		end	
	end	
end)

Here is the server-side script: (I have tried many updates in trying to reduce this lag)

local grenade = game.ServerStorage:WaitForChild("Grenade")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local debris = game:GetService("Debris")

game.ReplicatedStorage.RemoteEvents.Grenade.OnServerEvent:Connect(function(player)
	local character = player.Character
	local newgrenade = Grenade:Clone()
	newgrenade.CFrame = character.HumanoidRootPart.CFrame + Vector3.new(0,3,0)
	newgrenade.Parent = workspace
	newgrenade.CanCollide = true
	newgrenade.Velocity = (character.HumanoidRootPart.CFrame.lookVector * 75)
	newgrenade.Pin:Play()	
	wait(1.5)
	local explosion = Instance.new("Explosion", workspace)
	explosion.Position = newgrenade.Position
	explosion.BlastRadius = player.Character.QRad.Value --another int variable inside character
	explosion.BlastPressure = 250000
	explosion.DestroyJointRadiusPercent = 0
	local maxDamage = player.Character.QDam.Value --another int variable inside character
	local modelsHit = {}
	-- listen for contact
	explosion.Hit:Connect(function(part, distance)
		local parentModel = part.Parent
		if parentModel then 
			-- check to see if this model has already been hit 
			if modelsHit[parentModel] then
				return
			end
			-- log this model as hit
			modelsHit[parentModel] = true

			-- look for a humanoid
			local humanoid = parentModel:FindFirstChild("Humanoid")
			if humanoid then
				local chartar = humanoid.Parent
				local plrtar = Players:GetPlayerFromCharacter(chartar) --ensure only damages npcs
				if not plrtar then
					humanoid:TakeDamage(maxDamage)
				end	
			end	
		end
	end)
	newgrenade:Destroy()
end)

I have tried adding in ways to disconnect from the explosion.Hit, but it always prevents me from throwing another grenade or doing damage with subsequent grenades. Not sure how else I could be leaking memory.

edit: I also tried changing the script to parent the explosion after creating it because I heard that can cause issues in regards to performance, but I am still experiencing the frame rate drop.

That explosion.hit is most likely your issue. Have you tried disconnecting the explosion.hit when you apply the damage to the humanoid?

Since these events happen on their own thread, this should do the trick.

local event = explosion.Hit:Connect(function(part, distance)
		local parentModel = part.Parent
		if parentModel then 
			-- check to see if this model has already been hit 
			if modelsHit[parentModel] then
				return
			end
			-- log this model as hit
			modelsHit[parentModel] = true

		-- look for a humanoid
		local humanoid = parentModel:FindFirstChild("Humanoid")
		if humanoid then
			local chartar = humanoid.Parent
			local plrtar = Players:GetPlayerFromCharacter(chartar) --ensure only damages npcs
			if not plrtar then
				humanoid:TakeDamage(maxDamage)
                event:Disconnect()
			end	
		end	
	end
end)

Thanks for your help.

When I tried that, I got an error that said event was nil and I can’t call Disconnect() from nil.
I tried putting local event above the thread, then defining and disconnecting it, but it would only damage the first enemy hit in the radius and then disconnect before dealing damage to everything else. I tried running disconnect at all points later in the thread and only the first grenade did any damage and any others did no damage.

A better method of model checking would be to scan for a Humanoid, and then log the Humanoid.

local Event = explosion.Hit:Connect(function(part)
     local Character = part.Parent -- No need to check for this since objects
                                   -- MUST have a parent.
     local Humanoid = part.Parent:FindFirstChildOfClass("Humanoid")
     if (not Humanoid or modelsHit[Character.Name]) then return end 
     -- Better to use a string key for checking here.
     
     modelsHit[Charater.Name] = true
     Humanoid:TakeDamage(maxDamage)
end)
spawn(function() -- Spawn a new thread
     wait(1.5) -- Wait for explosion to expand
     if (Event) then Event:Disconnect() end -- Disconnect if not nil
end)

Hope this helps!

Side node, do not use Instance.new("Object", Parent)

You should use:

local Object = Instance.new("Object")
Object._______ = ___
...
Object.Parent = Parent -- This should ALWAYS be last.

Due to some quirks of the Roblox engine, it is much faster to do it this way.

Due to some quirks of the Roblox engine, it is much faster to do it this way.

Not exactly a quirk of the engine per se; this is a feature. Parenting an object that doesn’t ‘exist’ (e.g parented to nil), will send all the information about itself in one go, which is extremely efficient.

whereas if parenting is not the very last thing you do on object creation, property signals will need to fire, those same property changes will be added to the replication queue, and many visual changes such as those related to rendering will need to be pushed.

You also make yourself vulnerable to delays in property changes applying, as the act of continually setting properties may eventually end up taking longer than a frame or so, and result in noticable visual disparity.

Anyway enough nitpicking your point, about the OP’s post:

I do not believe this is your grenade script at work, no operation in it should produce even near the lag you’re mentioning. For now, and in the future; consider profiling the code to confirm it is the source, there are many ways to do this:

Press F9 (to open the developer log) and go to the memory section and track the memory usage of the script, this will reliably show you if your script is unintentionally using more and more memory, which would signify a memory leak and may result in the labored, yet eventual frame drops.

Another way you could profile is through the expanded microprofiler view: (CTRL + F6) + (CTRL + SHIFT + F6) This will show you the execution time, frame time, call count, etc. Various other attributes of your script execution and from there you can profile similarly to the developer log for any unintended increases.

Lastly, you can use script activity (View + Script Performance) to see if the activity goes up over time.

Through these methods, you can be sure, instead of just confident that this is the script causing it.
Either way, try profiling other scripts in your game the same to see the approximate source of the frame drops.

Trying checking for a humanoid immediately.

explosion.Hit:Connect(function(part, distance)
	local parentModel = part.Parent
	if parentModel and parentModel:FindFirstChild("Humanoid") and modelsHit[parentModel] == nil then 
		modelsHit[parentModel] = true

		local humanoid = parentModel.Humanoid
	end
end)

Rather than go into depth, I decided to simply explain it off as a quirk of the Roblox Engine rather than confuse OP with the technical jargon.

However, you do have a point with the profiling. It would be wise for @icunicu1983 to confirm that it is actually the grenade script causing this or something else.

1 Like

Thanks for the information.
I used those tools with some additional testing and confirmed it is not the grenade script, which is frustrating as I spent the better part of a day on testing it.

I believe it is something that I added to the Roblox Weapons Kit as the frame rate happens much sooner with the SMG than the pistol and correlates with shooting enemies. I will keep going through everything with these tools, but do you know of any additional resources on how to use these in practice other than the developer page on the topic?