How to make a selection gui like this?

Screenshot 2022-08-20 at 1.34.43 PM
When I hold and drag my mouse around, it makes a gui box and every single model inside the gui box is selected ( kind of like Selected = true ).

The problem is, how do I make this gui box thingy select the models? Is there a special function like “IsOverlappingGui” ?? Thanks

3 Likes

I will only give you the concept of it since I’m busy at the time (might come back to give a script).

What you can do is store the mouse.Hit in a variable when the user begins dragging (UserInputService.InputBegan) and when they release their mouse (UserInputService.InputEnded) you store the mouse.Hit in another variable and then use the first and second mouse.Hit to create a Region3 (make sure you use the minimum of both positions as the min parameter and the maximum of both positions as the max parameter) and don’t forget to make an arbitrary Y position in both Vector3 or you could also calculate the Y position based on the camera’s distance from the mouse.Hit points. Lastly, you can use the “workspace:GetPartBoundsInBox()” function to determine the parts in the gui!

2 Likes

Yes . What you say works well , here an example of first part of your solution. (its in a local script,I used it to rotate a dummy with mouse, and this code just gets X axis diffrence)

local Players = game:GetService("Players")
local Player = Players.LocalPlayer
local Character = workspace.Dummy
local Mouse = Player:GetMouse()

function OnButton1Down()
	local Event
	local EndDrag = false
	if not Mouse.Target then return end
	local Dummy = Mouse.Target:FindFirstAncestor("Dummy")
	if Dummy then
		Event = Mouse.Button1Up:Connect(function()
			EndDrag = true
			Event:Disconnect()
		end)
		while not EndDrag
		do
			local StartValue = Mouse.X 
			wait(0.01)
			local EndValue = Mouse.X
			local Difference = -(StartValue - EndValue)
			Character.PrimaryPart.CFrame *= CFrame.Angles(0,math.rad(Difference),0) 
		end
	end
end

Mouse.Button1Down:Connect(OnButton1Down)

if you want to do it in server , use remote events

What if I want it to start selecting the parts as the box expands instead of selecting all the parts after the mouse is released?

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)

4 Likes

For that you can have a boolean variable that keeps track of if the user’s mouse is currently down and then have a UserInputService.InputChanged event (check the the Input.UserInputType == Enum.UserInputType.MouseMovement). You can then convert the code that handles getting all the parts from the two mouse.Hit variable and then call the function each time in the InputChanged event

You should try @S3nt1ne3l solution though