Frame cloning by amount of players in game

Hi!

This sounds pretty weird, but I believe it’s actually happening. When I test a game in Studio by the “Play” feature, it works fine. But then, when I test it with 2 players, the Frame gets cloned two times, for some reason.

I believe that it gets cloned by how many players are in game currently, for some reason. Aka if there were 3 players, I assume it would get cloned three times.

For more context, I’m making a spectate menu.
LocalScript:

local Players = game:GetService("Players")
local player = Players.LocalPlayer
local RS = game:GetService("ReplicatedStorage")
local Teams = game:GetService("Teams")
local SpectateEvent = RS:WaitForChild("SpectateMenuEvents"):WaitForChild("SpectateMenu")
local DeadPlayerAssignedEvent = RS:WaitForChild("SpectateMenuEvents"):WaitForChild("DeadPlayerAssigned")
local Gui = player:WaitForChild("PlayerGui"):WaitForChild("SpectateMenu")
local HolderFrame = Gui:WaitForChild("Holder")
local SpectateButton = HolderFrame:WaitForChild("SpectateButton")
local PlayerMenu = HolderFrame:WaitForChild("PlayerMenu")
local ScrollingFrame = PlayerMenu:WaitForChild("ScrollingFrame")
local Title = HolderFrame:WaitForChild("Title")
local Spectating = HolderFrame:WaitForChild("Spectating")
local Camera = workspace.CurrentCamera

local DEBOUNCE = false
local SPECTATE_DEBOUNCE = false

local function spectateButton()
	if player.Team == Teams.Dead or player.Team == Teams.Spectators then
		SpectateButton.Visible = true
	elseif player.Team == Teams.Alive then
		SpectateButton.Visible = false
	end
end

SpectateButton.MouseButton1Click:Connect(function()
	if SPECTATE_DEBOUNCE == false then
		PlayerMenu.Visible = true
		Title.Visible = true
		HolderFrame.Spectating.Visible = true
		SPECTATE_DEBOUNCE = true
	elseif SPECTATE_DEBOUNCE == true then
		Title.Visible = false
		HolderFrame.Spectating.Visible = false
		PlayerMenu.Visible = false
		SPECTATE_DEBOUNCE = false
	end
end) 

local function TeamChange(targetPlayer)
	targetPlayer:GetPropertyChangedSignal("Team"):Connect(function()
		SpectateEvent:FireServer(targetPlayer)
	end)
end

for i,targetPlayer in pairs(Players:GetPlayers()) do
	TeamChange(targetPlayer)
end

Players.PlayerAdded:Connect(TeamChange)

DeadPlayerAssignedEvent.OnClientEvent:Connect(function(targetPlayer)
	local tableTemplates = ScrollingFrame:GetChildren()
	for i,v in pairs(tableTemplates) do
		if v.ClassName == "Frame" then
			if v.Name == targetPlayer.Name then
				v:Destroy()
				Spectating.Text = "SPECTATING: N/A"
				Camera.CameraSubject = player.Character:WaitForChild("Head")
			else
				v:Destroy()
			end
		end
	end
end)

ServerScript:

local RS = game:GetService("ReplicatedStorage")
local SS = game:GetService("ServerStorage")
local PlayerTemplate = SS:WaitForChild("PlayerTemplate")
local SpectateEvent = RS:WaitForChild("SpectateMenuEvents"):WaitForChild("SpectateMenu")
local DeadPlayerAssignedEvent = RS:WaitForChild("SpectateMenuEvents"):WaitForChild("DeadPlayerAssigned")
local Teams = game:GetService("Teams")
local Players = game:GetService("Players")
local Camera = workspace.CurrentCamera

local DEBOUNCE = false

SpectateEvent.OnServerEvent:Connect(function(player, targetPlayer)
	if targetPlayer.Team == Teams:FindFirstChild("Alive") then
		local tablePlayers = Players:GetPlayers()
		for i,v in pairs(tablePlayers) do
			local targetThumbnail = Players:GetUserThumbnailAsync(targetPlayer.UserId, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size352x352)
			local playerGui = v.PlayerGui:WaitForChild("SpectateMenu")
			local CloneTemplateParent = playerGui:WaitForChild("Holder"):WaitForChild("PlayerMenu"):WaitForChild("ScrollingFrame")
			local CloneTemplate = PlayerTemplate:Clone()
			CloneTemplate.Parent = CloneTemplateParent
			CloneTemplate.Name = targetPlayer.Name
			CloneTemplate.PlayerName.Text = targetPlayer.Name
			CloneTemplate.PlayerThumbnail.Image = targetThumbnail
			CloneTemplate.PlayerName.MouseButton1Click:Connect(function()
				local Spectating = CloneTemplateParent.Parent.Parent.Spectating
				if DEBOUNCE == false then
					DEBOUNCE = true
					Spectating.Text = "SPECTATING: "..targetPlayer.Name
					Camera.CameraSubject = targetPlayer.Character:WaitForChild("Head")
					print("after targetPlayer CameraSubject")
				elseif DEBOUNCE == true then
					Spectating.Text = "SPECTATING: N/A"
					Camera.CameraSubject = player.Character:WaitForChild("Head")
					print("after LocalPlayer CameraSubject")
					DEBOUNCE = false
				end
			end)
		end
	elseif targetPlayer.Team == Teams:FindFirstChild("Dead") or targetPlayer.Team == Teams:FindFirstChild("Spectators") then
		DeadPlayerAssignedEvent:FireAllClients(targetPlayer)
	end
end)
1 Like

You are always cloning the frame when someone joins the game, thats why you get more frames

How can I fix this issue? Because I want to constantly loop through all players in the LocalScript.

I am not sure, just made this in a couple seconds

local function TeamChange(targetPlayer)
	targetPlayer:GetPropertyChangedSignal("Team"):Connect(function()
		SpectateEvent:FireServer(targetPlayer)
	end)
end

for _, targetPlayer in pairs(Players:GetPlayers()) do
	TeamChange(targetPlayer)
end

Players.PlayerAdded:Connect(function(newPlayer)
	TeamChange(newPlayer)
end)

1 Like

Thanks! Though, unfortunately, the issue still remains.

Because every time a player joins you are cloning the frame for every player currently in the game.

		for i,v in pairs(tablePlayers) do
			local playerGui = v.PlayerGui:WaitForChild("SpectateMenu")
			local CloneTemplateParent = playerGui:WaitForChild("Holder"):WaitForChild("PlayerMenu"):WaitForChild("ScrollingFrame")
			local CloneTemplate = PlayerTemplate:Clone()

I don’t fully understand what’s going on in your code, but I don’t think the for loop is necessary. You can just parent CloneTemplate to Players[targetPlayer.Name]:WaitForChild("Holder"):WaitForChild("PlayerMenu"):WaitForChild("ScrollingFrame").
Right now, your code is parenting and cloning CloneTemplate to v
for i, v in pairs(tablePlayers)
This line is running a loop that iterates through every single player currently in the game and runs the code in that loop. So, once a second player joins, it will iterate through that loop again and clone another CloneTemplate gui into a player that already has it.

There’s a much more elegant solution, but a quick bandaid fix would be to just check if a player already has CloneTemplate.

local template = Players[v.Name].PlayerGui.SpectateMenu.Holder.PlayerMenu.ScrollingFrame:FindFirstChild(PlayerTemplate)
if template then continue end

This will check for PlayerTemplate inside the player being iterated through, and if it exists, it will continue and start iterating through the next player in the loop.

1 Like

Not sure if it’s correct, but this is how I did it referring to your reply.

SpectateEvent.OnServerEvent:Connect(function(player, targetPlayer)
    if targetPlayer.Team == Teams:FindFirstChild("Alive") then
        local tablePlayers = Players:GetPlayers()
		
		for i,v in pairs(tablePlayers) do
			local template = Players[v.Name].PlayerGui.SpectateMenu.Holder.PlayerMenu.ScrollingFrame:FindFirstChildOfClass("Frame")
			if template then continue end
			
			local CloneTemplateParent = Players[v.Name].PlayerGui.SpectateMenu.Holder.PlayerMenu.ScrollingFrame
			local CloneTemplate = PlayerTemplate:Clone()
end)

It still doesn’t work.

Can you add a print statement to if template then continue end

if template then
    print(template, v)
continue
end

and see if it prints anything?
also put the print after the if block to see if template is nil or not

It doesn’t print anything.

characters

Nothing at all? Even the print after the if block?

SpectateEvent.OnServerEvent:Connect(function(player, targetPlayer)
    if targetPlayer.Team == Teams:FindFirstChild("Alive") then
        local tablePlayers = Players:GetPlayers()
		
		for i,v in pairs(tablePlayers) do
			local template = Players[v.Name].PlayerGui.SpectateMenu.Holder.PlayerMenu.ScrollingFrame:FindFirstChildOfClass("Frame")
			if template then  
                print("Template found " ..template, v)
                continue 
            end
	        print("No template found " ..template, v)
			local CloneTemplateParent = Players[v.Name].PlayerGui.SpectateMenu.Holder.PlayerMenu.ScrollingFrame
			local CloneTemplate = PlayerTemplate:Clone()
end)

If it isn’t printing anything then it means the loop isn’t even running…?

print("No template found " ..template, v)

Otherwise than that, it doesn’t print anything.

Ok, that error just means template is nil, which it will be for the very first player that joins the game - you can remove that print now.

Players[v.Name].PlayerGui.SpectateMenu.Holder.PlayerMenu.ScrollingFrame:FindFirstChildOfClass("Frame")

Are you 100% certain this is the correct path? I can’t see any other reason why the code wouldn’t work. You have implemented it correctly.

It looks for the frame inside ScrollingFrame and if it exists, it continues to the next loop iteration, skipping all the code below it.

Yes, the path should be correct.
obrazek
It gets cloned from ServerStorage to the ScrollingFrame.

It looks to be working on my end. This is how I have it set up:
image

local RS = game:GetService("ReplicatedStorage")
local SS = game:GetService("ServerStorage")
local PlayerTemplate = SS:WaitForChild("PlayerTemplate")
local SpectateEvent = RS:WaitForChild("SpectateMenuEvents"):WaitForChild("SpectateMenu")
local DeadPlayerAssignedEvent = RS:WaitForChild("SpectateMenuEvents"):WaitForChild("DeadPlayerAssigned")
local Teams = game:GetService("Teams")
local Players = game:GetService("Players")
local Camera = workspace.CurrentCamera

local DEBOUNCE = false

SpectateEvent.OnServerEvent:Connect(function(player, targetPlayer)
	if targetPlayer.Team == Teams:FindFirstChild("Alive") then
		local tablePlayers = Players:GetPlayers()
		for i,v in pairs(tablePlayers) do
			local targetThumbnail = Players:GetUserThumbnailAsync(targetPlayer.UserId, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size352x352)
			local playerGui = v.PlayerGui:WaitForChild("SpectateMenu")
			local template = Players[v.Name].PlayerGui.SpectateMenu.Holder.PlayerMenu.ScrollingFrame:FindFirstChildOfClass("Frame")
			if template then 
				print(template, v)
				continue 
			end
			local CloneTemplateParent = playerGui:WaitForChild("Holder"):WaitForChild("PlayerMenu"):WaitForChild("ScrollingFrame")
			local CloneTemplate = PlayerTemplate:Clone()
			CloneTemplate.Parent = CloneTemplateParent
			CloneTemplate.Name = targetPlayer.Name
			CloneTemplate.PlayerName.Text = targetPlayer.Name
			CloneTemplate.PlayerThumbnail.Image = targetThumbnail
			CloneTemplate.PlayerName.MouseButton1Click:Connect(function()
				local Spectating = CloneTemplateParent.Parent.Parent.Spectating
				if DEBOUNCE == false then
					DEBOUNCE = true
					Spectating.Text = "SPECTATING: "..targetPlayer.Name
					Camera.CameraSubject = targetPlayer.Character:WaitForChild("Head")
					print("after targetPlayer CameraSubject")
				elseif DEBOUNCE == true then
					Spectating.Text = "SPECTATING: N/A"
					Camera.CameraSubject = player.Character:WaitForChild("Head")
					print("after LocalPlayer CameraSubject")
					DEBOUNCE = false
				end
			end)
		end
	elseif targetPlayer.Team == Teams:FindFirstChild("Dead") or targetPlayer.Team == Teams:FindFirstChild("Spectators") then
		DeadPlayerAssignedEvent:FireAllClients(targetPlayer)
	end
end)

In the test server with Player2 joining after Player1:

1 Like

Alright, the cloning seems to work, after I pasted in the code you just sent. However, the spectating itself doesn’t work. The camera still stays on the LocalPlayer, but the Spectating TextLabel changes to the targetPlayer.

1 Like

Do you know how this could be fixed? Because I believe that both of the player values are correct and valid.

I think it’s just the way we are finding the frames. To my understanding you want a new frame in each player’s ScrollingFrame for each player.

Replace

 local template = Players[v.Name].PlayerGui.SpectateMenu.Holder.PlayerMenu.ScrollingFrame:FindFirstChildOfClass("Frame")

with

local template = Players[v.Name].PlayerGui.SpectateMenu.Holder.PlayerMenu.ScrollingFrame:FindFirstChild(targetPlayer.Name)
1 Like

It doesn’t outprint any new errors (as it shouldn’t), but it didn’t help either, thanks though.

I assume the issue must be in this line:

Camera.CameraSubject = targetPlayer.Character:WaitForChild("Head")

As also seen by the fact that the print, which is located on the another line doesn’t get printed. Not sure how to fix this though.

That’s because you’re trying to access Camera on the server. You need to move that logic to a LocalScript as the CameraSubject property can only be accessed on the client

1 Like

OHHH, right! I’m so dumb :smiley: Thanks!

I’ll fire it back to the LocalScript and see if it works.

1 Like