Hey guys, So I have this tower defence game and I would like to ask what is the best way to change towers when they upgrade? Like I want them to change visually and I want their stats to change. I have tried changing it’s model, but that threw an error here and there and now I have it so that the model is the same but it just changes what accesories and stuff are visible and not, but this is very complicated and it throws errors when I try making it change “Shoot Position”. How would you do this?
what is the error message in the output
@Godzilla_RealLife Huh?
When I made one, I had used modules for the code of the Towers. And when you used a function to change the tower, it would disable the tower temporarily until the model is changed.
Now that I think about it, It might not have been an error that was unfixable…
Yeah, I could try that, but now after I realized that the error might not have been an error even related to the tower model then I might just use OOP
OOP is usually the way to go for these things.
If you need any help, let me know. I do a lot of module work.
Yeah, I literally just ChatGPT on how I should do it. I already have modules in my game but I think it would be more modular if I made the setup tower system a OOP system because right now it looks like this
(Server sends a placed tower)
-- COMPLETE FIXED TOWERS MODULE --
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CollectionService = game:GetService("CollectionService")
local RunService = game:GetService("RunService")
local Animations = require(ReplicatedStorage.Modules.Animations)
local Towers = {}
local targetCache = {}
local mobPositions = {}
-- Pre-calculate mob positions
RunService.Heartbeat:Connect(function()
mobPositions = {}
for _, mob in ipairs(workspace.Hitboxes:GetChildren()) do
if mob:FindFirstChild("Health") then
mobPositions[mob] = mob.Position
end
end
end)
-- Improved targeting with validation
local function GetNextTarget(tower)
-- Cache invalidation
--local cached = targetCache[tower]
--if cached then
-- if not cached.Parent or not cached:FindFirstChild("Health") or cached.Health.Value <= 0 then
-- targetCache[tower] = nil
-- cached = nil
-- else
-- return cached
-- end
--end
local Config = tower.Config
local HRP = tower.HumanoidRootPart
if not (Config and HRP) then return nil end
local range = tower:GetAttribute("Range")
local mode = Config:FindFirstChild("TargetingMode") and Config.TargetingMode.Value or "First"
local candidates = {}
for mob, position in pairs(mobPositions) do
if mob.Parent and mob:FindFirstChild("Health") and mob:FindFirstChild("Progress") and mob.Health.Value > 0 then
if not CollectionService:HasTag(mob, "Hidden") or (CollectionService:HasTag(mob, "Hidden") and tower:GetAttribute("HiddenDetection") == true) then
local dist = (position - HRP.Position).Magnitude
if dist <= range then
table.insert(candidates, {
mob = mob,
dist = dist,
progress = mob.Progress.Value,
health = mob.Health.Value
})
end
end
end
end
if #candidates == 0 then
return nil
end
-- Sorting logic
table.sort(candidates, function(a, b)
if mode == "Closest" then return a.dist < b.dist
elseif mode == "Farthest" then return a.dist > b.dist
elseif mode == "First" then return a.progress > b.progress
elseif mode == "Last" then return a.progress < b.progress
elseif mode == "Strongest" then return a.health > b.health
elseif mode == "Weakest" then return a.health < b.health
else return a.dist < b.dist end
end)
if mode == "Random" then
return candidates[math.random(1, #candidates)].mob
end
return candidates[1].mob
end
-- Robust tower setup
function Towers.SetupTower(tower)
-- Validate tower
if not tower.PrimaryPart or not tower:FindFirstChild("Config") then
tower:Destroy()
return
end
if tower.PrimaryPart then
tower.PrimaryPart.Anchored = true
else
tower:WaitForChild("HumanoidRootPart").Anchored = true
end
local connections = {}
local module = require(tower.Config.Module.Value)
local Config = tower.Config
-- Cleanup function
local function cleanUp()
for _, c in ipairs(connections) do
c:Disconnect()
end
targetCache[tower] = nil
end
-- Track destruction
local destroyConn = tower.AncestryChanged:Connect(function(_, parent)
if not parent then
cleanUp()
end
end)
table.insert(connections, destroyConn)
-- Animation setup
local idleTrack
if Config.IdleAnimation then
idleTrack = Animations.AnimateHumanoid(Config.IdleAnimation, tower, true, 1, Enum.AnimationPriority.Idle)
end
-- CACHE MOTOR6Ds once, before the while‑loop
local leftShoulder = tower:FindFirstChild("Left Shoulder", true)
local rightShoulder = tower:FindFirstChild("Right Shoulder", true)
local function aimArms(targetPos)
if not (leftShoulder and rightShoulder) then return end
local hrp = tower:FindFirstChild("HumanoidRootPart")
if not hrp then return end
local dir = (targetPos - hrp.Position).Unit
local yaw = math.atan2(dir.X, dir.Z) -- horizontal angle
leftShoulder.C0 = leftShoulder.C0 * CFrame.Angles(0, yaw, 0)
rightShoulder.C0 = rightShoulder.C0 * CFrame.Angles(0, yaw, 0)
end
-- Main tower loop
while tower:IsDescendantOf(workspace) do
if CollectionService:HasTag(tower, "Stunned") then task.wait(0.1) continue end
local target = GetNextTarget(tower)
if not target or not target.Parent or not target:FindFirstChild("Health") or target.Health.Value <= 0 then
task.wait(0.1)
continue
end
local targetPos = target.Position
task.wait(tower:GetAttribute("Cooldown"))
if CollectionService:HasTag(tower, "Stunned") then task.wait(0.1) continue end
-- Separate body and aiming rotations
if tower:FindFirstChild("HumanoidRootPart") and target then
local HRP = tower:FindFirstChild("HumanoidRootPart")
local leftShoulder = tower:FindFirstChild("Left Shoulder", true)
local rightShoulder = tower:FindFirstChild("Right Shoulder", true)
local originalLeftC0 = leftShoulder and leftShoulder.C0
local originalRightC0 = rightShoulder and rightShoulder.C0
-- 1) Yaw-only body rotation
local hrp = tower:FindFirstChild("HumanoidRootPart")
if hrp and target then
local fromPos = hrp.Position
local toPos = Vector3.new(target.Position.X, fromPos.Y, target.Position.Z)
local dir = (toPos - fromPos)
if dir.Magnitude > 0 then
hrp.CFrame = CFrame.new(fromPos, fromPos + dir.Unit)
end
end
aimArms(targetPos)
local shootPosition
if Config:FindFirstChild("ShootPosition").Value ~= nil then
shootPosition = Config:FindFirstChild("ShootPosition").Value.Position
else
shootPosition = tower:FindFirstChild("HumanoidRootPart") and tower:FindFirstChild("HumanoidRootPart").Position or tower.PrimaryPart.Position
end
if tower:FindFirstChild("HumanoidRootPart"):FindFirstChild("ShootSound") then
tower.HumanoidRootPart.ShootSound:Play()
end
if Config.AttackAnimation then
Animations.AnimateHumanoid(Config.AttackAnimation, tower, false, 1, Enum.AnimationPriority.Action, tower:GetAttribute("ProjectileType"), targetPos)
end
if target and target:FindFirstChild("Health") and target.Health.Value > 0 then
module.Attack(tower, shootPosition, target, tower:GetAttribute("Damage"))
end
end
end
task.wait(0.1)
end
return Towers
Note: this is not finished at all parts and may contain code that doesn’t do anything.
Now that I think about it, I think the tower system already is easy to expand on… Like I already have that the towers have a module chosen and that’s how they behave and it does this for all of them…
good deal. Just don’t do all this then get burnt out lol have a good one
You too! See ya around, happy programming (I’m just trying to hit the character minimum xd)
Ok, I’m done trying to figure out this myself, I know you are a programmer and not a moddeler or building but you know tower blitz? How can I make clothes like theirs but not make them look like this?
Here is some good examples of a tower blitz tower
the best way is to have specific meshes that are stored in “tier” folders so they can be swapped out when the tower’s tier changes. this is scalable for dual/tri paths as you can have T2P1, T2P2, etc so you only swap out the cosmetic relating to that path. for getting into code specifics, you need to store the tier meshes in a dictionary. since you are storing the tier meshes in a folder, you could most likely just clone the folder and parent it to that tower while doing all of the required weld stuff. when the tier is changed, just delete the previous tier’s folder and replace it with the new one. this system should work well since you are only swapping out specific parts of the rig instead of making an entire rig for each tier, storing that in the game files, cloning it on upgrade, destroying the previous rig, loading all of the animations again, etc
I do some modeling. They had used blender and used the “plane” object to manually wrap around the part, then they export the Torso shirt part and sleeves and welds them to the Players Parts.
Honestly, I do like how your clothing looks.
Yeah, I know that, but how do they get it so glossy and actually make it look like clothing and not like a flat tunic?
ice or plastic -----------------------------
Yeah, I don’t have much more excuses than I am not that good at modeling, but then I am decent at modeling but I just don’t know how I can come up with details, like how do you come up with put this little scratch here, and that bump there, and that knee pad there and etc. etc.


