How do I tell which GUI element was clicked?

I’m trying to create a GUI to allow player interaction with a modular system I’ve made for testing and implementation. Each component has its own frame that can be dragged around, and inside those frames are buttons for manipulation. The main frame everything is placed in as well can be clicked and dragged to scroll.

Because the interactions with the frames are controlled with InputBegan, I keep encountering an issue where if I click on any element, the InputBegan connection fires that element and all of its ancestors.

The obvious answer would be to put a debounce in every connection preventing its ancestors from being interacted with, but this would require a lot of work and maintenance, and would require adding connection to GUI elements that otherwise wouldn’t need them, like cosmetic text labels.

Is there anyway to check if the mouse is over a descendant when InputBegan is triggered?

1 Like

Maybe you could use UserInputService.InputBegan instead.

Then use BasePlayerGui:GetGuiObjectsAtPosition to get a list of every GUI you clicked on.

That list seems to be ordered top-to-bottom luckily but I can’t confirm this.

So check if the first one is a clickable one (maintain this set yourself like a “clicked” event) and either call the handler, or do nothing.

Seems to work:

image

local player = game.Players.LocalPlayer
local playerGui = player.PlayerGui
local UserInputService = game:GetService("UserInputService")

-- set of GUIs that can be clicked on (if not blocked)
local clickable = {
	[script.Parent.Frame] = true;
	-- script.Parent.Frame.Frame intentionally left out
	[script.Parent.Frame2] = true;
	[script.Parent.Frame2.Frame] = true;
}

-- make everything that's not clickable black so we know
for _, gui in pairs(script.Parent:GetDescendants()) do
	if gui:IsA("GuiObject") and not clickable[gui] then gui.BackgroundColor3 = Color3.new() end
end

local function OnClicked(gui, input)
	-- make only the one we clicked red
	gui.BackgroundColor3 = Color3.new(1, 0, 0)
end

UserInputService.InputBegan:Connect(function(input, gp)
	if not gp and input.UserInputType == Enum.UserInputType.MouseButton1 then
		-- reset everything to white
		for g in pairs(clickable) do g.BackgroundColor3 = Color3.new(1, 1, 1) end
		
		local pos = input.Position
		local under = playerGui:GetGuiObjectsAtPosition(pos.X, pos.Y)
		local top = under[1]
		
		if clickable[top] then OnClicked(top, input) end
	end
end)

You could extend it to loop through the list for the first GUI that you track, instead of just the first one on screen.

3 Likes