There is a reason why one would put a script in the respective UI than in PlayerScripts. ScreenGUI’s by default have the property ResetOnSpawn set to true, this means that a whole new copy of the UI is made when the player’s character is added (on server join or respawn), and the old one is destroyed. On the other hand, a script in PlayerScripts just stays there forever and is not impacted by deaths.
So if you reference to a ScreenGUI that resets on spawn from a player script, you are supposed to update the variable each time the character respawns. Otherwise, when you want to change something to any of the UI’s descendants in the future, said object will have been destroyed.
My conclusion is therefore that scripts that handle UI’s should be parented in the respective ScreenGUI, to account for the ResetOnSpawn behavior.
An example:
You have a simple health count UI, a ScreenGUI which by default has ResetOnSpawn set to true, which contains one TextLabel representing the health.
The script behind its functionality lies in PlayerScripts, and contains the following code:
local plr = game:GetService("Players").LocalPlayer
local ui = plr.PlayerGui.Health
local hpText = ui.TextLabel
while task.wait() do
hpText.Text = plr.Character.Humanoid.Health
end
Every frame, we refer to an object in the ScreenGUI and change its text property - there are obviously better ways of doing this but for the sake of the example we do it like this.
Suddenly, the player dies. A few seconds pass by, and their character gets re-added; the script throws an error: something along the lines of “attempt to index Text with nil”, this means that our variable hpText
is a nil value, the object it references to has been destroyed.
To fix this issue:
local plr = game:GetService("Players").LocalPlayer
local ui = plr.PlayerGui.Health
local hpText = ui.TextLabel
plr.CharacterAdded:Connect(function(_)
ui = plr.PlayerGui.Health
hpText = ui.TextLabel
end)
while task.wait() do
hpText.Text = plr.Character.Humanoid.Health
end
We update the variables to new ones. This obviously is an overcomplicated fix for an issue that is easily prevented by just placing the script within the UI; on player respawn, the script itself is destroyed too and a new one is made.
Edit: you might also need to use WaitForChild or a repeat loop until PlayerGui.Health is found, since the CharacterAdded call happens before the UI’s themselves are made.
Hopefully my explanation isn’t too confusing. If I am saying wrong things, do let me know.