I’m currently working on a retro style game, but since the old building tools are completely busted now, I have to recreate them. (the only thing I’m actively using is the move tool)
Currently, I have the new move tool pretty much down solid, though there is one thing that’s heavily bothering me, and that’s making the parts that are being dragged snap to the surface of the part they’re being dragged on.
I’ve tried multiple times to implement this and all those attempts have failed, sadly.
Here’s what I have:
Here’s what I want: (snapping to the surface only, nothing else is needed)
Script:
-- Dragger Local Side
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local CollectionService = game:GetService("CollectionService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local setPartEvent = ReplicatedStorage:WaitForChild("SetPart")
local setCollisionGroupEvent = ReplicatedStorage:WaitForChild("SetCollisionGroup")
local player = Players.LocalPlayer
local mouse = player:GetMouse()
local tool = script.Parent
local selectionBox = tool:WaitForChild("SelectionBox")
local dragDistance = 30
local isDragging = false
local partBeingDragged: Part
local selectionBoxConnec
-- Variables --
local function CleanUp()
if selectionBoxConnec then
selectionBoxConnec:Disconnect()
end
partBeingDragged = nil
selectionBox.Adornee = tool
end
local function PartIsOk(part)
if part and CollectionService:HasTag(part, "Draggable") and player:DistanceFromCharacter(part.Position) < dragDistance then
return true
end
return false
end
local function SetSelectionBox()
selectionBoxConnec = RunService.Heartbeat:Connect(function()
if PartIsOk(mouse.Target) and not isDragging then
selectionBox.Adornee = mouse.Target
selectionBox.Color3 = selectionBox.Adornee.Color
else
selectionBox.Adornee = tool
end
if isDragging then
selectionBox.Adornee = partBeingDragged
end
end)
end
local function MouseDragging()
local alignPos = Instance.new("AlignPosition")
local attachment = Instance.new("Attachment")
local alignOrien = Instance.new("AlignOrientation")
local attachment2 = Instance.new("Attachment")
alignPos.Mode = Enum.PositionAlignmentMode.OneAttachment
alignPos.ApplyAtCenterOfMass = true
alignPos.RigidityEnabled = true
alignPos.Attachment0 = attachment
alignPos.Parent = partBeingDragged
attachment.Position = Vector3.new(0, 0, 0)
attachment.Parent = partBeingDragged
if partBeingDragged.Name == "Dough" then -- Name checking is due to the parts in my game
alignOrien = Instance.new("AlignOrientation")
attachment2 = Instance.new("Attachment")
alignOrien.Mode = Enum.OrientationAlignmentMode.OneAttachment
alignOrien.RigidityEnabled = true
alignOrien.Attachment0 = attachment2
alignOrien.PrimaryAxis = Vector3.new(0, 1, 0)
alignOrien.Parent = partBeingDragged
attachment2.Position = Vector3.new(0, 0, 0)
attachment2.Parent = partBeingDragged
elseif string.match(partBeingDragged.Name, "Box") or partBeingDragged.Name == "Dew" then -- Name checking is due to the parts in my game
alignOrien = Instance.new("AlignOrientation")
attachment2 = Instance.new("Attachment")
alignOrien.Mode = Enum.OrientationAlignmentMode.OneAttachment
alignOrien.RigidityEnabled = true
alignOrien.Attachment0 = attachment2
alignOrien.PrimaryAxis = Vector3.new(1, 0, 0)
alignOrien.Parent = partBeingDragged
attachment2.Position = Vector3.new(0, 0, 0)
attachment2.Parent = partBeingDragged
end
while partBeingDragged do
local mousePos = mouse.Hit.Position
if player:DistanceFromCharacter(mousePos) < dragDistance then
alignPos.Position = mousePos
end
RunService.Heartbeat:Wait()
end
alignPos.Position = Vector3.new(mouse.Hit.Position.X, mouse.Hit.Position.Y + 0.25, mouse.Hit.Position.Z)
alignPos:Destroy()
attachment:Destroy()
alignOrien:Destroy()
attachment2:Destroy()
end
-- Upon player clicking, check if target is valid, then, move the part being dragged along with
-- the player's mouse, incrementing it as well
local function BeginDrag()
if not PartIsOk(mouse.Target) then return end
isDragging = true
partBeingDragged = mouse.Target
mouse.TargetFilter = partBeingDragged
setPartEvent:FireServer(partBeingDragged) -- Just here to set the player has network owner of the part
setCollisionGroupEvent:FireServer(partBeingDragged, true)
MouseDragging()
end
local function StopDrag()
local pos
if partBeingDragged then
setCollisionGroupEvent:FireServer(partBeingDragged, false)
end
mouse.TargetFilter = nil
partBeingDragged = nil
isDragging = false
end
-- When player equips tool, check if target is draggable, if it is, adorn selection box
tool.Equipped:Connect(function()
SetSelectionBox()
end)
tool.Unequipped:Connect(CleanUp)
tool.Activated:Connect(BeginDrag)
tool.Deactivated:Connect(StopDrag)
Thanks! This works for snapping it on the ground and preventing it from clipping through. Though, I assume for different orientations I would need to divide the x and z by 2 depending on where I’m dragging it?
the size of the part aka lets say ur parts size x = 2, y = 1 and z = 4
so for each of them u have to put the snap so
local function snap(Number,Snap)
return math.round(Number/Snap+ 0.5) * Snap
end
local snappedvector = Vector3.new(snap(--[[urnum]],part.Size.X),snap(--[[urnum]],part.Size.Y),snap(--[[urnum]],part.Size.Z))
local part = path.to.part
local mouse = game:GetService("Players").LocalPlayer:GetMouse()
game:GetService("UserInputService").InputChanged:Connect(function(input)
if (input.UserInputType == Enum.UserInputType.MouseMovement) then
local ray = workspace:Raycast(part.Position, Vector3.new(0,10,0))
part.Position = Vector3.new(
math.floor(mouse.Position.X),
part.Size.Y*0.5+ray.Position.Y,
math.floor(mouse.Position.Z)
)
end
end)
So implementing this, it’s actually mostly what I’m looking for, but I want the part to snap to every 1 stud rather than the part’s size.
For example, the part in this video is snapping to every 4 studs since it’s 4x4x4 in size:
Would you have any idea on how to go about this, or would I just need to mess around with it until it works?
What I have currently:
local x = (math.round(mousePos.X / partBeingDragged.Size.X + 0.5) * partBeingDragged.Size.X)
local y = (math.round(mousePos.Y / partBeingDragged.Size.Y + 0.5) * partBeingDragged.Size.Y)
local z = (math.round(mousePos.Z / partBeingDragged.Size.Z + 0.5) * partBeingDragged.Size.Z)
alignPos.Position = Vector3.new(x, y, z)
The only issue with it is that it doesn’t allow snapping against walls you drag the part against, only the floors. I’m not 100% sure if there’s a way to detect if you’re dragging a part against a wall with Roblox’s current features
RayCast allows you to get the Normal Vector of whatever part was hit by the ray. I actually wrote up a solution earlier that utilized the Normal Vector to allowing snapping to floors, ceilings, or walls - but I completely forgot to post it.
local UserInputService = game:GetService ("UserInputService")
local Part = Instance.new ("Part")
Part.Size = Vector3.new (math.random (1, 10), math.random (1, 10), math.random (1, 10))
Part.Anchored = true
Part.Parent = workspace
local Player = game:GetService ("Players").LocalPlayer
local Camera = workspace.CurrentCamera
local Params = RaycastParams.new ()
Params.FilterType = Enum.RaycastFilterType.Blacklist
Params.FilterDescendantsInstances = {Player, Part}
UserInputService.InputChanged:Connect (function (input, gameProcessed)
if gameProcessed or input.UserInputType ~= Enum.UserInputType.MouseMovement then
return
end
local mouseLocation = UserInputService:GetMouseLocation ()
local unitRay = Camera:ScreenPointToRay (mouseLocation.X, mouseLocation.Y)
local result = workspace:Raycast (unitRay.Origin, unitRay.Direction * 500, Params)
if result and result.Instance then
local roundedPosition = Vector3.new (math.round (result.Position.X), result.Position.Y, math.round (result.Position.Z))
Part.Position = roundedPosition + result.Normal * (Part.Size / 2)
end
end)
Yeah, I had no idea Roblox natively supported Normal Vectors with Raycasting. I only just found out about them recently and they’ve already helped me solve some problems that I could barely wrap my mind around.