BEFORE YOU READ THIS ARTICLE
This is an old article, though not exactly outdated and still very informative, it’s better to use the new Drag Detectors, they’re a much easier solution, give you very good control over the dragging, replicates for you, and hell, is even better than the solution in this post.
Certainly easier than just copy pasting the possibly broken code that I wrote here : )
YOU MAY READ IT NOW
Hi! This is one of my first and most popular resources here, and I’m proud of it. After a year or so, decided to check it out again. I found some mistakes and use of stuff that wasn’t deprecated back in the old days, but I also updated almost all of the article. I took a different approach, a different solution, and decided to only explain what’s going on instead of starting with a script and working on from there. A much better end result is provided in the end rather than the simplistic and rusty old version. The older version of the article be found here.
I’m sure you once were wondering how would you make the player able to drag objects around with his mouse, just like in Lumber Tycoon for example.
It is certain that someone who had some decent knowledge with the mouse object has tried to make this by himself utilizing the mouse.Hit
property of the mouse, which is the CFrame of the mouse in the 3D world.
We would set whatever the current mouse.Target
, the part that the mouse is currently hovering over, to the mouse.Hit.Position
, the position of the mouse in the 3D world (any CFrame has a .Position or .p (.p is out-dated) property which is just the position of that cframe (remember that cframe is position and rotation)). We can technically set the CFrame of the target straight away to mouse.Hit and not just the position but the rotation is kind of broken) each time the mouse.Move
event fires and only when the mouse is pressing down. Also setting the mouse.TargetFilter
to the target itself so the mouse ignores the target while calculating the mouse.Hit
to prevent many issues.
So your attempt will look like something similar to this
local player = game.Players.LocalPlayer --the local player
local mouse = player:GetMouse() --his mouse
local target --this variable will hold the part that's being currently dragged
local down --this determines wether we are pressing or not
mouse.Button1Down:connect(function()
if mouse.Target ~= nil and mouse.Target.Locked == false then --checking if the mouse is actually hovering over an object, the locked property isn't really important
target = mouse.Target --the target is set
mouse.TargetFilter = target --preventing issues
down = true
end
end)
mouse.Move:Connect(function()
if down == true and target ~= nil then --this event will be always firing, but we wanna change the target's position only when clicking, that's why we check if down is true
target.Position = mouse.Hit.Position --the part that sets the position!
end
end)
mouse.Button1Up:connect(function()
down = false --and remember that after ending the holding, you wanna reset some properties
mouse.TargetFilter = nil
target = nil
end)
Although this script is not the fanciest, and is definitely not perfect for a full-fledged game, it works!
Great efforts!
This would work, but not totally according to the plan. The parts would always be sticking to the ground; since really the mouse’s position is constantly landing there, it’s not in thin air. And even if you tried to drag the objects around in the sky above your head where there is nothing, they just disappear.
Perspective has a huge role in what’s going on.
What’s happening is, whenever you drag the mouse in the air, you’d think that the mouse.Hit
is in the air as well. But the mouse.Hit
, when calculated, will go in the same direction that the mouse is hovering in until it hits a surface. A way to prove that is, you try to drag it up in the air where there is no surface to land on, the part literally disappears because the mouse.Hit
will go on until it hits its maximum length, so the part is really far away. You can even print the .Magnitude
of the mouse.Hit.Position
while hovering the mouse in empty space and you’ll see that it’s a large number (Any Vector3 has a .Magnitude or a .magnitude property, which is the length of the vector, magnitude is basically length, size or anything that goes along that).
print(mouse.Hit.Position.Magnitude) --9986.2734375, always rounds to 9986
Side note
Notice how I said “looking from where the camera is”, and drew the line from an actual camera! This shouldn’t be confusing.
The camera’s CFrame would always be the point of view from where the player is looking. It’s a CFrame positioned where he is looking from (for visualization, if you call ScreenPointToRay and tell it to cast a ray from the middle of the screen, the resulting ray’s origin is the camera’s CFrame!) looking towards what he’s focusing on.
In the picture above, this is what everything would look like if I was at that camera’s CFrame
So, what’s the plan? Remember that mouse.Hit.Position
is the vector that’s laying on the ground, starting from 0,0,0
. What we want instead is the vector starting from the camera right there. And we don’t want the vector itself, we want only a part of it, because you can see that even this new vector is going all the way to the ground, we want to limit how far it goes, say to that point I highlighted above.
How!
Well, first thing we need is that vector starting from the camera. If you know how vector subtraction works, that should be! When you subtract two vectors, the resulting vector is actually the vector in-between them! The picture below is in 2D, but everything will apply the same way in 3D.
Note that order of the operands does matter. a-b
is the vector in-between looking at a
, b-a
is the vector in-between looking at b
.
It might also be confusing that the vector isn’t starting from the universal origin 0,0
in the pictures above, don’t all vectors start from there? Yes absolutely! I placed the vector there just for visualization, it does start from 0,0
. It’s as if I was told to calculate a-b
just by drawing, what I did is drew the vector starting from b
and looking at a
, and transformed it to 0,0
.
Ok, so in our case, what are the two operands? The mouse.Hit.Position
, and the camera.CFrame.Position
(camera
is just workspace.CurrentCamera
, it has a CFrame
property, we’re interested in vectors here so we take its .Position
). As I said, the order does matter, and in our case we want the vector to look at the mouse.Hit.Position
, so what we have is
mouse.Hit.Position - camera.CFrame.Position
which looks like this
Now the limiting part,
(mouse.Hit.Position - camera.CFrame.Position).Unit * 20
mouse.Hit.Position.Unit
is simply the direction of the mouse.Hit.Position
(any Vector3 has a .Unit
or a .unit
property, it is the direction of that vector3), but if you wanna dig deeper, .Unit gives back a unit vector.
Unit vectors are vectors with a length of one (it’s in the name, unit), and they are used to describe directions. These guys are one-long because we don’t really care about their length, just their direction.
Again, .Unit
is the direction of the given vector (given in the form of a unit vector). We take the direction of the vector resulting from the subtraction ((mouse.Hit.Position - camera.CFrame.Position).Unit
). Here is the fun part, you can multiply by vectors by numbers, this scales them (the number is called a scalar). We can be multiply (mouse.Hit.Position - camera.CFrame.Position).Unit
by, say 20, to get a vector in the same direction, but only 20 studs long. If the mouse was pointing very far away, we will only get back a 20 studs vector. This is how we limit.
Ok one last thing! Don’t let the placement of the blue vector fool you. That vector isn’t actually leading to that position, if I put him where he actually belongs (meaning starting from 0,0,0
), you’ll understand why.
If you understand vector addition, you might as well know how to get that light blue vector! Adding two vectors is like placing the second on the tip of the first.
Same applies in 3D of course. Have you guessed how we can get that light blue vector? Yes! camera.CFrame.Position
+ our resulting blue vector.
We’re actually done! This is the result we needed
camera.CFrame.Position + (mouse.Hit.Position - camera.CFrame.Position).Unit * 20
It’s dragged in the air, goal achieved, but kind of rusty, but this is only because our starting script isn’t really perfect. The simple procedure of passing network ownership (with :SetNetworkOwnership()
) to the client makes it way better, and also makes it wonderfully work in multiplayer.
Here is a way better script I made, using AlignPosition
, and some fancy additions.
Another thing as well, this whole time we’ve been doing things based on the camera, it is actually better to do this based on the character’s head! This is a mistake I did in the previous version of the article. Making the dragging centered around the camera is bad, since if the player zoomed out really far, the part will go with him that far away (since it’s around the camera’s position). With the head, it will always be limited to 20 studs (that’s the distance we chose here) from the head, solving this problem. What I mean, this would be our end result instead
character.Head.Position + (mouse.Hit.Position - character.Head.Position).Unit * 20
This version of the script can be found here: dragging.rbxl (29.9 KB)
Here is an even better version, with some fixes, and even the ability to rotate dragged parts even_better_dragging.rbxl (33.4 KB)
And also if you want a much more advanced system that you can check it out (it’s uncopylocked) made by @BenSBk!
That’s it, have a wonderful day!