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?

3 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


3 Likes