GetMouseLocation on SurfaceGui

This module gives you the mouse location on a SurfaceGui in udim2

Using UserInputService:GetMouseLocation() VS Module.GetMouseLocation()

Module
local module = {}

module.ScalingTypes = {
	UDim2Scale = 1,
	UDim2Offset = 2,
}

function module._GetMousePosition(part: Instance)
	local camera = workspace.CurrentCamera
	local mouse = game.UserInputService:GetMouseLocation()

	local params = RaycastParams.new()
	params.FilterDescendantsInstances = {part}
	params.FilterType = Enum.RaycastFilterType.Include

	local unitRay = camera:ViewportPointToRay(mouse.X, mouse.Y)
	unitRay = Ray.new(unitRay.Origin, unitRay.Direction*1000)

	local result = workspace:Raycast(unitRay.Origin, unitRay.Direction, params)

	return result
end

function module.GetMouseLocation(UI: SurfaceGui, scaling: any)
	local part: Part = UI.Adornee or UI.Parent
	assert(part:IsA("BasePart"))

	local result = module._GetMousePosition(part) -- mouse raycast

	if result then
		local topleftCFrame = part.CFrame * CFrame.new(part.Size.X/2, part.Size.Y/2, -part.Size.Z/2)
		local mouseCFrame = CFrame.lookAt(result.Position, result.Position+(result.Normal or Vector3.one)) * CFrame.Angles( 0, 0, math.rad(90) )
		local mouseCFrameRelative = topleftCFrame:ToObjectSpace(mouseCFrame)

		local alphaX = math.abs(mouseCFrameRelative.X)/part.Size.X
		local alphaY = math.abs(mouseCFrameRelative.Y)/part.Size.Y
		local mouseUDim2

		if scaling == module.ScalingTypes.UDim2Scale then
			mouseUDim2 = UDim2.fromScale(
				math.clamp( alphaX, 0, 1 ),
				math.clamp( alphaY, 0, 1 )
			)
		elseif scaling == module.ScalingTypes.UDim2Offset then
			mouseUDim2 = UDim2.fromOffset(
				math.clamp( alphaX*UI.AbsoluteSize.X, 0, UI.AbsoluteSize.X ),
				math.clamp( alphaY*UI.AbsoluteSize.Y, 0, UI.AbsoluteSize.Y )
			)
		end

		return mouseUDim2
	end
end

return module

Updated Model

SurfaceGui GetMouseLocation - Roblox

Example script:

local UIS = game:GetService("UserInputService")

local Module = require( script:WaitForChild("MouseLocation") )
local surfaceGui = script.Parent

UIS.InputBegan:Connect(function(input)
	local scalingType = Module.ScalingTypes.UDim2Offset -- or Module.ScalingTypes.UDim2Scale
	local mouseLocation = Module.GetMouseLocation(surfaceGui, scalingType)
	
	if input.UserInputType == Enum.UserInputType.MouseButton1 and mouseLocation then
		print("Mouse is inside SurfaceGui", mouseLocation)
	end
end)
5 Likes

here’s another version of the module that doesn’t use raycasting (results will only be accurate if the camera is infront of the ui)

local camera = workspace.CurrentCamera
local module = {}

module.ScalingTypes = {
	UDim2Scale = 1,
	UDim2Offset = 2,
}

function module.GetMouseLocation(UI: SurfaceGui, scaling: any)
	local part: Part = UI.Adornee or UI.Parent
	assert(part:IsA("BasePart"))
	
	local topleftCFrame = part.CFrame * CFrame.new(part.Size.X/2, part.Size.Y/2, -part.Size.Z/2)
	local downrightCFrame = part.CFrame * CFrame.new(-part.Size.X/2, -part.Size.Y/2, -part.Size.Z/2)
	
	local topleft: Vector3 = camera:WorldToScreenPoint(topleftCFrame.Position)
	local downright: Vector3 = camera:WorldToScreenPoint(downrightCFrame.Position)
	
	topleft = Vector2.new(topleft.X, topleft.Y)
	downright = Vector2.new(downright.X, downright.Y)
	
	local mouseLocation: Vector2 = game.UserInputService:GetMouseLocation()
	local mouseLocationRelative = mouseLocation-topleft
	local downrightLocationRelative = downright-topleft
	
	local alphaX = mouseLocationRelative.X/downrightLocationRelative.X
	local alphaY = mouseLocationRelative.Y/downrightLocationRelative.Y
	local mouseUDim2

	if scaling == module.ScalingTypes.UDim2Scale then
		mouseUDim2 = UDim2.fromScale(
			math.clamp( alphaX, 0, 1 ),
			math.clamp( alphaY, 0, 1 )
		)
	elseif scaling == module.ScalingTypes.UDim2Offset then
		mouseUDim2 = UDim2.fromOffset(
			math.clamp( alphaX*UI.AbsoluteSize.X, 0, UI.AbsoluteSize.X ),
			math.clamp( alphaY*UI.AbsoluteSize.Y, 0, UI.AbsoluteSize.Y )
		)
	end

	return mouseUDim2
end

return module

The code itself has a few questionable backend approaches that bend or break general golden rules. That being said, while this is a very specific use-case, it’s incredibly helpful nonetheless. Good work!

2 Likes