I made this for my game.
It’s a bit buggy but it does the job done.
Place a Local Script in a Tool.
Place a Normal script in the tool.
Place a Folder named “PartsFolder” in the workspace and place all the parts you want in the map.
Place a Folder named “PartsPreview” in the workspace for the prediction block.
Place a Remote event named “PlaceBlock” in the tool
This is the code for the Local Script:
local PlaceBlockEvent = script.Parent.PlaceBlock
local equipped = false
local uis = game:GetService("UserInputService")
local mouse = game.Players.LocalPlayer:GetMouse()
local character = game.Players.LocalPlayer.Character or game.Players.LocalPlayer.CharacterAdded:Wait()
local cam = workspace.CurrentCamera
local function findClosestPart(preview)
local lowestMagParts = {}
local currentPart = nil
local currentClosestDir = nil
local currentClsoestmag = nil
for _,v in pairs(game.Workspace.PartsFolder:GetDescendants()) do
if v:IsA("BasePart") then
local magnitude1 = (character.PrimaryPart.Position - v.Position).Magnitude
if magnitude1 <= 25 then
table.insert(lowestMagParts, v)
end
end
end
local lowestMagnitude = nil
for _,v in pairs(lowestMagParts) do
if v:IsA("BasePart") then
local mouse3DPos = character.PrimaryPart.Position + (mouse.Hit.Position - character.PrimaryPart.Position).Unit * (character.PrimaryPart.Position - v.Position).Magnitude
local mag = (v.Position - mouse3DPos).Magnitude
if not lowestMagnitude then
lowestMagnitude = mag
currentPart = v
continue
end
if mag < lowestMagnitude then
lowestMagnitude = mag
currentPart = v
end
end
end
local surfaceIDs = {
top = currentPart.Position + Vector3.new(0,25,0),
bottom = currentPart.Position - Vector3.new(0,25,0),
left = currentPart.Position - Vector3.new(25,0,0),
right = currentPart.Position + Vector3.new(25,0,0),
front = currentPart.Position - Vector3.new(0,0,25),
back = currentPart.Position + Vector3.new(0,0,25),
}
for surfaceId, pos in pairs(surfaceIDs) do
local mag = ((character.PrimaryPart.Position + (mouse.Hit.Position - character.PrimaryPart.Position).Unit * 25) - pos).Magnitude
if not currentClsoestmag then
currentClsoestmag = mag
currentClosestDir = surfaceId
continue
end
if mag < currentClsoestmag then
currentClsoestmag = mag
currentClosestDir = surfaceId
end
end
if currentClosestDir ~= nil then
table.clear(lowestMagParts)
return currentPart, currentClosestDir
end
end
local connection = nil
script.Parent.Equipped:Connect(function()
if equipped == false then
equipped = true
local clonePreview = Instance.new("Part")
clonePreview.Parent = game.Workspace:WaitForChild("PartsPreview")
clonePreview.Name = script.Parent.Parent.Name.."Preview"
clonePreview.Transparency = 1
clonePreview.Size = Vector3.new(3,3,3)
clonePreview.CanCollide = false
clonePreview.Anchored = true
local selection = Instance.new("SelectionBox", clonePreview)
selection.Visible = true
selection.Adornee = clonePreview
selection.LineThickness = 0.05
selection.Color3 = Color3.fromRGB(0,0,0)
for _,v in pairs(game.Workspace.PartsPreview:GetChildren()) do
if v ~= clonePreview then
v:Destroy()
end
end
mouse.TargetFilter = clonePreview
task.defer(function()
while wait() do
if equipped == true then
local mouseX,mouseY,mouseZ = mouse.Hit.X, mouse.Hit.Y, mouse.Hit.Z
local mouseTarget = mouse.TargetSurface
if mouse.Target ~= nil then
if mouseTarget == Enum.NormalId.Top then
clonePreview.CFrame = (CFrame.new(mouse.Target.Position + Vector3.new(0,3, 0))) --- the 3 is the size of the block
elseif mouseTarget == Enum.NormalId.Bottom then
clonePreview.CFrame = (CFrame.new(mouse.Target.Position - Vector3.new(0,3, 0))) --- the 3 is the size of the block
elseif mouseTarget == Enum.NormalId.Left then
clonePreview.CFrame = (CFrame.new(mouse.Target.Position - Vector3.new(3, 0,0))) --- the 3 is the size of the block
elseif mouseTarget == Enum.NormalId.Right then
clonePreview.CFrame = (CFrame.new(mouse.Target.Position + Vector3.new(3,0, 0))) --- the 3 is the size of the block
elseif mouseTarget == Enum.NormalId.Front then
clonePreview.CFrame = (CFrame.new(mouse.Target.Position - Vector3.new(0,0, 3))) --- the 3 is the size of the block
elseif mouseTarget == Enum.NormalId.Back then
clonePreview.CFrame = (CFrame.new(mouse.Target.Position + Vector3.new(0,0, 3))) --- the 3 is the size of the block
end
elseif mouse.Target == nil then
local closestPart, direction = findClosestPart(clonePreview)
if tostring(direction) == "top" then
clonePreview.CFrame = (CFrame.new(closestPart.Position + Vector3.new(0,3, 0))) --- the 3 is the size of the block
elseif tostring(direction) == "bottom" then
clonePreview.CFrame = (CFrame.new(closestPart.Position - Vector3.new(0,3, 0))) --- the 3 is the size of the block
elseif tostring(direction) == "left" then
clonePreview.CFrame = (CFrame.new(closestPart.Position - Vector3.new(3,0,0))) --- the 3 is the size of the block
elseif tostring(direction) == "right" then
clonePreview.CFrame = (CFrame.new(closestPart.Position + Vector3.new(3,0, 0))) --- the 3 is the size of the block
elseif tostring(direction) == "front" then
clonePreview.CFrame = (CFrame.new(closestPart.Position - Vector3.new(0,0, 3))) --- the 3 is the size of the block
elseif tostring(direction) == "back" then
clonePreview.CFrame = (CFrame.new(closestPart.Position + Vector3.new(0,0, 3))) --- the 3 is the size of the block
end
closestPart = nil
direction = nil
end
end
end
end)
connection = mouse.Button1Down:Connect(function()
if equipped == true then
for _,v in pairs(game.Workspace:WaitForChild("PartsFolder"):GetDescendants()) do
if v:IsA("BasePart") then
if clonePreview.Position == v.Position then
return
end
end
end
local primaryPart = clonePreview.CFrame
PlaceBlockEvent:FireServer(primaryPart)
end
end)
end
end)
script.Parent.Unequipped:Connect(function()
if equipped == true then
equipped = false
if workspace.PartsPreview:FindFirstChild(script.Parent.Parent.Name.."Preview") then
local preview = workspace.PartsPreview:FindFirstChild(script.Parent.Parent.Name.."Preview")
preview:Destroy()
end
end
connection:Disconnect()
connection = nil
end)
Here is the code for the Normal script:
local event = script.Parent.PlaceBlock
local part = game.ReplicatedStorage:WaitForChild("Wood Plank") ---- name of the part in the replicated storage you want to clone for the block placement system
event.OnServerEvent:Connect(function(plr, pos)
local Clone = part:Clone()
Clone.Parent = game.Workspace:WaitForChild("PartsFolder")
Clone.CFrame = pos
end)
This is my first time making a reply so let me know if you have problems!