Need help with touched and runservice changing materials

I have a part (Invisible hitbox) that changes material to neon (to light up) when it’s touched and back to smoothplasic when not touched. It’s a bit glitchy since when the player runs around on the part it turns off and on. The main issue is when multiple players are on the same part (Pic 1, 2). I want the part to remain neon until there are no players on the part but when one player leaves the part it switches back. I am guessing it’s an issue with part.touchended but I don’t know of any way to fix the issue. When there’s someone on the part I wish it to stay neon. is it a simple matter or running through all the players on the part or something.


Capture2

Here is the script.

local hitBox = script.Parent
local startButton = script.Parent.Parent
local buttonValue = startButton:GetAttribute("ButtonValue")
local connections = {}

local function isConnected(player)
	return connections[player] ~= nil
end

local function updateNumbersValue()
	for player in pairs(connections) do
		local leaderstats = player:FindFirstChild("leaderstats")
		if leaderstats then
			local energy = leaderstats:FindFirstChild("Energy")
			if energy then
				energy.Value = energy.Value + buttonValue -- multiply in the other modifiers
			end
		end
	end
end

hitBox.Touched:Connect(function(otherPart)
	local player = game.Players:GetPlayerFromCharacter(otherPart.Parent)
	if player and player.Character and player.Character:FindFirstChild("HumanoidRootPart") then
		connections[player] = true
		script.Parent.Parent.ButtonLight.Material = Enum.Material.Neon
	end
end)

hitBox.TouchEnded:Connect(function(otherPart)
	local player = game.Players:GetPlayerFromCharacter(otherPart.Parent)
	if player and player.Character and player.Character:FindFirstChild("HumanoidRootPart") then
		connections[player] = nil
		script.Parent.Parent.ButtonLight.Material = Enum.Material.SmoothPlastic
	end
end)

-- Start touch automation
game:GetService("RunService").Heartbeat:Connect(function()
	updateNumbersValue()
end)

So, first of all, if this is on server, runservice.heartbeat is a bit too much for numbers updates, consider updating numbers when a new player enter the zone instead, for player detection I recommend doing this: On touched start a loop that checks how many players are there in the zone through a get partsinpart check having as whitelist all the players characters in the server or a GetPartBoundsInBox, then once there are no more players stop the loop until another player touches the zone again. Do not use touch ended.

Thx for the suggestion. I’ve ended up with this new iteration which seems to increment just fine and the parts material while I’m in the hitbox. While standing still it increments once a second but when I run around on the button it speeds up the incremental a lot. I’m not entirely sure what’s happening since I’m adding the players that have already been calculated into a table so in theory they shouldn’t be added again (is that correct)?

anyway here’s what I have so far.

local players = game:GetService("Players")
local hitBox = script.Parent
local startButton = script.Parent.Parent
local buttonLight = startButton.ButtonLight
local buttonValue = startButton:GetAttribute("ButtonValue")
local timerVariable = 1  -- Timer variable in seconds

local function incrementEnergyForPlayer(player)
	local leaderstats = player:FindFirstChild("leaderstats")
	if leaderstats then
		local energy = leaderstats:FindFirstChild("Energy")
		if energy then
			energy.Value = energy.Value + buttonValue
		end
	end
end

local function isTouched(otherPart)
	if otherPart.Parent then
		local char = otherPart.Parent
		local humanoidRootPart = char:FindFirstChild("HumanoidRootPart")
		local player = players:GetPlayerFromCharacter(char)

		if player and humanoidRootPart then
			incrementEnergyForPlayer(player)
		end
	end
end

hitBox.Touched:Connect(isTouched)

local lastIterationTime = tick()
local processedPlayers = {}

while true do
	local currentTime = tick()
	local elapsedTime = currentTime - lastIterationTime

	if elapsedTime >= timerVariable then
		lastIterationTime = currentTime
		processedPlayers = {}  -- Reset the processed players for the new iteration

		local touchingParts = game.Workspace:GetPartsInPart(script.Parent)
		local playersInHitbox = {}

		for _, touching in pairs(touchingParts) do
			local humanoid = touching.Parent:FindFirstChild("Humanoid")
			if humanoid then
				local player = players:GetPlayerFromCharacter(touching.Parent)
				if player and not processedPlayers[player] then
					table.insert(playersInHitbox, player)
					processedPlayers[player] = true
				end
			end
		end

		if #playersInHitbox > 0 then
			buttonLight.Material = Enum.Material.Neon
		else
			buttonLight.Material = Enum.Material.SmoothPlastic
		end

		for _, player in ipairs(playersInHitbox) do
			incrementEnergyForPlayer(player)
		end
	end

	task.wait()  -- Do I need this?
	
end