I need help with this Ring system

You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve? Keep it simple and clear!
    Hello! I am designing a platformer game where the player collects rings. I used :Once() on the .Touched event for it to fire more smoothly, but now, when another player touches the ring, they can’t pick them up.
  2. What is the issue? Include screenshots / videos if possible!
    This is in a server script also. How can I have the IntValue add by 1, and not add by more than it’s supposed to.
  3. What solutions have you tried so far? Did you look for solutions on the Developer Hub?
    I have tried adding a debounce, but the same issue still occurs. I am using CollectionService for the rings with the tag ‘Ring’.

Server Script

local Players = game:GetService("Players")

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RemoteEvents = ReplicatedStorage:FindFirstChild("RemoteEvents")
local RingEvent = RemoteEvents:WaitForChild("RingEvent")

local CollectionService = game:GetService("CollectionService")

local Rings = CollectionService:GetTagged("Ring")

for i, ring in pairs(Rings) do
	if ring:IsA("BasePart") then
		ring.Touched:Once(function(Hit)
			local player = Players:GetPlayerFromCharacter(Hit.Parent)
			if player then
				RingEvent:FireClient(player, ring)
			end
		end)
	end
end

The rings are also not that far apart. They are in groups.

1 Like

The :Once signal will (I believe) only be fired once. The moment someone touches the ring the event will be disconnected, meaning no-one else can ‘touch’ it. For this scenario I’d probably recommend sticking with just .Touched and using some other method (table?) to ensure a player can’t be rewarded twice.

Something along these lines:

for i, ring in pairs(Rings) do
	if ring:IsA("BasePart") then
		local ringTouched = {};
		ring.Touched:Connect(function(Hit)
			local player = Players:GetPlayerFromCharacter(Hit.Parent)
			if player and not ringTouched[player] then
				ringTouched[player] = true
				RingEvent:FireClient(player, ring)
			end
		end)
	end
end
2 Likes

So like a table of debounces? Thank you for the that example I will try it out.

2 Likes

Um, I forgot how to do a table of debounces. What dooes Table[Object] even mean again?

1 Like

Ok here is my full script:

local Players = game:GetService("Players")

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RemoteEvents = ReplicatedStorage:FindFirstChild("RemoteEvents")
local RingEvent = RemoteEvents:WaitForChild("RingEvent")

local CollectionService = game:GetService("CollectionService")

local Rings = CollectionService:GetTagged("Ring")

local debounceTable = {}

for i, ring in pairs(Rings) do
	if ring:IsA("BasePart") then
		ring.Touched:Connect(function(Hit)
			local player = Players:GetPlayerFromCharacter(Hit.Parent)
			if player then
				if not debounceTable[ring] then -- it's saying if not true which means false then it will not run this below
					debounceTable[ring] = true
					RingEvent:FireClient(player, ring)
				end
			end
		end)
	end
end
1 Like

So you need a table for each ring, which is why I put it just before the .Touched event. This means each ring can keep track of which players it has touched. The way you’re setting up here would mean that every time a player touched one ring, they wouldn’t be able to touch any other rings either.

By indexing a table with the player, it’s just a way to make the code look cleaner (in my opinion) and I believe once the player leaves the game it will automatically clean that entry from the table (as player == nil).

local Table = {}

Table[player] = true

print(Table[player]) -- prints 'true'
print(Table)

-- prints:
-- {
--     [player] = true;
-- }

You could do this normally with just player entries in a table:

local Table = {}

table.insert(Table, player)

print(table.find(Table, player)) -- prints '1' as player is the first index in the table
print(Table)

-- prints:
-- {
--     player;
-- }

But then you’ll need to use table.find:

if not table.find(Table, player) then
   -- FireClient...
end
2 Likes

Yeah when I did:

if not debounceTable[theRing] then

this printed out nil

so in reality it’s saying

if not nil

then we set it to true

debounceTable[theRing] = true

then it searches again

if not debounceTable[theRing] then -- since we set it to true it's saying if not true, which equivalates to false
1 Like

You’re not debouncing the ring however, you’re debouncing the player. If you have another look at the code I submitted originally, it’s essentially creating a new ‘ringTouched’ table for each ring. This table will record which players touched that specific ring previously. Every time a player touches that ring, it creates a new entry in the ringTouched table that can be checked/debounced next time a player touches that ring.

OHHHHHHHHHHHHHHHHHHh I understand

1 Like

The player variable that I made is getting the player from the character:

local player = Players:GetPlayerFromCharacter(Hit.Parent)

Exactly, I’ll comment out my code to make it clearer:

for i, ring in pairs(Rings) do -- For every ring:
	if ring:IsA("BasePart") then -- If the ring is a part:
		local ringTouched = {}; -- Create a new debounce table for this specific ring
		ring.Touched:Connect(function(Hit) -- When something touches the ring
			local player = Players:GetPlayerFromCharacter(Hit.Parent) -- Try to get the player from it
			if player and not ringTouched[player] then -- If a player exists, and that player does not exist in the ringTouched table
				ringTouched[player] = true -- Add that player to the ringTouched table
				RingEvent:FireClient(player, ring) -- Fire the event
			end
		end)
	end
end

I will use this in my future platformer games. Thanks a lot :+1:

Mission passed + Respect

Why did you put a semicolon here?

Just to clarify as well, when we talk about indexing tables you don’t have to use numerical indexes. You’re probably used to:

local Table = {
    'hello',
    'world'
}

Where ‘hello’ is the 1st index, and ‘world’ is the 2nd index. This can be re-written as:

local Table = {
    [1] = 'hello',
    [2] = 'world'
}

Which is exactly the same thing. You’d usually index this by using Table[1] → Returns ‘hello’.

However, you don’t have to use numerical indexes. Tables can be indexed with pretty much anything you want. In this case, I’m indexing it with the player instance. This limits the functionality of the table, as we’re no longer able to iterate through it numerically (as there’s no numerical order anymore), but as we don’t need to index through it numerically at any point we don’t care.

Also, semi-colons are just personal preference. You don’t need them, and the code works with-or-without. It’s purely just my personal way of doing things as I transition between languages quite a lot and many require semi-colons, so it just makes life easier for me.

1 Like

When I do this:

it’s sort of the equivalent to this:

for i, theRing in pairs(Rings) do
	if theRing:IsA("BasePart") then
		local debounceTable = {}
		theRing.Touched:Connect(function(Hit)
			local player = Players:GetPlayerFromCharacter(Hit.Parent)
			if player then
				if not debounceTable[player] then
					debounceTable[player] = true
					RingEvent:FireClient(player, theRing)
				end
			end
		end)
	end
end

Not quite. So think about this logically. In the first example:

Imagine you have 3 rings, each ring gets a .Touched event. The moment a player touches one of the rings, they get added to the debounce table. If that same player tried to touch another ring, they’ve already been added to the debounceTable previously, so the script won’t fire the RingEvent. I assume this is not what you want to achieve.

In the second example, a debounceTable is created for each ring, so if a player touches one of the rings, they only get added to the table for that specific ring. Then when they go to touch another ring, their name hasn’t been added to that ring’s specific debounceTable, so it will allow them to touch it.

1 Like

I forgot to mention that the rings get destroyed. Not sure if you are aware. I see what you’re saying though.

I believe your confusion is with the idea of ‘scopes’.

Any local variables created within a function, for loop, if statement etc. is ONLY read-able by code within that ‘scope’, so within that function, for loop, if statement etc.

local variable = true;

for i=1,10 do
    local variable2 = 1

    print(variable) -- -> 'true'
    print(variable2) -- -> '1'
end

print(variable) -- -> 'true'
print(variable2) -- -> 'Error: 'variable2' does not exist in this scope'

If you :Destroy() the ring, then the .Touched event will be disconnected automatically, so there should be no issues.

It’s working the same way it did when I used :Once() except now any player can touch it freely.