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:
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
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.
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
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.
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?
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.
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.
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.