Simple Part Placement Tool Tutorial for Beginners (WITHOUT RAYCASTING)

Simple Part Placement Tutorial for Beginners (WITHOUT RAYCASTING)

Hi there! I created a simple placement tool and I wanted to share it with beginners who want to make something like this. I know some my friends don’t know a lot about raycasting so I decided to make it like this for any beginner scripter who doesn’t know how to raycast reading this. Anyways, let’s begin!

Detecting Tool Activation

How to set the part’s position properly

Final touches

Conclusion

BONUS CHAPTER


Detecting Tool Activation

First, we’ll create a tool. Place it in starter pack. Now, we’ll add a local script. I’ll explain everything, so don’t worry if it gets confusing. Now, type this:

script.Parent.Equipped:Connect(function(mouse)
   mouse.Button1Down:Connect(function()
   end)
end)

By now you should have realized that it checks if the tool is equipped. Then it takes the player’s mouse and connects a function to the Button1Down event. If you’ve been scripting for a while and have been looking at the API reference for a while, you might be wondering, "Why not use the Activated event instead? It still checks if you click the button while it’s equipped!” Well, it takes no mouse parameter and the mouse will be very useful later on, so we’ll leave out that event. Now that we detected the tool activation, we can add the part and set it’s anchor!

script.Parent.Equipped:Connect(function(mouse)
   mouse.Button1Down:Connect(function()
      local newpart = Instance.new("Part")
      newpart.Parent = game.Workspace
      newpart.Anchored = true
   end)
end)

Now that we’re done with this, it’s time to start

How to set the part’s position properly

This is where it gets a bit harder. To start, let’s set the part’s position to the mouse’s position:

newpart.Position = mouse.Hit.p --Without p, it would be a CFrame instead of a Vector3, but since Vector3 is simpler and doesn’t affect the rotation, we'll use the Vector3 position.

Sadly we are not done yet. Every time you use this it sets the Y coordinate to 0 all the time. If you don’t want to fix this, skip until the conclusion. Now, I have a formula for getting the correct Y position of a part (at least if you want to place it on the top of any surface), and here it is:

local target = mouse.Target
newpart.Position = Vector3.new(part.Position.X, ((target.Position.Y + target.Size.Y/2) + newpart.Size.Y/2), part.Position.Z)

The X and Z values are not affected because right now we are fixing the Y coordinate. I’ll explain this with pictures to make it simpler:

In the first picture directly below this text, you can see the ‘target.Position.Y’ part of the formula:

In this 2nd picture, I added half the Y axis size of the target, meaning it goes up…

…until you remember that the position of the part is set to the pivot (which is defaulted to the center of the part). Which is why we add half of the new part’s size to compensate for the bottom part stuck though the target:

Now that we’re done we’ve come to the

Final Touches

Are we done yet? Of course not! A proper placement script has to correctly place on every surface, right? So, we just copy-paste the formula, but plug it in for the X and Z axis, depending on where you want it. To start, we need to detect which surface the mouse is targeting so we can have a correct placement system. So, we do this:

if mouse.TargetSurface == Enum.NormalId.PLACEHOLDER then —Replace the placeholder with the surface that should be detected
   —The copy-pasted code
end

This checks which surface is detected. Now that we have all the cleared up, it’s time for the important note:

NOTE: Since there are opposites for each surface (Top and Bottom, Left and Right, Front and Back) you need to switch the + with - at times. This is a tutorial and not a place where you simply copy-paste the final script, so I’ll leave you to figure out when to place the + and the - as a challenge. :smiley: Good luck!

Ready for the answer? Check it below:

Answer
if mouse.TargetSurface == Enum.NormalId.Top then --Top
   newpart.Position = Vector3.new(newpart.Position.X, (mouse.Target.Position.Y +(mouse.Target.Size.Y/2)) + newpart.Size.Y/2, newpart.Position.Z)
elseif mouse.TargetSurface == Enum.NormalId.Bottom then --Bottom
   newpart.Position = Vector3.new(newpart.Position.X, (mouse.Target.Position.Y - (mouse.Target.Size.Y/2)) - newpart.Size.Y/2, newpart.Position.Z)
elseif mouse.TargetSurface == Enum.NormalId.Right then --Right
   newpart.Position = Vector3.new((mouse.Target.Position.X + (mouse.Target.Size.X/2)) + newpart.Size.X/2, newpart.Position.Y, newpart.Position.Z)
elseif mouse.TargetSurface == Enum.NormalId.Left then --Left
   newpart.Position = Vector3.new((mouse.Target.Position.X - (mouse.Target.Size.X/2)) - newpart.Size.X/2, newpart.Position.Y, newpart.Position.Z)
elseif mouse.TargetSurface == Enum.NormalId.Back then --Back
   newpart.Position = Vector3.new(newpart.Position.X, newpart.Position.Y, (mouse.Target.Position.Z + (mouse.Target.Size.Z/2)) + newpart.Size.Z/2)
elseif mouse.TargetSurface == Enum.NormalId.Front then --Front
   newpart.Position = Vector3.new(newpart.Position.X, newpart.Position.Y, (mouse.Target.Position.Z - (mouse.Target.Size.Z/2)) - newpart.Size.Z/2)
end

Now that we set everything up, we can show everyone what you made! If your game is for single player or whatever, skip this part. Now, we have to create a remote event in replicated storage. For this example, I’ll name it ‘BuildEvent’. Now, add this in your code:

game.ReplicatedStorage.BuildEvent:FireServer(mouse.Hit.p, mouse.Target, mouse.TargetSurface)

This will fire the event and pass these arguments to use. Now, we go to server script service and add a script. Paste this in:

game.ReplicatedStorage.BuildEvent.OnServerEvent:Connect(function(plr, hit, target, surface)
end)

The player is for the player parameter (which we don’t need), and the hit, target, and surface parameters are from the arguments we placed earlier. Now, we take the Instance.new, the properties setting, and the position setting out the local script and right in the connected function of the server script! We’re done!


Conclusion

You have reached the conclusion of this tutorial without even seeing me use raycasting in the tutorial! Congratulations! Now that we finished it up, you can edit the script to make it have the feel of your game plot.

The single-player script:

script.Parent.Equipped:Connect(function(mouse)
   mouse.Button1Down:Connect(function()
      local part = Instance.new(“Part”)
      local target = mouse.Target
      part.Parent = game.Workspace
      part.Anchored = true
      part.Position = mouse.Hit.p
      if mouse.TargetSurface == Enum.NormalId.Top then --Top
         newpart.Position = Vector3.new(newpart.Position.X, (mouse.Target.Position.Y + (mouse.Target.Size.Y/2)) + newpart.Size.Y/2, newpart.Position.Z)
      elseif mouse.TargetSurface == Enum.NormalId.Bottom then --Bottom
         newpart.Position = Vector3.new(newpart.Position.X, (mouse.Target.Position.Y -(mouse.Target.Size.Y/2)) - newpart.Size.Y/2, newpart.Position.Z)
      elseif mouse.TargetSurface == Enum.NormalId.Right then --Right
         newpart.Position = Vector3.new((mouse.Target.Position.X + (mouse.Target.Size.X/2)) + newpart.Size.X/2, newpart.Position.Y, newpart.Position.Z)
      elseif mouse.TargetSurface == Enum.NormalId.Left then --Left
         newpart.Position = Vector3.new((mouse.Target.Position.X - (mouse.Target.Size.X/2)) - newpart.Size.X/2, newpart.Position.Y, newpart.Position.Z)
      elseif mouse.TargetSurface == Enum.NormalId.Back then --Back
         newpart.Position = Vector3.new(newpart.Position.X, newpart.Position.Y, (mouse.Target.Position.Z + (mouse.Target.Size.Z/2)) + newpart.Size.Z/2)
      elseif mouse.TargetSurface == Enum.NormalId.Front then --Front
         newpart.Position = Vector3.new(newpart.Position.X, newpart.Position.Y, (mouse.Target.Position.Z - (mouse.Target.Size.Z/2)) - newpart.Size.Z/2)
      end
   end)
end)

BONUS CHAPTER
You’ve finished the tutorial, but are you ready for the bonus chapter? Let’s make it so that we make the part face the character who placed it if it’s placed on the top of the mouse’s target!
We’ll edit this part of the script:

if mouse.TargetSurface == Enum.NormalId.Top then
   newpart.Position = Vector3.new(newpart.Position.X, (mouse.Target.Position.Y + (mouse.Target.Size.Y/2)) + newpart.Size.Y/2, newpart.Position.Z)

This is pretty simple. First we get the player and the character:

local player = game.Players.LocalPlayer
local char = player.CharacterAdded:Wait()

And now to add the torso! (If your game is R15, change this to Upper Torso)

local torso = char:WaitForChild("Torso")

For the looking at the character part of the script, simply add this:

newpart.CFrame = CFrame.lookAt(newpart.Position, torso.Position, newpart.Position.Y)

This makes the part’s CFrame look at the torso’s position, on the part’s Y coordinate!

if mouse.TargetSurface == Enum.NormalId.Top then
   newpart.Position = Vector3.new(newpart.Position.X, (mouse.Target.Position.Y + (mouse.Target.Size.Y/2)) + newpart.Size.Y/2, newpart.Position.Z)
   local player = game.Players.LocalPlayer
   local char = player.CharacterAdded:Wait()
   local torso = char:WaitForChild("Torso")
   newpart.CFrame = CFrame.lookAt(newpart.Position, torso.Position, newpart.Position.Y)

If you play around with the part size, you might just create the simpler version of trowel tool in Super Doomspire :rofl:! Please put your feedback in the replies section so I can improve this tutorial! Thanks!

9 Likes

Great tutorial! I might use this sometime in one of my games.

1 Like

I added a bonus chapter for some Doomspire fans and other scripters :slight_smile:

But what if we want the whole server to see what we have placed? Can we add a remote event and fire it from the local script?

Yes, you can. I designed it for single-player only so I’ll update the tutorial with remote events. Thanks for telling me :slight_smile: I already added it

1 Like

Here’s a more efficient version:

script.Parent.Activated:Connect(function()
    
end)
3 Likes

As I already said, the mouse will be useful later on so the Activated event won’t be helpful.

1 Like