The way the towers in my tower defense game use their targetting system uses it in a way which makes it in unison; and it is hard to manage in separate scripts as the towers, when placed, are cloned with their script inside each tower.
Is there a way to make it so the towers do not achieve this simultaneous behavior?
This is the targetting function used to fire to the zombie parented in the tower(server side):
while task.wait(tower_cooldown.Value) do
local target = getzombie()
if target and target:FindFirstChild("Humanoid") and target.Humanoid.Health > 0 then
tower_shoot:FireAllClients(tower, "Shoot", nil, target)
task.spawn(facetarget, tower, target, 0.1)
task.wait(0.1)
if target:FindFirstChild("Humanoid") then
target:FindFirstChild("Humanoid"):TakeDamage(tower_damage.Value)
target:FindFirstChild("Humanoid").HealthChanged:Connect(function(health)
if health == 0 then
game.Players[tower:FindFirstChild("PlayerPlaced").Value].Cash.Value += math.ceil(target:FindFirstChild("Humanoid").MaxHealth/2)
end
end)
end
end
end
Yes, So I am going to do a quick overview of your code clean some thing sup.
But, the first thing you may want to consider looking at OOP and instead of managing them via scripts inside of each one, which is kind of like OOP, you manage it with a module and OOP style. Basically it will achieve the same effect, but you wont need to manage like a ton of scripts.
--[[
STEP one lets clean this code up, will make notes on changes
NOTE due to not having full code, consider this a psudo code it should work in your situation but
you may need to take some time with it.
]]
--[[OLD CODE
while task.wait(tower_cooldown.Value) do
local target = getzombie()
if target and target:FindFirstChild("Humanoid") and target.Humanoid.Health > 0 then
tower_shoot:FireAllClients(tower, "Shoot", nil, target)
task.spawn(facetarget, tower, target, 0.1)
task.wait(0.1)
if target:FindFirstChild("Humanoid") then
target:FindFirstChild("Humanoid"):TakeDamage(tower_damage.Value)
target:FindFirstChild("Humanoid").HealthChanged:Connect(function(health)
if health == 0 then
game.Players[tower:FindFirstChild("PlayerPlaced").Value].Cash.Value += math.ceil(target:FindFirstChild("Humanoid").MaxHealth/2)
end
end)
end
end
end
--]]
--[[
We get rid of your 3 depth "and" in MOST cases I do not suggest doing 3 depth ANDS
if you do, I suggest a dropping format BUT a general more effecient easier to read is
to treat them as predicates -> as you see below we do all the same checks
you used to do, but if any of them fail on teh way, wthen we immedantly return false
again this is EXACTLY waht will happen in code, except when you use an AND it has to check to make sure aLL
of the conditions meet it. In this case, they do not.
STeer clear of using " if thing and " <-- its just not reading well better to do "if thing ~= nil" or use 'not'
]]
local function getValidTarget(target)
if not target then return false end
if not target:FindFirstChild("Humanoid") then return false end
if target.Humanoid.Health <= 0 then return false end
return true
end
local function InitiateTowerBehaviors()
task.spawn(
facetarget,
tower,
target,
0.1
)
end
--[[
TODO
]]
local function _towerHealthChangedEvent(health)
-- See we dont want to do anything if our health is still greator then 0
-- again this is example of a predicte
if health > 0 then return end
-- Doing some of this just to icnreas readability. this could be reorganized for a few less calls but i doubt a perf prob
local TargetHumanoid = target:FindFirstChild("Humanoid")
local PlayerPlaced = tower:FindFirstChild("PlayerPlaced").Value
local PlayerOwner = game.Players[PlayerPlaced.Value]
PlayerOwner.Cash.Value += math.ceil(TargetHumanoid.MaxHealth/2)
end
local function DamageEvent(target)
-- We should only call this once at the start and use as more effecient
-- as well as readable
-- And we will do our first predicate check which will bounce us out if nil
-- Reduces our if blocks more and now since we have a reusable vvar
--TargetHumanoid makes everything more readable
local TargetHumanoid = target:FindFirstChild("Humanoid")
if TargetHumanoid == nil then return end
TargetHumanoid:TakeDamage(tower_damage.Value)
-- You need to manage this event somewhere right now you are like making events every time
-- unless this only triggers off once
-- So basically if this only happens ONCE then sure but if not you need to memory clean up these connections
TargetHumanoid.HealthChanged:Connect(_towerHealthChangedEvent)
end
-- So I don't particular undersatnd this pattern you have here
-- It looks like you should want to use
-- It looks liek we should replace the while wait with
task.delay(
tower_coolDown.Value,
function()
-- Now we have a predicate function we can easily add more conditions too easy to debug or break out
local target = getzombie()
if not getValidTarget(target) then return end
tower_shoot:FireAllClients(
tower,
"Shoot",
nil,
target
)
InitiateTowerBehaviors()
task.delay(0.1, DamageEvent(target))
end
)
--[[ OK
so now its a bit easier to diagnose waht is going on.
So while i changed it and its hard to decern, I am going to guess your WHILE Loop
somehow RESETS in some way that you have done things.
So that every "period of time" they then do something.
The deal is that when you make that setting it looks like they are all pointing to that value.
So any targeting and watiing is always uniform between them because of the detection time.
I suggest looking for some kind of recuring timer that you can use for towers that on creation it starts checking each time.
OR you need some random value that is either initilized on finding a target OR on each turret that add a little bit of warm up time
before tarcking onto the target.
Essentially they are all uniformally using the same value for detection time, on the while loop, so after the first or second runs thats just going
to flat line out and they all be in sync.
If you care about rotation and target aquistion times, you could always speed that up based on what the warm up time was that way
in the end they will still have the same balanced target aquistiion time
]]
I know my code is slightly unreadable but after some tweaking to your script to adjust it to my game, it wouldnt work with multiple functions working like this.
I’d suggest maybe a way from my script to be able to communicate with towers that the zombie has been dealt with and that they should target the next zombie.