Dragging System

hi, i’m trying to make a dragging system, similar to the one below:

this system is exactly what i need, and i can come pretty close to that with a simple drag detector:

however, while the x and y axis work fine, if you try moving in the z axis relative to the player, this happens:

i’ve noticed that in the game that i showed that has proper dragging, the distance between the object and the camera is always the same, so i’m pretty sure its moved through camera position, ect., but i’ve been trying to get this to work for atleast 5 hours now and i haven’t even come close to making it work with the z axis.

any help?

4 Likes

Without looking at the script directly, you most likely need to add an offset based on the players position and update it using RunService.RenderStepped:Connect

You can take a look at this script that I put together to get some ideas, though mine uses mouse position instead of camera position to update the parts position directly. Also, this is local side, so it does not effect other players.

Hope it helps

Local script:

--=======================
-- Local Script for Dragging Objects
-- Handles detecting mouse input and triggering the DragDetector module.
--=======================

--=======================
-- Module & Player Setup
--=======================
local DragDetector = require(script:WaitForChild("DragDetector")) -- Load DragDetector module
local player = game.Players.LocalPlayer
local mouse = player:GetMouse()

--=======================
-- Draggable Object Configuration
-- Determines whether a part is draggable based on attributes.
--=======================
local isDraggable = true -- Default draggable setting (can be overridden per part)

-- Example usage:
-- local basepart = workspace.TestPart
-- basepart:SetAttribute("Draggable", true) -- Make this specific part draggable
-- Alternatively, add the attribute manually via the properties window in Studio.

--=======================
-- Mouse Click Detection
-- Listens for left mouse clicks and checks if the clicked part is draggable.
--=======================
mouse.Button1Down:Connect(function()
	local target = mouse.Target

	if target and target:IsA("BasePart") then
		-- Check if the part has a 'Draggable' attribute, defaulting to `true` if not set
		local isDraggable = target:GetAttribute("Draggable") or true 

		-- Start dragging the part if it is draggable
		DragDetector.StartDragging(player, target, isDraggable)
	end
end)

Module Script:

--=======================
-- DragDetector Module
-- Handles dragging parts based on the player's mouse input, 
-- adjusting distance using right-click + W/S, and locking player movement.
--=======================

local DragDetector = {}

--=======================
-- Services
--=======================
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")

--=======================
-- Configuration
--=======================
local MAX_DRAG_DISTANCE = 30   -- Maximum allowed distance from player
local MIN_DRAG_DISTANCE = 10   -- Minimum allowed distance
local MOVE_SENSITIVITY = 1     -- Speed at which the part moves closer/further
local movementLocked = false   -- Track if the player’s movement is locked

--=======================
-- Lock / Unlock Player Movement
--=======================
local function lockPlayerMovement(player, lock)
	local character = player.Character
	local humanoid = character and character:FindFirstChildOfClass("Humanoid")
	
	if humanoid then
		if lock then
			humanoid.WalkSpeed = 0
			humanoid.JumpPower = 0
			movementLocked = true
		else
			humanoid.WalkSpeed = 16 -- Default WalkSpeed
			humanoid.JumpPower = 50 -- Default JumpPower
			movementLocked = false
		end
	end
end


--=======================
-- UI Generator for Controls
-- Creates a ScreenGui with controls for adjusting the distance (W/S)
--=======================

local function createDistanceAdjustUI(player)
	-- Create the ScreenGui
	local screenGui = Instance.new("ScreenGui")
	screenGui.Name = "DragControlsUI"
	screenGui.Parent = player:WaitForChild("PlayerGui")

	-- Create the Frame to hold the labels
	local frame = Instance.new("Frame")
	frame.Name = "ControlFrame"
	frame.Size = UDim2.new(0, 200, 0, 100)  -- Size of the frame (adjustable)
	frame.Position = UDim2.new(1, -210, 1, -110)  -- Positioned at the bottom right of the screen
	frame.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
	frame.BackgroundTransparency = 0.5
	frame.BorderSizePixel = 0
	frame.Parent = screenGui

	-- Create Label for W (Increase distance)
	local labelW = Instance.new("TextLabel")
	labelW.Name = "LabelW"
	labelW.Size = UDim2.new(1, 0, 0.5, 0)  -- Takes up half of the frame
	labelW.Position = UDim2.new(0, 0, 0, 0)
	labelW.Text = "Hold W + Right Click to increase distance"
	labelW.TextColor3 = Color3.fromRGB(255, 255, 255)
	labelW.TextScaled = true
	labelW.BackgroundTransparency = 1
	labelW.Parent = frame

	-- Create Label for S (Decrease distance)
	local labelS = Instance.new("TextLabel")
	labelS.Name = "LabelS"
	labelS.Size = UDim2.new(1, 0, 0.5, 0)  -- Takes up the second half of the frame
	labelS.Position = UDim2.new(0, 0, 0.5, 0)
	labelS.Text = "Hold S + Right Click to decrease distance"
	labelS.TextColor3 = Color3.fromRGB(255, 255, 255)
	labelS.TextScaled = true
	labelS.BackgroundTransparency = 1
	labelS.Parent = frame

	-- Return the created ScreenGui for further management if needed
	return screenGui
end



--=======================
-- Show/Hide UI based on dragging state
-- Shows the UI when an item is being dragged, hides when not
--=======================
local function toggleUIDuringDrag(screenGui, isDragging)
	if isDragging then
		screenGui.Enabled = true
	else
		screenGui.Enabled = false
	end
end


--=======================
-- Start Dragging Function
--=======================
function DragDetector.StartDragging(player, targetPart, isDraggable)
	-- Ensure the part is draggable before proceeding
	if not isDraggable then
		return -- Do nothing if the part is not draggable
	end

	local screenGui = createDistanceAdjustUI(player)  -- Create the UI when dragging starts
	screenGui.Enabled = false  -- Hide the UI initially

	-- Validate the target part
	if not targetPart or not targetPart:IsA("BasePart") or targetPart.Anchored then --Add in different rule sets to determine which part can be dragged. Currenlty only unAnchored BaseParts can be
		return 
	end

	-- Get necessary player objects
	local mouse = player:GetMouse()
	local character = player.Character
	local humanoidRootPart = character and character:FindFirstChild("HumanoidRootPart")
	if not humanoidRootPart then return end

	-- Dragging state variables
	local dragging = true
	local lastCharacterPosition = humanoidRootPart.Position
	local distanceFromPlayer = (targetPart.Position - humanoidRootPart.Position).Magnitude

	-- Clamp distance within allowed range
	distanceFromPlayer = math.clamp(distanceFromPlayer, MIN_DRAG_DISTANCE, MAX_DRAG_DISTANCE)

	--=======================
	-- Distance Adjustment While Holding Right Click + W/S
	--=======================
	local isHoldingW = false
	local isHoldingS = false

	-- Function to continuously adjust distance while holding W or S
	local function adjustDistance()
		lockPlayerMovement(player, true) -- Lock player movement when adjusting

		while dragging and (isHoldingW or isHoldingS) do
			if isHoldingW then
				distanceFromPlayer = math.clamp(distanceFromPlayer + MOVE_SENSITIVITY, MIN_DRAG_DISTANCE, MAX_DRAG_DISTANCE)
			elseif isHoldingS then
				distanceFromPlayer = math.clamp(distanceFromPlayer - MOVE_SENSITIVITY, MIN_DRAG_DISTANCE, MAX_DRAG_DISTANCE)
			end
			task.wait() -- Allow continuous movement updates
		end
	end

	--=======================
	-- Placeholder for Rotation (Future Implementation)
	--=======================
	local function RotatePart()
		-- TODO: Implement rotation mechanics if needed.
	end

	--=======================
	-- Input Handling for Distance Adjustment
	--=======================
	local function onKeyDown(input, gameProcessedEvent)
		if gameProcessedEvent then return end
		if not UserInputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton2) then return end -- Ensure Right Click is held

		if input.KeyCode == Enum.KeyCode.W then
			isHoldingW = true
			task.spawn(adjustDistance) -- Start adjusting continuously
		elseif input.KeyCode == Enum.KeyCode.S then
			isHoldingS = true
			task.spawn(adjustDistance) -- Start adjusting continuously
		end
	end

	local function onKeyUp(input, gameProcessedEvent)
		if gameProcessedEvent then return end

		if input.KeyCode == Enum.KeyCode.W then
			isHoldingW = false
		elseif input.KeyCode == Enum.KeyCode.S then
			isHoldingS = false
		end

		-- Unlock player movement if neither key is held
		if not isHoldingW and not isHoldingS then
			lockPlayerMovement(player, false)
		end
	end

	local keyDownConnection = UserInputService.InputBegan:Connect(onKeyDown)
	local keyUpConnection = UserInputService.InputEnded:Connect(onKeyUp)

	--=======================
	-- Update Function (Moves Part Based on Mouse)
	--=======================
	local function onUpdate()
		if not dragging then return end

		-- Adjust for player movement
		local characterOffset = humanoidRootPart.Position - lastCharacterPosition
		lastCharacterPosition = humanoidRootPart.Position

		-- Calculate new position and enforce distance constraint
		local targetPosition = mouse.Hit.Position + characterOffset
		local direction = (targetPosition - humanoidRootPart.Position).Unit
		local clampedPosition = humanoidRootPart.Position + (direction * distanceFromPlayer)

		-- Update target part's position
		targetPart.Position = clampedPosition
	end

	local updateConnection = RunService.RenderStepped:Connect(onUpdate)

	--=======================
	-- Stop Dragging Function
	--=======================
	local function stopDragging()
		if dragging then
			dragging = false
			updateConnection:Disconnect()
			keyDownConnection:Disconnect()
			keyUpConnection:Disconnect()
			toggleUIDuringDrag(screenGui, false)
		end
	end

	-- Stop dragging on mouse release or player death
	mouse.Button1Up:Connect(stopDragging)
	humanoidRootPart.AncestryChanged:Connect(stopDragging) -- Stop if player dies
	targetPart.AncestryChanged:Connect(stopDragging) -- Stop if part is deleted
	toggleUIDuringDrag(screenGui, true)
end

return DragDetector


4 Likes

Hello, where do i put these scripts? ive been trying to achieve the same type of dragging that it always annoys me when i try dragging a thing forward

Hi, You will have two scripts, one a Local Script and another a Module script called DragDetector.

You will put the Local script in StaterCharacterScripts and you will attach the Module script to the local script

So in your explorer it would look like: StarterPlayer < StaterCharacterScripts < LocalScript < DragDetector

Hope that helps.

Thanks! I’ll try it out and tell you if it works!

image
like this?

image

No need for an actual Drag Detector, You will name the module script DragDetector and paste in the code above for the module script.

Ohhh, I’ll try that out, I’ll tell you if it works or not!

Weird… I set the script just like yours… exact names and things and it still doesnt work…

External Media

Sorry for the laggy video lol…

Not sure, It looks like you have a drag detector on the part itself.

I whipped up the following, making some slight changes to my script above.

Make sure to read the installation notes.

Hope it helps,

Thank you so much! I truly don’t know how to thank you!!

Hey! You don’t have to, but I’m trying to make a dead rails type game and was looking for a dragging system, and this is honestly an amazing dragging system, but I want to ask, how do I make it so you can drag npcs?

This type of dragging system wouldn’t really work (for a dead rails type game), at least it didn’t for me, however I figured out how to pretty much completely replicate the dragging system from dead rails. I won’t give the code but I’ll explain how it works so you can try to make it yourself.

(this might be a little inefficient, but personally I didn’t experience lag long term, just a warning because this sends ALOT of remotes)

First, add a CFrameValue into the player, then constantly be updating the CFrame value to be the camera’s position * offset (for offset, leave X and Y as 0, and Z as a negative number, the bigger the negative number, the further away the part from the player will be) using remote events, since the server needs to have access to the camera’s position.

Second, detect when the left mouse button is held down. I also recommend using tags to add a tag to a part like “Drag”, to do that you’d do something like “part:AddTag(“Drag”)”, and add some code in a server script that automatically adds an align position and align orientation into the part if it has the tag “Drag”. (Also make sure to use the “OneAttachment” setting for both alignPosition and alignOrientation)

Then, if the mouse is held down, check if the object you’re hovering has a tag with “Drag” using collection service. If it is, send a remote to the server with parameters, 2 of which are necessary being the part itself and the status (status as in is it being dragged or not).

If the status is true, then on the server, create an attachment inside the part, and set the part’s align position to the CFrameValue which we set up earlier. (ALSO ITS VERY IMPORTANT TO SET THE NETWORK OWNER OF THE PART TO THE PLAYER THAT’S DRAGGING THE PART OTHERWISE THE DRAGGING IS GONNA BE VERY DELAYED). Then set the alignOrientation to the attachment you made inside the part. You’ll also need to be updating the align position and align orientation constantly with repeat loops.

If the status is false, then on the server, delete any attachment instances inside the part and disable both the alignPosition and alignOrientation, but don’t delete them.

And that should be it! If you get any errors or need any help, let me know and I’ll try to help.

2 Likes

yo rockgod, i’ve done what you said and it works perfectly thank you for the info, but im confused on how i should get the distance of the player from the object before i drag it so that i can prevent dragging if a player is farther than the required distance to drag the object, if that doesnt make sense, please let me know and ill explain further.

sorry for the late response, i dont check the dev forums all that often

you mean that you want the player not to be able to pick up an object from any distance, right? you’d need to do a magnitude check

basically, get the position of the object youre dragging, and take away the position of your character, then add .magnitude
something like this:
(object.Position - character.HumanoidRootPart.Position).Magnitude

then check if that is more than a certain number, for example:
if (object.Position - character.HumanoidRootPart.Position).Magnitude > 15 then
–run code
end

if you need any help, let me know

sorry i figured out how to do it and forgot about this post but i have 1 question, how do i lessen the fling? like half it or something like that, because its very powerful right now.

again, sorry for the late response, but you can have weights to objects by changing the align position’s MaxForce setting to a smaller number. personally, i use around 7000-8000. also make sure to lower the responsiveness to 10 or slightly more, depending on what you want