What would be the best-practice way to organize the contents of this script?

I tried to separate everything into chunks separated by comments… If y’all need me to explain what something does, I absolutely can.
I also left a comment near the end. (line 280)
MainLocalScript.lua (13.4 KB)
Thanks in advance :D

1 Like

Could you please add script context to post?Some people like me do use devforum on mobile and generally not everyone wants to download anything.

1 Like

It’s quite a large script, but sure:

-- game:GetService()

local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")
local ContextActionService = game:GetService("ContextActionService")

-- Replicated Storage

local ToolBarKeybindsFolder = ReplicatedStorage:WaitForChild("ToolBarKeybinds")
local PartButtonsKeybindsFolder = ReplicatedStorage:WaitForChild("PartButtonsKeybinds")
local JointButtonsKeybindsFolder = ReplicatedStorage:WaitForChild("JointButtonsKeybinds")
local ConstraintButtonsKeybindsFolder = ReplicatedStorage:WaitForChild("ConstraintButtonsKeybinds") 
local SolidModelingButtonsKeybindsFolder = ReplicatedStorage:WaitForChild("SolidModelingButtonsKeybinds")
local AdditionalSettings = ReplicatedStorage:WaitForChild("AdditionalSettings")

local DeselectAllButtonsValue = AdditionalSettings:WaitForChild("DeselectAllButtons")

-- Player and Gui

local Player = game:GetService("Players").LocalPlayer
local camera = workspace.CurrentCamera
local mouse = Player:GetMouse()

local PlayerGui = Player.PlayerGui
local PlayerScripts = Player.PlayerScripts
local ScreenGui = PlayerGui:WaitForChild("ScreenGui")

local Frame = ScreenGui.Frame

local TopButtons = Frame.TopButtons
local BottomButtons = Frame.BottomButtons

local TopMiddleBar        = TopButtons.TopMiddleBar
local TopMiddleKeybindBar = TopButtons.TopMiddleKeybindBar

local BottomMiddleBar        = BottomButtons.BottomMiddleBar
local BottomMiddleKeybindBar = BottomButtons.BottomMiddleKeybindBar

local TransformationButtons:Frame = Frame.TransformationButtons

local UseLocalDirectionButton = TransformationButtons.UseLocalDirectionButton
local UseOffsetButton         = TransformationButtons.UseOffsetButton
local AdjustOffsetButton      = TransformationButtons.AdjustOffsetButton

local PartButtonsPopUp = Frame.PartButtonsPopUp
local PartButtonsBar = PartButtonsPopUp.PartButtonsBar
local PartButtonsKeybindBar = PartButtonsPopUp.PartButtonsKeybindBar

local JointButtonsPopUp = Frame.JointButtonsPopUp
local JointButtonsBar = JointButtonsPopUp.JointButtonsBar
local JointButtonsKeybindBar = JointButtonsPopUp.JointButtonsKeybindBar

local ConstraintButtonsPopUp = Frame.ConstraintButtonsPopUp
local ConstraintButtonsBar = ConstraintButtonsPopUp.ConstraintButtonsBar
local ConstraintButtonsKeybindBar = ConstraintButtonsPopUp.ConstraintButtonsKeybindBar

local SolidModelingButtonsPopUp = Frame.SolidModelingButtonsPopUp
local SolidModelingButtonsBar = SolidModelingButtonsPopUp.SolidModelingButtonsBar
local SolidModelingButtonsKeybindBar = SolidModelingButtonsPopUp.SolidModelingButtonsKeybindBar

--local UnionButton     = SolidModelingButtonsBar.Union
--local IntersectButton = SolidModelingButtonsBar.Intersect
--local SubtractButton  = SolidModelingButtonsBar.Subtract
--local SeparateButton  = SolidModelingButtonsBar.Separate

-- Module Scripts

local CommonlyUsedVariablesModule = require(script.CommonlyUsedVariablesModule)

local ButtonSelectionFunctionsModule = require(script.ButtonSelectionFunctionsModule)
local SelectFunctionsModule = require(script.SelectFunctionsModule)
local MoveFunctionsModule = require(script.MoveFunctionsModule)
local RotateFunctionsModule = require(script.RotateFunctionsModule)
local ResizeFunctionsModule = require(script.ResizeFunctionsModule)
local PartFunctionsModule = require(script.PartFunctionsModule)
local JointFunctionsModule = require(script.JointFunctionsModule)
local ConstraintFunctionsModule = require(script.ConstraintFunctionsModule)
local WeldFunctionsModule = require(script.WeldFunctionsModule)
local SolidModelingFunctionsModule = require(script.SolidModelingFunctionsModule)

--

local debounce = false

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
-- Setup
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 

CommonlyUsedVariablesModule.init()
ButtonSelectionFunctionsModule.init()
PartFunctionsModule.init()
JointFunctionsModule.init()
ConstraintFunctionsModule.init()

-- -- -- -- -- 

local function createButtonKeybindsArray(ButtonKeybindBar, ButtonKeybindsFolder)

	local ButtonsKeybindsArray = {}

	for _, button in ButtonKeybindBar:GetChildren() do
		if button:IsA("TextButton") then
			table.insert(ButtonsKeybindsArray, button)
			button.Text = ButtonKeybindsFolder[button.Name].Value
		end
	end

	return ButtonsKeybindsArray
	
end

local function SelectSpecificPopUpButton(inputKeyCodeName, ButtonPopUp, ButtonKeybindsFolder, FunctionsModule, ButtonsOrderArray)
	if ButtonPopUp.Visible == true then
		for _, stringValue in ButtonKeybindsFolder:GetChildren() do
			if inputKeyCodeName == stringValue.Value then
				FunctionsModule.SelectSpecificButton(ButtonsOrderArray[tonumber(stringValue.Name)].Name)
				return
			end
		end
	end		
end

local function toggle(event, handler)

	local conn = nil

	local function Toggle()
		if conn == nil then
			conn = event:Connect(handler)
		else
			conn:Disconnect()
			conn = nil
		end

		return conn
	end

	return Toggle

end

-- -- -- -- -- 

local table1 = createButtonKeybindsArray(TopMiddleKeybindBar, ToolBarKeybindsFolder)
local table2 = createButtonKeybindsArray(BottomMiddleKeybindBar, ToolBarKeybindsFolder)

local ToolBarButtonsKeybindsArray = table.move(table2, 1, #table2, #table1 + 1, table1)

local PartButtonsKeybindsArray = createButtonKeybindsArray(PartButtonsKeybindBar, PartButtonsKeybindsFolder)
local JointButtonsKeybindsArray = createButtonKeybindsArray(JointButtonsKeybindBar, JointButtonsKeybindsFolder)
local ConstraintButtonsKeybindsArray = createButtonKeybindsArray(ConstraintButtonsKeybindBar, ConstraintButtonsKeybindsFolder)
local SolidModelingButtonsKeybindsArray = createButtonKeybindsArray(SolidModelingButtonsKeybindBar, SolidModelingButtonsKeybindsFolder)

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
-- Toolbar Button Selection 
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 

for _, button in CommonlyUsedVariablesModule.toolBarButtonsDictionary do -- selects the toolbar buttons when clicked
	button.MouseButton1Click:Connect(function()

		if debounce == true then
			return
		end

		debounce = true

		if button == ButtonSelectionFunctionsModule.currentToolBarButton then	
			ButtonSelectionFunctionsModule.DeselectCurrentToolBarButton()			
		else	
			ButtonSelectionFunctionsModule.DeselectCurrentToolBarButton()
			ButtonSelectionFunctionsModule.SelectToolBarButton(button)
		end

		task.wait(0.1)
		debounce = false
		return

	end)
end

local function listenToKeyPresses(input, gameProcessedEvent)
	
	if gameProcessedEvent == true or debounce == true then
		return
	end
	
	for _, stringValue in ToolBarKeybindsFolder:GetChildren() do
		if stringValue.Value == input.KeyCode.Name then

			local button = CommonlyUsedVariablesModule.toolBarButtonsDictionary[stringValue.Name]		

			if button == ButtonSelectionFunctionsModule.currentToolBarButton then
				ButtonSelectionFunctionsModule.DeselectCurrentToolBarButton()
				return
			end

			ButtonSelectionFunctionsModule.DeselectCurrentToolBarButton()
			ButtonSelectionFunctionsModule.SelectToolBarButton(button)

			return

		end
	end
	
	SelectSpecificPopUpButton(input.KeyCode.Name, PartButtonsPopUp, PartButtonsKeybindsFolder, PartFunctionsModule, CommonlyUsedVariablesModule.partButtonsOrderArray)
	SelectSpecificPopUpButton(input.KeyCode.Name, JointButtonsPopUp, JointButtonsKeybindsFolder, JointFunctionsModule, CommonlyUsedVariablesModule.jointButtonsOrderArray)
	SelectSpecificPopUpButton(input.KeyCode.Name, ConstraintButtonsPopUp, ConstraintButtonsKeybindsFolder, ConstraintFunctionsModule, CommonlyUsedVariablesModule.constraintButtonsOrderArray)
	SelectSpecificPopUpButton(input.KeyCode.Name, SolidModelingButtonsPopUp, SolidModelingButtonsKeybindsFolder, SolidModelingFunctionsModule, CommonlyUsedVariablesModule.SolidModelingButtonsOrderArray)

	if DeselectAllButtonsValue.Value == input.KeyCode.Name then
		ButtonSelectionFunctionsModule.DeselectCurrentToolBarButton()
		return
	end
	
end

local function changeKeybindWhenClicked(button:TextButton, keybindFolder) -- changes the keybinds	
	button.MouseButton1Click:Connect(function()
		toggle(UserInputService.InputBegan, listenToKeyPresses)
		button.Text = "..."
		local connection = nil
		connection = UserInputService.InputBegan:Connect(function(input, gameProcessedEvent)
			if gameProcessedEvent == true then
				return
			end
			if input.KeyCode then
				button.Text = input.KeyCode.Name
				keybindFolder[button.Name].Value = input.KeyCode.Name
				toggle(UserInputService.InputBegan, listenToKeyPresses)
				connection:Disconnect()
			end
		end)
	end)
end

for _, button in ToolBarButtonsKeybindsArray do 
	changeKeybindWhenClicked(button, ToolBarKeybindsFolder)
end

for _, button in PartButtonsKeybindsArray do
	changeKeybindWhenClicked(button, PartButtonsKeybindsFolder)
end

for _, button in JointButtonsKeybindsArray do
	changeKeybindWhenClicked(button, JointButtonsKeybindsFolder)
end

for _, button in ConstraintButtonsKeybindsArray do
	changeKeybindWhenClicked(button, ConstraintButtonsKeybindsFolder)
end

for _, button in SolidModelingButtonsKeybindsArray do
	changeKeybindWhenClicked(button, SolidModelingButtonsKeybindsFolder)
end

-- -- -- -- -- 

for button, _ in CommonlyUsedVariablesModule.transformationButtonsSelectedTable do -- selects the transformation button when clicked
	button.MouseButton1Click:Connect(function()

		if debounce == true then
			return
		end

		debounce = true

		ButtonSelectionFunctionsModule.SelectTransformationButton(button)

		task.wait(0.1)
		debounce = false
		return

	end)
end




-- also how can this be included?


--[[

local function dragAndSelectFunction(ButtonsBar, FunctionsModule, postion_config, orderArray)

	local function dragButtonsToGrid(proposedPosition, proposedRotation)
		return UDim2.fromScale(0, math.clamp(math.round(proposedPosition.Y.Scale / postion_config[1]) * postion_config[1], 0, postion_config[2])+postion_config[3]), proposedRotation
	end

	--

	local ButtonsInitialPosition = 0
	local ButtonsClick:BoolValue? = nil
	local selectedButton:TextButton? = nil

	local DragDetectorTable = {}
	local ButtonsPositionYScaleTable = {}

	for _, button:TextButton in ButtonsBar:GetChildren() do

		DragDetectorTable[button] = button:FindFirstChildOfClass("UIDragDetector")

		ButtonsPositionYScaleTable[button] = button.Position.Y.Scale

	end

	-- -- -- -- --

	for uiDragDetectorButton:TextButton, uiDragDetector:UIDragDetector in pairs(DragDetectorTable) do		

		uiDragDetectorButton.MouseEnter:Connect(function()		
			if selectedButton == nil then
				uiDragDetectorButton.BackgroundColor3 = Color3.fromRGB(200, 200, 200)
			end
		end)		

		uiDragDetectorButton.MouseLeave:Connect(function()
			if selectedButton == nil then
				uiDragDetectorButton.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
			end
		end)		

		uiDragDetector:AddConstraintFunction(1, dragButtonsToGrid)		

		uiDragDetector.DragStart:Connect(function(inputPosition)

			selectedButton = uiDragDetector.Parent

			ButtonsInitialPosition = inputPosition
			ButtonsClick = true

		end)		

		uiDragDetector.DragContinue:Connect(function(inputPosition)

			if ButtonsClick == true then
				if (ButtonsInitialPosition-inputPosition).Magnitude > 10 then
					ButtonsClick = false
				end
			end

			for button:TextButton, PositionYScale in ButtonsPositionYScaleTable do

				if math.abs(selectedButton.Position.Y.Scale-PositionYScale) < 0.0001 and button ~= selectedButton then

					local selectedButtonIndex = table.find(orderArray, selectedButton)
					orderArray[ table.find(orderArray, button) ] = selectedButton
					orderArray[selectedButtonIndex] = button

					button.Position = UDim2.fromScale(1, ButtonsPositionYScaleTable[selectedButton]) -- move the button that got stepped on into the now vacant spot

					ButtonsPositionYScaleTable[button] = button.Position.Y.Scale
					ButtonsPositionYScaleTable[selectedButton] = selectedButton.Position.Y.Scale

				end 

			end

		end)		

		uiDragDetector.DragEnd:Connect(function(inputPosition)	

			if ButtonsClick == true then
				FunctionsModule.SelectSpecificButton(selectedButton.Name) 
			end

			selectedButton = nil

		end)

	end

end

-- -- -- -- --

dragAndSelectFunction(
	JointButtonsBar, 
	JointFunctionsModule, 
	{0.2, 0.8, 0.01}, -- button spacing
	CommonlyUsedVariablesModule.jointButtonsOrderArray
)

dragAndSelectFunction(
	ConstraintButtonsBar, 
	ConstraintFunctionsModule, 
	{0.2, 0.8, 0.01}, -- button spacing
	CommonlyUsedVariablesModule.constraintButtonsOrderArray
)

dragAndSelectFunction(
	PartButtonsBar, 
	PartFunctionsModule, 
	{0.2, 0.8, 0.01}, -- button spacing 
	CommonlyUsedVariablesModule.partButtonsOrderArray
)

dragAndSelectFunction(
	SolidModelingButtonsBar, 
	SolidModelingFunctionsModule, 
	{0.25, 1, 0.015}, -- button spacing
	CommonlyUsedVariablesModule.SolidModelingButtonsOrderArray
)

]]
1 Like

To me that pretty readable already so i dunno man, i would probably move requires to be near services and move debounce variable closer to code where it is used.
You should avoid use of anonymous function if possible as since they are kinda costly for perfomance and the fact that you being able to have only 1 function being connected to multiple events instead,avoid any indexing of variables inside table/instances and instead caching the value and lastly you should consider adding typecheck and avoiding all type casting that is possible and using --!strict mode for better linting.

Could you elaborate on the third sentence? What is “anonymous function”? Could you split it up into bullet points and explain them, I’m lost lol

Anonymous functions could be useful if your function always has unique envirement (unique instance/table/user data referances)
In your code i noticed you creating function to every button press event even thought as i see all of them having same exact envirement!
Hower if it does have same envirement that eould be just waste of memory

You could fix it this way:

local function MyButton():()
--Code here
end

For loop

button.MouseButton1Click:Connect(MyButton)

end



:() in the end of function means that it returns an empty tuple (void) aka doesn’t return anything, typecheck thing

Are you talking about this part?

for _, button in ToolBarButtonsKeybindsArray do 
	changeKeybindWhenClicked(button, ToolBarKeybindsFolder)
end

for _, button in PartButtonsKeybindsArray do
	changeKeybindWhenClicked(button, PartButtonsKeybindsFolder)
end

for _, button in JointButtonsKeybindsArray do
	changeKeybindWhenClicked(button, JointButtonsKeybindsFolder)
end

for _, button in ConstraintButtonsKeybindsArray do
	changeKeybindWhenClicked(button, ConstraintButtonsKeybindsFolder)
end

for _, button in SolidModelingButtonsKeybindsArray do
	changeKeybindWhenClicked(button, SolidModelingButtonsKeybindsFolder)
end

No im talking about that…symbol limt

The script itself is already very readable and organized, mainly about the Module Scripts. However, you have a lot of for loops that make similar things. What I mean is this part:

As you can see, there are several similar for loops; the only things that distinguish them are the array and folder. For example:

for _, button in ConstraintButtonsKeybindsArray do
	changeKeybindWhenClicked(button, ConstraintButtonsKeybindsFolder)
    -- Loop running through array constraint buttons keybinds, and calling function with respective folder
end

for _, button in SolidModelingButtonsKeybindsArray do
	changeKeybindWhenClicked(button, SolidModelingButtonsKeybindsFolder)
    -- Loop running through array solid modeling button keybinds, and calling function with respective folder.
end

By that, you could store those in a table, called “arrays,” for example. You iterate through this table and then do everything you need:

local arrays = { --> you can make it with string or number indexes, you choose
  SolidModelingButtonsKeybinds = {SolidModelingButtonsKeybindsArray, SolidModelingButtonsKeybindsFolder},
  ConstraintButtonsKeybinds = {ConstraintButtonsKeybindsArray, ConstraintButtonsKeybindsFolder},
  -- The remaining ones you implement above
}

-- Then here you iterate through then:

for _, array in arrays do
    local childArray, folder = table.unpack(array)

    for _, button in childArray do
        changeKeybindWhenClicked(button, folder)
    end
end

Way cleaner, isn’t it?

okay, interesting. How would that work?

How would I combine

for button, _ in CommonlyUsedVariablesModule.transformationButtonsSelectedTable do -- selects the transformation button when clicked
	button.MouseButton1Click:Connect(function()

		if debounce == true then
			return
		end

		debounce = true

		ButtonSelectionFunctionsModule.SelectTransformationButton(button)

		task.wait(0.1)
		debounce = false
		return

	end)
end

and

for _, button in ToolBarButtonsKeybindsArray do 
	changeKeybindWhenClicked(button, ToolBarKeybindsFolder)
end

for _, button in PartButtonsKeybindsArray do
	changeKeybindWhenClicked(button, PartButtonsKeybindsFolder)
end

for _, button in JointButtonsKeybindsArray do
	changeKeybindWhenClicked(button, JointButtonsKeybindsFolder)
end

for _, button in ConstraintButtonsKeybindsArray do
	changeKeybindWhenClicked(button, ConstraintButtonsKeybindsFolder)
end

for _, button in SolidModelingButtonsKeybindsArray do
	changeKeybindWhenClicked(button, SolidModelingButtonsKeybindsFolder)
end

You could change the anonymous function by creating a named function in your script:

local function clicked(button)
    if debounce then return end

    debounce = true
    
    ButtonSelectionFunctionsModule.SelectTransformationButton(button)

    task.wait(.1)
    debounce = false
end

EDIT: Then you replace the anonymous function inside Clicked event and put clicked(button).

Person above already pointed about 2nd snippet

local ButtonSelectionFunctionsModule_SelectTransformationButton = ButtonSelectionFunctionsModule.SelectTransformationButton
local button_clicked(button:TextButton):()
		if debounce == true then return end
		debounce = true

		ButtonSelectionFunctionsModule_SelectTransformationButton(button)

		task.wait(0.1)
		debounce = false
	end
end


for button, _ in CommonlyUsedVariablesModule.transformationButtonsSelectedTable do -- selects the transformation button when clicked
	button.MouseButton1Click:Connect(function():()
		button_clicked(button)
	end)
end

Way faster and cleaner,right?
Edit yeah i see a problem its fixed now

1 Like

So, something like this?

Edit: It now makes total sense :D

Okay I get what you’re saying… basically bring the function from inside the :Connect() to the outside.

Do I do the same thing with the “changeKeybindWhenClicked” function?

But this also makes my code longer… and I have to think of a name for the new function lol

Additionally, I don’t know if it is just me, but I think you should add some spaces accordingly, so the code doesn’t feel “sticky” in lines. If you don’t get what I mean, example:

There are no “break lines” to separate similar parts of the script. Personally, I put “break lines” between if statements and the rest of the code, or for example, variables, functions… It makes the code more readable. I would format this part like this:

button.MouseButton1Click:Connect(function()
		local connection

		toggle(UserInputService.InputBegan, listenToKeyPresses)
		button.Text = "..."

		connection = UserInputService.InputBegan:Connect(function(input, gameProcessedEvent)
			if gameProcessedEvent then return end

			if input.KeyCode then
				button.Text = input.KeyCode.Name
				keybindFolder[button.Name].Value = input.KeyCode.Name

				toggle(UserInputService.InputBegan, listenToKeyPresses)

				connection:Disconnect()
			end
		end)
	end)

Don’t worry about perfomance of this btw
Script anslisis will unwrap function to be directly inside connect instesd of function call
Also im not sure hoe you made that function so i cannot say anything for sure

By the way, it is not needed to write if something is existent like that: if x == true then, because this is the same as if x then. In my opinion, the second case of checking for the existence of something is more readable, although it doesn’t change anything. It won’t make the code faster or slower.

Yep, I agree, I’ll do that :D

1 Like

I agree with that too, but I cannot force myself to read them the same way. For some strange reason, there’s a strong feeling of uncertainty when I do if x then instead of if x == true then :C

1 Like

Alright, I’ll try not to :sweat_smile: