nice bro this is an amazing resource.
Is there going to be new updates to this?
If so, features where you can enable dragging a GuiObject on only one axis would be great.
I got two errors:
Players.GEILER123456.PlayerScripts.Client.DragModule:40: invalid argument #2 (Vector3 expected, got nil)
and
Players.GEILER123456.PlayerScripts.Client.DragModule:42: attempt to index nil with ‘X’
Maybe my code is wrong, here it is:
local pressing = false
local pressedFor = 0
local button1Down
local button1Up
local cloneDrag = DragModule.new(clone)
button1Down = clone.MouseButton1Down:Connect(function()
pressing = true
while pressing do
pressedFor += RunService.Heartbeat:Wait()
if pressedFor > 1 then
cloneDrag:Enable()
break
end
end
end)
button1Up = clone.MouseButton1Up:Connect(function()
pressing = false
if pressedFor < 1 then
print("Client fire drop!")
freeSlot.Item = nil
dropRemote:FireServer(name)
clone:Destroy()
button1Down:Disconnect()
button1Up:Disconnect()
else
print("It was just for positioning")
cloneDrag:Disable()
end
pressedFor = 0
end)
I attempted to fix it myself:
Changed local startPos = nil
to local startPos = object.Position
,
Changed local dragStart = nil
to local dragStart = Vector3.new(0, 0, 0)
After these changes, it doesn’t throw the errors anymore.
Here the changed version:
--[[
@Author: Spynaz
@Description: Enables dragging on GuiObjects. Supports both mouse and touch.
For instructions on how to use this module, go to this link:
https://devforum.roblox.com/t/simple-module-for-creating-draggable-gui-elements/230678
--]]
local UDim2_new = UDim2.new
local UserInputService = game:GetService("UserInputService")
local DraggableObject = {}
DraggableObject.__index = DraggableObject
-- Sets up a new draggable object
function DraggableObject.new(Object)
local self = {}
self.Object = Object
self.DragStarted = nil
self.DragEnded = nil
self.Dragged = nil
self.Dragging = false
setmetatable(self, DraggableObject)
return self
end
-- Enables dragging
function DraggableObject:Enable()
local object = self.Object
local dragInput = nil
local dragStart = Vector3.new(0, 0, 0)
local startPos = object.Position
local preparingToDrag = false
-- Updates the element
local function update(input)
local delta = input.Position - dragStart
local newPosition = UDim2_new(startPos.X.Scale, startPos.X.Offset + delta.X, startPos.Y.Scale, startPos.Y.Offset + delta.Y)
object.Position = newPosition
return newPosition
end
self.InputBegan = object.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
preparingToDrag = true
--[[if self.DragStarted then
self.DragStarted()
end
dragging = true
dragStart = input.Position
startPos = Element.Position
--]]
local connection
connection = input.Changed:Connect(function()
if input.UserInputState == Enum.UserInputState.End and (self.Dragging or preparingToDrag) then
self.Dragging = false
connection:Disconnect()
if self.DragEnded and not preparingToDrag then
self.DragEnded()
end
preparingToDrag = false
end
end)
end
end)
self.InputChanged = object.InputChanged:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch then
dragInput = input
end
end)
self.InputChanged2 = UserInputService.InputChanged:Connect(function(input)
if object.Parent == nil then
self:Disable()
return
end
if preparingToDrag then
preparingToDrag = false
if self.DragStarted then
self.DragStarted()
end
self.Dragging = true
dragStart = input.Position
startPos = object.Position
end
if input == dragInput and self.Dragging then
local newPosition = update(input)
if self.Dragged then
self.Dragged(newPosition)
end
end
end)
end
-- Disables dragging
function DraggableObject:Disable()
self.InputBegan:Disconnect()
self.InputChanged:Disconnect()
self.InputChanged2:Disconnect()
if self.Dragging then
self.Dragging = false
if self.DragEnded then
self.DragEnded()
end
end
end
return DraggableObject
Good job on this module!
I was getting a very annoying bug where my draggable GUI would go off screen whenever I happen to be moving diagonally. It turns out dragStart
was being reset to the origin since clicking and dragging and pressing input keys with no position at the right moment can break the dragging. For anyone curious on how to fix this, similar to how self.InputChanged
checks input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch
, you must do this same check in self.InputChanged2
along with preparingToDrag
to prevent non-dragging related input positions to affect dragStart
TL;DR If GUI dragging is glitchy (disappears off screen / teleports somewhere else) when moving, use this instead
@Author: Spynaz
@Description: Enables dragging on GuiObjects. Supports both mouse and touch.
For instructions on how to use this module, go to this link:
https://devforum.roblox.com/t/simple-module-for-creating-draggable-gui-elements/230678
--]]
local UDim2_new = UDim2.new
local UserInputService = game:GetService("UserInputService")
local DraggableObject = {}
DraggableObject.__index = DraggableObject
-- Check if either mouse movement or touch input
function MouseOrTouchMovement(input)
return input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch
end
-- Sets up a new draggable object
function DraggableObject.new(Object)
local self = {}
self.Object = Object
self.DragStarted = nil
self.DragEnded = nil
self.Dragged = nil
self.Dragging = false
setmetatable(self, DraggableObject)
return self
end
-- Enables dragging
function DraggableObject:Enable()
local object = self.Object
local dragInput = nil
local dragStart = nil
local startPos = nil
local preparingToDrag = false
-- Updates the element
local function update(input)
local delta = input.Position - dragStart
local newPosition = UDim2_new(startPos.X.Scale, startPos.X.Offset + delta.X, startPos.Y.Scale, startPos.Y.Offset + delta.Y)
object.Position = newPosition
return newPosition
end
self.InputBegan = object.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
preparingToDrag = true
local connection
connection = input.Changed:Connect(function()
if input.UserInputState == Enum.UserInputState.End and (self.Dragging or preparingToDrag) then
self.Dragging = false
connection:Disconnect()
if self.DragEnded and not preparingToDrag then
self.DragEnded()
end
preparingToDrag = false
end
end)
end
end)
self.InputChanged = object.InputChanged:Connect(function(input)
if MouseOrTouchMovement(input) then
dragInput = input
end
end)
self.InputChanged2 = UserInputService.InputChanged:Connect(function(input)
if object.Parent == nil then
self:Disable()
return
end
if MouseOrTouchMovement(input) and preparingToDrag then
preparingToDrag = false
if self.DragStarted then
self.DragStarted()
end
self.Dragging = true
dragStart = input.Position
startPos = object.Position
end
if input == dragInput and self.Dragging then
local newPosition = update(input)
if self.Dragged then
self.Dragged(newPosition)
end
end
end)
end
-- Disables dragging
function DraggableObject:Disable()
self.InputBegan:Disconnect()
self.InputChanged:Disconnect()
self.InputChanged2:Disconnect()
if self.Dragging then
self.Dragging = false
if self.DragEnded then
self.DragEnded()
end
end
end
return DraggableObject
Other than that works like a charm, thank you @Spynaz
Great resource love this a ton.
Quick question, though?
Do you know how I could use this to only let users drag a certain distance and space?
Here is my idea -
Just to keep the little button they drag in the position of “White Speed Bar”.
So only certain distances.
I see you give the positions exactly but just curious.
Everything works perfectly on PC, however, mobile seems to bug out on this, here is a video explaining my situation. Is anyone able to help me out with this?
It’s because you’re disconnecting the draggable object before you’re enabling it.
self.InputBegan and the other variables haven’t been assigned a connection yet!
How would you make it so that when something is reparented (which then causes the position to be displaced), it doesn’t become offset from the mouse?
This is what’s happening with me:
This really solves the problem . Please let’s update marketplace model with this bugfix cause market model is outdated.
I’m having an issue where it seems like DragEnded doesn’t fire when I let go of the mouse. For some reason that also means that the next segment of code doesn’t fire and the UI doesn’t return to its original position.
local function inventorySlotSelected(index, slotFrame, dragObject)
local item = inventory.items[index]
-- Check if double click
if tick() - lastClickTick <= 0.35 then
dragObject:Disable()
if item then
if character:FindFirstChild(item.Name) then
InventoryController:UnequipTools()
InventoryController:ToggleUI(false)
else
InventoryController:EquipTool(item)
InventoryController:ToggleUI(false)
end
else
InventoryController:UnequipTools()
InventoryController:ToggleUI(false)
end
else
dragObject.DragEnded = function()
print("Drag Ended")
if not collidesWith(slotFrame.TextButton, INVENTORY_UI:FindFirstChild("Slots")) then
-- DROP
print("Drop")
return
else
-- Check If Collides With Other
print("Swap")
return
end
end
end
dragObject:Disable()
lastClickTick = tick()
slotFrame.TextButton.Position = UDim2.new(0,0,0,0)
print("Move Back")
dragObject:Enable()
end
Can you share the code please? I can’t access the place.
--[[
@Author: Spynaz
@Description: Enables dragging on GuiObjects. Supports both mouse and touch.
For instructions on how to use this module, go to this link:
https://devforum.roblox.com/t/simple-module-for-creating-draggable-gui-elements/230678
--]]
local UDim2_new = UDim2.new
local UserInputService = game:GetService("UserInputService")
local currentlyDragging = false
local DraggableObject = {}
DraggableObject.__index = DraggableObject
-- Sets up a new draggable object
function DraggableObject.new(Object)
local self = {}
self.Object = Object
self.DragStarted = nil
self.DragEnded = nil
self.Dragged = nil
self.Dragging = false
setmetatable(self, DraggableObject)
return self
end
-- Enables dragging
function DraggableObject:Enable()
local object = self.Object
local dragInput = nil
local dragStart = nil
local startPos = nil
local preparingToDrag = false
-- Updates the element
local function update(input)
local delta = input.Position - dragStart
local newPosition = UDim2_new(startPos.X.Scale, startPos.X.Offset + delta.X, startPos.Y.Scale, startPos.Y.Offset + delta.Y)
object.Position = newPosition
return newPosition
end
self.InputBegan = object.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch and not currentlyDragging then
currentlyDragging = true
preparingToDrag = true
--[[if self.DragStarted then
self.DragStarted()
end
dragging = true
dragStart = input.Position
startPos = Element.Position
--]]
local connection
connection = input.Changed:Connect(function()
if input.UserInputState == Enum.UserInputState.End and (self.Dragging or preparingToDrag) then
self.Dragging = false
connection:Disconnect()
if self.DragEnded and not preparingToDrag then
self.DragEnded()
end
currentlyDragging = false
preparingToDrag = false
end
end)
end
end)
self.InputChanged = object.InputChanged:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch then
dragInput = input
end
end)
self.InputChanged2 = UserInputService.InputChanged:Connect(function(input)
if object.Parent == nil then
self:Disable()
return
end
if preparingToDrag then
preparingToDrag = false
if self.DragStarted then
self.DragStarted()
end
self.Dragging = true
dragStart = input.Position
startPos = object.Position
end
if input == dragInput and self.Dragging then
local newPosition = update(input)
if self.Dragged then
self.Dragged(newPosition)
end
end
end)
end
-- Disables dragging
function DraggableObject:Disable()
self.InputBegan:Disconnect()
self.InputChanged:Disconnect()
self.InputChanged2:Disconnect()
if self.Dragging then
self.Dragging = false
if self.DragEnded then
self.DragEnded()
end
end
end
return DraggableObject
I know this is somewhat old, but this is probably the most simple and reliable dragging module out there.
Nice and easy to set up, very lightweight, has function based events which is far more reliable than most signal modules I can find, and overall, a very nice little module.
I know this is probably old by now but how would I take into account rotation while its being dragged? Currently when rotated whilst dragging the position is way off to the mouse (which is understandable since yk its dragging)
But how do i fix this?
btw this is my implementation of turning the position from offset → scale
for any one of you wondering
local function toScale(pos:UDim2,ancestor:GuiObject)
local scaleXPos = pos.X.Offset/ancestor.AbsoluteSize.X + pos.X.Scale
local scaleYPos = pos.Y.Offset/ancestor.AbsoluteSize.Y + pos.Y.Scale
return UDim2.fromScale(scaleXPos,scaleYPos)
end
local function update(input)
local delta = input.Position - dragStart
local newPosition = UDim2_new(startPos.X.Scale, startPos.X.Offset + delta.X, startPos.Y.Scale, startPos.Y.Offset + delta.Y)
newPosition = toScale(newPosition,object:FindFirstAncestorOfClass("Frame"))
object.Position = newPosition
return newPosition
end
I wanted to add a draggable option to my recently made UI (they’re very poorly made) and had no idea what I was getting myself into.
Thanks to your resource, I was able to easily implement UI drag feature into my game fairly easily.
One thing that I even appreciate, is the fact that the UI do not reset position when the player close/re open.
Altho it could be considered as bad practice (mostly on my end for not reseting it properly) I do see it as a nice touch of personal flavor for the user’s end.