Minimal-Scripting Grab System

I wasn’t a big fan of the default DragDetector styles, I was hoping they would have a more Lumber-Tycoon-2-style drag option but the closest was TranslateViewPlane, which still wasn’t ideal.

So I put some constraints inside the part that help “glue” it to the player properly, along with some custom cosmetics. Would love to hear anyone’s thoughts on this system, how to improve it, etc. (I am novice coder).

Right now the only thing I don’t like about this system is how the part seems to “lag” (not literally but physically) when the player walks either directly towards or away from it, even with the AlignPosition set to max responsiveness/force/velocity. Not sure how to improve that.

Roblox Studio 2_25_2024 7_33_32 PM



[^ DragDetector and constraints settings]

local part = script.Parent
local clicker = part.DragDetector
local thing = part.AlignOrientation
local att = part.Attachment
local goal = nil

local distance

clicker.DragStart:Connect(function(plr, ray, viewFrame, hitFrame, BP, other, key)
	goal = plr.Character.Head.FaceFrontAttachment
	att.WorldOrientation = goal.WorldOrientation
	att.WorldPosition = hitFrame.Position
	thing.Attachment1 = goal
	part.Massless = true
	
	distance = (att.WorldPosition - goal.WorldPosition).magnitude
	
	part.AlignPosition.Enabled = true
	part.AlignPosition.Position = part.Position
end)

clicker.DragContinue:Connect(function(plr, ray, viewFrame, other, key)
	local newFrame = CFrame.new(goal.WorldPosition, goal.WorldPosition + ray.Direction.Unit)

	newFrame = newFrame * CFrame.new(0, 0, -distance * 1.25)
	part.AlignPosition.Position = newFrame.Position
end)

clicker.DragEnd:Connect(function(plr)
	part.Massless = false
	thing.Attachment1 = nil
	
	part.AlignPosition.Enabled = false
end)

[^ Script]


Sorry for compression quality

9 Likes

I turned it into a ServerScriptService handler by distributing the click detector and constraints via a for loop, and put the functions in there too. Now i can just tag a part or model with “Grab” (and assign it a primary part if it’s a model) and it’s pick-up-able.

local collectionService = game:GetService("CollectionService")

local one = script.DragDetector
local two = script.AlignOrientation
local three = script.AlignPosition

for i, PartOrModel in collectionService:GetTagged("Grab") do
	local clicker = one:Clone()
	local orient = two:Clone()
	local posit = three:Clone()
	local att = Instance.new("Attachment")
		att.Name = "grabAtt"
	
	local goal = nil
	local distance = 8
	
	local mainPart = PartOrModel
		if PartOrModel:IsA("Model") then
			mainPart = PartOrModel.PrimaryPart
		end
		
	clicker.Parent = PartOrModel
	orient.Parent = PartOrModel
	posit.Parent = PartOrModel
	att.Parent = mainPart
	orient.Attachment0 = att
	posit.Attachment0 = att
	
	clicker.DragStart:Connect(function(plr, ray, viewFrame, hitFrame, BP, other, key)
		goal = plr.Character.Head.FaceFrontAttachment
		att.WorldOrientation = goal.WorldOrientation
		att.WorldPosition = hitFrame.Position
		orient.Attachment1 = goal


		distance = (att.WorldPosition - goal.WorldPosition).magnitude
		mainPart.Massless = true
		posit.Position = mainPart.Position
		posit.Enabled = true
	end)

	clicker.DragContinue:Connect(function(plr, ray, viewFrame, other, key)
		local newFrame = CFrame.new(goal.WorldPosition, goal.WorldPosition + ray.Direction.Unit)
		newFrame = newFrame * CFrame.new(0, 0, (-distance - 1))

		posit.Position = newFrame.Position
	end)

	clicker.DragEnd:Connect(function(plr)
		mainPart.Massless = false
		orient.Attachment1 = nil
		posit.Enabled = false
	end)
end

Same clickdetector and constraint settings, but parented to the script in ServerScriptService instead of individual parts in the workspace

2 Likes

That looks great! And you even shared your code, what a legend.

The code was so confusing to me, so I tweaked it a little bit. If anyone still reads this, here you go:

for _,v in game:GetService("CollectionService"):GetTagged("Grab") do
	local DragDetector = script.DragDetector:Clone()
	local AlignOrientation = script.AlignOrientation:Clone()
	local AlignPosition = script.AlignPosition:Clone()
	local Attachment = Instance.new("Attachment")

	Attachment.Name = "GrabAttachment"
	local goal
	local part = v
	local distance = DragDetector.MaxActivationDistance / 2
	if v:IsA("Model") then part = v.PrimaryPart end

	DragDetector.Parent = v
	AlignOrientation.Parent = v
	AlignPosition.Parent = v
	Attachment.Parent = part

	AlignOrientation.Attachment0 = Attachment
	AlignPosition.Attachment0 = Attachment
	DragDetector.ReferenceInstance = Attachment

	part.Massless = false
	AlignOrientation.Attachment1 = nil
	AlignPosition.Enabled = false

	DragDetector.DragStart:Connect(function(plr, _, _, hitFrame)
		goal = plr.Character.Head.FaceFrontAttachment
		Attachment.WorldOrientation = goal.WorldOrientation
		Attachment.WorldPosition = hitFrame.Position
		AlignOrientation.Attachment1 = goal

		distance = (Attachment.WorldPosition - goal.WorldPosition).Magnitude
		part.Massless = true
		AlignPosition.Position = part.Position
		AlignPosition.Enabled = true
		Attachment.Visible = true
		DragDetector.ReferenceInstance = Attachment
	end)

	DragDetector.DragContinue:Connect(function(_, ray)
		local viewFrame = CFrame.new(goal.WorldPosition, goal.WorldPosition + ray.Direction.Unit)
		viewFrame *= CFrame.new(0, 0, (-distance * 1.25))

		AlignPosition.Position = viewFrame.Position
	end)

	DragDetector.DragEnd:Connect(function(plr)
		part.Massless = false
		AlignOrientation.Attachment1 = nil
		AlignPosition.Enabled = false
		Attachment.Visible = false
		DragDetector.ReferenceInstance = part
	end)
end
1 Like