Why do you have to use or is a good to use a remote event in this scenario?

I am still a newbie and trying to learn remote events.

So basically, I created a game with a textbutton that you can click and when you click it, it removes 200 cash from your cash value which is in leaderstats and a knife gets added to your backpack.

Local script:

local SG = script.Parent
local TB = SG.TextButton

TB.Activated:Connect(function()
	game.ReplicatedStorage.RemoteEvent:FireServer()
end)

Serverscript:

local players = game:GetService("Players")
local knife = game.Workspace:WaitForChild("Knife")

players.PlayerAdded:Connect(function(plr)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = plr
	
	local cash = Instance.new("IntValue")
	cash.Name = "Cash"
	cash.Parent = leaderstats
	cash.Value = 500
	
	game.ReplicatedStorage.RemoteEvent.OnServerEvent:Connect(function(plr)
		if cash.Value >= 200 then
			local knife2 = knife:clone()
			knife2.Parent = plr.Backpack
		end
	end)
end)

So, I understand why this works but why can’t you just in the serverscript in the playeradded event, create a variable for the textbutton and then check from there if the textbutton is activated then add the item to the player backpack.

I have a few predictions on why this won’t work such as like a knife will be parented to all the players instead of the one single player but I don’t think that is true or like something related to like exploiters.

Thank you! :slightly_smiling_face:

Having this OnServerEvent each time a player joins is unoptimal. One connection is enough.

2 Likes

@SomeFedoraGuy is correct and you should follow his advice.

but why can’t you just in the serverscript in the playeradded event, create a variable for the textbutton and then check from there if the textbutton is activated then add the item to the player backpack

You are technically correct, but it’s bad in practice for 2 main reasons:

  1. You are relying on the server to wait for the children of StarterGui to clone to the player’s PlayerGui.

If you didn’t know, if a player’s Character spawns, every children under the player’s PlayerGui (player.PlayerGui) will destroy itself, and every children under StarterGui will (re)clone inside the player’s PlayerGui unless it is a ScreenGui with the ResetOnSpawn set to false. In this case, it will only clone the first time and wont destroy itself and it’s children.

Because of this, you are forced to wait for these children on CharacterAdded and have to do workarounds.

local function bindScreenGui(player, screenGui)
	local imageButton = screenGui:WaitForChild("ImageButton")
	imageButton.MouseButton1Click:Connect(function()
		print(player, "clicked the ImageButton")
	end)
end

game:GetService("Players").PlayerAdded:Connect(function(player)
	-- Wait for the player's character to spawn 
	player.CharacterAdded:Connect(function(character)
		-- Make sure the PlayerGui has loaded and parented
		local playerGui = player:WaitForChild("PlayerGui")
		-- Check if the ScreenGui exists when the player's character spawns
		local screenGui = playerGui:FindFirstChild("ScreenGui")
		if screenGui == nil then
			-- If not, wait for the ScreenGui to spawn
			local screenGuiConnection
			screenGuiConnection = playerGui.ChildAdded:Connect(function(child)
				if child.Name == "ScreenGui" then
					screenGuiConnection:Disconnect()
					-- Bind the screen gui
					bindScreenGui(player, screenGui)
				end
			end)
		else
			-- If so, bind the screen gui
			bindScreenGui(player, screenGui)
		end
	end)
end)

Instead of doing this hacky method, let the LocalScript communicate with the server instead using the normal Remotes.

  1. Dynamically changing Gui is trickier and, in some cases, near impossible to code if you are doing it purely server-side.

You should be modifying the player’s Guis locally. Not only is it easier because you technically don’t have to rely on the server (unless updating information), you also reduce load on the server that should really only be handling updating any important game and data information.

An example of this is an in-game shop menu with different vendors. Say whenever you are near an NPC and hit the key “Q”, you want to update the shop frame to match what the vendor sells. Under the hood, you will need to delete every existing item button and create new item buttons for every item the vendor sells, then make the shop frame visible.

Already, you cannot get the player’s input using UserInputService/ContextActionService on the server alone, you will need to check that in the client and pass it onto the server using a Remote.

Assuming you do the shop frame updating on the server: Bad practice, as I’ve mentioned that the server should really only be handling important stuff to save resources. Imagine handling 100 players with 20 different distinct Gui, you can see how painful that is for you to code and for the server to handle.

Assuming you do the shop frame on the client: Better, but remember that changing things on the client alone does not replicate on the server. In this case, the server wont see any updates to your Guis anyways, making it impossible for the server to connect any events to dynamically changing Guis.

Hope this helps!

2 Likes

Some of that I didn’t understand but from what I understood from your reply, I am getting at that, if I use my method, it will take way more time than if I use a remote event (what do you mean when the player’s character spawns that every child under the PlayerGui will destroy itself and every child in startergui will reclone in playergui) and that GUI coding should mostly be done locally

Not only that, if you want to update your GUI like different things sell at different times, the server will have to delete and create things and doing this on many players will make the server handle less?

1 Like

it will take way more time than if I use a remote event

Pretty much, yes.

what do you mean when the player’s character spawns that every child under the PlayerGui will destroy itself and every child in startergui will reclone in playergui

Let’s do a couple of exercises to get you familiar with the behavior.
Go to a new baseplate on Studio and create 2 ScreenGuis and name them “ScreenGuiA” and “ScreenGuiB”. Inside both of those, create a TextLabel with the text “A” and “B” respectively and position them differently so you can identify which is which. You should get something like this

For ScreenGuiA, set the ResetOnSpawn property to false.
For ScreenGuiB, set the ResetOnSpawn property to true.

For this first exercise, let’s go to Players service on the Explorer and set the CharacterAutoLoads property set to false:

Let’s hit play (F5). If you notice, the ScreenGui wont get cloned, meaning it does not exist under Player.PlayerGui, only the “Freecam” created by the hidden CoreGui (see explorer) because our character is not loading.

Let’s stop the test and set the CharacterAutoLoads back to true. When you hit play again, you will then see the Guis get cloned.

Do not stop the test, as we’ll be going to exercise 2.

For this exercise, while you are in test mode, let’s change both TextLabel’s BackgroundColor3 to any color we want in the explorer and properties window. Remember, don’t change the elements under StarterGui. Instead, change the elements under the player’s PlayerGui. I made mine red in this example:

Now go ESC → Reset (do not stop the test play).

You’ll notice that the ScreenGuiA’s (the one with ResetOnSpawn property set to false) TextLabel’s BackgroundColor3 is still red. This is because this ScreenGui was never destroyed in the first place.

But ScreenGuiB on the other hand (the one with ResetOnSpawn property set to true), it’s TextLabel’s BackgroundColor3 turned white. This is because the old ScreenGuiB got destroyed and got recloned from StarterGui.

This is the behavior of StarterGui and PlayerGui. Any non-ScreenGuis directly under StarterGui or ScreenGuis with it’s ResetOnProperty set to true will get destroyed from the player’s PlayerGui and recloned every time the player’s character spawns.

2 Likes

Continuing on,

and that GUI coding should mostly be done locally

It should 100% be done locally. If you need information from the server, use Remotes to request that information. If you want to listen for information from the server, use Remotes to listen to that information.

Not only that, if you want to update your GUI like different things sell at different times, the server will have to delete and create things and doing this on many players will make the server handle less?

Don’t do it on the server. As mentioned above, it should 100% be done locally. The server has it’s own hardware, and clients have their own: their computer. Dedicate the client’s computer to update their own guis.

1 Like

I understand now. I didn’t think you would actually take the time to do that but thank you very much!

So basically, to sum up what you explained is that if the ResetOnProperty is set to true it will be recloned every time the character spawns and if it is set to false, it will just be the same screengui and will never be recloned.

2 Likes

That is correct.

And remember, any non-ScreenGuis (LocalScripts, Folders, etc.) will also automatically get recloned if it’s directly under StarterGui.

2 Likes

Is it better to have this property on true or on false?

1 Like

By direct, do you mean like the immediate child like

StarterGui
   ScreenGui
      Frame

or like everything under StarterGui or the ScreenGui?

1 Like

This is totally dependent on what you want to achieve. Say, for example, you want to code a health bar. It is likely better to set ResetOnSpawn to true, that way your health bar gets completely reset back to it’s original state and the script will re-run to a clean state.

Example:

local Players = game:GetService("Players")

local Player = Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local Humanoid = Character:WaitForChild("Humanoid")

print("Original health: " ..Humanoid.Health)
Humanoid.HealthChanged:Connect(function()
	print("New health: " ..Humanoid.Health)
	-- Update health bar
end)

This will run just fine with no issues under a ScreenGui with ResetOnSpawn set to true.

However, it will not be the case with ResetOnSpawn set to false. It will work the first time around, but what if the player dies and respawns? Both Humanoid and Character variable will be nil the second time around, because the old humanoid is under the old character which is gone.

Say you wanted to make a custom player list. You can totally have the ResetOnSpawn set to false since you are not really needing to reset the gui whenever the player spawns anyways.

The advantage of ResetOnSpawn set to false in this case is that:
You don’t need to recreate each frame for each player.
You don’t need to recreate connections to receive data for each player.
Everything will just be running in the background without interruptions.

By direct, do you mean like the immediate child

Example:

StarterGui
    LocalScript -- resets
    ScreenGui -- ResetOnSpawn = true, resets
        TextLabel -- resets
        LocalScript -- resets
        Frame -- resets
            ImageLabel -- resets
    ScreenGui -- ResetOnSpawn = false, does not reset
        Folder -- does not reset
            IntValue -- does not reset
        LocalScript -- does not reset
    Folder -- resets
2 Likes

Thank you very much for the help as I understand well now.

Also, with the script, I provided in my topic, how can I make it so there is only one connection like @SomeFedoraGuy said.

2 Likes
-- For good practice, capitalize your services
local Players = game:GetService("Players")
-- For good practice, put all of your services in variables
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- For good practice, put all your reusable assets somewhere safe,
-- like ServerStorage where it is inaccessible by all clients
local ServerStorage = game:GetService("ServerStorage")
local knife = ServerStorage:WaitForChild("Knife")

-- For good practice, spell out your variables
Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"

	local cash = Instance.new("IntValue")
	cash.Name = "Cash"
	cash.Value = 500
	-- For good practice, parent your cash IntValue Folder AFTER you set your properties
	-- Read: https://create.roblox.com/docs/reference/engine/classes/Instance#Parent
	-- "When creating an object then setting many properties, it's recommended to set Parent last.
	-- This ensures the object replicates once, instead of replicating many property changes"
	cash.Parent = leaderstats
	
	-- For good practice, parent your leaderstats Folder AFTER you create all your values
	-- Same reason as above
	leaderstats.Parent = player
end)

-- Instead, put it outside of your PlayerAdded event.
-- You do not want to be reconnecting OnServerEvent
-- everytime a player joins. One is enough.
ReplicatedStorage.RemoteEvent.OnServerEvent:Connect(function(player)
	local leaderstats = player:FindFirstChild("leaderstats")
	-- Check if leaderstats exist. If not, drop the request.
	if leaderstats == nil then
		return
	end
	
	local cash = leaderstats:FindFirstChild("Cash")
	-- Check if cash exists. If not, drop the request.
	if cash == nil then
		return
	end
	
	if cash.Value >= 200 then
		local knifeCloned = knife:clone()
		knifeCloned.Parent = player.Backpack
	end
end)

Read the comments for good coding practices.

2 Likes

What happens when it returns? Does it try again or try to find a leaderstats variable again?

1 Like

No, return will just stop the current function scope. Technically those are just sanity checks and you don’t really need them, but it’s good practice to do security and/or sanity checks in your functions/events that the player interacts with on the server in case of exploits and/or rare edge cases.

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