The issue could be that your current code only positions objects at the mouse location without checking for nearby parts to snap to.
Here’s an improved version with snapping logic:
local SNAP_DISTANCE = 2
local SNAP_ENABLED = true
local function GetObjectSize(object)
if object:IsA("Model") then
return object:GetExtentsSize()
else
return object.Size
end
end
local function GetObjectPosition(object)
if object:IsA("Model") then
return object:GetPivot().Position
else
return object.Position
end
end
local function SetObjectPosition(object, position)
if object:IsA("Model") then
object:PivotTo(CFrame.new(position))
else
object.Position = position
end
end
local function FindNearbyParts(position, searchRadius)
local parts = {}
local region = Region3.new(
position - Vector3.new(searchRadius, searchRadius, searchRadius),
position + Vector3.new(searchRadius, searchRadius, searchRadius)
)
region = region:ExpandToGrid(4)
local overlapParams = OverlapParams.new()
overlapParams.FilterDescendantsInstances = {CurrentObject}
overlapParams.FilterType = Enum.RaycastFilterType.Exclude
parts = workspace:GetPartBoundsInBox(CFrame.new(position), Vector3.new(searchRadius * 2, searchRadius * 2, searchRadius * 2), overlapParams)
return parts
end
local function CalculateSnapPosition(currentPos, currentSize)
if not SNAP_ENABLED then
return currentPos
end
local nearbyParts = FindNearbyParts(currentPos, SNAP_DISTANCE + 10)
local bestSnapPos = currentPos
local closestDist = math.huge
for _, part in ipairs(nearbyParts) do
if part.Parent == workspace.Terrain or part == CurrentObject then
continue
end
local partPos = part.Position
local partSize = part.Size
local snapPoints = {
Vector3.new(partPos.X + partSize.X/2 + currentSize.X/2, currentPos.Y, partPos.Z),
Vector3.new(partPos.X - partSize.X/2 - currentSize.X/2, currentPos.Y, partPos.Z),
Vector3.new(partPos.X, currentPos.Y, partPos.Z + partSize.Z/2 + currentSize.Z/2),
Vector3.new(partPos.X, currentPos.Y, partPos.Z - partSize.Z/2 - currentSize.Z/2),
Vector3.new(partPos.X, partPos.Y + partSize.Y/2 + currentSize.Y/2, partPos.Z),
Vector3.new(partPos.X, partPos.Y - partSize.Y/2 - currentSize.Y/2, partPos.Z),
}
for _, snapPoint in ipairs(snapPoints) do
local dist = (currentPos - snapPoint).Magnitude
if dist < SNAP_DISTANCE and dist < closestDist then
closestDist = dist
bestSnapPos = snapPoint
end
end
end
return bestSnapPos
end
local function MoveCurrentObjectToMouse(dt)
if not CurrentObject then
return
end
if PerformanceMode then
mcotm_elapsed += dt
if mcotm_elapsed < mcotm_interval then
return
end
mcotm_elapsed = 0
end
Mouse.TargetFilter = CurrentObject
local hitPos = Mouse.Hit.Position
local ray_origin = hitPos + Vector3.new(0, 10, 0)
local ray_direction = Vector3.new(0, -30, 0)
local objectSize = GetObjectSize(CurrentObject)
if (RootPart.Position - hitPos).Magnitude > 70 then
return
end
local rayParams = RaycastParams.new()
rayParams.FilterDescendantsInstances = {workspace.Terrain}
rayParams.FilterType = Enum.RaycastFilterType.Include
local rayResult = workspace:Raycast(ray_origin, ray_direction, rayParams)
if not rayResult then
return
end
local basePos = hitPos + Vector3.new(0, objectSize.Y/2, 0)
local snappedPos = CalculateSnapPosition(basePos, objectSize)
SetObjectPosition(CurrentObject, snappedPos)
end
local function CreateSnapIndicator()
local indicator = Instance.new("Part")
indicator.Size = Vector3.new(0.5, 0.5, 0.5)
indicator.Anchored = true
indicator.CanCollide = false
indicator.Material = Enum.Material.Neon
indicator.Color = Color3.fromRGB(0, 255, 0)
indicator.Transparency = 0.5
indicator.Name = "SnapIndicator"
indicator.Parent = workspace
return indicator
end
The CreateSnapIndicator function is just for visual feedback and I’m not 100% if this works