Script doesn't recognize PlayerGui

I’m trying to make a round UI similar to the one in Piggy where once you click a button, it shows all other players who’ve clicked the button and joined the round. Currently there is no round system in place. Just the joining system.

My problem is when a player leaves, it doesn’t remove a cloned template that has their name on it. The error being “PlayerGui is not a valid member of [PLAYER]” This is in a local script so it’s able to show the round to everyone. I’m using a list to keep track of the players who’ve clicked the button and get their usernames so I can find the PlayerGui. I’m not sure why this is happening as server side does have access to PlayerGui.

I’ve tried changing the scripts a bit, but I’m not too sure if it has anything to do with calling “In pairs” twice in one script, or if it’s something to do with me calling the player “v”. The script is below, and the function “heheheyup” activates once a player clicks on the button to get into a round. I took some of this from roblox’s page for remote events, so some defined things may be different than what they say they are.

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteEvent = ReplicatedStorage:WaitForChild("Events").round

local playing = {}

local Players = game:GetService("Players")
-- Create a new part
local function heheheyup(player)
	print(player.Name .. " fired the remote event")
	local Button = player.PlayerGui.round.Frame.clonetemplate 
	local ScrollingFrame = player.PlayerGui.round.Frame
		local plrname = player.Name
		table.insert(playing,plrname)
		print(playing)
		local string1 = Instance.new("StringValue")
		string1.Value = plrname
		string1.Name = plrname
		string1.Parent = game.Players:WaitForChild("Clickedplay")
		local CloneButton = Button:Clone()
		CloneButton.Name = plrname
		CloneButton.Text = plrname
		CloneButton.Parent = ScrollingFrame
	CloneButton.Visible = true
	
end

local function playedadded(plr)
	for i,v in pairs(playing) do
		print(v)
		print(plr)
		local wowplay = v
	--	if plr:FindFirstChild("PlayerGui"):WaitForChild("round") then
		--	print("ok...")
		--end
		local Button = game.Players:FindFirstChild(plr.Name).PlayerGui:WaitForChild("round").Frame.clonetemplate 
		local ScrollingFrame = game.Players:FindFirstChild(plr.Name).PlayerGui:WaitForChild("round").Frame 
		local plrname = v
		local CloneButton = Button:Clone()
		CloneButton.Name = v
		CloneButton.Text = v
		CloneButton.Parent = ScrollingFrame
		CloneButton.Visible = true
		end
	end


local function PlayerLeft(plr)
	
	for i, v in pairs(game.Players.Clickedplay:GetChildren()) do -- all players
		
		if v.Name == plr.Name then
			game.Players.Clickedplay:FindFirstChild(plr.Name):Destroy()
		end
		print(v)
		for i, v in pairs(game.Players:FindFirstChild(v.Name).PlayerGui.round.Frame:GetChildren()) do
			local name = v.Name
			game.Players.Name.PlayerGui.round.Frame:FindFirstChild(plr.Name):Destroy()
			if v.Name == plr.Name then
				print("ok")
				local plrname = plr.Name
				table.remove(playing, plrname)
				
		end

		end
	end
end

-- Call "onCreatePart()" when the client fires the remote event
remoteEvent.OnServerEvent:Connect(heheheyup)
Players.PlayerAdded:Connect(playedadded)
Players.PlayerRemoving:Connect(PlayerLeft)

If theres anything I can do to fix this, or if anyone needs more information about this please let me know.

To access PlayerGui player must be fully loaded. And please, tell me where does error show?

Your code is kinda complicating stuff.
I suggest, if you have a StarterGui with a Play button, the player will get that on join.
After clicking the Play button, you should use the RemoteEvent to tell all playing Clients that a new player is in game. A local script will receive the signal and clone a button, change name, etc, and add it into the player’s GUIs
Add the new player into a server table.
When a player leaves, you fire the RemoteEvent to all players in that table to remove the button of the player that left.

A basic example should be like this:

Server Script in ServerScriptService:

-- Players that clicked Play button
local PlayersPlaying = {}

local RemoteEventToPlay = game.ReplicatedStorage:WaitForChild("RemoteEventToPlay")

RemoteEventToPlay.OnServerEvent:Connect(function(player)
	warn(player, "clicked to Play")
	-- Add GUI button to show player name etc, into all Clients playing
	for _, user in pairs(PlayersPlaying) do
		RemoteEventToPlay:FireClient(user, player, true)
	end
	table.insert(PlayersPlaying, player)
end)

Players.PlayerAdded:Connect(function(player)
	-- Give GUI with button to Play
	-- Or maybe its a StarterGui so, nothing to do here
end)

Players.PlayerRemoving:Connect(function(player)
	table.remove(PlayersPlaying, table.find(PlayersPlaying, player))
	-- Remove GUI button in all clients playing
	for _, user in pairs(PlayersPlaying) do
		RemoteEventToPlay:FireClient(user, player, false)
	end
end)

Local Script in a Starter GUI:

script.Parent:WaitForChild("PlayButton").Activated:Connect(function()
	RemoteEventToPlay:FireServer()	
end)

local ButtonToClone = -- Reference the button that should be cloned

RemoteEventToPlay.OnClientEvent:Connect(function(player, mode)
	if mode then
		-- Add player into GUI
		local ButtonCloned = ButtonToClone:Clone()
		ButtonCloned .Name = tostring(player.UserId)
        -- Add the name of the player, thumbnail or whatever thing you want into the cloned element
		ButtonCloned.Parent = script.Parent.GUIofPlayers
	else
		-- Remove player from GUI
		script.Parent.GUIofPlayers:FindFirstChild(tostring(player.UserId)):Destroy()
	end
end)

You also can’t acces other’s gui.

What you mean?
Server can access a PlayerGui, and add or delete elements, theres no problem with that.
But, would be better if you let the local script to handle that, unless is extremely important that the server handles stuff in the clients gui

He said this code is in local script. So local script of player A can’t access player B’s gui.

The code that I’ve showed is in a server script,(not sure if I made that clear, sorry) and it just fires after someone clicks a button (local script)

1 Like

How that would be a local script, check this line… Thats a server script, otherwise theres no reason to use OnServerEvent

This seems to be getting closer, but it never duplicates into the players round frame, I’ve attached an image of the GUI components, and a video of me play testing.
Screenshot 2023-01-29 at 2.33.49 AM

thats because this line:
table.insert(PlayersPlaying, player) should happen before firing the event. And I did a little mispelling, its FireClient(), not Fire() on line 12

I slighlty edited the script, as I said its an example of a simpler approach, Im gonna record a video and improve the script a little

Server Script:

local Players = game:GetService("Players")

-- Players that clicked Play button
local PlayersPlaying = {}

-- A remote event
local RemoteEventToPlay = game.ReplicatedStorage:WaitForChild("RemoteEventToPlay")
-- A remote function
local AskCurrentPlayers = game.ReplicatedStorage:WaitForChild("AskCurrentPlayers")

-- Event triggered from Play button
RemoteEventToPlay.OnServerEvent:Connect(function(player)
	warn(player, "clicked to Play")
	table.insert(PlayersPlaying, player)
	-- Add GUI button to show player name etc, into all Clients playing
	for _, user in pairs(PlayersPlaying) do
		RemoteEventToPlay:FireClient(user, player, true)
	end
end)

Players.PlayerAdded:Connect(function(player)
	-- Give GUI with button to Play
	-- Or maybe its a StarterGui so, nothing to do here
end)

Players.PlayerRemoving:Connect(function(player)
	table.remove(PlayersPlaying, table.find(PlayersPlaying, player))
	-- Remove GUI button in all clients playing
	for _, user in pairs(PlayersPlaying) do
		RemoteEventToPlay:FireClient(user, player, false)
	end
end)

-- Remote event called to send back current list of players
local function returnPlayersInGame()
	return PlayersPlaying
end
AskCurrentPlayers.OnServerInvoke = returnPlayersInGame

Local Script in GUI:

local RemoteEventToPlay = game.ReplicatedStorage:WaitForChild("RemoteEventToPlay")
local AskCurrentPlayers = game.ReplicatedStorage:WaitForChild("AskCurrentPlayers")

-- Button to start playing
script.Parent:WaitForChild("PlayButton").Activated:Connect(function()
	RemoteEventToPlay:FireServer()	
end)

local ButtonToClone = game.ReplicatedStorage.GUI_Elements:WaitForChild("Player_Button")

RemoteEventToPlay.OnClientEvent:Connect(function(player, mode)
	if mode then
		-- Add player into GUI
		local ButtonCloned = ButtonToClone:Clone()
		ButtonCloned.Name = tostring(player.UserId)
		ButtonCloned.PlayerName.Text = player.Name
		ButtonCloned.Parent = script.Parent.GUIofPlayers
	else
		-- Remove player from GUI
		script.Parent.GUIofPlayers:FindFirstChild(tostring(player.UserId)):Destroy()
	end

end)

-- Populate all current players playing when client joins
local listOfPlayers = AskCurrentPlayers:InvokeServer()
if listOfPlayers then
	for _, p in pairs(listOfPlayers) do
		local ButtonCloned = ButtonToClone:Clone()
		ButtonCloned.Name = tostring(p.UserId)
		ButtonCloned.PlayerName.Text = p.Name
		ButtonCloned.Parent = script.Parent.GUIofPlayers
	end
end

Stuff in ReplicatedStorage:
ThingsInReplicatedStorage.rbxm (5.9 KB)

Stuff in StarterGui:
aStarterGui.rbxm (9.5 KB)

You are very talented! Thank you so much :slight_smile:

1 Like

Thank you so much :3
Im glad I did help a little :yum:

I have noticed one thing about it thats, lets say for example player one joins and then player two joins, if player two joins the round before player one, player one never see’s that player two joined the round. Do you think it would be possible for a loop once a to happen once a player joins, that when another player join it will show them other players in the menu. Or if theres any other way to fix that. Sorry if this sounds confusing

1 Like

Sure, its possible to use a loop, in that way the updating is constant.
Maybe something like this, Im using a folder in ReplicatedStorage to save an ObjectValue inside it, storing the Player instance into the ObjectValue from ServerSide, then, in ClientSide the local script would have a while loop, to check the Objects in the ReplicatedStorage folder to update the GUI list.
That client loop is independant, I mean each client has its own updating loop running in their devices only, and it starts running when the client joins game, not round. So the player can see the players that are inside round since the beginning.

Then could be 2 ways for the client to update their GUI list. The first one (grayedOut in the script) could be, ClearAllChildren() from the GUI list, and iterate the Folder to create a button per player in there, that would happen each iteration.

Or option 2, iterate the folder and add gui buttons for players that are not in the GUI and exist in folder, and iterate the GUI list to find players in GUI that are not in the folder (players that left) to delete them from GUI.

Both options would use a debouce, in which the server tells the client that the list of players has changed and each client should update their current GUI list, so the clients doesnt need to constantly update the list if the list has not changed yet.

Something like this:

Server Script:

local Players = game:GetService("Players")

-- A folder in Replicated Storage holding Object values for each player
local PlayersInRound = game.ReplicatedStorage:WaitForChild("PlayersInRound")

-- Remote event from client -> Server to Play
-- Server -> Client to tell them to update the GUI
local RemoteEventToPlay = game.ReplicatedStorage:WaitForChild("RemoteEventToPlay")

-- Event triggered from Play button
RemoteEventToPlay.OnServerEvent:Connect(function(player)
	warn(player, "clicked to Play")
	-- Create object value in folder for the new player joined round
	local newPlyr = Instance.new("ObjectValue")
	newPlyr.Name = tostring(player.UserId)
	newPlyr.Value = player
	newPlyr.Parent = PlayersInRound
	-- Fire all clients to tell them to update their GUI
	RemoteEventToPlay:FireAllClients(true)
end)

Players.PlayerRemoving:Connect(function(player)
	if PlayersInRound:FindFirstChild(tostring(player.UserId)) then
		PlayersInRound:FindFirstChild(tostring(player.UserId)):Destroy()
		-- Fire all clients to tell them to update their GUI
		RemoteEventToPlay:FireAllClients(true)
	end
end)

Client Local Script:

-- A folder in Replicated Storage holding Object values for each player
local PlayersInRound = game.ReplicatedStorage:WaitForChild("PlayersInRound")

-- The ScrollingFrame in GUI
local GUIofPlayers = script.Parent:WaitForChild("GUIofPlayers")

local RemoteEventToPlay = game.ReplicatedStorage:WaitForChild("RemoteEventToPlay")
-- Button to start playing
script.Parent:WaitForChild("PlayButton").Activated:Connect(function()
	RemoteEventToPlay:FireServer()	
end)

-- GUI Elements to clone
local ButtonToClone = game.ReplicatedStorage:WaitForChild("GUI_Elements"):WaitForChild("Player_Button")
local UIListLayout = game.ReplicatedStorage:WaitForChild("GUI_Elements"):WaitForChild("UIListLayout") -- I added this one if the first approach to update the list is used

-- Update debounce
local UpdateLock = true

-- Server said list has changed
RemoteEventToPlay.OnClientEvent:Connect(function(isUpdate)
	UpdateLock = isUpdate
end)

-- Constantly update all players in round each 5 seconds
while true do
	if UpdateLock then -- Update runs only when list has changed
		print("Updating:", UpdateLock)
		UpdateLock = false
		
		-- 2 ways to update the list
		
		--[[ 1.- Delete GUI list completely and build a new one based on the folder ]]
		--[[
		GUIofPlayers:ClearAllChildren() -- Delete previous gui list
		UIListLayout:Clone().Parent = GUIofPlayers -- apply list layout
		for _, player in pairs(PlayersInRound:GetChildren()) do
			-- Add player into GUI
			warn(player.Value)
			local ButtonCloned = ButtonToClone:Clone()
			ButtonCloned.Name = tostring(player.Value.UserId)
			ButtonCloned.PlayerName.Text = player.Value.Name
			ButtonCloned.Parent = GUIofPlayers
		end
		]]
		
		--[[ OR 2.- By iterating 2 times ]]
		-- Iterate both, Folder to find which player is missing in GUI to add it
		for _, player in pairs(PlayersInRound:GetChildren()) do
			if not GUIofPlayers:FindFirstChild(tostring(player.Value.UserId)) then
				-- Create player button missing
				local ButtonCloned = ButtonToClone:Clone()
				ButtonCloned.Name = tostring(player.Value.UserId)
				ButtonCloned.PlayerName.Text = player.Value.Name
				ButtonCloned.Parent = GUIofPlayers
			end
		end
		-- And iterate GUI to find which player left in Folder to delete it
		for _, guiPlayer in pairs(GUIofPlayers:GetChildren()) do
			if guiPlayer:IsA("ImageButton") then -- Im using ImageButtons for the GUI elements of players
				if not PlayersInRound:FindFirstChild(guiPlayer.Name) then
					-- Delete button in GUI that doesnt exist in folder
					guiPlayer:Destroy()
				end
			end
		end
		
	end
	wait(1)
end

Little detail about this approach would be the speed of the while loop, Im using 1 second wait for the iteration, maybe you would wish to increase it, depends on how fast you want the update to happen

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.