Skip to content

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.

Settings

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 of label and icon)
  • 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 Parts

...
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.