Need some quick help

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.

2 Likes

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…

1 Like

good deal. Just don’t do all this then get burnt out lol have a good one

1 Like

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.