Code issues - Preventing Spam when Saving Player Positions

Hello! I’d like to know what’s wrong with my code. It’s generating spam when saving the player’s position, and I only want it to happen once when the platformArea is touched and when it’s touchedended.

image

script:

local platform = script.Parent.Parent.Platform
local platformArea = platform.platformArea

local checkpointParts = {
	platform.Arrow,
	platform.Part1,
	platform.Part2,
	platform.part3
}

local colorsToChange = {
	Color3.fromRGB(255, 0, 0),  -- Red
	Color3.fromRGB(0, 255, 0),  -- Green
	Color3.fromRGB(0, 0, 255),  -- Blue
	Color3.fromRGB(255, 255, 0) -- Yellow
}

local defaultColor = Color3.fromRGB(192, 192, 192) -- Default color: Gray

-- SAVE DATA

local DataStoreService = game:GetService("DataStoreService")

-- Define the key to store the player's position in DataStore
local PLAYER_POSITION_KEY = "PlayerPosition"

-- Get the DataStore instance
local playerDataStore = DataStoreService:GetDataStore("PlayerDataStore")

-- Table to keep track of which players have already saved their position during the current touch event
local savedPositions = {}

-- Function to save the player's position in DataStore once, based on a specific part
local function savePlayerPositionOnce(player, basePart)
	-- Check if the player's position has already been saved for this touch event
	if savedPositions[player.UserId] then
		return -- If position has already been saved, exit the function
	end

	-- Check if the basePart argument is a BasePart
	if not basePart:IsA("BasePart") then
		error("Argument 'basePart' must be a BasePart.")
	end

	-- Construct the key to store the player's position
	local key = PLAYER_POSITION_KEY .. player.UserId
	-- Get the position of the specified basePart
	local position = basePart.Position
	-- Convert Vector3 position to string
	local positionString = table.concat({position.X, position.Y, position.Z}, ",")
	-- Attempt to save the player's position in DataStore
	local success, _ = pcall(function()
		playerDataStore:SetAsync(key, positionString)
	end)
	-- Check if the saving operation was successful
	if success then
		print("Player position saved successfully for player: " .. player.Name)
		-- Mark player's position as saved to prevent saving again for this touch event
		savedPositions[player.UserId] = true
	else
		warn("Failed to save player position for player: " .. player.Name)
	end
end

-- END SAVE DATA

local function changeColors(color)
	for i, part in ipairs(checkpointParts) do
		if part then
			part.Color = color[i] or defaultColor -- Use the corresponding color or default color
		end
	end
end

platformArea.Touched:Connect(function(hit)
	local character = hit.Parent
	local player = game.Players:GetPlayerFromCharacter(character)
	if player then
		print("platformArea touched by player: " .. player.Name) -- Debug print
		changeColors(colorsToChange)
		savePlayerPositionOnce(player, platformArea)
	end
end)

platformArea.TouchEnded:Connect(function()
	print("platformArea touch ended") -- Debug print
	changeColors({defaultColor, defaultColor, defaultColor, defaultColor}) -- Change all colors to default (gray)
	-- Reset saved positions table for the next touch event
	savedPositions = {}
end)
1 Like

This looks like a debounce issue - the problem is that Touched will fire several times, and I think it’s the same with TouchEnded.

Usually the solution is just to add a debounce variable, check that variable, and then not do anything if you’re already within the Touched handler. There’s some Documentation around that here: Debounce Patterns | Documentation - Roblox Creator Hub

1 Like

Right, I tried that, but it still keeps spamming. I think the debounce isn’t working properly.

local platform = script.Parent.Parent.Platform
local platformArea = platform.platformArea

local checkpointParts = {
	platform.Arrow,
	platform.Part1,
	platform.Part2,
	platform.part3
}

local colorsToChange = {
	Color3.fromRGB(255, 0, 0),  -- Red
	Color3.fromRGB(0, 255, 0),  -- Green
	Color3.fromRGB(0, 0, 255),  -- Blue
	Color3.fromRGB(255, 255, 0) -- Yellow
}

local defaultColor = Color3.fromRGB(192, 192, 192) -- Default color: Gray

-- SAVE DATA

local DataStoreService = game:GetService("DataStoreService")

-- Define the key to store the player's position in DataStore
local PLAYER_POSITION_KEY = "PlayerPosition"

-- Get the DataStore instance
local playerDataStore = DataStoreService:GetDataStore("PlayerDataStore")

-- Table to keep track of which players have already saved their position during the current touch event
local savedPositions = {}

-- Function to save the player's position in DataStore once, based on a specific part
local function savePlayerPositionOnce(player, basePart)
	-- Check if the player's position has already been saved for this touch event
	if savedPositions[player.UserId] then
		return -- If position has already been saved, exit the function
	end

	-- Check if the basePart argument is a BasePart
	if not basePart:IsA("BasePart") then
		error("Argument 'basePart' must be a BasePart.")
	end

	-- Construct the key to store the player's position
	local key = PLAYER_POSITION_KEY .. player.UserId
	-- Get the position of the specified basePart
	local position = basePart.Position
	-- Convert Vector3 position to string
	local positionString = table.concat({position.X, position.Y, position.Z}, ",")
	-- Attempt to save the player's position in DataStore
	local success, _ = pcall(function()
		playerDataStore:SetAsync(key, positionString)
	end)
	-- Check if the saving operation was successful
	if success then
		print("Player position saved successfully for player: " .. player.Name)
		-- Mark player's position as saved to prevent saving again for this touch event
		savedPositions[player.UserId] = true
	else
		warn("Failed to save player position for player: " .. player.Name)
	end
end

-- END SAVE DATA

local function changeColors(color)
	for i, part in ipairs(checkpointParts) do
		if part then
			part.Color = color[i] or defaultColor -- Use the corresponding color or default color
		end
	end
end

local debounceTime = 2 -- Tiempo de espera en segundos
local debounceActiveTouched = false -- Indica si el debounce para Touched está activo
local debounceActiveTouchEnded = false -- Indica si el debounce para TouchEnded está activo

platformArea.Touched:Connect(function(hit)
	if debounceActiveTouched then
		return
	end

	local character = hit.Parent
	local player = game.Players:GetPlayerFromCharacter(character)
	if player then
		print("platformArea touched by player: " .. player.Name) -- Debug print
		changeColors(colorsToChange)
		savePlayerPositionOnce(player, platformArea)
	end

	debounceActiveTouched = true
	wait(debounceTime)
	debounceActiveTouched = false
end)

platformArea.TouchEnded:Connect(function()
	if debounceActiveTouchEnded then
		return
	end

	print("platformArea touch ended") -- Debug print
	changeColors({defaultColor, defaultColor, defaultColor, defaultColor}) -- Change all colors to default (gray)
	-- Reset saved positions table for the next touch event
	savedPositions = {}

	debounceActiveTouchEnded = true
	wait(debounceTime)
	debounceActiveTouchEnded = false
end)
1 Like

I’d move your debounceActiveTouched right away to after you check it - the saving is going to take some time to run (specifically the SetAsync call) and that’s where the problems would be coming from here.

So I’d change it up to:

  1. Touched fires
  2. Check debounce
  3. Set debounce flag
  4. Run your save
  5. Unset debounce

Would be the same case with TouchEnded

1 Like

I am personally not a fan of TouchEnded, so I suggest using something like ZonePlus to handle player touching logic. Additionally, your debounce will block every player, is that really what you want?

2 Likes

Good point, probably do that separately then.

Change up what I said to:

  1. Touched fires
  2. Check debounce
  3. Set debounce flag
  4. Slight delay (even just a wait() should be fine)
  5. Unset debounce
  6. Save

Also I’m curious about that ZonePlus module - how efficient is it? I usually try avoiding those GetPartBoundsInBox methods because they seem pretty slow if you repeatedly use them. That’s a super useful module if it can run efficiently though.

i tried it and still dont work, whats wrong? :frowning:

local platform = script.Parent.Parent.Platform
local platformArea = platform.platformArea

local checkpointParts = {
	platform.Arrow,
	platform.Part1,
	platform.Part2,
	platform.part3
}

local colorsToChange = {
	Color3.fromRGB(255, 0, 0),  -- Red
	Color3.fromRGB(0, 255, 0),  -- Green
	Color3.fromRGB(0, 0, 255),  -- Blue
	Color3.fromRGB(255, 255, 0) -- Yellow
}

local defaultColor = Color3.fromRGB(192, 192, 192) -- Default color: Gray

-- SAVE DATA

local DataStoreService = game:GetService("DataStoreService")

-- Define the key to store the player's position in DataStore
local PLAYER_POSITION_KEY = "PlayerPosition"

-- Get the DataStore instance
local playerDataStore = DataStoreService:GetDataStore("PlayerDataStore")

-- Table to keep track of which players have already saved their position during the current touch event
local savedPositions = {}

-- Function to save the player's position in DataStore once, based on a specific part
local function savePlayerPositionOnce(player, basePart)
	-- Check if the player's position has already been saved for this touch event
	if savedPositions[player.UserId] then
		return -- If position has already been saved, exit the function
	end

	-- Check if the basePart argument is a BasePart
	if not basePart:IsA("BasePart") then
		error("Argument 'basePart' must be a BasePart.")
	end

	-- Construct the key to store the player's position
	local key = PLAYER_POSITION_KEY .. player.UserId
	-- Get the position of the specified basePart
	local position = basePart.Position
	-- Convert Vector3 position to string
	local positionString = table.concat({position.X, position.Y, position.Z}, ",")
	-- Attempt to save the player's position in DataStore
	local success, _ = pcall(function()
		playerDataStore:SetAsync(key, positionString)
	end)
	-- Check if the saving operation was successful
	if success then
		print("Player position saved successfully for player: " .. player.Name)
		-- Mark player's position as saved to prevent saving again for this touch event
		savedPositions[player.UserId] = true
	else
		warn("Failed to save player position for player: " .. player.Name)
	end
end

-- END SAVE DATA

local debounceTime = 2 -- Tiempo de espera en segundos

local debounceActiveTouched = false -- Indica si el debounce para Touched está activo
local debounceActiveTouchEnded = false -- Indica si el debounce para TouchEnded está activo

local function changeColors(color)
	for i, part in ipairs(checkpointParts) do
		if part then
			part.Color = color[i] or defaultColor -- Use the corresponding color or default color
		end
	end
end

local function handleTouch(hit, debounceFlag)
	local character = hit.Parent
	local player = game.Players:GetPlayerFromCharacter(character)
	if player then
		print("platformArea touched by player: " .. player.Name) -- Debug print
		changeColors(colorsToChange)
		savePlayerPositionOnce(player, platformArea)
	end

	debounceFlag = true
	wait(debounceTime)
	debounceFlag = false
end

platformArea.Touched:Connect(function(hit)
	if debounceActiveTouched then
		return
	end

	handleTouch(hit, debounceActiveTouched)
end)

platformArea.TouchEnded:Connect(function()
	if debounceActiveTouchEnded then
		return
	end

	print("platformArea touch ended") -- Debug print
	changeColors({defaultColor, defaultColor, defaultColor, defaultColor}) -- Change all colors to default (gray)
	-- Reset saved positions table for the next touch event
	savedPositions = {}

	debounceActiveTouchEnded = true
	wait(debounceTime)
	debounceActiveTouchEnded = false
end)

Okay instead of doing alla that let’s make this a little simpler using a table, that way you can also track what player is in the cooldown.

Your code is a little bit everywhere (not saying this as an insult but because I’m simply too tired to read all of it) so I’ll just hope this is easier to understand:

local DebounceTable = {}

platformArea.Touched:Connect(function(hit)
	-- is it only supposed to work when players hit it? i'll assume so
	if hit.Parent:FindFirstChildOfClass("Humanoid") then -- character
		if not table.find(DebounceTable, hit.Parent.Name) then
			table.insert(DebounceTable, hit.Parent.Name)
			handleTouch(hit, debounceActiveTouched) -- idk how relevant this is so i wont touch it
		end
	end
end)

platformArea.TouchEnded:Connect(function(hit)
	if hit.Parent:FindFirstChildOfClass("Humanoid") then
		if table.find(DebounceTable, hit.Parent.Name) then

			print("platformArea touch ended") -- Debug print
			changeColors({defaultColor, defaultColor, defaultColor, defaultColor}) -- Change all colors to default (gray)
			-- Reset saved positions table for the next touch event
			savedPositions = {}

			task.wait(debounceTime)
			table.remove(DebounceTable, table.find(DebounceTable, hit.Parent.Name))
		end
	end
end)

though i will agree using touchended is bad cuz it sux but do wat u wanna do