Maybe not an elegant solution but it works. It maps an array of instances if not given all descendants of workspace and checks if the worldToScreenPoint is between our selection bound.
Best performance is with and filterList and filterType “Whitelist” ofcourse
--- Services
local RunService = game:GetService("RunService")
local ContextActionService = game:GetService("ContextActionService")
--- Constants
local camera = workspace.Camera
local player = game:GetService("Players").LocalPlayer
local mouse = player:GetMouse()
--- Local vars
local renderConn
local selectionFrame
type FilterType = "Blacklist"|"Whitelist"
local function findPartsInGuiObject(guiObject: GuiObject, filterList: {Instance}?, filterType: FilterType?): {BasePart}
local a = guiObject.AbsolutePosition
local b = guiObject.AbsolutePosition + guiObject.AbsoluteSize
--- make sure a is the top left and b the bottom right
a, b = Vector2.new(math.min(a.X, b.X), math.min(a.Y, b.Y)), Vector2.new(math.max(a.X, b.X), math.max(a.Y, b.Y))
filterType = filterList and filterType or "Whitelist"
filterList = filterList or (filterType == "Blacklist" and {}) or workspace:GetDescendants()
local isBlacklist = filterType == "Blacklist"
local isWhitelist = filterType == "Whitelist"
local instances: {BasePart} = {};
local rel, x, y
for _, instance: BasePart in ipairs(isWhitelist and filterList or workspace:GetDescendants()) do
local point, onScreen = camera:WorldToScreenPoint(instance.Position)
if not onScreen then
continue
end
--- if FilterType is Blacklist check if the instance is a Descendant or not and skip if so
if isBlacklist then
local skip = false
for _, blacklistedInstance: Instance in ipairs(filterList :: {Instance}) do
if instance == blacklistedInstance or instance:IsDescendantOf(blacklistedInstance) then
skip = true
break
end
end
if skip then
continue
end
end
if a.X < point.X and point.X < b.X and a.Y < point.Y and point.Y < b.Y then
table.insert(instances, instance);
end
end
return instances;
end
--- Gui
--- Create the selection frame on the gui
local function createSelectionFrame()
local frame = Instance.new("Frame")
frame.BorderColor3 = Color3.new(0.278431, 0.568627, 1)
frame.BackgroundColor3 = Color3.new(0.278431, 0.568627, 1)
frame.BackgroundTransparency = 0.75
frame.Parent = script.Parent
return frame
end
--- Selection
--- Start the selection loop
local function startSelect()
if renderConn then
warn("Should not get called if its allready running!")
return
end
local startX, startY = mouse.X, mouse.Y
selectionFrame = createSelectionFrame()
renderConn = RunService.Heartbeat:Connect(function()
local endX, endY = mouse.X, mouse.Y
selectionFrame.Size = UDim2.fromOffset(endX - startX, endY - startY)
selectionFrame.Position = UDim2.fromOffset(startX, startY)
end)
end
--- Stops the selection loop
local function stopSelect()
if not renderConn then
warn("Should not get called if its not running!")
return
end
renderConn:Disconnect()
renderConn = nil
if selectionFrame then
selectionFrame:Destroy()
selectionFrame = nil
end
end
--- Input
ContextActionService:BindAction("CustomSelection", function(action: string, state: Enum.UserInputState)
if state == Enum.UserInputState.Begin then -- Check if input is down
startSelect() -- Start selection
elseif state == Enum.UserInputState.End then -- Check if input is up
-- usage: findPartsInGuiObject(GuiObject, {Instance}?, "Whitliste" | "Blacklist") -> {Instance}
--[[
use findPartsInGuiObject(selectionFrame, <<filterList>>, <<"Whitelist" | "Blacklist">>) here
]]--
stopSelect() -- Stop selection
end
end, false, Enum.UserInputType.MouseButton1) -- Bind to MouseButton1
If you want it to select while draging use the function inside a while task.wait or heartbeat loop.
local selectionSteppedConn
local whitelist = {}
ContextActionService:BindAction("CustomSelection", function(action: string, state: Enum.UserInputState)
if state == Enum.UserInputState.Begin then -- Check if input is down
startSelect() -- Start selection
local parts = {}
selectionSteppedConn = game:GetService("RunService").Heartbeat:Connect(function()
-- Clear hightlight from previous parts result
for _, part in ipairs(parts) do
part.SelectionBox.Visible = false
end
parts = findPartsInGuiObject(selectionFrame, whitelist, "Whitelist")
-- Hightlight new parts result
for _, part in ipairs(parts) do
part.SelectionBox.Visible = false
end
end)
elseif state == Enum.UserInputState.End then -- Check if input is up
if selectionSteppedConn then
selectionSteppedConn:Disconnect()
selectionSteppedConn = nil
end
stopSelect() -- Stop selection
end
end, false, Enum.UserInputType.MouseButton1) -- Bind to MouseButton1
Preview place:
Selection on release: SelectionExample.rbxl (80.3 KB)
Selection while dragging:
SelectionWhileDragExample.rbxl (81.0 KB)