Hey guys! Today I will be showing you how you can make any GUI object draggable, it’s really not hard!
This tutorial should work with any UI object, no matter the setup. Let’s get into it!
For this tutorial I will simple be using a frame with a local script and a text label inside of it
Now, we will open up the local script.
The first step, as always, is to grab our services, for this we will need the Players
Service and UserInputService
--// Services
local Players = game:GetService('Players')
local UIS = game:GetService("UserInputService")
Next it’s time for our variables, we got quite a few of them.
--// Variables
local UI = script.Parent
local Player = Players.LocalPlayer
local Mouse = Player:GetMouse()
local Hovered = false
local Holding = false
local MoveCon = nil
local InitialX, InitialY, UIInitialPos
The UI variable just references our frame, or our object that we want to become draggable.
We need the Player so we can access it’s mouse object which will allow us to know where on the screen the mouse is.
Next up we have the hovered and holding variables, the hovered is used to tell us if we are currently hovering over the frame and the holding will tell us when we hold down our mouse button.
The MoveCon will represent a Mouse.Move
connection (runs everytime we move our mouse), we are storing it in a variable so we can disconnect it once the user stops holding down their mouse.
The InitialX,Y, and InitialUI are all just going to be used while calculating the position to drag to, you’ll see what I mean later.
Next up what we want to do is set up our connections that will control our hovered and holding variables. To know when we our mouse starts (and stops) hovering the frame we will use the MouseEnter
and MouseLeave
events:
UI.MouseEnter:Connect(function()
Hovered = true
end)
UI.MouseLeave:Connect(function()
Hovered = false
end)
Simple, when our mouse enters, hover becomes true. When the mouse leaves, hover becomes false.
Now we want to do a similar thing, but this time for holding. Now this is the part we need to use UserInputService, if we used Mouse.Button1Down
then it wouldn’t run since we would be hovering a gui object, but with user input service it’ll run either way.
UIS.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
Holding = Hovered
end
end
UIS.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
Holding = false
end
end)
For both the InputBegan
and InputEnded
we check if the user is (or was) either Holding down MouseButton1 (left mouse button) or touch
which means pressing down on the screen (with like a finger or stylus), this makes it both mobile and pc friendly! Now notice in the InputBegan
we set Holding = Hovered
this is because we know we are holding the mouse button but we only want to set Holding
to true
when Hovered == true
(when we are hovering). So if hovered is false, even if we are holding down our mouse, holding will also be false.
Now, in our InputEnded
function, directly after Holding = Hovered
(before the end
) we want to continue onto our dragging logic. The first thing we need is to check that Hovered == true
. If hovered is true we would then want to set the InitialX
and InitialY
variables to the Mouse’s X
and Y
values, aswell as setting the UIInitialPos
equal to the UI’s position. These are the inital, as in before dragging, values.
if Holding then
InitialX, InitialY = Mouse.X, Mouse.Y
UIInitialPos = UI.Position
end
Next we want to assign our MouseCon
variable to a Mouse.Move
event that connects to a function called Drag
:
MoveCon = Mouse.Move:Connect(Drag)
You guessed it! It’s time to make the Drag function!
The first thing we want to do in this drag function is make sure that Holding == true
if it’s not then we want to Disconnect
the MoveCon
and exit the function using a return
:
function Drag()
if Holding == false then MoveCon:Disconnect(); return end
end
Next we want to calculate the distance we moved our mouse, this is where our InitialX
and InitialY
variables come into play. The distance is equal to Initial - Current
, for example to get the distance moved on the X
axis it is InitialX - Mouse.X
since Mouse.X
is the current X
position.
local distanceMovedX = InitialX - Mouse.X
local distanceMovedY = InitialY - Mouse.Y
Now, the FINAL step is very similar to the previous one. We now just need to set the UI’s position. Once again Pos = Initial - Current
. So if we translate that pseudocode into our script it’ll look like this:
UI.Position = UIInitialPos - UDim2.new(0, distanceMovedX, 0, distanceMovedY)
The important thing here is that we must use offset and not scale.
Here is the final code:
--// Services
local Players = game:GetService('Players')
local UIS = game:GetService("UserInputService")
--// Variables
local UI = script.Parent
local Player = Players.LocalPlayer
local Mouse = Player:GetMouse()
local Hovered = false
local Holding = false
local MoveCon = nil
local InitialX, InitialY, UIInitialPos
--// Functions
local function Drag()
if Holding == false then MoveCon:Disconnect(); return end
local distanceMovedX = InitialX - Mouse.X
local distanceMovedY = InitialY - Mouse.Y
UI.Position = UIInitialPos - UDim2.new(0, distanceMovedX, 0, distanceMovedY)
end
--// Connections
UI.MouseEnter:Connect(function()
Hovered = true
end)
UI.MouseLeave:Connect(function()
Hovered = false
end)
UIS.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
Holding = Hovered
if Holding then
InitialX, InitialY = Mouse.X, Mouse.Y
UIInitialPos = UI.Position
MoveCon = Mouse.Move:Connect(Drag)
end
end
end)
UIS.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
Holding = false
end
end)
And for the final results:
Drag Relative To Parent
So this is pretty similar aswell, the only thing that gets changed is in our drag function. We also need to add two more variables for our viewport size and the speed (you’ll see why we need the speed):
local ViewPortSize = workspace.Camera.ViewportSize
local Speed = 2
Ok so now into our drag function we keep our distanceMoved
variables, but multiply them by the Speed
variable or else it moves too slow when using scale, and then make a new variable called Pos
Pos = UIInitialPos - UDim2.new(distanceMovedX/ViewPortSize.X, 0, distanceMovedY/ViewPortSize.Y, 0)
This is basically the same thing we did last time except we are converting the offset value (provided by the distanceMoved
X and Y variables and converting them to Scale
by diving by ViewPortSize.
X or Y. Credits to this solution for proving the information on converting to scale
Now the last thing we want to do is set the position of the UI object, however, we want it to stay inbounds of it’s parent, to do this we will use math.clamp
UI.Position = UDim2.new(math.clamp(Pos.X.Scale, 0, 1-UI.Size.X.Scale), 0, math.clamp(Pos.Y.Scale, 0, 1-UI.Size.Y.Scale), 0)
The minimum scale value is 0, this will be when the object is all the way to the left (or top), maximum scale is 1 all the way right (or bottom). Now based off this you’d probably come up with math.clamp(Pos.X.Scale, 0, 1)
, we are clamping the Scale value to a minimum of 0 and a max of 1, this works for the minimum, but you can make the object go it’s full width (or height) outside of the box since we didn’t take into account the size of the object itself, that’s why you see in code above we subtract 1
from the UI objects size. Now these values will need adjusting if you change the anchor point.
Final Code
--// Services
local Players = game:GetService('Players')
local UIS = game:GetService("UserInputService")
--// Variables
local UI = script.Parent
local Player = Players.LocalPlayer
local Mouse = Player:GetMouse()
local Hovered = false
local Holding = false
local MoveCon = nil
local InitialX, InitialY, UIInitialPos
local ViewPortSize = workspace.Camera.ViewportSize
local Speed = 2
--// Functions
local function Drag()
if Holding == false then MoveCon:Disconnect(); return end
local distanceMovedX = (InitialX - Mouse.X)*Speed
local distanceMovedY = (InitialY - Mouse.Y)*Speed
local Pos = UIInitialPos - UDim2.new(distanceMovedX/ViewPortSize.X, 0, distanceMovedY/ViewPortSize.Y, 0)
UI.Position = UDim2.new(math.clamp(Pos.X.Scale, 0, 1-UI.Size.X.Scale), 0, math.clamp(Pos.Y.Scale, 0, 1-UI.Size.Y.Scale), 0)
end
--// Connections
UI.MouseEnter:Connect(function()
Hovered = true
end)
UI.MouseLeave:Connect(function()
Hovered = false
end)
UIS.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
Holding = Hovered
if Holding then
InitialX, InitialY = Mouse.X, Mouse.Y
UIInitialPos = UI.Position
MoveCon = Mouse.Move:Connect(Drag)
end
end
end)
UIS.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
Holding = false
end
end)
Results:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
0 voters