Disconnect all remoteevents when unequipping a tool

So im trying to make a gun system but theres one issue.

When i reequip my gun all the remoteevents multiple which causes the gun to have more recoil and deal more damage than intended

So i wanna disconnect all the remoteevents when i unequip my gun.

Heres the localscript.

local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local LogService = game:GetService("LogService")
local RunService = game:GetService("RunService")

local player = Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()

local function setupViewModel()
	-- Destroy existing viewmodel if it exists
	if workspace.CurrentCamera:FindFirstChild("ViewModelMP5SD") then
		workspace.CurrentCamera.ViewModelMP5SD:Destroy()
	end

	-- Clone the viewmodel from ReplicatedStorage
	local viewmodel = ReplicatedStorage:WaitForChild("ViewModelMP5SD"):Clone()
	viewmodel.Parent = workspace.CurrentCamera

	-- Get the Animator
	local animator = viewmodel:WaitForChild("Humanoid"):WaitForChild("Animator")

	-- Load the animations
	local Ranimation = Instance.new("Animation")
	Ranimation.AnimationId = "rbxassetid://121937746273937" -- Replace with your animation ID
	local RanimationTrack = animator:LoadAnimation(Ranimation)
	RanimationTrack.Priority = Enum.AnimationPriority.Action2
	local REanimation = Instance.new("Animation")
	REanimation.AnimationId = "rbxassetid://75766370456110" -- Replace with your animation ID
	local REanimationTrack = animator:LoadAnimation(REanimation)
	REanimationTrack.Priority = Enum.AnimationPriority.Action4
	local REEmptyAnimation = Instance.new("Animation")
	REEmptyAnimation.AnimationId = "rbxassetid://111792193575216" -- Replace with your animation ID for empty reload
	local REEmptyAnimationTrack = animator:LoadAnimation(REEmptyAnimation)
	REEmptyAnimationTrack.Priority = Enum.AnimationPriority.Action4
	local RanimationL = Instance.new("Animation")
	RanimationL.AnimationId = "rbxassetid://118944095797406" -- Replace with your animation ID for last shot
	local RanimationLTrack = animator:LoadAnimation(RanimationL)
	local WalkAnimation = Instance.new("Animation")
	WalkAnimation.AnimationId = "rbxassetid://88135397333059" -- Replace with your animation ID
	local WalkAnimationTrack = animator:LoadAnimation(WalkAnimation)
	WalkAnimationTrack.Looped = true
	-- Function to stop all playing animations
	local function stopAllAnimations()
		for _, track in ipairs(animator:GetPlayingAnimationTracks()) do
			track:Stop()
		end
	end
	local camera = workspace.CurrentCamera

	local function moveCameraUp()
		local currentCFrame = camera.CFrame
		local newCFrame = currentCFrame * CFrame.Angles(math.rad(0.8), 0, 0)
		camera.CFrame = newCFrame
	end

	-- Function to play the animations
	local function RecoilAnimation()
		stopAllAnimations()
		RanimationTrack:Play()
	end

	local function ReloadAnimation()
		stopAllAnimations()
		REanimationTrack.Priority = Enum.AnimationPriority.Action4
		REanimationTrack:Play()
	end

	local function ReloadEmptyAnimation()
		stopAllAnimations()
		REEmptyAnimationTrack:Play()
	end

	local function LastShotAnimation()
		stopAllAnimations()
		RanimationLTrack:Play()
	end

	-- Lock position at the end of the last shot animation
	RanimationLTrack.Stopped:Connect(function()
		local lastFrameCFrame = viewmodel.PrimaryPart.CFrame -- Assuming PrimaryPart is set
		viewmodel:SetPrimaryPartCFrame(lastFrameCFrame)
	end)

	local MP5SDFire = game.ReplicatedStorage["MP5SD RE"]:WaitForChild("MP5SDFire")
	local MP5SDFireLast = game.ReplicatedStorage["MP5SD RE"]:WaitForChild("MP5SDFireLast")
	local ReloadMP5SDNotEmpty = game.ReplicatedStorage["MP5SD RE"]:WaitForChild("ReloadMP5SDNotEmpty")
	local ReloadMP5SDEmpty = game.ReplicatedStorage["MP5SD RE"]:WaitForChild("ReloadMP5SDEmpty")
	
	MP5SDFire.OnClientEvent:Connect(function() -- IMPORTANT PART
		moveCameraUp()
		RecoilAnimation()
	end)
	
	MP5SDFireLast.OnClientEvent:Connect(function() -- IMPORTANT PART
		moveCameraUp()
		LastShotAnimation()
	end)
	ReloadMP5SDNotEmpty.OnClientEvent:Connect(function() -- IMPORTANT PART
		ReloadAnimation()
	end)
	ReloadMP5SDEmpty.OnClientEvent:Connect(function() -- IMPORTANT PART
		ReloadEmptyAnimation()
	end)
	-- Function to handle walking animation
	local function updateWalkingAnimation()
		if character.Humanoid.MoveDirection.Magnitude > 0 then
			if not WalkAnimationTrack.IsPlaying then
				WalkAnimationTrack:Play()
			end
		else
			if WalkAnimationTrack.IsPlaying then
				WalkAnimationTrack:Stop()
			end
		end
	end
	-- Connect the walking animation update to the RenderStepped event
	RunService.RenderStepped:Connect(updateWalkingAnimation)
end

local function onToolEquipped(tool)
	if tool.Name == "MP5SD" then
		setupViewModel()
	end
end

-- Connect the tool equipped event
player.Character.ChildAdded:Connect(function(child) -- IMPORTANT PART (i guess)
	if child:IsA("Tool") then
		onToolEquipped(child)
	end
end)

-- Handle tool replacement
player.Character.ChildRemoved:Connect(function(child)
	if child:IsA("Tool") and child.Name == "MP5SD" then -- IMPORTANT PART
		setupViewModel()
	end
end)

All the important parts are higlighted with a tag saying IMPORTANT PART

3 Likes

For a robust system like an FPS/TPS gun engine, you would want to use a Maid / Janitor module to clean up connections when you’re ready to clean up. However, a native way to do this is to define a table for all of your connections and add them in it.

table.insert() will be your best friend for this.

As for disconnecting, you would simply loop through the table and disconnect.

Here’s a snippet example:

local connections = {}

local function setupViewModel()
    -- Do your setup
    local runConnection = RunService.RenderStepped:Connect(function(deltaTime: number)
        -- Your rendering code
    end)

    table.insert(connections, runConnection)
end

tool.Unequipped:Connect(function()
    for _, connection in connections do
        -- We verify that the connection isn't nil or already disconnected
        if connection then
            connection:Disconnect()
        end
    end

    -- Clean the table's memory pointers
    connections = {}
end)
3 Likes

can u pls give me a brief explanation of what is done here bc i kinda dont understand it

Yeah, so basically when you make a RemoteEvent connection, it will return the connection. You can assign a variable with that, and it will give you the ability to handle that connection. When you have a table of those connections in the top of your script, you can access all of them and disconnect them.

Based on what you’ve written in your original post, you want to disconnect them. So you can for loop the table and disconnect them altogether.

oh and just 1 more question bc ive seen that used in many scripts but idk what is that
for _, connection in connections do

for _, connection in connections do is looping through all of your connections in that connections table, which is basically saying " let’s go through every single item in that table and do something ". I’ve written it in a weird way, but you might see it like this everywhere else:

for i,v in pairs(connections) do

which is also an option that you can do. Either way, it will go through that list of connections and disconnect it. And as you can see, it’s wrapped inside the unequip, so you will disconnect the events automatically, because it’s based on the event of unequipping your tool.

sry for asking sm but what should be in the rendering code

No worries, man.

Your rendering code is in your original post, it’s the updateWalkingAnimation function. You can do this:

local runConnection = RunService.RenderStepped:Connect(updateWalkingAnimation)

oooh i understand i now thx :smiley:

1 Like

No problem, if you have anymore questions don’t be afraid to ask me here. I’m glad someone like you is attempting to make an FPS system. One of the most difficult things to make in game development.

is this right?

Yeah, exactly. You can make a variable for all of your connections, just be sure to table.insert after that.

Also note, your tool would be in ReplicatedStorage, but when you clone the tool to the player’s backpack, the tool reference needs to be the tool in your backpack. That’s because when you clone a tool, it’s not the same tool as the one in ReplicatedStorage (because it’s a new tool, so it’s a new reference id).

So if your LocalScript or Script is inside the tool, all that you would essentially do is:

local tool = script.Parent
-- We assume that the script is parented in the tool.
-- If your script is not parented to the tool, you will have to change the reference.

That way, when you clone the tool, it will also clone the script. Which means the reference is always relative to the new tool.

so if i make the connections a variable do i do

local connection = remoteevent.OnClientEvent:Connect(function()

?

(and then make a table for them)

Yes, that’s correct. It’s basically the same as the ones above, except you change the name to whatever you like.

Yeah you would have one single table that holds all of them.

whats wrong here i cant get it
image

So when you make a local table, you don’t need the local syntax for each connection; that’s because the engine already knows that any item inside the table is local (because you wrote it for local connections = { ...)

So you just remove local from the items inside the table.

now the second variable is undefined


and i get