Trying to make a dragging system

I’ve been trying to make a dragging script, similar to that of a Lumber Tycoon 2. I made 2 scripts. The first one basically checks if part can be picked up, and highlights it, if it can be picked up. Script goes through several checks such as “is part anchored” or “is part connected to another anchored part” or “is part connected to a seat, that is currently being occupied”. The idea was that when all checks return positive, a function is being bind to a players mouse, so that when he holds down mouse, it moves the part to mouse location, and when he releases mouse button, the part is also being released. I did not really come up how would that function look, but here is the script:

local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ContextActionService = game:GetService("ContextActionService")
local TweenService = game:GetService("TweenService")

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

local highlight = ReplicatedStorage.PlayersHighlight

local function GetParts(chosenPart)
	local toCheck = {chosenPart}
	local checked = {}

	while #toCheck > 0 do
		for i, part in pairs(toCheck) do
			if table.find(checked, part) then
				continue
			else
				local directlyConnectedParts = part:GetConnectedParts(true)
				local indirectlyConnectedParts = {}

				local joints = part:GetJoints()

				for i, joint in pairs(joints) do
					if joint:IsA("HingeConstraint") then
						local hinge = joint
						if hinge.Attachment0.Parent == part then
							if not table.find(toCheck, hinge.Attachment1.Parent) then
								table.insert(indirectlyConnectedParts, hinge.Attachment1.Parent)
							end
						elseif hinge.Attachment1.Parent == part then
							if not table.find(toCheck, hinge.Attachment0.Parent) then
								table.insert(indirectlyConnectedParts, hinge.Attachment0.Parent)
							end
						end
					end
				end

				for i, con in pairs(indirectlyConnectedParts) do
					table.insert(toCheck, con)
				end

				for i, v in pairs(directlyConnectedParts) do
					if not table.find(toCheck, v) then
						table.insert(toCheck, v)
					end
				end
				table.insert(checked, part)
			end
		end
		if #toCheck == #checked then
			break
		end
	end
	return checked
end


local function Drag(Name, inputState, inputObject)
	if Name == "Dragging" then
		if inputState == Enum.UserInputState.Begin then
			print("clicked")
		elseif inputState == Enum.UserInputState.End then
			print("released")
		end	
	end
end

local MapCheck = false
local RangeCheck = false
local AnchorCheck = false
local ButtonCheck = false
local ConnectionsCheck = false
local SeatsCheck = false

RunService.RenderStepped:Connect(function()
	local target = mouse.Target
	highlight.Adornee = target
	
	if target ~= nil then
		
		if target.Parent ~= workspace.Map then
			MapCheck = true
		else
			MapCheck = false
		end
		
		if (player.Character:FindFirstChild("HumanoidRootPart").Position - target.Position).Magnitude <= 20 then
			RangeCheck = true
		else
			RangeCheck = false
		end
		
		if target.Anchored == false then
			AnchorCheck = true
		else
			AnchorCheck = false
		end
		
		if target:FindFirstChild("ClickDetector") == nil then
			ButtonCheck = true
		else
			ButtonCheck = false
		end
		
		local allConnectedParts = GetParts(target)
		local connectedParts = target:GetConnectedParts(true)
		local anchoredConnections = {}
		local occupiedSeats = {}
		
		for i, part in pairs(connectedParts) do
			if part.Anchored == true then
				table.insert(anchoredConnections, part)
			end
		end
		
		if #anchoredConnections == 0 then
			ConnectionsCheck = true
		else
			ConnectionsCheck = false
		end
		
		for i, part in pairs(allConnectedParts) do
			if part:IsA("Seat") or part:IsA("VehicleSeat") then
				if part.Occupant ~= nil then
					table.insert(occupiedSeats, part)
				end
			end
		end
		
		if #occupiedSeats == 0 then
			SeatsCheck = true
		else
			SeatsCheck = false
		end
		
		if MapCheck == true and RangeCheck == true and ButtonCheck == true and AnchorCheck == true and SeatsCheck == true and ConnectionsCheck == true then
			highlight.Enabled = true
			ContextActionService:BindAction("Dragging", Drag, false, Enum.UserInputType.MouseButton1)
		else
			highlight.Enabled = false
			ContextActionService:UnbindAction("Dragging")
		end
	else
		highlight.Enabled = false
		ContextActionService:UnbindAction("Dragging")
	end
end)

It works as of now with the current version of the bind function that just prints out if you clicked or released the button.

I also made a 2nd script. Its much shorter and its a somewhat of a prototype I would use for the dragging system. The part is being held away from the players camera on specific distance, that also includes the cameras current zoom out distance referred as “difference”. Here is how it looks:

local TweenService = game:GetService("TweenService")
local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local camera = workspace.CurrentCamera
camera.CameraType = Enum.CameraType.Scriptable

local RunService = game:GetService("RunService")

local part = Instance.new("Part")
part.Anchored = false
part.Transparency = 0.5
part.Parent = workspace

RunService.RenderStepped:Connect(function()
	local direction = mouse.UnitRay.Direction
	
	local difference = (camera.CFrame.Position - player.Character:WaitForChild("Head").Position).Magnitude
	
	local goal = {
		Position = Vector3.new(camera.CFrame.Position.X + direction.X * (10 + difference), camera.CFrame.Position.Y + direction.Y * (10 + difference), camera.CFrame.Position.Z + direction.Z * (10 + difference))
	}
	
	local tween = TweenService:Create(part, TweenInfo.new(0.01, Enum.EasingStyle.Sine, Enum.EasingDirection.In, -1, false), goal)
	
	tween:Play()
end)

It also works pretty well, but it still has some issues. For example, part clips through other parts probably because I just change the parts position, However its not always the case, and it even looks really cool sometimes.


How would I potentially fix the issue with clipping part, and how would I bind that function in the first script?

2 Likes

Offset by half the size


If I were to point my camera downwards for example, while in first person, the offset would be too small.

I think the key part is getting the mouse position and using that to move the object in world space…

you need to track the initial click and store the position difference between the object and the mouse world position then while dragging you keep updating the object based on the new mouse world point minus that offset to keep it smooth also make sure the object is anchored or that the player has network ownership to avoid lag or jitter sometimes it helps to run the update logic on the client for responsiveness then sync it back to the server when needed

I came up with probably better solution. With position part would mostly clip through the ground, and I don’t want that. I also don’t want the part to be anchored. I think if I made a part go to specific point using something like vector force, it would not clip through parts so easily. However I don’t know how to do that

1 Like

I will probably get mouse position and extract part position from it, to get the directional vector. Put that vector into vector force and it might just work, who knows.

For some reason it just does not work. The value of the Vector force is being changed, but it just does not apply? Like it does not move for some reason.

*Turns out I put server script in replicated storage, which is why it didnt work

Further attempts showed that it is quite inefficient to use VectorForce. I will try using alignPosition Constraint instead

I think I managed to make it work pretty well. Here is what I did:

Local script:

local RunService = game:GetService("RunService")
local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local camera = workspace.CurrentCamera
camera.CameraType = Enum.CameraType.Scriptable

local holdPoint = workspace:WaitForChild("HoldPoint")

local event = game.ReplicatedStorage.RemoteEvent

local attachment = holdPoint:WaitForChild("Attachment1")


RunService.RenderStepped:Connect(function()
	local direction = mouse.UnitRay.Direction
	local difference = (camera.CFrame.Position - player.Character:WaitForChild("Head").Position).Magnitude
	local position = Vector3.new(camera.CFrame.Position.X + direction.X * (10 + difference), camera.CFrame.Position.Y + direction.Y * (10 + difference), camera.CFrame.Position.Z + direction.Z * (10 + difference))
	
	local cframe = camera.CFrame - camera.CFrame.Position + position
	holdPoint.CFrame = cframe
	
	event:FireServer(position, attachment, cframe)
end)

Server script:

local event = game.ReplicatedStorage.RemoteEvent

local part = Instance.new("Part")
part.Anchored = true
part.Parent = workspace
part.CanCollide = false
part.Transparency = 1

local orientation = Instance.new("AlignOrientation")

orientation.Parent = part

local att0 = Instance.new("Attachment")
local att1 = Instance.new("Attachment")
att0.Parent = part
att1.Parent = workspace:WaitForChild("FollowingPart")

orientation.Attachment0 = att0
orientation.Attachment1 = att1

event.OnServerEvent:Connect(function(player, position, attachment, cframe)
	
	attachment.Parent = part
	part.Position = position
	part.CFrame = cframe
	
end)

You can actually change the modes of align position and align orientation to 1attachment but its not too essential, and would require to change the locals a bit.

Cant you just use Roblox’s dragdetector? it can be added to parts and it is easily customizable.

Here is an article you can use to help you learn more about it:
https://create.roblox.com/docs/reference/engine/classes/DragDetector

My experience with drag detectors was not quite enjoyable. Despite trying multiple styles, I could not replicate the behavior I wanted to achieve.