Part Dragging System (MOBILE SUPPORT)

How would I go about making a dragging system that works for mobile aswell.

My aim is something that is smooth not jagged.

1 Like

Check this out:

Already gave this a try a while ago, they dont work as intended.
You can hold the part and move it etc, but when you move your character away or towards the block doesn’t move.

Example from another devforum post

1 Like

If you want it to feel like the object is being held in 3D space you will need to make the script yourself. 3D drag detectors are kinda limited nowadays. Try this script:

local UIS = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local camera = workspace.CurrentCamera

local dragging = false
local draggedPart = nil
local distance = 10 -- how far from camera to hold the object

-- Raycast to pick up the part
mouse.Button1Down:Connect(function()
	local result = mouse.Target
	if result and result:IsA("BasePart") then
		dragging = true
		draggedPart = result
		distance = (camera.CFrame.Position - result.Position).Magnitude
	end
end)

-- Release when mouse lifted
mouse.Button1Up:Connect(function()
	dragging = false
	draggedPart = nil
end)

-- Update dragged part position
RunService.RenderStepped:Connect(function()
	if dragging and draggedPart then
		local unitRay = camera:ScreenPointToRay(mouse.X, mouse.Y)
		local targetPosition = unitRay.Origin + unitRay.Direction * distance

		-- Optional: Tween or smooth this movement
		draggedPart.CFrame = CFrame.new(targetPosition)
	end
end)

I’ll give this a go soon, would this also work for mobile?

At the moment I’ve got this

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local InputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

local Modules = ReplicatedStorage.Modules
local Packages = ReplicatedStorage.Packages

local Mouse = require(Packages.Mouse)
local JanitorModule = require(Packages.Janitor)

local BlockController = require(script.Parent.BlockController)

local DragController = {
	isDragging = false :: RBXScriptConnection
}

function DragController:stopDragging()
	if not self.isDragging then return end
	self.isDragging:Disconnect()
end

function DragController:startDragging(part: BasePart)
	self.isDragging = RunService.Heartbeat:Connect(function()
		part.Position = Mouse.Hit.Position
	end)
end

function DragController:Init()
	
end

function DragController:Start()
	InputService.InputBegan:Connect(function(input: InputObject, GPE: boolean)
		if GPE then return end
		
		if input.UserInputType == Enum.UserInputType.Touch then
			-- mobile
		elseif input.UserInputType == Enum.UserInputType.MouseButton1 then
			-- computa
		end
	end)
	
	InputService.InputEnded:Connect(function(input: InputObject)
		
	end)
end

return DragController

Well, no.
But with a few tweaks it may work:

local UIS = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

local player = game.Players.LocalPlayer
local camera = workspace.CurrentCamera

local dragging = false
local draggedPart = nil
local holdDistance = 10
local touchPosition = Vector2.new()

UIS.InputChanged:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseMovement then
		touchPosition = input.Position
	end
end)

UIS.InputBegan:Connect(function(input, processed)
	if processed then return end

	if input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseButton1 then
		touchPosition = input.Position

		local ray = camera:ScreenPointToRay(touchPosition.X, touchPosition.Y)
		local raycastParams = RaycastParams.new()
		raycastParams.FilterDescendantsInstances = {player.Character}
		raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
		local result = workspace:Raycast(ray.Origin, ray.Direction * 100, raycastParams)

		if result and result.Instance and result.Instance:IsA("BasePart") then
			dragging = true
			draggedPart = result.Instance
			holdDistance = (camera.CFrame.Position - result.Position).Magnitude
		end
	end
end)

UIS.InputEnded:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseButton1 then
		dragging = false
		draggedPart = nil
	end
end)

RunService.RenderStepped:Connect(function()
	if dragging and draggedPart then
		local ray = camera:ScreenPointToRay(touchPosition.X, touchPosition.Y)
		local targetPos = ray.Origin + ray.Direction * holdDistance
		draggedPart.CFrame = CFrame.new(targetPos)
	end
end)


Try this one, I changed it so it may work now for both mobile and PC (and maybe console)
I found this script inside a place I made 2 years ago btw. But it still works since Roblox still uses mouse

Let me know if it worked

This works as intended but how would i make it so it isnt with the camera constantly, rather capped around holddistance

The issue with using the camera ray directly is that the dragged part will always try to follow the exact direction of the camera, which can make it ‘float’ too farwhen the player moves
Instead of locking the part to the ray, what you can do is define a “hold distance” (like 10 studs) in front of the character, raycast from the screen like normal, but instead of moving the part exactly to where the ray hits.
That way, it doesn’t glitch when the player walks around.

It still lets you move and drag the part with touch or mouse, but the part doesn’t fly off or stick too close to the camera. It also works on mobile without needing to manually position attachments or welds.

Here’s the updated script:

local UIS = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

local player = game.Players.LocalPlayer
local camera = workspace.CurrentCamera

local character = player.Character or player.CharacterAdded:Wait()
local rootPart = character:WaitForChild("HumanoidRootPart")

local dragging = false
local draggedPart = nil
local maxDistance = 10
local touchPosition = Vector2.zero

UIS.InputChanged:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseMovement then
		touchPosition = input.Position
	end
end)

UIS.InputBegan:Connect(function(input, processed)
	if processed then return end

	if input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseButton1 then
		touchPosition = input.Position

		local ray = camera:ScreenPointToRay(touchPosition.X, touchPosition.Y)
		local raycastParams = RaycastParams.new()
		raycastParams.FilterDescendantsInstances = {character}
		raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
		local result = workspace:Raycast(ray.Origin, ray.Direction * 100, raycastParams)

		if result and result.Instance and result.Instance:IsA("BasePart") then
			local partPos = result.Instance.Position
			local distance = (rootPart.Position - partPos).Magnitude
			if distance <= maxDistance then
				dragging = true
				draggedPart = result.Instance
			end
		end
	end
end)

UIS.InputEnded:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseButton1 then
		dragging = false
		draggedPart = nil
	end
end)

RunService.RenderStepped:Connect(function()
	if dragging and draggedPart then
		local ray = camera:ScreenPointToRay(touchPosition.X, touchPosition.Y)
		local target = ray.Origin + ray.Direction * maxDistance
		local origin = rootPart.Position + rootPart.CFrame.LookVector * maxDistance

		local clampedPos = origin:Lerp(target, 0.5)
		draggedPart.CFrame = CFrame.new(clampedPos)
	end
end)

Let me know if this works. Sorry for the long explanation, I just wanted you to understand :smiling_face_with_tear:

It slightly works but it still moves with the camera, also doesn’t follow the mouse fully anymore, rather slightly drifts towards it and stops

Can you show me a video showcasing this? So I can update the script ‘more clearly’

Yep, I think I already saw the problem.
First of all, make sure it’s on a seperated script [ignore this if you’re sure that it’s correctly placed (atleast the source)]
If you put the source inside a script already made before, tell me if it’s Server script or Local, because the script I gave you is only for Local.
Second of all, make sure the script is placed inside a tool.

Here’s the script

local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local camera = workspace.CurrentCamera
local runService = game:GetService("RunService")
local uis = game:GetService("UserInputService")

local character = player.Character or player.CharacterAdded:Wait()
local root = character:WaitForChild("HumanoidRootPart")

local dragging = false
local draggedPart = nil

local maxDistance = 15
local holdOffset = Vector3.new(0, 0, -8) -- Position to hold object in front of player

-- Get a point in front of the character (ignores camera angle)
local function getHoldPosition()
	return root.Position + root.CFrame:VectorToWorldSpace(holdOffset)
end

-- Try to find a draggable part
local function getDraggablePart()
	local ray = Ray.new(camera.CFrame.Position, (mouse.Hit.p - camera.CFrame.Position).Unit * maxDistance)
	local part = workspace:FindPartOnRay(ray, character)
	return part
end

-- Start dragging
local function beginDrag(part)
	dragging = true
	draggedPart = part
	if draggedPart:IsA("BasePart") then
		draggedPart:SetNetworkOwner(player)
	end
end

-- Stop dragging
local function endDrag()
	dragging = false
	draggedPart = nil
end

-- Input handling
mouse.Button1Down:Connect(function()
	local part = getDraggablePart()
	if part then
		beginDrag(part)
	end
end)

mouse.Button1Up:Connect(endDrag)

uis.InputEnded:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.Touch then
		endDrag()
	end
end)

-- Update loop
runService.RenderStepped:Connect(function()
	if dragging and draggedPart then
		local desiredPosition = getHoldPosition()
		local direction = (mouse.Hit.p - desiredPosition)
		local targetPosition = desiredPosition + direction.Unit * math.min(direction.Magnitude, maxDistance)

		-- Smoothly move the part
		draggedPart.Velocity = (targetPosition - draggedPart.Position) * 10
	end
end)

Again… Let me know if it’s better now
I think the problem was that the part was influenced by the camera, and the drift happens because the part is trying to lerp or tween towards a target that’s moving around too ‘fast’

1 Like

Would this be a server script, as for the SetNetworkOwner

1 Like

LocalScript, client sets owner
Server has to set an owner, but local is already LocalPlayer
You can also send the whole script so I can make some changes so it fits better.

But is your script local or server?

Ive been using a localscript inside of StarterPlayerScripts

Ok, perfect!
It could be inside a Tool or inside StarterPlayerScripts. Both work. I forgot to mention that too.
Well, but did the last script I gave you work?

It didn’t it seemed to just not function at all, also get a “Client can’t set network ownership error”

My bad.
Remove that line, It should work :+1:Mixed up Server with Local. Sorry

Doesn’t work
Local Script inside of StarterPlayerScript

This time it doesnt drag at all

Try to put the script inside the tool.
And make sure the part is unachored