Prevent placing blocks inside other blocks

So I’ve been trying to prevent this but it doesn’t seem to work.

local RunService = game:GetService("RunService")

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Modules = ReplicatedStorage:WaitForChild("Modules")
local Remotes = ReplicatedStorage:WaitForChild("Remotes")

local ItemsData = require(Modules:WaitForChild("ItemsData"))

local tool = script.Parent

local itemData = ItemsData.GetBlocksData(tool.Name)

local currentCamera = workspace.CurrentCamera

local player = game.Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local mouse = player:GetMouse()

local BlocksFolder = workspace:WaitForChild("BlocksFolder")

local equipped = false

local maxPlaceBlockRange = itemData.PlaceRange
local displayBlockConnection

local blockPrediction = false

local displayBlock = Instance.new("Part")
displayBlock.Size = Vector3.new(3, 3, 3)
displayBlock.Anchored = true
displayBlock.CanCollide = false
displayBlock.Transparency = 1
displayBlock.Parent = workspace

local selectionBox = Instance.new("SelectionBox")
selectionBox.LineThickness = 0.05
selectionBox.Color3 = Color3.fromRGB(0, 0, 0)
selectionBox.Adornee = displayBlock
selectionBox.Parent = displayBlock

local closestSelectionBox = Instance.new("SelectionBox")
closestSelectionBox.LineThickness = 0.05
closestSelectionBox.Color3 = Color3.fromRGB(255, 0, 0)

local currentClosestPart = nil
local placementCFrame = nil
local obstruction = false

mouse.TargetFilter = displayBlock

local function setDisplayBlockConnectionNil()
	if displayBlockConnection then
		displayBlockConnection:Disconnect()
		displayBlockConnection = nil
	end
end

local function findClosestPart()
	local currentPart = nil
	local currentClosestDir = nil
	local currentClosestMag = nil

	local mouseRay = currentCamera:ScreenPointToRay(mouse.X, mouse.Y)

	for i,v in ipairs(BlocksFolder:GetDescendants()) do
		if v:IsA("BasePart") then
			local mouse3DPos = mouseRay:ClosestPoint(v.Position)
			local magnitude = (v.Position - mouse3DPos).Magnitude
			
			if not currentClosestMag then
				currentClosestMag = magnitude
				currentPart = v
			elseif magnitude < currentClosestMag then
				currentClosestMag = magnitude
				currentPart = v
			end
		end
	end

	if currentPart then
		local surfaceOffset = Vector3.new(25, 25, 25) -- Offset to determine surface positions

		local surfaceIDs = {
			Left = currentPart.Position - Vector3.new(surfaceOffset.X, 0, 0),
			Right = currentPart.Position + Vector3.new(surfaceOffset.X, 0, 0),
			Front = currentPart.Position - Vector3.new(0, 0, surfaceOffset.Z),
			Back = currentPart.Position + Vector3.new(0, 0, surfaceOffset.Z),
		}

		if mouse.Target == nil then
			local closestMag = nil
			local rayDir = mouseRay.Direction
			local rayOrigin = mouseRay.Origin

			for surfaceId, pos in pairs(surfaceIDs) do
				local dir = (pos - rayOrigin).Unit
				local magnitude = (rayDir - dir).Magnitude

				if not closestMag then
					closestMag = magnitude
					currentClosestDir = surfaceId
				elseif magnitude < closestMag then
					closestMag = magnitude
					currentClosestDir = surfaceId
				end
			end
		else
			local mouse3DPos = mouseRay:ClosestPoint(currentPart.Position)
			local closestMag = nil

			for surfaceId, pos in pairs(surfaceIDs) do
				local magnitude = (mouse3DPos - pos).Magnitude

				if not closestMag then
					closestMag = magnitude
					currentClosestDir = surfaceId
				elseif magnitude < closestMag then
					closestMag = magnitude
					currentClosestDir = surfaceId
				end
			end
		end

		if currentClosestDir then
			return currentPart, currentClosestDir
		end
	end
end

local function display()
	local mouseTarget = mouse.TargetSurface
	local HumanoidRootPart = character:FindFirstChild("HumanoidRootPart") or character.PrimaryPart

	if HumanoidRootPart then
		if mouse.Target then
			if (mouse.Target.Position - HumanoidRootPart.Position).Magnitude <= maxPlaceBlockRange then
				blockPrediction = false
				displayBlock.CFrame = CFrame.new(mouse.Target.Position)

				local newPlacementCFrame = nil

				if mouseTarget == Enum.NormalId.Top then
					newPlacementCFrame = CFrame.new(mouse.Target.Position + Vector3.new(0, 3, 0))
				elseif mouseTarget == Enum.NormalId.Bottom then
					newPlacementCFrame = CFrame.new(mouse.Target.Position - Vector3.new(0, 3, 0))
				elseif mouseTarget == Enum.NormalId.Left then
					newPlacementCFrame = CFrame.new(mouse.Target.Position - Vector3.new(3, 0, 0))
				elseif mouseTarget == Enum.NormalId.Right then
					newPlacementCFrame = CFrame.new(mouse.Target.Position + Vector3.new(3, 0, 0))
				elseif mouseTarget == Enum.NormalId.Front then
					newPlacementCFrame = CFrame.new(mouse.Target.Position - Vector3.new(0, 0, 3))
				elseif mouseTarget == Enum.NormalId.Back then
					newPlacementCFrame = CFrame.new(mouse.Target.Position + Vector3.new(0, 0, 3))
				end

				placementCFrame = newPlacementCFrame
			else
				local closestPart, direction = findClosestPart()
				local offsetVector = Vector3.new()

				blockPrediction = true
				currentClosestPart = closestPart

				if closestPart and direction then
					if tostring(direction) == "Left" then
						offsetVector = Vector3.new(-3, 0, 0)
					elseif tostring(direction) == "Right" then
						offsetVector = Vector3.new(3, 0, 0)
					elseif tostring(direction) == "Front" then
						offsetVector = Vector3.new(0, 0, -3)
					elseif tostring(direction) == "Back" then
						offsetVector = Vector3.new(0, 0, 3)
					end

					local targetPos = closestPart.Position + offsetVector

					displayBlock.CFrame = CFrame.new(targetPos)
					placementCFrame = displayBlock.CFrame

					closestPart = nil
					direction = nil
				end
			end
		else
			local closestPart, direction = findClosestPart()
			local offsetVector = Vector3.new()

			blockPrediction = true
			currentClosestPart = closestPart

			if closestPart and direction then
				if tostring(direction) == "Left" then
					offsetVector = Vector3.new(-3, 0, 0)
				elseif tostring(direction) == "Right" then
					offsetVector = Vector3.new(3, 0, 0)
				elseif tostring(direction) == "Front" then
					offsetVector = Vector3.new(0, 0, -3)
				elseif tostring(direction) == "Back" then
					offsetVector = Vector3.new(0, 0, 3)
				end

				local targetPos = closestPart.Position + offsetVector

				displayBlock.CFrame = CFrame.new(targetPos)
				placementCFrame = displayBlock.CFrame

				closestPart = nil
				direction = nil
			end
		end
	end
end

local function displayBlockPlacement()
	displayBlockConnection = RunService.Heartbeat:Connect(function()
		display()

		local HumanoidRootPart = character:FindFirstChild("HumanoidRootPart") or character.PrimaryPart

		if currentClosestPart and placementCFrame and HumanoidRootPart and (placementCFrame.Position - HumanoidRootPart.Position).Magnitude <= maxPlaceBlockRange then
			obstruction = false

			closestSelectionBox.Adornee = currentClosestPart
			closestSelectionBox.Parent = currentClosestPart
			
			for i,v in ipairs(BlocksFolder:GetDescendants()) do
				if v:IsA("BasePart") then
					if v.Position == placementCFrame.Position or (blockPrediction and v.Position == currentClosestPart.Position) or (blockPrediction and displayBlock.Position == v.Position) then
						--obstruction = true
						--print("Block obstruction detected.")
						break
					end
				end
			end
		else
			--obstruction = true
		end

		if obstruction then
			selectionBox.Visible = false
		else
			selectionBox.Visible = true
		end
	end)
end

Video:

The black selection box is the predicted position and the red selection box is the closest part.

2 Likes

Assuming your game uses a grid, just create a dictionary named Blocks and whenever you create a block, check if an item exists in the dictionary with the key as the Vector3 position where the block is to be placed, if not, then place the block and then add it to the dictionary.

I can but I would like to exclude it in this part of the code:

local function findClosestPart()
	local currentPart = nil
	local currentClosestDir = nil
	local currentClosestMag = nil

	local mouseRay = currentCamera:ScreenPointToRay(mouse.X, mouse.Y)

	for i,v in ipairs(BlocksFolder:GetDescendants()) do
		if v:IsA("BasePart") then
			local mouse3DPos = mouseRay:ClosestPoint(v.Position)
			local magnitude = (v.Position - mouse3DPos).Magnitude
			
			if not currentClosestMag then
				currentClosestMag = magnitude
				currentPart = v
			elseif magnitude < currentClosestMag then
				currentClosestMag = magnitude
				currentPart = v
			end
		end
	end

	if currentPart then
		local surfaceOffset = Vector3.new(25, 25, 25) -- Offset to determine surface positions

		local surfaceIDs = {
			Left = currentPart.Position - Vector3.new(surfaceOffset.X, 0, 0),
			Right = currentPart.Position + Vector3.new(surfaceOffset.X, 0, 0),
			Front = currentPart.Position - Vector3.new(0, 0, surfaceOffset.Z),
			Back = currentPart.Position + Vector3.new(0, 0, surfaceOffset.Z),
		}

		if mouse.Target == nil then
			local closestMag = nil
			local rayDir = mouseRay.Direction
			local rayOrigin = mouseRay.Origin

			for surfaceId, pos in pairs(surfaceIDs) do
				local dir = (pos - rayOrigin).Unit
				local magnitude = (rayDir - dir).Magnitude

				if not closestMag then
					closestMag = magnitude
					currentClosestDir = surfaceId
				elseif magnitude < closestMag then
					closestMag = magnitude
					currentClosestDir = surfaceId
				end
			end
		else
			local mouse3DPos = mouseRay:ClosestPoint(currentPart.Position)
			local closestMag = nil

			for surfaceId, pos in pairs(surfaceIDs) do
				local magnitude = (mouse3DPos - pos).Magnitude

				if not closestMag then
					closestMag = magnitude
					currentClosestDir = surfaceId
				elseif magnitude < closestMag then
					closestMag = magnitude
					currentClosestDir = surfaceId
				end
			end
		end

		if currentClosestDir then
			return currentPart, currentClosestDir
		end
	

Here:

local currentPart = nil
	local currentClosestDir = nil
	local currentClosestMag = nil

	local mouseRay = currentCamera:ScreenPointToRay(mouse.X, mouse.Y)

	for i,v in ipairs(BlocksFolder:GetDescendants()) do
		if v:IsA("BasePart") then
			local mouse3DPos = mouseRay:ClosestPoint(v.Position)
			local magnitude = (v.Position - mouse3DPos).Magnitude
			
			if not currentClosestMag then
				currentClosestMag = magnitude
				currentPart = v
			elseif magnitude < currentClosestMag then
				currentClosestMag = magnitude
				currentPart = v
			end
		end
	end
1 Like

There’s two solutions to this, you can either do what Jqck said and use a dictionary and index it to check if a Block already exists on the point you’re interested in, or use spatial query to check, either
GetPartsInPart or GetPartsBoundInBox

I only want to check the positions, not checking if its inside.

By the way, when trying to predict the next position, the prediction is inside another block:
image

Black = Where to place block
Red = Prediction

1 Like