I am making a tower defense game at the moment. Currently, towers target enemies within range and deal damage to enemies as expected. The problem arises when two towers simultaneously target and fire at the same enemy within a short time frame of one another.
The Problem:
Two scripts from two separate towers target the same enemy. The first script deals damage to the enemy, causing it to drop to 0 health and destroy itself via a script that is a child to the enemy being destroyed.
Then, the second script attempts to target and deal damage to the same enemy targeted by the first tower, but the object it is targeting has been destroyed. As such, any code referencing the object being targeted now throws an error as the target is nil
from being destroyed.
I’m stuck trying to find a way to implement this which will cause the second tower to always see the object it is trying to damage is nil, and fail to cause errors by indexing an object set to nil
The specific line(s) of code causing the error:
-- This is where the error occurs.
-- If the enemy is destroyed after the first line's conditional executes, it will throw an error.
-- Despite ClosestBlox being not nil during the conditional, it may be destroyed after executing as true.
-- This causes issues when many enemies are on screen at once or many towers firing at the same enemy.
if ClosestBlox then
local HitsToPop = ClosestBlox:FindFirstChild("Values"):FindFirstChild("HitsToPop")
if HitsToPop.Value > 0 then
HitsToPop.Value -= 1
end
end
Main Driving Code For Targeting after Target is found:
while true do
-- Get Enemy Furthest Along Track in Radius
local ClosestBlox = First()
if ClosestBlox then
-- If A Blox exists, rayast for visuals
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {Model}
raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
raycastParams.IgnoreWater = true
local RaycastResult = workspace:Raycast(
PrimaryPart.Position,
(ClosestBlox.PrimaryPart.Position - PrimaryPart.Position) * 50,
raycastParams)
if RaycastResult then
-- RayCast Exists, Display Beam
local distance = (PrimaryPart.Position - ClosestBlox.PrimaryPart.Position).Magnitude
local p = Instance.new("Part")
p.Anchored = true
p.CanCollide = false
p.Size = Vector3.new(0.1, 0.1, distance)
p.Color = Color3.new(1, 0, 0)
p.CFrame = CFrame.new(PrimaryPart.Position, RaycastResult.Position)*CFrame.new(0, 0, -distance/2)
p.Parent = workspace
wait(0.1)
p:Destroy()
if ClosestBlox then
local HitsToPop = ClosestBlox:FindFirstChild("Values"):FindFirstChild("HitsToPop")
if HitsToPop.Value > 0 then
HitsToPop.Value -= 1
end
end
else
print("no raycast")
end
end
wait(FireRate.Value)
end
Full Targeting Code:
local Model = script.Parent
local PrimaryPart = script.Parent.PrimaryPart
local FiringPosition = script.Parent.PrimaryPart.Position
local FireRate = script.Parent:WaitForChild("Values"):WaitForChild("BaseFireRate")
local Radius = script.Parent:WaitForChild("Values"):WaitForChild("AttackRadius")
local FiringModes = require(game.ReplicatedStorage.Modules:WaitForChild("Enums")).FiringModes
local FiringMode = FiringModes.First
function First()
local AllBlox = game.workspace.Map:WaitForChild("BloxOnTrack"):GetChildren()
local AllBloxInRadius = {}
local ClosestNodeBlox = {}
if (#AllBlox < 1) then
wait(0.1)
return nil
end
for i = 1, #AllBlox do
local BloxPosition = AllBlox[i].PrimaryPart.Position
if ( (FiringPosition - BloxPosition).Magnitude <= Radius.Value) then
table.insert(AllBloxInRadius, AllBlox[i])
end
end
if #AllBloxInRadius > 0 then
local HighestNode = 0
for i = 1, #AllBloxInRadius do
if(HighestNode < AllBloxInRadius[i]:WaitForChild("Values"):WaitForChild("CurrentNode").Value) then
HighestNode = AllBloxInRadius[i]:WaitForChild("Values"):WaitForChild("CurrentNode").Value
end
end
if (#AllBloxInRadius < 1) then
return
end
for i = 1, #AllBloxInRadius do
if(AllBloxInRadius[i]:WaitForChild("Values"):WaitForChild("CurrentNode").Value == HighestNode) then
table.insert(ClosestNodeBlox, AllBloxInRadius[i])
end
end
local NextNodePosition = game.workspace.Map:WaitForChild("PathNodes"):WaitForChild(tostring(HighestNode + 1)).Position
local ClosestMagnitude = (ClosestNodeBlox[1].PrimaryPart.Position - NextNodePosition).Magnitude
local ClosestBlox = ClosestNodeBlox[1]
for i = 2, #ClosestNodeBlox do
local Magnitude = (ClosestNodeBlox[i].PrimaryPart.Position - NextNodePosition).Magnitude
if ( Magnitude < ClosestMagnitude) then
ClosestMagnitude = Magnitude
ClosestBlox = ClosestNodeBlox[i]
end
end
return ClosestBlox
else
return nil
end
end
while true do
local ClosestBlox = First()
if ClosestBlox then
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {Model}
raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
raycastParams.IgnoreWater = true
local RaycastResult = workspace:Raycast(
PrimaryPart.Position,
(ClosestBlox.PrimaryPart.Position - PrimaryPart.Position) * 50,
raycastParams)
if RaycastResult then
local distance = (PrimaryPart.Position - ClosestBlox.PrimaryPart.Position).Magnitude
local p = Instance.new("Part")
p.Anchored = true
p.CanCollide = false
p.Size = Vector3.new(0.1, 0.1, distance)
p.Color = Color3.new(1, 0, 0)
p.CFrame = CFrame.new(PrimaryPart.Position, RaycastResult.Position)*CFrame.new(0, 0, -distance/2)
p.Parent = workspace
wait(0.1)
p:Destroy()
if ClosestBlox then
local HitsToPop = ClosestBlox:FindFirstChild("Values"):FindFirstChild("HitsToPop")
if HitsToPop.Value > 0 then
HitsToPop.Value -= 1
end
end
else
print("no raycast")
end
end
wait(FireRate.Value)
end