Im trying to make this building/placement system work i mean like the visual part that shows you where your gonna place it but it keeps clipping underground with large models (i know its mostly ai thats because i already tried to fix it myself but couldnt get it to work)
local plr = game.Players.LocalPlayer
local tool = script.Parent
local Event = tool:WaitForChild("Place")
local Equipped = false
local CanPlace = false
-- Preview part
local VisPart = Instance.new("Part")
VisPart.Anchored = true
VisPart.CanCollide = false
VisPart.Transparency = 0.5
VisPart.Material = Enum.Material.ForceField
VisPart.Name = "PreviewPart"
-- OverlapParams for collision checks
local overlapParams = OverlapParams.new()
overlapParams.FilterType = Enum.RaycastFilterType.Exclude
-- Raycast params for ground check
local rayParams = RaycastParams.new()
rayParams.FilterType = Enum.RaycastFilterType.Exclude
rayParams.FilterDescendantsInstances = {VisPart}
-- Equip
tool.Equipped:Connect(function()
Equipped = true
VisPart.Parent = workspace
-- Match size with stored model in ReplicatedStorage
local storedModel = game.ReplicatedStorage:FindFirstChild(tool.Name)
if storedModel and storedModel:FindFirstChild("Collision") then
VisPart.Size = storedModel.Collision.Size
else
VisPart.Size = Vector3.new(4, 4, 4) -- fallback size
end
-- Update loop
while Equipped do
task.wait()
local hrp = plr.Character and plr.Character:FindFirstChild("HumanoidRootPart")
if not hrp then
continue
end
-- Start preview 10 studs forward
local forwardOffset = 10
VisPart.CFrame = hrp.CFrame * CFrame.new(0, 0, -forwardOffset)
-- Push forward if overlapping with player
local maxPushes = 10 -- up to 100 studs
local pushes = 0
repeat
overlapParams.FilterDescendantsInstances = {VisPart}
local touching = workspace:GetPartBoundsInBox(VisPart.CFrame, VisPart.Size, overlapParams)
local collidingWithPlayer = false
for _, part in ipairs(touching) do
if plr.Character and part:IsDescendantOf(plr.Character) then
collidingWithPlayer = true
break
end
end
if collidingWithPlayer then
forwardOffset += 10
VisPart.CFrame = hrp.CFrame * CFrame.new(0, 0, -forwardOffset)
pushes += 1
else
break
end
until pushes >= maxPushes
-- Raycast directly under preview part to check if it's grounded
local rayOrigin = VisPart.Position
local rayDirection = Vector3.new(0, -((VisPart.Size.Y / 2) + 0.5), 0) -- a little below the part
local rayResult = workspace:Raycast(rayOrigin, rayDirection, rayParams)
local isGrounded = rayResult ~= nil
-- General collision check
overlapParams.FilterDescendantsInstances = {VisPart, plr.Character}
local touching = workspace:GetPartBoundsInBox(VisPart.CFrame, VisPart.Size, overlapParams)
local blocked = false
for _, part in ipairs(touching) do
if part:IsA("BasePart") and part:HasTag("Collidable") then
blocked = true
break
end
end
-- Final placement validity
if not isGrounded or blocked then
CanPlace = false
VisPart.BrickColor = BrickColor.new("Really red")
else
CanPlace = true
VisPart.BrickColor = BrickColor.new("Forest green")
end
end
end)
-- Unequip
tool.Unequipped:Connect(function()
Equipped = false
VisPart.Parent = nil
end)
-- Activate (place)
tool.Activated:Connect(function()
if CanPlace then
Event:FireServer(VisPart.CFrame, tool.Name)
end
end)



