Should I be making a module for main menu?

Hi, I’m making a main menu and I’m wondering if I should use a module for main menus since there will be a way to access the main menu once u play the game. Or should I utilize using local scripts and modules.

The reason I am asking this is I already started on it and it works but I can’t tween regularly so I’m feeling that I should add a local script to the main menu thats being parented to the player.

Ongoing Module Script

--] Variables
local Menu = {}
Menu.__index = Menu

function Menu.new(player,ui)
	local self = setmetatable({},Menu)
	self.Player = player
	self.Instance = ui
	return self
end

function Menu:Initilize()
	print("INITILIZED: Main Menu | Player: "..self.Player.Name)
	self.Instance:Clone().Parent = self.Player.PlayerGui
	Menu:ButtonClicked(self.Instance.Frame.ButtonGrid)
end

function Menu:ButtonClicked(Frame)
	for _,button in ipairs(Frame:GetChildren()) do
		if button:IsA("ImageButton") then
			button.MouseButton1Click:Connect(function()
				Menu:ButtonFunctionality(button)
			end)
		end
	end
end

function Menu:ButtonFunctionality(Button)
	print(Button.Name)
	if Button:GetAttribute("DeleteOnClick") then
		Menu:Destroy(Button.Parent.Parent.Parent)
	end
end

function Menu:Destroy(instance)
	instance:Destroy()
end

return Menu

It’s really up to you but I’ll share how I would do it personally.

I would probably make a class for the individual components of your main menu, like custom button controllers, custom UI objects etc, not just have a whole class for an object that will only exist one time.

For example, this is our class library, there will (I think) always be more than one of these at any given time, with some exceptions like _types and UtilityObjects which is just a utility module.
image

Particular to the module I try to use events whenever possible. For example in my ButtonGeneric instead of having a :OnClicked method, I have an event that lets me bind an arbitrary function to an event, in place of having a generic method that gets called whenever I need to add functionality. I make necessary connections and signals in my constructor, then if another script needs to subscribe or disconnect from one of my object’s events, it’s up to said script to do this. Note I don’t ever call the class methods unless it’s in an event, the initialization of an object should be done inside of the constructor.

function module.new(object: Frame)
    -- avoid duplicate creation of buttons, it's not anything necessary, it's just that my main UIController module acts as a "pool" of all my objects which allows them to be easily accessed by other objects.
	local objectAlreadyCreated = uiController:GetObjectByReference(object)
	if objectAlreadyCreated then
		return objectAlreadyCreated
	end

    -- define properties inside of table constructor when possible, immediately set metatable:
	local self = setmetatable({
		CaptureBounds = (object:FindFirstChild('CaptureBounds') or utilObjects.captureBounds(object)) :: TextButton;
		Bottom = object:FindFirstChild('Bottom') or utilObjects.offsetFrame(object, 'Bottom', DARKEN_AMOUNT);
		Top = object:FindFirstChild('Top') or utilObjects.offsetFrame(object, 'Top');
		_frameRef = object;
		Enabled = true
	}, module)

    -- Our buttons are composed of 2 parts: top and bottom, so set those up & create mouse button down & up tweens:
	self.Bottom.Name = 'Bottom'
	self.Top.Name = 'Top'
	self.Top:SetAttribute('OriginalBackgroundColor3', self.Top.BackgroundColor3)
	self.DownTweens = {
		tweenService:Create(self.Top, TweenInfo.new(0.125), {
			BackgroundColor3 = self.Bottom.BackgroundColor3;
			AnchorPoint = Vector2.new(0,1);
			Position = UDim2.fromScale(0,1)
		})
	}

	self.UpTweens = {
		tweenService:Create(self.Top, TweenInfo.new(0.125), {
			BackgroundColor3 = self.Top.BackgroundColor3;
			AnchorPoint = Vector2.new();
			Position = UDim2.new()
		});
	}

	for i,v in ipairs(self.Top:GetDescendants()) do
		local madeGoals, goals = getTweenGoals(v)
		if madeGoals then
			table.insert(self.DownTweens, tweenService:Create(v, TweenInfo.new(0.125), goals.GoalsDown))
			table.insert(self.UpTweens, tweenService:Create(v, TweenInfo.new(0.125), goals.GoalsUp))
		end
	end

    -- clear out excess instances
	for i,v in ipairs(object:GetChildren()) do
		if v:GetAttribute('HasCloned') then
			v:Destroy()
		end
	end

    -- create events:
	self.MouseButton1Click = framework.signal.new()

    -- create connections that call the object's own methods, do this sparingly (eg. when working with button core functionality):
	self.CaptureBounds.MouseButton1Down:Connect(function()
		if not self.Enabled then
			return
		end
		self:Down()
	end)

	self.CaptureBounds.MouseButton1Up:Connect(function()
		if not self.Enabled then
			return
		end
		self:Up()
	end)

	self.CaptureBounds.MouseLeave:Connect(function()
		if not self.Enabled then
			return
		end
		self:Up()
	end)

	self.CaptureBounds.MouseButton1Click:Connect(function()
		if not self.Enabled then
			return
		end
		framework.soundManager:Play(optionsHandler:GetOptionValue('SoundVolume').Value)
		if not self._frameRef:GetAttribute('IgnoreHaptics') then
			framework.inputManager:PlayHaptic()
		end
		self.MouseButton1Click:Fire()
	end)

    -- finally make the object's background transparency 1, now the object's creation is complete.
	object.BackgroundTransparency = 1

	return self
end

So to summarize, my constructor:
1- Creates properties and related objects (including signals) and when necessary exposes them to other scripts.
2- Sets up core functionality of the object (i.e. hook into real button (instance) events and do visuals and whatnot, and fire events)

It is up to other scripts to hook into my button object’s events and make my object behave in some way and how the button interacts with other buttons, etc.

2 Likes

This looks very neat. Thank you :smile:

1 Like

Question, how do sounds work if its played on server? Does the sound play for everyone?

It would play for every player depending on where it’s parented to. For example if you’re playing a sound in a specific player’s PlayerGui on the server, it would only play for that specific player because the PlayerGui isn’t replicated to other clients.

But if you’re playing a sound in a part in the workspace, for example, it would play for every player. For sound effects and background music that doesn’t emit from anywhere (again like the PlayerGui), I try to do that on the client whenever possible.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.