In my game, I’m trying to make a bunch of menus for various functions. I am trying to create a system that only allows one menu open at a time so users don’t have to manually close every tab they have opened.
Currently, I am using a tweening function to open and close the menus
-- Function to open the GUI by tweening it upwards
local function openGUI()
local targetPosition = UDim2.new(0.5, 0, 0.5, 0) -- Center of the screen
local tween = TweenService:Create(Frame, getTweenInfo(), {Position = targetPosition})
tween:Play()
end
-- Function to close the GUI by tweening it downwards
local function closeGUI()
local targetPosition = UDim2.new(0.5, 0, 1.5, 0) -- Below the screen
local tween = TweenService:Create(Frame, getTweenInfo(), {Position = targetPosition})
tween:Play()
end
To my knowledge, it is not really possible for scripts to communicate with and call functions from one another (my guess is that they’re just shy :P). I’ve tried using a boolValue and then having the script listen for it:
local isOpen = script:FindFirstChild("Open")
local function toggleDoor()
if isOpen == true then
closeGUI()
isOpen = false
elseif isOpen == false then
openGUI()
isOpen = true
else
print("failure")
end
isOpen = not isOpen
end
isOpen:GetPropertyChangedSignal("Value"):Connect(toggleDoor)
ImageButton.MouseButton1Click:Connect(toggleDoor())
But somehow, when I click on the variable, it prints “failure”. Then when I click it again it functions, however, it is reversed. The variable is true when it should be false.
Is this the best solution, or is there a more efficient method?
Preferably, there’d be some kind of table where I can add more GUIs if necessary.
This is happening because isOpen is referring to the BoolValue and not its Value property, which means that the conditional statements that check if isOpen == true or if isOpen == false is comparing the object to true or false.
Updating it to check if isOpen.Value == true or false should resolve that issue:
Example Revision
local isOpen = script:FindFirstChild("Open")
local function toggleDoor()
if isOpen.Value == true then
closeGUI()
isOpen.Value = false
elseif isOpen.Value == false then
openGUI()
isOpen.Value = true
else
print("failure")
end
isOpen.Value = not isOpen.Value
end
isOpen:GetPropertyChangedSignal("Value"):Connect(toggleDoor)
ImageButton.MouseButton1Click:Connect(toggleDoor)
*And @Lauri9 was also correct about the function call, but that was only 1 of 2 issues that were present. With both of those changes, it should work as intended.
Oh gosh, I could have sworn I fixed that last night. It was almost midnight though… It must have not saved properly.
Ok, I revised my code (dont ask me why I called the function toggleDoor and not toggleGUI in the first place), but now the code automatically runs twice on game startup, and refuses to respond.
local isOpen = script:FindFirstChild("Open") -- Initialize a boolean variable with a value of false
local function toggleGUI()
print("Toggling")
if isOpen.Value == true then
closeGUI()
print("Attempting to close...")
elseif isOpen.Value == false then
print("Attempting to open...")
openGUI()
else
print("Attempt Failure")
end
isOpen = not isOpen
end
isOpen:GetPropertyChangedSignal("Value"):Connect(toggleGUI())
ImageButton.MouseButton1Click:Connect(toggleGUI())
(Immediately after joining)
Then it tells me that the boolvalue isnt there anymore?
Make sure to change that to isOpen.Value = not isOpen.Value, so that it updates the Value property. Otherwise, it’ll update the variable itself to false instead of referring to the BoolValue object (hopefully I explained that properly).
Edit 1: Also make sure to update the last line of code with the change that @Lauri9 suggested:
Yup, the :GetPropertyChangedSignal() listener is calling the function over and over again because isOpen.Value is being changed every time the function is called.
Now that I think about it, I don’t think isOpen.Value = not isOpen.Value would be necessary to include in the function at all, since the necessary changes that need to happen are only related to opening or closing the GUI after you’ve detected the value being changed (so you could remove that from the function and then that error will no longer occur).
ImageButton.MouseButton1Click:Connect(function()
if isOpen.Value == true then
isOpen.Value = false
elseif isOpen.Value == false then
isOpen.Value = true
end
end)
But now I’m running into the same problem where, when the game starts, the code somehow thinks that the variable is true?
When I had the output open and the bool value selected, I clicked the button and it set the bool value to true, but the output says “Attempting to close…”
Oh yeah, I forgot that it’s not being updated from a single place somewhere else in the game.
Before trying to explain a possible solution, I think it’d be good to have some more clarification about how the menus are set up, first.
Are all of the menus within the same ScreenGui?
Were you planning on having these functions included in separate scripts for each menu, or would everything be in a single script?
Would this singular BoolValue be referenced by each of the menus?
And sorry about taking quite a while to help ya figure out a viable solution. It’s past 6 AM here and I should have gone to sleep a long time ago… so after this I’m going to actually get some rest haha
Right now, all the menus are seperate screenGUIs. “Main” is the toolbar with the buttons on it.
Maybe it would be better to consolidate it under one Screen gui
The button controls the frames right now, which may not be a great idea for efficiency and organization.
The buttons would reference the bool values of all the other menus (except for the toolbar and main start menu).
Thanks for clarifying! With all of that in mind, here’s one possible way of going about that should be able to be scaled fairly easily *(without using BoolValues, though), even if you create more menus.
Create a ModuleScript in the ReplicatedStorage that will act as the centralized place for handling the opening / closing of GUIs and keeping track of the menu that is currently open.
Whenever one of the buttons that is meant to open or close the Frame is pressed, have it call the relevant function in the ModuleScript so it can handle everything without needing to copy and paste the functions into scripts for each menu.
If you have any questions, feel free to ask! Although I may not respond for several hours because it is now 7 AM and I am going to sleep lol.
Example ModuleScript
local MenuModule = {}
MenuModule.activeMenu = nil
local function openGUI(Frame, tweenInfo) -- Function based on what was included in the original post
if not tweenInfo then
-- Consider creating a backup tweenInfo in case one wasn't sent through
end
local targetPosition = UDim2.new(0.5, 0, 0.5, 0) -- Center of the screen
local tween = TweenService:Create(Frame, tweenInfo, {Position = targetPosition})
tween:Play()
end
local function closeGUI(Frame, tweenInfo) -- Function based on what was included in the original post
if not tweenInfo then
-- Consider creating a backup tweenInfo in case one wasn't sent through
end
local targetPosition = UDim2.new(0.5, 0, 1.5, 0) -- Below the screen
local tween = TweenService:Create(Frame, tweenInfo, {Position = targetPosition})
tween:Play()
end
function MenuModule.updateCurrentMenu(Frame, desiredFrameVisibility, tweenInfo)
if Frame ~= nil then
if desiredFrameVisibility == true then
local activeMenuCheck = MenuModule.activeMenu
if activeMenuCheck ~= nil then
closeGUI(activeMenuCheck)
end
--[[
Consider creating a backup tweenInfo somewhere (preferably in this Module,
for ease of access), so that in the case where the currently active menu needs,
to be closed as a result of another menu being opened, the currently visible Frame
will have appropriate settings given the desired style of the closing animation.
--]]
MenuModule.activeMenu = Frame
openGUI(Frame, tweenInfo)
elseif desiredFrameVisibility == false then
closeGUI(Frame, tweenInfo)
end
end
end
Example of a LocalScript communicating with the ModuleScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local MenuModule = require(ReplicatedStorage.MenuModule)
local ImageButton -- Reference to ImageButton here
local Frame -- Reference to relevant Frame here
local function getTweenInfo()
--[[ If this needs to be specific for each menu, then you can retrieve it
in the same way you are already, but then have it sent to the ModuleScript.
Otherwise, a universal TweenInfo could be stored in the ModuleScript so these
LocalScripts don't have to communicate it every time a menu needs to be
opened or closed --]]
-- If the function below calls this function, have it return the TweenInfo
end
ImageButton.Activated:Connect(function()
local currentFrameVisibility = Frame.Visible
local desiredFrameVisibility = not currentFrameVisibility
--[[ If the Frame is currently invisible, the menu is closed. Since the player
pressed the button, they want to open it, so we can just reverse the value of
Frame.Visible and send that to the ModuleScript so it knows what value it
should be updated to --]]
local tweenInfo = getTweenInfo()
MenuModule.updateCurrentMenu(Frame, desiredFrameVisibility, tweenInfo)
end)
Okay, after a while of trial and error, I ended up with these two scripts.
They work well, no errors yet. But the GUIs are still overlapping. Can you help me start on the activeMenu check?
Module
local MenuModule = {}
MenuModule.activeMenu = nil
local TweenService = game:GetService("TweenService")
local function openGUI(Frame, tweenInfo) -- Function based on what was included in the original post
if not tweenInfo then
return TweenInfo.new(
0.5, -- Time
Enum.EasingStyle.Quad, -- Easing style
Enum.EasingDirection.Out, -- Easing direction
0, -- Repeat count (0 = no repeat)
false, -- Reverses (true = reverse)
0 -- Delay time
)
end
local targetPosition = UDim2.new(0.5, 0, 0.5, 0) -- Center of the screen
local tween = TweenService:Create(Frame, tweenInfo, {Position = targetPosition})
tween:Play()
end
local function closeGUI(Frame, tweenInfo) -- Function based on what was included in the original post
if not tweenInfo then
return TweenInfo.new(
0.5, -- Time
Enum.EasingStyle.Quad, -- Easing style
Enum.EasingDirection.Out, -- Easing direction
0, -- Repeat count (0 = no repeat)
false, -- Reverses (true = reverse)
0 -- Delay time
)
end
local targetPosition = UDim2.new(0.5, 0, 1.5, 0) -- Below the screen
local tween = TweenService:Create(Frame, tweenInfo, {Position = targetPosition})
tween:Play()
end
function MenuModule.updateCurrentMenu(Frame, desiredFrameVisibility, tweenInfo)
print(MenuModule.activeMenu)
if Frame ~= nil then
if desiredFrameVisibility == true then
local activeMenuCheck = MenuModule.activeMenu
if activeMenuCheck ~= nil then
closeGUI(activeMenuCheck)
end
MenuModule.activeMenu = Frame
openGUI(Frame, tweenInfo)
elseif desiredFrameVisibility == false then
closeGUI(Frame, tweenInfo)
end
end
end
return MenuModule
Local Script
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local MenuModule = require(ReplicatedStorage.MenuModule)
print(MenuModule)
local associatedButtonName = script.Parent.Name
local ImageButton = script.Parent.Parent.Parent:WaitForChild("Toolbar"):WaitForChild(associatedButtonName)
local Frame = script.Parent
local function getTweenInfo()
return TweenInfo.new(
0.5, -- Time
Enum.EasingStyle.Quad, -- Easing style
Enum.EasingDirection.Out, -- Easing direction
0, -- Repeat count (0 = no repeat)
false, -- Reverses (true = reverse)
0 -- Delay time
)
end
local currentFrameVisibility = false
ImageButton.MouseButton1Click:Connect(function()
local desiredFrameVisibility = not currentFrameVisibility
local tweenInfo = getTweenInfo()
MenuModule.updateCurrentMenu(Frame, desiredFrameVisibility, tweenInfo)
currentFrameVisibility = not currentFrameVisibility
end)```
Sure! I think I know which section of code is causing that to happen, but there are a few things I’d like to mention:
For the example LocalScript code, the way that currentFrameVisibility and desiredFrameVisibility are defined and updated could cause some issues. For example, if any of the Menus happen to start with their Visible property set to true and the player tried to close it directly, it would require them to press it twice because the script would begin by setting desiredFrameVisibility to true and sending a request to the module to make it visible.
In addition to that, because currentFrameVisibility is only updated upon the activation of that ImageButton, this would very quickly create discrepancies when another menu is opened, since it would cause this frame to be automatically closed by the closeGUI function in the module without updating the currentFrameVisibility variable in the LocalScript (since the player needed to press the ImageButton to do so).
As a result, it’d be more reliable to move currentFrameVisibility into the function and to directly reference the Frame.Visible property for currentFrameVisibility, as that would ensure that the function will always be referring to the current visibility of the Frame whenever the player decides to press the button.
Within the openGUI and closeGUI functions, you wouldn’t need to return the TweenInfo since it would be used in those functions (and returning is typically meant for sending something back to the line of code where the function was called from). In this case, you would just need to override the tweenInfo variable with that newly created TweenInfo. And since both of the backup TweenInfos happen to have the same settings, it may be better to keep that stored in a single place (such as within the module’s table) for ease of access and organization.
As far as I understand, this is what was causing the menus to continue overlapping, since the activeMenu did not have a tweenInfo sent with it to the closeGUI function. As a result, it would have to create a new TweenInfo, but when it reaches that part of the function and it returns it, it stops the rest of the function from running, meaning that the frame is not moved off of the screen.
There’s also one more change I’d make to the ModuleScript that I didn’t consider earlier, which would be to set activeMenu to nil if the updateCurrentMenu function is called with the desiredFrameVisibility being set to false, so that the next time a menu is opened, it won’t try to set that menu’s visibility to false again since it was already manually closed by the player. It would have still worked properly without making this change, but this creates more consistency.
Here’s an example revision of both of those scripts:
Revised ModuleScript Example
local MenuModule = {}
MenuModule.activeMenu = nil
MenuModule.defaultTweenInfo = TweenInfo.new(
0.5, -- Time
Enum.EasingStyle.Quad, -- Easing style
Enum.EasingDirection.Out, -- Easing direction
0, -- Repeat count (0 = no repeat)
false, -- Reverses (true = reverse)
0 -- Delay time
)
local TweenService = game:GetService("TweenService")
local function openGUI(Frame, tweenInfo) -- Function based on what was included in the original post
if not tweenInfo then
tweenInfo = MenuModule.defaultTweenInfo
end
local targetPosition = UDim2.new(0.5, 0, 0.5, 0) -- Center of the screen
local tween = TweenService:Create(Frame, tweenInfo, {Position = targetPosition})
tween:Play()
end
local function closeGUI(Frame, tweenInfo) -- Function based on what was included in the original post
if not tweenInfo then
tweenInfo = MenuModule.defaultTweenInfo
end
local targetPosition = UDim2.new(0.5, 0, 1.5, 0) -- Below the screen
local tween = TweenService:Create(Frame, tweenInfo, {Position = targetPosition})
tween:Play()
end
function MenuModule.updateCurrentMenu(Frame, desiredFrameVisibility, tweenInfo)
print(MenuModule.activeMenu)
if Frame ~= nil then
if desiredFrameVisibility == true then
local activeMenuCheck = MenuModule.activeMenu
if activeMenuCheck ~= nil then
closeGUI(activeMenuCheck)
end
MenuModule.activeMenu = Frame
openGUI(Frame, tweenInfo)
elseif desiredFrameVisibility == false then
if Frame == MenuModule.activeMenu then
MenuModule.activeMenu = nil
end
closeGUI(Frame, tweenInfo)
end
end
end
return MenuModule
Revised LocalScript Example
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local MenuModule = require(ReplicatedStorage.MenuModule)
print(MenuModule)
local Frame = script.Parent
local associatedButtonName = Frame.Name
local ToolBar = Frame.Parent.Parent:WaitForChild("Toolbar")
--[[
I'd recommend defining the other objects (e.g. Frame.Parent and then
the Parent of that Parent) so that if you ever change its hierarchy, you
know what object it was supposed to be referring to at each step
--]]
local ImageButton = Toolbar:WaitForChild(associatedButtonName)
local function getTweenInfo()
return TweenInfo.new(
0.5, -- Time
Enum.EasingStyle.Quad, -- Easing style
Enum.EasingDirection.Out, -- Easing direction
0, -- Repeat count (0 = no repeat)
false, -- Reverses (true = reverse)
0 -- Delay time
)
end
ImageButton.MouseButton1Click:Connect(function()
local currentFrameVisibility = Frame.Visible
local desiredFrameVisibility = not currentFrameVisibility
local tweenInfo = getTweenInfo()
MenuModule.updateCurrentMenu(Frame, desiredFrameVisibility, tweenInfo)
end)
Is there a way to do this without making it invisible? I want the tween to be visible.
Secondly, when I didn’t put the tweenInfo in again, it gave me the error “Argument 2 missing or nil.” when opening a second time. (Start > Open menu > Close menu > Open menu (ERROR))
And lastly, how can I set up a close button within the frame?
The Tween would not be invisible; the currentFrameVisibility variable is just referring to the current Visibility value of the Frame, and when desiredFrameVisibility is set to not currentFrameVisibility, it’s only storing a value in that variable which is the opposite of currentFrameVisibility. It doesn’t actually change the visibility of the frame, it stores the value in the variable so it can be sent to the updateCurrentMenu function within the module.
Which function is that error coming from? *And, so we’re on the same page, what do both of the scripts look like now?
Ah, I had structured the LocalScript in a way where if the player pressed the same ImageButton that they pressed to open the menu, it would close it. If you wanted a completely separate button to close it (with the option to also click the original button that had opened it), you could create a new function within the LocalScript which calls the updateCurrentMenu function exclusively with a second value of false. Here’s an example:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local MenuModule = require(ReplicatedStorage.MenuModule)
print(MenuModule)
local Frame = script.Parent
local associatedButtonName = Frame.Name
local closeButton -- Reference to the newly created "close menu" button here
local ToolBar = Frame.Parent.Parent:WaitForChild("Toolbar")
--[[
I'd recommend defining the other objects (e.g. Frame.Parent and then
the Parent of that Parent) so that if you ever change its hierarchy, you
know what object it was supposed to be referring to at each step
--]]
local ImageButton = Toolbar:WaitForChild(associatedButtonName)
local function getTweenInfo()
return TweenInfo.new(
0.5, -- Time
Enum.EasingStyle.Quad, -- Easing style
Enum.EasingDirection.Out, -- Easing direction
0, -- Repeat count (0 = no repeat)
false, -- Reverses (true = reverse)
0 -- Delay time
)
end
ImageButton.MouseButton1Click:Connect(function()
local currentFrameVisibility = Frame.Visible
local desiredFrameVisibility = not currentFrameVisibility
local tweenInfo = getTweenInfo()
MenuModule.updateCurrentMenu(Frame, desiredFrameVisibility, tweenInfo)
end)
closeButton.MouseButton1Click:Connect(function()
local tweenInfo = getTweenInfo()
MenuModule.updateCurrentMenu(Frame, false, tweenInfo)
end)
The Tween would not be invisible; the currentFrameVisibility variable is just referring to the current Visibility value of the Frame,
But when Frame.Visible is set to false, the Frame wont be rendered. I also don’t see anything that would switch that value, wouldn’t it stay at true forever? If it wasn’t clear, I am not changing the value of the Frame.Visible property, I’m just hiding it off screen. Would you explain more?
Which function is that error coming from? *And, so we’re on the same page, what do both of the scripts look like now?
I’m pretty sure it was coming from the Module, but I cant check now because Im not at my computer. I deduced that it was talking about openGUI(Frame, tweenInfo) so I put the tweenInfo in again.
Both scripts right now look like the ones I sent here
you could create a new function within the LocalScript which calls the updateCurrentMenu function exclusively with a second value of false . Here’s an example:
Ahhh for some reason I had been stuck thinking that the Frame’s visibility was also being set to false after it went off-screen. Thanks for pointing that out because I kept missing that every time lol.
In that case, I have an idea which would simultaneously allow for the Tweens within the open and close GUI to appear as intended while also making the usage of referring to Frame.Visible possible so that the currentFrameVisibility variable would not be susceptible to returning inaccurate values if the menu is closed from the opening of a different menu.
Ideas
When closing a menu, listen for the completion of the Tween with Tween.Completed, and then update the Frame’s visibility to false.
local function closeGUI(Frame, tweenInfo) -- Function based on what was included in the original post
if not tweenInfo then
tweenInfo = MenuModule.defaultTweenInfo
end
local targetPosition = UDim2.new(0.5, 0, 1.5, 0) -- Below the screen
local tween = TweenService:Create(Frame, tweenInfo, {Position = targetPosition})
tween:Play()
tween.Completed:Wait()
Frame.Visible = false
end
When opening a menu, immediately set the Frame’s visibility to true and then play the Tween.
local function openGUI(Frame, tweenInfo) -- Function based on what was included in the original post
if not tweenInfo then
tweenInfo = MenuModule.defaultTweenInfo
end
Frame.Visible = true
local targetPosition = UDim2.new(0.5, 0, 0.5, 0) -- Center of the screen
local tween = TweenService:Create(Frame, tweenInfo, {Position = targetPosition})
tween:Play()
end
This would ensure that the Frame is not unnecessarily set to visible when it’s meant to be hidden off-screen while avoiding any interference with the visualization of the Tweens (making sure that it doesn’t suddenly appear or disappear while the Tween is still playing), all while allowing the function in the LocalScript to be able to refer to the current “state” (open or closed) of the menu, through its Visible property.
Oh; the ModuleScript code in that post still has the return TweenInfo in both the openGUI and closeGUI functions, which would be stopping the function from running when there is no TweenInfo provided.
I explained an example solution for that and a possible revision in the post right after you sent that one, if you want to give that a try (although I don’t think that is what would be causing the error to appear, because it would stop the function before it had a chance to error, if I understand correctly).
*If you decide to also go with the idea for updating the Frame’s visibility before / after the Tweens, then the example LocalScript code I provided in that same post would be compatible with that (since it’s checking the value of Frame.Visible when the player presses the ImageButton).
Whenever you have the time to check it, please let me know (no rush)!