I am trying to achieve a model selecting system, like those used in roblox studio where you click and drag.
The issue about this though, is that I don’t know what I should do to get the models selected.
So far, I have searched google and also the developer hub, but to no avail.
Here’s an example of what I mean by selection:
image not mine, googled it
TL;DR: I need a way of getting models selected with a frame on a gui.
I got the basic frame click and drag, but it doesn’t know what got selected.
Could anyone point me to the solution?
If you already have the click-and-drag effect down, it seems the next step would be to create a method to determine if two frames are intersecting. Each mouse movement event should then run this method to find the frames that are colliding with the selection frame.
Edit 8/9/2021: I’ve cleaned up some the wording and improved the code example a bit.
Have you seen this post it pretty much answers the Main part of your question:
Probably one of the easiest and cheapest ways to get a model, in particular, would be just to get its’ AABB or Bounding Box instead of its’ individual parts. Here’s a code example with the math used above:
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local GuiService = game:GetService('GuiService')
local Inset = GuiService:GetGuiInset()
local Camera = workspace.CurrentCamera
local selectionFrame = script.Parent
local searchTable = workspace.searchable_objs:GetChildren() --// array of searchable objs
local mouseDown = false
local lastPos
local function To3dSpace(pos)
return Camera:ScreenPointToRay(pos.x, pos.y).Origin
end
local function CalcSlope(vec)
local rel = Camera.CFrame:pointToObjectSpace(vec)
return Vector2.new(rel.x/-rel.z, rel.y/-rel.z)
end
local function Overlaps(cf, a1, a2)
local rel = Camera.CFrame:ToObjectSpace(cf)
local x, y = rel.x / -rel.z, rel.y / -rel.z
return (a1.x) < x and x < (a2.x)
and (a1.y < y and y < a2.y) and rel.z < 0
end
local function Swap(a1, a2)
return Vector2.new(math.min(a1.x, a2.x), math.min(a1.y, a2.y)),
Vector2.new(math.max(a1.x, a2.x), math.max(a1.y, a2.y))
end
local function Search(objs, p1, p2)
local Found = {}
local a1 = CalcSlope(p1)
local a2 = CalcSlope(p2)
a1, a2 = Swap(a1, a2)
for _ ,obj in ipairs(objs) do
local cf = obj:IsA("Model") --// for models use GetBoundingBox()
and obj:GetBoundingBox() or obj.CFrame
if Overlaps(cf,a1, a2) then
table.insert(Found, obj)
end
end
return Found
end
UserInputService.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
lastPos = Vector2.new(input.Position.x, input.Position.y)
mouseDown = true
end
end)
UserInputService.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
local pos = Vector2.new(input.Position.x, input.Position.y)
local result = Search(searchTable, To3dSpace(lastPos), To3dSpace(pos))
mouseDown = false; selectionFrame.Visible = false
print(result) --// print "found" table
end
end)
RunService.Heartbeat:Connect(function() --// render selectionFrame gui
if mouseDown then
local pos = UserInputService:GetMouseLocation()
local lastPos = lastPos + Inset
local Center = ((lastPos+ pos) * .5) - Inset --// if no inset remove ' - Inset'
local DistX = math.abs(lastPos.X - pos.X)
local DistY = math.abs(lastPos.Y - pos.Y)
selectionFrame.Position = UDim2.new(0, Center.X,0, Center.Y)
selectionFrame.Size = UDim2.new(0, DistX,0, DistY)
selectionFrame.Visible = true --// if not visible
end
end)
Old/Orginal Code
local Mouse = game.Players.LocalPlayer:GetMouse()
local RunService = game:GetService("RunService")
local camera = workspace.CurrentCamera
local UIS = game:GetService("UserInputService")
local LastHit = Vector2.new(Mouse.x, Mouse.y)
local Boxselect = script.Parent ---- Selection Gui
local Cache = {} -- For the Mouse Start and end positions
local function SearchForObjects(Objects,HitCache, r1, r2) ---- Math and conditions and stuff....(Adapted from source above)
if r1 and r2 then
local a1, a2 = Vector2.new(math.min(r1.x, r2.x), math.min(r1.y, r2.y)), Vector2.new(math.max(r1.x, r2.x), math.max(r1.y, r2.y));
local rel, x, y;
local ObjectsFound = HitCache or {}
for i, obj in pairs(Objects) do
rel = camera.CoordinateFrame:ToObjectSpace(obj.CFrame);
x, y = rel.x / -rel.z, rel.y / -rel.z;
if (a1.x) < x and x < (a2.x) and (a1.y < y and y < a2.y) and rel.z < 0 then
ObjectsFound[obj] = obj
end
end
return ObjectsFound
end
end
local function HitCache(ModelFolder) ----Function For adding mouse target to the hit cache
if Mouse.Target and table.find(ModelFolder, Mouse.Target) then
Cache[Mouse.Target] = Mouse.Target
end
end
local function CalcSlope(vec) --- Calculate Slope of Object space cords
local rel=workspace.CurrentCamera.CoordinateFrame:pointToObjectSpace(vec)
return Vector2.new(rel.x/-rel.z, rel.y/-rel.z)
end
local function ModelsToPart( ModelFolder ) -- Transform any models into parts
local Table = {}
for _, obj in pairs(ModelFolder:GetChildren()) do
if obj:IsA("Model") then
local BoundingBox = Instance.new("Part")
BoundingBox.Name = obj.Name
BoundingBox.Anchored = true
BoundingBox.Transparency = 1
BoundingBox.CanCollide = false
local AABB_Pos, AABB_Size = obj:GetBoundingBox()
BoundingBox.Size = AABB_Size
BoundingBox.CFrame = AABB_Pos
BoundingBox.Parent = obj
table.insert(Table , BoundingBox)
elseif obj:IsA("Part") then
table.insert(Table , obj)
end
end
return Table
end
local AllModels = ModelsToPart(game.Workspace.Folder)
local MouseDown = false
local StartSlope
local EndSlope
UIS.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
local MousePos = Vector2.new(Mouse.x, Mouse.y)
Cache = {} ---Reset Cache
Boxselect.Visible = true
StartSlope = CalcSlope(Mouse.Hit.p)
LastHit = MousePos
HitCache(AllModels)
MouseDown = true
end
end)
UIS.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
local MousePos = Vector2.new(Mouse.x, Mouse.y)
LastHit = MousePos
EndSlope = CalcSlope(Mouse.Hit.p)
HitCache(AllModels)
local Objects = SearchForObjects(AllModels, Cache , StartSlope, EndSlope)
for _, Obj in pairs(Objects) do
print(Obj.Name.." Found!") --Print What we found
---Do whatever from here.....
end
MouseDown =false
Boxselect.Visible = false
end
end)
RunService.RenderStepped:Connect(function() --- For Selection GUI
if MouseDown then
local Hit = Vector2.new(Mouse.x, Mouse.y)
local Distance = (LastHit - Hit).Magnitude
local Center = (LastHit + Hit) / 2
local DistX = math.abs(LastHit.X - Hit.X)
local DistZ = math.abs(LastHit.Y - Hit.Y)
Boxselect.Position = Boxselect.Position:Lerp( UDim2.new(0, Center.X,0, Center.Y), 1)
Boxselect.Size = Boxselect.Size:Lerp( UDim2.new(0, DistX,0, DistZ ), 1)
end
end)
if you wanted to get all models in the workspace you can use get descendants, i used the DescendantAdded event in replicated first it seemed to not lag at all, but i haven’t tested get descendants, so you’ll probably have to do some testing on that part
The code given in your example seems to be inverted, and i’ve tried using negatives and different numbers. In the end, it didn’t seem to work as expected, could you help me out there?
So, when i click and drag, it doesn’t clip to the mouse pointer and resizes in a weird way. Reproducing: Just copy and paste your script inside a localscript in a frame.