3. SettingsButton¶
Prerequisite
- Installed Roblox Studio
- Familiar with OOP in Lua
- Familiar with the Client-Server model
- Familiar with Roblox script containers
Overview¶
We want to create a simple Settings menu with different pages but Roblox only has some basic GUI classes to work with. We will use OOP to design reusable UI components for our settings menu. Firstly, we will focus on designing a single SettingsButton class which contains a description of a setting and a display of the current option.
Tasks¶
Task 1: Setting up ScreenGui
and Frame
¶
Before we can start displaying any GuiObject on the screen, we need to set up a container for all GuiObjects we want to render for the player.
Unlike the custom pseudoclasses we have written in Lua so far, Roblox Engine Classes which are all subclasses of Instance must be constructed with the Instance.new(className)
constructor.
For example, here we create a Part, make it transparent and add it to the workspace:
local part = Instance.new("Part") -- ref to an unrendered part (parent = nil)
part.Transparency = 0.5
part.Parent = game.Workspace -- set parent last as an optimization to not render early
In the case of GUIs, GuiObjects
only render if they are somewhere under a ScreenGui
object which is placed in the players PlayerGui
folder. Typically, we will also have a Frame
under the ScreenGui
which is a container for related GuiObjects
.
Write a LocalScript under either the ReplicatedFirst or StarterGui folders which creates the necessary hierarchy of ScreenGui
and Frame
such that new GuiObjects
such as TextLabel
can be rendered.
Recall that we can get a reference to the players PlayerGui
folder like so:
local playerGui = game.Players.LocalPlayer.PlayerGui
local playerGui = script.Parent -- only works if the script is in the StarterGui folder
Remember to also adjust the Frame
fields so that it takes up the whole screen (or however much of the screen as you want) for us to test our future SettingsButton
.
Task 2: SettingsButton
constructor¶
Now we are finally ready to create our SettingsButton
class. Note that in the image example, we have two types of settings buttons: one that changes between several options (e.g. View Distance changing between Low, Medium High) and another that runs some function (e.g. Save Progress saving to the database). For simplicity sake, we will only write the latter for now.
We should use composition in this implementation with these suggested fields:
self.label
(TextLabel
containing the setting name)self.icon
(ImageLabel
containing the setting icon)self.button
(ImageButton
which is the parent oflabel
andicon
)self.callback
(function
that we want to call on button press)
We should be able to create a SettingsButton
like so:
local frame = ... --- parent frame
local saveData = function() print("Saving...") end
local saveIcon = "rbxassetid://12967618029"
local settingsButton = SettingsButton.new("Save Progress", saveIcon, saveData, frame)
Task 3: Connecting callbacks¶
Roblox follows event-based programming which means that most, if not all Instance
come with events which we can attach callback functions to. For our case, we want to attach our callback to the Activated
event on ImageButton
.
For example, this Part
calls a function when touched (i.e. the Touched
event is fired).
local part = game.Workspace.Part1 -- some arbitrary part
local function hello()
print("Hello world!")
end
part.Touched:Connect(hello)
Task 4: Destroy()
cleanup¶
We should also implement a Destroy()
method to delete all instances and disconnect all events.
For example, this is how we destroy a Box
containing many Part
s
...
function Box.new()
local self = setmetatable({}, Box)
self.partOne = Instance.new("Part")
self.partTwo = Instance.new("Part")
return self
end
function Box:Destroy()
self.partOne:Destroy()
self.partTwo:Destroy()
setmetatable(self, nil) -- break linkage to metatable
end
...
If we create connections, we may also want to explictly disconnect them.
Solution¶
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local SettingsButton = require(ReplicatedStorage.Modules.SettingsButton)
local player = game.Players.LocalPlayer
-- Create GUI and Frame containers
local gui = Instance.new("ScreenGui")
gui.Parent = player:WaitForChild("PlayerGui")
local frame = Instance.new("Frame")
frame.Size = UDim2.new(1, 0, 1, 0)
frame.Transparency = 1
frame.Parent = gui
-- Create SettingsButton instance
local saveData = function()
print("Saving...")
end
local saveIcon = "rbxassetid://12345678"
local settingsButton = SettingsButton.new("Save Progress", saveIcon, saveData, frame)
local SettingsButton = {}
SettingsButton.__index = SettingsButton
function SettingsButton.new(text: string, image: string, callback: () -> (), parent: Instance)
local self = setmetatable({}, SettingsButton)
self.callback = callback
local imageButton = Instance.new("ImageButton") :: ImageButton
imageButton.Size = UDim2.new(0.4, 0, 0.2, 0)
imageButton.AnchorPoint = Vector2.new(0.5, 0.5)
imageButton.Position = UDim2.new(0.5, 0, 0.5, 0)
imageButton.BackgroundColor3 = Color3.fromRGB(200, 200, 200)
imageButton.Activated:Connect(callback)
self.button = imageButton
local imageLabel: ImageLabel = Instance.new("ImageLabel")
imageLabel.Image = image
imageLabel.Parent = imageButton
imageLabel.Size = UDim2.new(0, 20, 0, 20)
imageLabel.AnchorPoint = Vector2.new(0, 0.5)
imageLabel.Position = UDim2.new(0.8, 0, 0.5, 0)
self.icon = imageLabel
local textLabel: TextLabel = Instance.new("TextLabel")
textLabel.Text = text
textLabel.Parent = imageButton
textLabel.AnchorPoint = Vector2.new(0, 0.5)
textLabel.Position = UDim2.new(0.2, 0, 0.5, 0)
self.label = textLabel
self.button.Parent = parent
return self
end
function SettingsButton:Destroy()
self.label:Destroy()
self.icon:Destroy()
self.button:Destroy()
setmetatable(self, nil)
end
return SettingsButton
This is not an ideal solution in the real world, but it suffices to show the application of classes for UI components.
Extra¶
Why not try adding a header to the settings page using a TextLabel
, or a ScrollingFrame
under the main container so that we can have a long list of SettingsButton
. Why not add more event connections like an onHover effect to make it more responsive? There are many different objects and constraints available to allow you to style and customize the UI. Read the API and experiment.
Notice that for this example, we don't actually need any methods since we can use only the single constructor. Wouldn't it be wiser to just write this as a function as part of a SettingsButton
module? Notice that classes only become really useful when we have to manage state as well as organise our code.
In an actual game, we might have many different types of settings options such as sliders and dropdown menus. In that case, we would write a general "abstract" class/local function which is used by concrete subclasses/ exported functions which we use in our menus. Try writing such an implementation.