Irrelevant with the actual issue, Today I’ve created a ModuleScript that works around these bugs.
This is for anyone that finds these bugs conflicting with their objective.
I call it the SelectionService
and simply replaces GuiService
.
Right now, it only covers the basic navigation needs by bypassing bugs and adding extra functionalities to the Selection (created internally) used by GuiService.
SelectionService
:
--// Settings //--
local Settings = {}
Settings.ContextActionPriority = Enum.ContextActionPriority.Default.Value + 1 -- The default BindAction priority to use
Settings.ContextActionResult = Enum.ContextActionResult.Sink -- Enum.ContextActionResult.Pass if you want other functions binded to the same key to be ran
Settings.StrictSelection = false -- Determines whether the GuiObject's Selection needs to match the next GuiObject's Selection when navigating
Settings.NavigationThumbsticks = {
["LeftThumbstick"] = true, -- Determines whether Thumbstick1 can be used to navigate
["RightThumbstick"] = false, -- Determines whether Thumbstick2 can be used to navigate
["Target"] = 0.5, -- 0-1, Recommended > 0.25 - It will navigate if the Thumbstick distance >= Target
["Reset"] = 0.1 -- 0-1, Recommended < 0.1 - It will re-allow navigation if the Thumbstick distance <= Reset
-- Note that the thumbstick direction flag will also reset if a different navigation direction was triggered (i.e. Allowing Up -> Right -> Up without resetting at Reset)
}
Settings.NavigationBinds = { -- Extra keybinds
["Left"] = {
--Enum.KeyCode.A,
--Enum.KeyCode.Left,
Enum.KeyCode.DPadLeft
},
["Right"] = {
--Enum.KeyCode.D,
--Enum.KeyCode.Right,
Enum.KeyCode.DPadRight
},
["Up"] = {
--Enum.KeyCode.W,
--Enum.KeyCode.Up,
Enum.KeyCode.DPadUp
},
["Down"] = {
--Enum.KeyCode.S,
--Enum.KeyCode.Down,
Enum.KeyCode.DPadDown
}
}
--// Dependencies //--
local GuiService = game:GetService("GuiService")
local ContextActionService = game:GetService("ContextActionService")
--// Initialization //--
local SelectionService = {}
SelectionService.__index = SelectionService
--// Selection Functions //--
function SelectionService:SelectGuiObject(guiObject)
GuiService.SelectedObject = guiObject
end
function SelectionService:SelectPrimaryGuiObjectFromSelection(selection)
if self.Selections[selection] then
GuiService.SelectedObject = self.Selections[selection][1]
end
end
function SelectionService:Unselect()
GuiService.SelectedObject = nil
end
function SelectionService:AddToSelection(selection, tuple)
tuple = typeof(tuple) == "Instance" and {tuple} or tuple
for _, guiObject in ipairs(tuple) do
if not table.find(self.Selections[selection], guiObject) then
table.insert(self.Selections[selection], guiObject)
end
end
end
function SelectionService:RemoveFromSelection(selection, tuple)
tuple = typeof(tuple) == "Instance" and {tuple} or tuple
for _, guiObject in ipairs(tuple) do
local position = table.find(self.Selections[selection], guiObject)
if position then
table.remove(self.Selections[selection], position)
end
end
end
function SelectionService:NewSelection(selection, tuple)
if not self.Selections[selection] then
self.Selections[selection] = {}
if tuple then
self:AddToSelection(selection, tuple)
end
end
end
function SelectionService:RemoveSelection(selection)
if self.Selections[selection] then
self.Selections[selection] = nil
end
end
function SelectionService:GetSelectionFromGuiObject(guiObject)
for selection, objects in pairs(self.Selections) do
for _, object in ipairs(objects) do
if object == guiObject then
return selection
end
end
end
end
function SelectionService:GetSelectedGuiObject()
return GuiService.SelectedObject
end
--// Navigation Functions //--
function SelectionService:Navigate(direction)
local currentGuiObject = self:GetSelectedGuiObject()
if currentGuiObject == nil then
return Enum.ContextActionResult.Sink
end
if direction == "Left" then
if currentGuiObject.NextSelectionLeft ~= nil then
if Settings.StrictSelection then
if self:GetSelectionFromGuiObject(currentGuiObject) == self:GetSelectionFromGuiObject(currentGuiObject.NextSelectionLeft) then
self:SelectGuiObject(GuiService.SelectedObject.NextSelectionLeft)
end
else
self:SelectGuiObject(GuiService.SelectedObject.NextSelectionLeft)
end
end
elseif direction == "Right" then
if currentGuiObject.NextSelectionRight ~= nil then
if Settings.StrictSelection then
if self:GetSelectionFromGuiObject(currentGuiObject) == self:GetSelectionFromGuiObject(currentGuiObject.NextSelectionRight) then
self:SelectGuiObject(GuiService.SelectedObject.NextSelectionRight)
end
else
self:SelectGuiObject(GuiService.SelectedObject.NextSelectionRight)
end
end
elseif direction == "Up" then
if currentGuiObject.NextSelectionUp ~= nil then
if Settings.StrictSelection then
if self:GetSelectionFromGuiObject(currentGuiObject) == self:GetSelectionFromGuiObject(currentGuiObject.NextSelectionUp) then
self:SelectGuiObject(GuiService.SelectedObject.NextSelectionUp)
end
else
self:SelectGuiObject(GuiService.SelectedObject.NextSelectionUp)
end
end
elseif direction == "Down" then
if currentGuiObject.NextSelectionDown ~= nil then
if Settings.StrictSelection then
if self:GetSelectionFromGuiObject(currentGuiObject) == self:GetSelectionFromGuiObject(currentGuiObject.NextSelectionDown) then
self:SelectGuiObject(GuiService.SelectedObject.NextSelectionDown)
end
else
self:SelectGuiObject(GuiService.SelectedObject.NextSelectionDown)
end
end
end
return Settings.ContextActionResult
end
function SelectionService:GetNavigateDirectionFromThumbstickPosition(position)
if math.abs(position.X) > math.abs(position.Y) then
if position.X < 0 then
return "Left"
elseif position.X > 0 then
return "Right"
end
elseif math.abs(position.Y) > math.abs(position.X) then
if position.Y > 0 then
return "Up"
elseif position.Y < 0 then
return "Down"
end
end
return "Neutral"
end
function SelectionService:EnableNavigationLeft(priority)
if #self.Binds.Left > 0 then
ContextActionService:BindActionAtPriority("SelectionServiceNavigateLeft", (function(action, state, input)
if state == Enum.UserInputState.Begin then
self:Navigate("Left")
end
end), false, priority or Settings.ContextActionPriority, unpack(self.Binds.Left))
end
end
function SelectionService:EnableNavigationRight(priority)
if #self.Binds.Right > 0 then
ContextActionService:BindActionAtPriority("SelectionServiceNavigateRight", (function(action, state, input)
if state == Enum.UserInputState.Begin then
self:Navigate("Right")
end
end), false, priority or Settings.ContextActionPriority, unpack(self.Binds.Right))
end
end
function SelectionService:EnableNavigationUp(priority)
if #self.Binds.Up > 0 then
ContextActionService:BindActionAtPriority("SelectionServiceNavigateUp", (function(action, state, input)
if state == Enum.UserInputState.Begin then
self:Navigate("Up")
end
end), false, priority or Settings.ContextActionPriority, unpack(self.Binds.Up))
end
end
function SelectionService:EnableNavigationDown(priority)
if #self.Binds.Down > 0 then
ContextActionService:BindActionAtPriority("SelectionServiceNavigateDown", (function(action, state, input)
if state == Enum.UserInputState.Begin then
self:Navigate("Down")
end
end), false, priority or Settings.ContextActionPriority, unpack(self.Binds.Down))
end
end
function SelectionService:EnableNavigationByThumbstick(priority)
local thumbsticks = {}
if Settings.NavigationThumbsticks["LeftThumbstick"] then
table.insert(thumbsticks, Enum.KeyCode.Thumbstick1)
end
if Settings.NavigationThumbsticks["RightThumbstick"] then
table.insert(thumbsticks, Enum.KeyCode.Thumbstick2)
end
if #thumbsticks > 0 then
ContextActionService:BindActionAtPriority("SelectionServiceNavigateByThumbstick", (function(action, state, input)
local thumbstickFlags = (input.KeyCode == Enum.KeyCode.Thumbstick1 and self.Flags.Thumbstick1) or (input.KeyCode == Enum.KeyCode.Thumbstick2 and self.Flags.Thumbstick2)
if thumbstickFlags then -- weird bug where `thumbstickFlags` would return false if thumbstick moves too fast (during initialization)
if math.abs(input.Position.X) <= Settings.NavigationThumbsticks.Reset then
thumbstickFlags.XReady = true
end
if math.abs(input.Position.Y) <= Settings.NavigationThumbsticks.Reset then
thumbstickFlags.YReady = true
end
if math.abs(input.Position.X) >= Settings.NavigationThumbsticks.Target or math.abs(input.Position.Y) >= Settings.NavigationThumbsticks.Target then
local direction = self:GetNavigateDirectionFromThumbstickPosition(input.Position)
if direction ~= "Neutral" then
local directionReady = ((direction == "Left" or direction == "Right") and "XReady") or ((direction == "Up" or direction == "Down") and "YReady")
if thumbstickFlags[directionReady] then
thumbstickFlags[directionReady] = false
thumbstickFlags[(directionReady == "XReady" and "YReady") or (directionReady == "YReady" and "XReady")] = true
self:Navigate(direction)
end
end
end
end
end), false, priority or Settings.ContextActionPriority, unpack(thumbsticks))
end
end
function SelectionService:EnableNavigation(priority)
self:EnableNavigationLeft(priority)
self:EnableNavigationRight(priority)
self:EnableNavigationUp(priority)
self:EnableNavigationDown(priority)
self:EnableNavigationByThumbstick(priority)
end
function SelectionService:DisableNavigationLeft()
ContextActionService:UnbindAction("SelectionServiceNavigateLeft")
end
function SelectionService:DisableNavigationRight()
ContextActionService:UnbindAction("SelectionServiceNavigateRight")
end
function SelectionService:DisableNavigationUp()
ContextActionService:UnbindAction("SelectionServiceNavigateUp")
end
function SelectionService:DisableNavigationDown()
ContextActionService:UnbindAction("SelectionServiceNavigateDown")
end
function SelectionService:DisableNavigationByThumbstick()
ContextActionService:UnbindAction("SelectionServiceNavigateByThumbstick")
end
function SelectionService:DisableNavigation()
self:DisableNavigationLeft()
self:DisableNavigationRight()
self:DisableNavigationUp()
self:DisableNavigationDown()
self:DisableNavigationByThumbstick()
end
--// Navigation Binds //--
function SelectionService:BindToNavigate(direction, tuple)
tuple = typeof(tuple) == "EnumItem" and {tuple} or typeof(tuple) == "table" and tuple or {}
for _, key in ipairs(tuple) do
if typeof(key) == "EnumItem" and not table.find(self.Binds[direction], key) then
table.insert(self.Binds[direction], key)
end
end
local navigateInfo = ContextActionService:GetBoundActionInfo("SelectionServiceNavigate" ..direction)
if next(navigateInfo) ~= nil then
if direction == "Left" then
self:DisableNavigationLeft()
self:EnableNavigationLeft(navigateInfo.priorityLevel)
elseif direction == "Right" then
self:DisableNavigationRight()
self:EnableNavigationRight(navigateInfo.priorityLevel)
elseif direction == "Up" then
self:DisableNavigationUp()
self:EnableNavigationUp(navigateInfo.priorityLevel)
elseif direction == "Down" then
self:DisableNavigationDown()
self:EnableNavigationDown(navigateInfo.priorityLevel)
end
end
end
function SelectionService:UnbindFromNavigate(direction, tuple)
tuple = typeof(tuple) == "EnumItem" and {tuple} or typeof(tuple) == "table" and tuple or {}
for _, key in ipairs(tuple) do
if typeof(key) == "EnumItem" then
local position = table.find(self.Binds[direction], key)
if position then
table.remove(self.Binds[direction], position)
end
end
end
local navigateInfo = ContextActionService:GetBoundActionInfo("SelectionServiceNavigate" ..direction)
if next(navigateInfo) ~= nil then
if direction == "Left" then
self:DisableNavigationLeft()
self:EnableNavigationLeft(navigateInfo.priorityLevel)
elseif direction == "Right" then
self:DisableNavigationRight()
self:EnableNavigationRight(navigateInfo.priorityLevel)
elseif direction == "Up" then
self:DisableNavigationUp()
self:EnableNavigationUp(navigateInfo.priorityLevel)
elseif direction == "Down" then
self:DisableNavigationDown()
self:EnableNavigationDown(navigateInfo.priorityLevel)
end
end
end
function SelectionService:AutoBindDefaults(selection)
self:BindToNavigate("Left", Settings.NavigationBinds.Left)
self:BindToNavigate("Right", Settings.NavigationBinds.Right)
self:BindToNavigate("Up", Settings.NavigationBinds.Up)
self:BindToNavigate("Down", Settings.NavigationBinds.Down)
end
--// Constructor //--
function SelectionService.new()
GuiService.AutoSelectGuiEnabled = false
GuiService.GuiNavigationEnabled = false
local self = setmetatable({}, SelectionService)
self.Binds = {}
self.Binds.Left = {}
self.Binds.Right = {}
self.Binds.Up = {}
self.Binds.Down = {}
self.Flags = {}
self.Flags.Thumbstick1 = {}
self.Flags.Thumbstick1.XReady = true
self.Flags.Thumbstick1.YReady = true
self.Flags.Thumbstick2 = {}
self.Flags.Thumbstick2.XReady = true
self.Flags.Thumbstick2.YReady = true
self.Selections = {}
self:AutoBindDefaults()
return self
end
return SelectionService.new()
Code + Main Functions:
local SelectionService = require(path)
SelectionService:EnableNavigation()
-- Yep, that's it. Upon requiring the module, it will automatically setup
-- the keybinds for you.
-- You will still need to select an object before enabling navigation.
SelectionService:SelectGuiObject(newGameButton)
-- If you need to disable navigation for things such as a busy state,
-- (i.e. waiting to load a player's inventory frame), you can use the following
SelectionService:DisableNavigation()
-- It is also recommended (by me, at least) to unselect the current selected
-- GuiObject just as a safety measure
SelectionService:Unselect()
-- If you want to use Settings.StrictSelection, you can use either/both of
-- the following functions:
SelectionService:NewSelection("MenuTabs", {
MenuTabs.Stats,
MenuTabs.Inventory,
MenuTabs.Settings
})
if playerStatus == "Developer" then
MenuTabs.AdminCommands.Visible = true
SelectionService:AddToSelection("MenuTabs", {
MenuTabs.AdminCommands
})
end
-- If you offer players the ability to change keybinds, you can use:
SelectionService:UnbindFromNavigate("Up", Enum.KeyCode.DPadUp)
SelectionService:BindToNavigate("Up", Enum.KeyCode.W)
Just know that this only covers the basic navigation functionalities, nothing more. Feel free to take and expand upon this pseudo-service of mine 