Tower Defense Unit Placement Issue

I made a tower defense unit placement. It works fine. Everything works fine but I’m attempting to create a barrier that doesnt allow units to be placed next to them. (This is so you cant place all units on top of each other) Ive gone through many different versions of this script to try and get it to work but it does not.

The main issue is the barrier doesnt change color or stop me from placing units within the barrier. This video shows the main issue: https://gyazo.com/4ed9bec601b5e9390e8214dec7914476

local gui = script.Parent

local rs = game:GetService("ReplicatedStorage")
local units = rs.Units.Units
local unitfolder = game.Workspace.TestMap.Units
local RunService = game:GetService("RunService")

local UIS = game:GetService("UserInputService")

local camera = game.Workspace.CurrentCamera

local slot1 = gui.Slot1.Main:FindFirstChildOfClass("TextButton")
local slot2 = gui.Slot2.Main:FindFirstChildOfClass("TextButton")
local slot3 = gui.Slot3.Main:FindFirstChildOfClass("TextButton")
local slot4 = gui.Slot4.Main:FindFirstChildOfClass("TextButton")
local slot5 = gui.Slot5.Main:FindFirstChildOfClass("TextButton")
local slot6 = gui.Slot6.Main:FindFirstChildOfClass("TextButton")

local unitToSpawn = nil
local canPlace = false
local isPreviewOverlapping = false

local placemode = false

local unitlimit = 4
local unitamount = 0

local previewBarrierTemplate = Instance.new("Part")
previewBarrierTemplate.Material = Enum.Material.ForceField
previewBarrierTemplate.Size = Vector3.new(2.5,2.5,2.5)
previewBarrierTemplate.CastShadow = false
previewBarrierTemplate.Shape = Enum.PartType.Cylinder
previewBarrierTemplate.CanTouch = true
previewBarrierTemplate.CanCollide = false
previewBarrierTemplate.Anchored = false
previewBarrierTemplate.Transparency = 0.5


local function placementMode()
	if placemode == true then
		for _, model in unitfolder:GetChildren() do
			if model:IsA("Model") and model:FindFirstChild("HumanoidRootPart") then
				local existingPlacedBarrier = model:FindFirstChild("Barrier")
				if existingPlacedBarrier then existingPlacedBarrier:Destroy() end

				local barrier1 = previewBarrierTemplate:Clone()
				barrier1.Name = "Barrier"
				barrier1.Parent = model
				barrier1.CFrame = model.HumanoidRootPart.CFrame
				barrier1.Color = Color3.fromRGB(255,0,0)
				barrier1.Transparency = 0.7
				
				local weld = Instance.new("Weld")
				weld.Parent = barrier1
				weld.Part0 = model.HumanoidRootPart
				weld.Part1 = barrier1
				weld.C0 = CFrame.new(0,0,0) 
			end
	end
	elseif placemode == false then
		for _, model in unitfolder:GetChildren() do
			if model:isA("Model") then
				local existingBarrier = model:FindFirstChild("Barrier")
				if existingBarrier and existingBarrier:IsA("BasePart") then
					existingBarrier:Destroy()
				end
			end
		end
	end
end


local function MouseRaycast(blacklist)
	local mousePosition = UIS:GetMouseLocation()
	local mouseRay = camera:ViewportPointToRay(mousePosition.X, mousePosition.Y)
	
	local raycastParams = RaycastParams.new()
	raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
	raycastParams.FilterDescendantsInstances = blacklist
	
	local raycastResult = game.Workspace:Raycast(mouseRay.Origin, mouseRay.Direction * 1000, raycastParams)
	
	return raycastResult
end

local function removeholder()
	if unitToSpawn then
		unitToSpawn:Destroy() 
		unitToSpawn = nil
	end
end

local function unitadder(slot, name)
	local existing = slot.Name
	
	if existing == name then
		placemode = true
		placementMode() 
		removeholder() 
		
		local unitModel = units:FindFirstChild(name)
		if not unitModel then
			warn("Unit model not found in ReplicatedStorage:", name)
			return
		end
		unitToSpawn = unitModel:Clone()
		unitToSpawn.Parent = game.Workspace.TestMap.PlaceholderUnit 
		
		local hrp = unitToSpawn:FindFirstChild("HumanoidRootPart")
		if not hrp or not hrp:IsA("BasePart") then
			warn("UnitToSpawn", unitToSpawn.Name, "is missing HumanoidRootPart. Cannot add barrier.")
			unitToSpawn:Destroy()
			unitToSpawn = nil
			return
		end

		local oldBarrier = unitToSpawn:FindFirstChild("Barrier")
		if oldBarrier then
			oldBarrier:Destroy()
		end
		
		local currentPreviewUnitBarrier = previewBarrierTemplate:Clone()
		currentPreviewUnitBarrier.Name = "Barrier" 
		currentPreviewUnitBarrier.Parent = unitToSpawn
		currentPreviewUnitBarrier.CFrame = hrp.CFrame 
		
		local weld = Instance.new("Weld")
		weld.Parent = currentPreviewUnitBarrier
		weld.Part0 = hrp
		weld.Part1 = currentPreviewUnitBarrier
		weld.C0 = CFrame.new(0, 0, 0) 
		
		for _, object in unitToSpawn:GetDescendants() do
			if object:IsA("BasePart") and object.Name ~= "Barrier" then 
					object.CollisionGroup = "Unit" 
			end
		end
	end
end

slot1.Activated:Connect(function()
	unitadder(slot1, slot1.Name)
end)
slot2.Activated:Connect(function()
	unitadder(slot2, slot2.Name)
end)
slot3.Activated:Connect(function()
	unitadder(slot3, slot3.Name)
end)
slot4.Activated:Connect(function()
	unitadder(slot4, slot4.Name)
end)
slot5.Activated:Connect(function()
	unitadder(slot5, slot5.Name)
end)
slot6.Activated:Connect(function()
	unitadder(slot6, slot6.Name)
end)


UIS.InputBegan:Connect(function(input, processed)
	if processed then
		return
	end
	
	if unitToSpawn and input.UserInputType == Enum.UserInputType.MouseButton1 then
		if unitamount < unitlimit then
			if canPlace == true and not isPreviewOverlapping then
				local event = rs.UnitSpawn
				event:FireServer(unitToSpawn.Name, unitToSpawn:GetPivot())
				
				removeholder() 
				unitamount = unitamount + 1
				placemode = false
				placementMode() 
			else
				if not canPlace then
					warn("Cannot place unit here: Invalid surface or target.")
				elseif isPreviewOverlapping then
					warn("Cannot place unit: Overlapping with another unit's barrier or a restricted area.")
				end
			end
		else
			warn("Unit limit reached. Cannot place more units.")
		end
	end
end)

RunService.RenderStepped:Connect(function()
	if unitToSpawn then
		local result = MouseRaycast({unitToSpawn})
		local currentPreviewUnitBarrier = unitToSpawn:FindFirstChild("Barrier") 
		
		if result and result.Instance then
			if result.Instance.Parent and result.Instance.Parent.Name == "Green" and unitamount < unitlimit then
				canPlace = true
			else
				canPlace = false
			end
			
			local y_offset = 0
			local hrp = unitToSpawn:FindFirstChild("HumanoidRootPart")
			local humanoid = unitToSpawn:FindFirstChildOfClass("Humanoid")

			if hrp and humanoid then
				y_offset = humanoid.HipHeight + (hrp.Size.Y / 2)
			elseif hrp then 
				y_offset = hrp.Size.Y / 2 
			end

			local x = result.Position.X
			local y = result.Position.Y + y_offset
			local z = result.Position.Z
			local CF = CFrame.new(x,y,z)
			unitToSpawn:PivotTo(CF)
			
			if currentPreviewUnitBarrier then
				isPreviewOverlapping = false 
				local touchingParts = currentPreviewUnitBarrier:GetTouchingParts()
				for _, part in touchingParts do
					if (part.Name == "Barrier" and not part:IsDescendantOf(unitToSpawn)) or part.Name == "Path" then
						isPreviewOverlapping = true
						break
					end
				end
			else
				isPreviewOverlapping = true 
				 warn("Preview unit '" .. unitToSpawn.Name .. "' is missing its Barrier part in RenderStepped!")
			end
			
		else 
			canPlace = false
		end

		if currentPreviewUnitBarrier then
			if canPlace == true and not isPreviewOverlapping then
				currentPreviewUnitBarrier.Color = Color3.fromRGB(0,255,0)
			else
				currentPreviewUnitBarrier.Color = Color3.fromRGB(255,0,0)
			end
		end
	else
		canPlace = false
		isPreviewOverlapping = false 
	end
end)


Hey So if im seeing this right the main issue seems to be with the way you’re detecting overlaps with the units “Barrier” part. Specifically:

You’re using GetTouchingParts() to detect if the preview unit overlaps another unit’s barrier or path, However

  1. GetTouchingParts() only works if CanTouch = true and CanCollide = true
  2. Your barriers are currently set to CanCollide = false, so overlap checks fail.
  3. Also, if the CollisionGroup "Unit" is set to ignore itself, since barriers won’t detect each other either.

Instead of GetTouchingParts(), use workspace:GetPartsInPart(), which works better for these non collidable overlap checks.

if currentPreviewUnitBarrier then
	isPreviewOverlapping = false
	
	local overlapParams = OverlapParams.new()
	overlapParams.FilterType = Enum.RaycastFilterType.Exclude
	overlapParams.FilterDescendantsInstances = {unitToSpawn}

	local overlappingParts = workspace:GetPartsInPart(currentPreviewUnitBarrier, overlapParams)
	for _, part in overlappingParts do
		if (part.Name == "Barrier" and not part:IsDescendantOf(unitToSpawn)) or part.Name == "Path" then
			isPreviewOverlapping = true
			break
		end
	end
else
	isPreviewOverlapping = true 
	warn("Preview unit is missing its Barrier part!")
end

Also make sure your "Unit" collision group isn’t ignoring itself (check via Model > Collision Groups in explorer)

ps. Im making this post at 2am so I might be entirely wrong about this, if this doesn’t work js lmk I can try looking more into it in the morning if you would like.

1 Like

Seems what you said solved the issue perfectly. Thank you!

1 Like

no problem, glad to see what I said helped.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.