Module Script Function Stops Working When Model is Destroyed?

  1. What do you want to achieve?
    So, I have been working on a Tower Defense game solo, and I am attmepting to make a track tower for the first time. Now, I have a module function to control enemy and track unit spawns, and they are quite similar, however, when spawning a unit the function will stop working if the for loop in it does not go through all the way. This usually happens because the unit damages an enemy and gets destroyed when it doesn’t have enough health to kill the enemy.

  2. What is the issue?
    As briefly stated above, if my unit for a tower in my tower defense game dies from damaging enemies, it causes no more units to be spawned. It’s really annoying because I don’t know what is causing this issue as I get no output errors, and on paper the code shouldn’t have any issues.

  3. What solutions have you tried so far?
    This problem is quite specific so I didn’t expect to find anything on the Devforum. However, at first I thought it was an issue caused by lacking a wait time between spawning units. However, once I disabled the unit’s damage script, the function works fine and doesn’t stop executing. I also thought it could be solved with putting some returns, as maybe the function isn’t concluding when the unit is destroyed, however that didn’t seem to wor either. I am not sure what is causing the script to simply stop running so I thought I would take to the devforum to get some in put.

Here is any relevant code needed to analyze the issue:
Spawn Function:

function waveManager.SpawnUnit(tower, level, unitName)
	print("Spawning ".. unitName)

	local map = workspace:FindFirstChild(replicatedStorage.MatchData.MapName.Value)
	local paths = map:FindFirstChild("UnitCheckpoints")
	local numPaths = #paths:GetChildren()
	local unit = replicatedStorage.Towers[tower][level][unitName]:Clone()
	unit.HumanoidRootPart.Position = map.UnitCheckpoints:FindFirstChild("0").Position
	unit.Parent = workspace.Units
	unit.Name = unitName
	for i = 1, numPaths - 1, 1 do
		if unit ~= nil then
			local trackPart = paths[i]
			local previousVal = i - 1
			local previousTrack = paths[previousVal]
			if unit ~= nil and unit.PrimaryPart ~= nil then
				unit:SetPrimaryPartCFrame(previousTrack.CFrame)
			else
				return
			end
			local endPoint = {
				Position = trackPart.CFrame.Position
			}
			
			local speed
			local tweenInfo
			local tween
			
			if unit and unit:FindFirstChildOfClass("Humanoid") then
				speed = ((paths:FindFirstChild(i).Position - paths:FindFirstChild(previousVal).Position).Magnitude) / (1 + unit:FindFirstChild("Humanoid").WalkSpeed)
				
				tweenInfo = TweenInfo.new(speed, Enum.EasingStyle.Linear, Enum.EasingDirection.Out)
				
				tween = tweenService:Create(unit.PrimaryPart, tweenInfo, endPoint)
			else
				return
			end
			
			if tween ~= nil then
				tween:Play()
				
				tween.Completed:Wait()
			end
			
			if i == numPaths - 1 and unit ~= nil then
				unit:Destroy()
			end 
		elseif unit == nil then
			return
		end
	end
end

Unit Damage Script:

local pirate = script.Parent

local enemyFolder = workspace.Enemies

local nearbyEnemies = {}

local attacking = false

local function Attack(enemy, damage)
	--idleAnim:Stop()

	attacking = true

	local attackSound = pirate.Humanoid:FindFirstChild("AttackSound")
	attackSound:Play()

	local humanoid = script.Parent.Humanoid
	local animator = humanoid.Animator
	local attack = humanoid.Attack

	local animTrack = animator:LoadAnimation(attack)
	animTrack.Priority = Enum.AnimationPriority.Action
	animTrack:Play()

	if enemy ~= nil then
		local enemyHealth = enemy.Humanoid.Health
		
		if pirate.Configuration.Health.Value > enemyHealth then
			pirate.Configuration.Health.Value -= enemyHealth
			
			enemy:Destroy()
			
			print("Pirate has ".. pirate.Configuration.Health.Value .." health leftover")
		elseif pirate.Configuration.Health.Value == enemyHealth then
			pirate:Destroy()
			
			enemy:Destroy()
		elseif pirate.Configuration.Health.Value < enemyHealth then
			enemy.Humanoid:TakeDamage(pirate.Configuration.Health.Value)
			
			pirate:Destroy()
		end
	end

	attacking = false
end

local function CheckTargets()
	local targetEnemy

	local highestDistance = 0

	local furthest

	for _, enemy in pairs(enemyFolder:GetChildren()) do
		if enemy and enemy.HumanoidRootPart and pirate.PrimaryPart then
			if (pirate.PrimaryPart.Position - enemy.HumanoidRootPart.Position).Magnitude <= pirate.Configuration.Range.Value and nearbyEnemies[enemy] == nil then
				table.insert(nearbyEnemies, enemy)
			end

			for _, enemyToAttack in pairs(nearbyEnemies) do
				if enemyToAttack:FindFirstChild("Configuration") ~= nil and enemyToAttack:FindFirstChild("Configuration").Distance.Value > highestDistance then
					highestDistance = enemyToAttack.Configuration.Distance.Value

					furthest = enemyToAttack

					if (pirate.PrimaryPart.Position - furthest.HumanoidRootPart.Position).Magnitude <= pirate.Configuration.Range.Value then
						Attack(furthest, pirate.Configuration.Health.Value)
					end
				end
			end
		end
	end

	for _, enemy in pairs(enemyFolder:GetChildren()) do
		for i, closeEnemy in pairs(nearbyEnemies) do
			if enemy ~= nil and enemy.HumanoidRootPart and pirate.PrimaryPart then
				if (pirate.PrimaryPart.Position - enemy.HumanoidRootPart.Position).Magnitude > pirate.Configuration.Range.Value then
					if closeEnemy == enemy then
						table.remove(nearbyEnemies, i)
					end
				end
			end
		end

		enemy.Humanoid.Died:Connect(function()
			for i, closeEnemy in pairs(nearbyEnemies) do
				if enemy == closeEnemy then
					table.remove(nearbyEnemies, i)
				end
			end
		end)
	end
end

pirate.PrimaryPart:GetPropertyChangedSignal("CFrame"):Connect(function()
	CheckTargets()
	
	wait(0.1)
end)

Unit Spawn Script:

local replicatedStorage = game:GetService("ReplicatedStorage")

local spawnModule = require(game.ServerScriptService.SpawnModule)

local tower = script.Parent

while true do
	spawnModule.SpawnUnit(tower.Name, tower.Configuration.Level.Value, tower.Configuration.UnitName.Value)
	
	wait(0.1)
end

I’m really hoping I can find some way to fix this bug, as I would like to add more unit spawning towers to my game in the near future, however this has been a HUGE road block for me getting the opportunity to make any progress.

Edit: I am thinking there needs to be a way I can forcefully end the function, as maybe when trying to spawn the unit I need to make sure the function isn’t ongoing any longer. However, clearly what I have tried is not working at the moment.

Are you getting an output error?

I can’t say I fully understand what you’re saying, but if your SpawnUnit function throws an error, it’ll break the thread that your while loop is in, so it’ll completely stop working.

I can offer you another solution, although it’s definitely not the ideal solution (I can try to help you actually address the issue if I have more info to work with, such as any output errors)

But the solution I can give you for now is to wrap your while loop body statements in a pcall. That should silence any output errors to not affect the main thread (process that’s running your while loop)

while true do
pcall(function()
--//spawn the unit here
end)

wait(.1)
end

Unfortunately I was not getting any errors in the output, which is what initially caused me to make this post. However, pcall could potentially fix my issue. I feel like I should’ve had this included to begin with, but I definitely forgot about it. I’ll do some testing and see if everything works after including a pcall.

I’ve been trying a few things with pcall, but still eventually after a few units are destroyed the function stops working. Maybe it could be something else but I am not sure what it is.