Intro to UI Programming (& Controlling UI From the Server)

Video Version

Text Version

UI programming is one of those things that has some weird unintuitive quirks to it, but once you learn those quirks you can work with them fairly quickly. Today I’ll walk you through how to create UIs and control them with code, as well as how to properly change client UI from the server.

Contents

ResetOnSpawn

ScreenGui objects have one very important property that we’ll want to change: ResetOnSpawn. With the default true value, the GUI will get completely reset whenever the player respawns. There are cases in which this is useful, but if we have a script running and suddenly all of the UI elements get replaced and all of our connections and references are now nil, the script is going to throw an error real quick. For our use case, we’ll want this to be false.
PropertiesEdit

StarterGui vs. PlayerGui

Now, if you try to change a UI element property in StarterGui like you would some part in workspace, you’ll quickly find out that it doesn’t work for some reason. Why? Well, StarterGui isn’t actually what the player sees on their screen. Rather, it’s a template that players receive snapshots of when they join the game, and they can then the client code can control and change that snapshot. If you keep changing the template, StarterGui, that won’t change the snapshots–the player would need to come back and take new ones. These “snapshots” are saved in PlayerGui, a child of the Player object. The proper way to change UI properties is like so:

game.Players.LocalPlayer:WaitForChild("PlayerGui"):WaitForChild("ScreenGui"):WaitForChild("TextLabel").Text="yeehaw v2"

WaitForChild

In that code example, you may notice a large amount of WaitForChild calls. Why not just reference the objects directly? Thing is, client scripts can execute before the player’s components are finished replicating, meaning you can’t always expect UI components to be in place at runtime. In cases like this, WaitForChild is your friend. However, once you’ve typed it out 50 times, it becomes tedious. You can reduce the amount of typing by saving references to these objects in variables, but that only helps so much. I have a WaitForPath function available to make things simpler.

Changing UI From the Server


Here’s a seemingly correct example of showing a message to all clients from the server:
local message="asfugnaw9dfgndo9fgidogmo"
for i,v in pairs(game.Players:GetPlayers()) do
	v:WaitForChild("PlayerGui"):WaitForChild("ScreenGui"):WaitForChild("TextLabel").Text=message
end

However, directly changing player UI from the server is by far the worst way to go about this. There are dozens of cases in which this example could fail. Expecting all the clients to have decent connection speed and completely loaded UI is like having 5 unemployed roommates and expecting all of them to pay their share of the rent on time when your name’s on the lease, and also one of them takes two weeks to respond to your texts. It ain’t happening, basically.

There’s other issues with this too, like since the loop yields to wait for each client to be loaded, it’s very possible that a client can leave while the loop is executing, so when it gets to them, it breaks the script. Also, client-side UI changes don’t replicate back to the server, so if the server opens a menu for the player and there’s a client-side script controlling the close button, the server won’t be able to open the menu again because it will eternally perceive it as already open.

Yes, it’s technically possible to circumvent all of this stuff with pcalls and coroutines and sanity checks out the wazoo, but the amount of effort it takes to implement all that stuff is significantly less than the effort it would take to learn the proper way to do it.

Remotes!

Learning remotes may seem daunting at first glance, but it’s actually no more difficult than any other API. Since we’re just trying to send a message, and we don’t need to get anything back, we can use a RemoteEvent. The function we’ll be using is FireAllClients, which allows us to fire the event for every player at the same time–no loops required! If we wanted to send the message to only one player, we could use FireClient, and if we wanted to send a message from the client to the server, we could use FireServer.

We first need to add a remote. In my opinion, ReplicatedStorage is the best place to store remotes, since it’s visible to both the client and the server. Technically, you could store them in the Player object, but ReplicatedStorage is much easier to access.
image

On the server, using the remote is as simple as calling the FireAllClients function, passing through our message. Look at how clean this is.

local message="asfugnaw9dfgndo9fgidogmo"
game.ReplicatedStorage.MessageBroadcast:FireAllClients(message)

On the client, we’ll connect to OnClientEvent like we would any other event. We’ll receive the message and drop that into the text property of our label.

function messageReceived(message)
	game.Players.LocalPlayer:WaitForChild("PlayerGui"):WaitForChild("ScreenGui"):WaitForChild("TextLabel").Text=message
end
game.ReplicatedStorage.MessageBroadcast.OnClientEvent:Connect(messageReceived)

It’s that easy! We now have a significantly more stable system, and it only took a tiny bit more work.

RemoteFunctions

Haven’t had enough yet? Let’s talk about RemoteFunctions. A RemoteFunction is like a RemoteEvent, except it’s actually designed to get a response from whatever receives the invocation. This does mean there are some limitations, including:

  • The script invoking the function will yield until a result is returned
  • Only one script can be listening to the remote at a time on either end
  • You can’t invoke it for all clients at once

Actually, you just shouldn’t invoke it for clients at all. Similar to our bad way of changing UI from the server, invoking the client with a RemoteFunction is dependent on them being able to receive the call and return the correct result, which is never 100% guaranteed. If you need to get client data, just have it send the data through a RemoteEvent every once in a while, or if you need it at specific times, have the server ping the client with a RemoteEvent and have the client set up to send the data back through the RemoteEvent when pinged.

image
Again, we need to drop a RemoteFunction into ReplicatedStorage. On the client, we can just call the InvokeServer function, saving the result to a variable, and then setting the result as the TextLabel's text:

local message=game.ReplicatedStorage.GetMessage:InvokeServer()
game.Players.LocalPlayer:WaitForChild("PlayerGui"):WaitForChild("ScreenGui"):WaitForChild("TextLabel").Text=message

The tricky part is on the server: instead of connecting to some kind of event, we’re going to set the OnServerInvoke callback like it’s a property, providing a function to run when the server is invoked. The function technically receives a player argument, but we don’t need that in our case.

local message="asfugnaw9dfgndo9fgidogmo"
game.ReplicatedStorage.GetMessage.OnServerInvoke=function()
	return message
end

Congratulations, you know how to program UI on Roblox! And once you discover Roact, you’ll have to learn it all over again. :stuck_out_tongue:

18 Likes

Oh my gosh this was exactly what I needed! Thank you so much! This is a great tutorial :smile:

Good tutorial, it’s funny hearing it as “gooey” instead of " Gee You Eye"

2 Likes

Nice tutorial I have to say, the explanaition was really good and all was clarified, I hope to see more tutorials from you on the future, also, I didn’t read the guide I just saw the video, haha.

Anyway, I have to say that you teached your audience to actually use the RemoteEvents and RemoteFunctions (not all the stuff, but the basic introduction) it was really awesome to see, to be honest I wasn’t expecting such a good tutorial.

Keep the hard work. :wink:

Edit: I need to learn more English tbh.

1 Like