Properly scripting UI buttons, the orderly way

A few years ago, I would go and put a localscript in every button and not really think about how hard this was being on me. Not only was the workspace cluttered with all the TextButtons opened, but there were A LOT of scripts. This method only uses 2; and no descendants in the TextButtons.

Firstly, we need to exploit a very useful feature, ModuleScripts. My explanation of them will be brief, since this isn’t about modulescripts, but, you can read more about them here.

Modulescripts can hold functions and variables that can be used by more then one script. Using a modulescript here is not required, you can just put all the functions in the localscript we are working on,
but it looks much nicer and is easier to work with.

Lets say we have a StarterGui that looks like this:

image

Since my module will likely be indexed by more then one script, I will put it inside StarterGui itself, and name it UIModule. This enables easier access through all localscripts in StarterGui, let it be more then 1.

We could put this localscript anywhere, but since the buttons are located inside TopMenuBar, thats where I will put it.

Inside that localscript I have a for looping through the children of TopMenuBar, and if its a TextButton, it attaches this function to it:

local UIModule = require(script.Parent.Parent.Parent:WaitForChild("UIModule"))
v.MouseButton1Click:Connect(function()
	if UIModule[v.Name] then
		UIModule[v.Name]()
	else
		warn("No function for TextButton..".. v.Name)
	end
end)

Inside the module, we have functions named after each button where if the name matches the function, it attaches that function to the TextButton’s MouseButton1Click event, executed the function whenever the button is clicked.

This is what it looks like:

In localscript:

local UIModule = require(script.Parent.Parent.Parent:WaitForChild("UIModule"))

for _,Button in pairs(script.Parent:GetChildren())do
	if Button:IsA("TextButton")then
		Button.MouseButton1Click:Connect(function()
			if UIModule[Button.Name] then
				UIModule[Button.Name]()
			else
				warn("No function for TextButton..".. Button.Name)
			end
		end)
	end
end

in module:

local module = {}
module.Spawn = function()
	print("Player Spawned!")
end
module.Factions = function()
	print("Factions Opened!")
end
module.Settings = function()
	print("Settings Opened!")
end
module.Stats = function()
	print("Stats Opened!")
end
return module

This is the result:


Note: the hover-over effect is not included within the code for is it off-topic.

This is my first guide, and it covers a simple topic. Any feedback is well appreciated.

21 Likes

Nice tutorial, this is great for beginners who havent ascended started using technologies like Roact.

Worth mentioning that for arrays you should use ipairs, you can use _ to ignore variables when they’re not required (like the index/i variable in your example). You should also consider giving your variables clear names; v or uimod are not immediately obvious, better names would be button or UIModule.

2 Likes

Editted, thank you for your response.

1 Like

I personally recommend just making a module that does the animations, etc.

Something like this:

local module = {}

function module.register(obj)
    if obj then
        local event = Instance.new("BindableEvent")

        obj.InputBegan:Connect(function(input)
            if input.UserInputType = Enum.UserInputType.MouseButton1 or input.UserInputType = Enum.UserInputType.Touch then
                -- do effects
            end 
        end)

        obj.InputEnded:Connect(function(input)
            if input.UserInputType = Enum.UserInputType.MouseButton1 or input.UserInputType = Enum.UserInputType.Touch then
                event:Fire()
            end 
        end)

        return event.Event
    end
end

return module

which, to connect it, simply do

local ButtonXClicked = module.register(PathToButtonX)

ButtonXClicked:Connect(function()
       -- callback goes here
end)
1 Like

you could just do this

local Buttons = {
	[Frame.ButtonName] = function()
		
	end,
	[Frame.ButtonName] = function()
		
	end,
	[Frame.ButtonName] = function()
		
	end,
	[Frame.ButtonName] = function()
		
	end,
}

for k,v in pairs(Buttons) do -- k: Button, v: function
	k.MouseButton1Down:Connect(v)
end

it’s a lot better and straight forward

2 Likes

I personally just use 1 LocalScript under the ScreenGui, and then a seperate module script located under each main frame. Have the LocalScript just require all the modules, and then each module just sticks to its own frames contents. Any repeating stuff like button animations, etc. I have a completely seperate module stored in like RepStorage.