Tower defense game wont place correctly with specific mesh

I am making a tower defense game and everything works correctly but with this specific mesh that is literally no different than other meshes makes it not place the tower in the spot. It juts teleports the tower back to the original spot where I was making it at on workspace. Here are the meshes and the properties:
Screenshot 2025-01-21 193937


I do not know if it is the scripts but here is this scripts. If you ask me for more of the scripts I can show you more of it:

local function MouseRaycast(model)
	local mousePosition = UserInputService:GetMouseLocation()
	local mouseRay = camera:ViewportPointToRay(mousePosition.X, mousePosition.Y)
	local raycastParams = RaycastParams.new()
	
	local blacklist = camera:GetChildren()
	table.insert(blacklist, model)
	raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
	raycastParams.FilterDescendantsInstances = blacklist

	local raycastResult = workspace:Raycast(mouseRay.Origin, mouseRay.Direction * 1000, raycastParams)
	
	return raycastResult
end

local function CreateRangeCircle(tower, placeholder)
	
	local range = tower.Config.Range.Value
	local height = (tower.PrimaryPart.Size.Y / 2) + tower.Humanoid.HipHeight
	local offset = CFrame.new(0, -height, 0)
	
	local p = Instance.new("Part")
	p.Name = "Range"
	p.Shape = Enum.PartType.Cylinder
	p.Material = Enum.Material.Neon
	p.Color = Color3.fromRGB(0, 115, 255)
	p.Transparency = 0.9
	p.Size = Vector3.new(2, range * 2, range * 2)
	p.TopSurface = Enum.SurfaceType.Smooth
	p.BottomSurface = Enum.SurfaceType.Smooth
	p.CFrame = tower.PrimaryPart.CFrame * offset * CFrame.Angles(0, 0, math.rad(90))
	p.CanCollide = false
	
	if placeholder then
		p.Anchored = false
		local weld = Instance.new("WeldConstraint")
		weld.Part0 = p
		weld.Part1 = tower.PrimaryPart
		weld.Parent = p
		p.Parent = tower
	else
		p.Anchored = true
		p.Parent = workspace.Camera
	end
	
end

local function RemovePlaceholderTower()
	if towerToSpawn then
		towerToSpawn:Destroy()
		towerToSpawn = nil
		rotation = 0
		gui.Controls.Visible = false
	end
end

local function AddPlaceholderTower(name)
	
	local towerExists = towers:FindFirstChild(name)
	if towerExists then
		RemovePlaceholderTower()
		towerToSpawn = towerExists:Clone()
		towerToSpawn.Parent = workspace
		
		CreateRangeCircle(towerToSpawn, true)
		
		for i, object in ipairs(towerToSpawn:GetDescendants()) do
			if object:IsA("BasePart") then
				PhysicsService:SetPartCollisionGroup(object, "Tower")
				if object.Name ~= "Range" then
					object.Material = Enum.Material.Metal
					object.Transparency = 0
				end
			end
		end
		
		gui.Controls.Visible = true
	end
end

local function ColorPlaceholderTower(color)
	for i, object in ipairs(towerToSpawn:GetDescendants()) do
		if object:IsA("BasePart") then
			object.Color = color
		end
	end
end

local function toggleTowerInfo()
	workspace.Camera:ClearAllChildren()
	gui.Towers.Title.Text = "Towers: " .. placedTowers .. "/" .. maxTowers
	
	if selectedTower then
		CreateRangeCircle(selectedTower)
		gui.Selection.Visible = true
		local config = selectedTower.Config
		gui.Selection.Stats.Damage.Value.Text = config.Damage.Value
		gui.Selection.Stats.Range.Value.Text = config.Range.Value
		gui.Selection.Stats.Cooldown.Value.Text = config.Cooldown.Value
		gui.Selection.Title.TowerName.Text = selectedTower.Name
		gui.Selection.Title.TowerImage.Image = config.Image.Texture
		gui.Selection.Title.OwnerName.Text = config.Owner.Value .. "'s"
		
		local modes = {
			["First"] = "rgb(150, 150, 150)",
			["Last"] = "rgb(50, 50, 50)", 
			["Near"] = "rgb(50, 150, 0)", 
			["Strong"] = "rgb(200, 50, 50)", 
			["Weak"] = "rgb(50, 100, 200)"
		}
		local color = modes[config.TargetMode.Value]
		gui.Selection.Action.Target.Title.Text = "Target: <font color=\"" .. color .. "\">" .. config.TargetMode.Value .. "</font>"
		
		if config.Owner.Value == Players.LocalPlayer.Name then
			gui.Selection.Action.Visible = true
			
			local upgradeTower = config:FindFirstChild("Upgrade")
			if upgradeTower then
				gui.Selection.Action.Upgrade.Visible = true
				gui.Selection.Action.Upgrade.Title.Text = "Upgrade (" .. upgradeTower.Value.Config.Price.Value .. ")"
			else
				gui.Selection.Action.Upgrade.Visible = false
			end
		else
			gui.Selection.Action.Visible = false
		end
		
	else
		gui.Selection.Visible = false
	end
end

local function SpawnNewTower()
	if canPlace then
		local placedTower = spawnTowerFunction:InvokeServer(towerToSpawn.Name, towerToSpawn.PrimaryPart.CFrame)
		if placedTower then
			placedTowers += 1
			selectedTower = placedTower
			RemovePlaceholderTower()
			toggleTowerInfo()
		end
	end
end
1 Like

Can you explain exactly what’s broken a bit better? As of now I think you mean:
~ Using the Base as the model’s PrimaryPart makes it spawn in the correct place, but using the Turret as the model’s PrimaryPart doesn’t.

I also don’t know what Server code you have running after you use :InvokeServer(). If all you do is change the PrimaryPart’s CFrame on the Server, that can cause issues. I am not certain, but I think this is because the Roblox Engine stars relying on whatever part the “RootPart” is (how Roblox determines the RootPart has weird rules surrounding it, and it’s best to not rely on the RootPart anyways. Also note, RootPart isn’t the same thing as “HumanoidRootPart”. It’s different.).

What you should do with models to place them at a different CFrame is to use :PivotTo()
~ Please note that the more intuitively named :SetPrimaryPartCFrame() is deprecated, and you shouldn’t use it for that very reason.

If my assumptions are wrong, I’d like to see what you do Server-sided to position your real tower where you want it to go.

I said it wont place the towers down whenever the “base” mesh is sent to the model. When I test it in-game it just does not place wherever I place it to, it just sends it back to the original Cframe. But all the other meshes work perfectly, for example the turret. It has the same welding as the turret so I dont know why it is doing this. Whenever I add a mesh to other towers it works but not this tower that is called “Supply Crate”

I believe I completely failed at guessing what was happening here, and I am still failing to understand. Is this an issue with where the placeholder tower on the Client is, or the real tower made by the Server, or both? Or is it an issue when you specifically weld the Base to whichever part you chose in the model to weld it to? Maybe I am just missing something but I feel I am not getting enough information on exactly when the positioning is going wrong.


The welds are both connected to the humanoidrootpart of the tower. Here is what it looks like whenever I try placing it:
Screenshot 2025-01-20 172752
The circle part is from the script which puts it around the primaryPart not from the actual model.

If you look closely in this picture above you can see a tiny dot falling. That is the tower with the base mesh in it. With other meshes it would not do this

An example of a working tower is this:


All the highlights are the meshes that are welded and works

This has definitely helped with identifying the issue. I still have to play with some guesswork here, so I’m assuming that the purpose of your MouseRaycast function returning a raycastResult is to move the circle you call “Range” to where you click. “Range” is welded to the HumanoidRootPart of your model. If welding your mesh named “Base” to the HumanoidRootPart causes your model to remain where you were making it in Studio, then the “Base” mesh is either Anchored or it’s the AssemblyRootPart.

If it’s Anchored, then whoops, silly mistake. The pictures you provided of the properties of “Turret” and “Base” do not show the Anchored property. the Anchored property is found under the “Part” tab in the “Properties” window.

If it’s the AssemblyRootPart, and you indeed are moving the “Range” by directly setting the CFrame property, then you are probably relying on “Range” being the AssemblyRootPart and you’re not aware of it. (I mistakenly called the “AssemblyRootPart” the “RootPart” earlier. Sorry, that was its old name). The AssemblyRootPart has some weird rules around how it’s determined.


Image origin: Assemblies | Documentation - Roblox Creator Hub


Alright, it’s not anchored. Now to address the second issue I brought up, what’s the AssemblyRootPart of the “Range” while you’re trying to place your tower? Is it the same as when you’re placing a tower that places correctly?

AssemblyRootPart? The range stays the same when I place it

Yeah, AssemblyRootPart. It’s a Read-only property whose chosen Part is automatically determined by the Roblox engine. The AssemblyRootPart property is found under the “Assembly” section in the Properties window. I’m running on an assumption there so I’m probably wasting your time with that right now. So I’ll ask a question I can try to stop giving potentially bad advice:

When do you move your tower to where you want it to go? I can’t seem to find it in the code. Please tell me where it is if I am blind. I see you position your “Range” right under the HumanoidRootPart and then weld it to the HumanoidRootPart (good!), but I don’t know what you do to get your “Range” to show up on the ground next to you while the rest of the tower (and probably its HumanoidRootPart too) is 500 studs over yonder. Is it a change in CFrame for the “Range”? Is it a :PivotTo() for the tower’s model? Are you changing the Position of the HumanoidRootPart and somehow it’s just not moving at all but the “Range” is? I’m just fishing for more information.

22_game.rbxl (1.7 MB)
Here is a kit of a tower defense

Mmm, it was a kit all along. I don’t expect you to understand everything about the code anymore. By the way, the answer to my question was that it moves the HumanoidRootPart by just setting the CFrame. No :PivotTo(), no :SetPrimaryPartCFrame(), just assinging a value to CFrame. It also does it on the Server module script named “Tower”. It’s at the “newTower.HumanoidRootPart.CFrame = cframe” line.

function tower.Spawn(player, name, cframe, previous)
	local allowedToSpawn = tower.CheckSpawn(player, name, previous)
	
	if allowedToSpawn then
		
		local newTower
		local oldMode = nil
		if previous then
			oldMode = previous.Config.TargetMode.Value 
			previous:Destroy()
			newTower = ReplicatedStorage.Towers.Upgrades[name]:Clone()
		else
			newTower = ReplicatedStorage.Towers[name]:Clone()
			player.PlacedTowers.Value += 1
		end
		
		local ownerValue = Instance.new("StringValue")
		ownerValue.Name = "Owner"
		ownerValue.Value = player.Name
		ownerValue.Parent = newTower.Config
		
		local targetMode = Instance.new("StringValue")
		targetMode.Name = "TargetMode"
		targetMode.Value = oldMode or "First"
		targetMode.Parent = newTower.Config
		
		newTower.HumanoidRootPart.CFrame = cframe
		newTower.Parent = workspace.Towers
		newTower.HumanoidRootPart:SetNetworkOwner(nil)
		
		local bodyGyro = Instance.new("BodyGyro")
		bodyGyro.MaxTorque = Vector3.new(math.huge, math.huge, math.huge)
		bodyGyro.D = 0
		bodyGyro.CFrame = newTower.HumanoidRootPart.CFrame
		bodyGyro.Parent = newTower.HumanoidRootPart

		for i, object in ipairs(newTower:GetDescendants()) do
			if object:IsA("BasePart") then
				PhysicsService:SetPartCollisionGroup(object, "Tower")
			end
		end
		
		player.Gold.Value -= newTower.Config.Price.Value
		
		coroutine.wrap(tower.Attack)(newTower, player)
		
		return newTower
	else
		warn("Requested tower does not exist:", name)
		return false
	end
end
spawnTowerFunction.OnServerInvoke = tower.Spawn

That’s just asking for issues to arise because its relying on the HumanoidRootPart to be the AssemblyRootPart. All you need is to have something with a high enough Mass to mess that up!

Make the “Base” mesh massless by enabling its “Massless” property.

Also be forewarned, this kit looks outdated. I haven’t looked for long, but I already see a “BodyGyro” being used. Those were already deprecated in 2021 or earlier, and therefore shouldn’t still be in use.

1 Like

No way that was the entire solution the entire time. Tysm! But I do know like over 80% of the code because I have added some addons to it.

While it is a solution, it’s definitely not the ideal solution, it’s just a solution where “it just works”. But, if you’re content with relying on the AssemblyRootPart being the HumanoidRootPart, then that’s all that matters right now. I’ve definitely had my supersized share of stuff like that in the past.

You can influence which is the AssemblyRootPart with “RootPriority”, “Mass” (it’s read-only but can be changed using the Density in the CustomPhysicalProperties), “Massless”, and apparently Names. So be aware, AssemblyRootPart logic runs on a formula I haven’t seen and don’t know where to find.

If you’re bent on doing it in a way were you don’t have to rely on a formula for AssemblyRootPart that you can’t see, use :PivotTo() on the Model instead of directly setting .CFrame on a Part.