never made an actually working tower system until recently so i want to know if its optimized enough or how i can improve optimization as tower defense games are known for being unoptimized
main tower module
local tower = {}
local TowerStats = require(game:GetService("ReplicatedStorage").TowerStats)
tower.__index = tower
local TowerMods = {}
for _, v in pairs(game:GetService("ServerScriptService").TowerModule:GetChildren()) do
if v:IsA("ModuleScript") then
TowerMods[v.Name] = v
end
end
print(TowerMods)
function tower.new(towerType, towerInstance)
local self = setmetatable({}, tower)
self.towerType = towerType
self.level = 0
self.Instance = towerInstance
return self
end
--[[ functions that happen once ]]--
function tower:newRange(towerD, range)
local physicalRange = Instance.new("Part")
physicalRange.Parent = towerD.Instance
physicalRange.Anchored = true
physicalRange.Transparency = 1
physicalRange.CanCollide = false
physicalRange.Shape = "Cylinder"
physicalRange.BrickColor = BrickColor.new("Bright blue")
physicalRange.Size = Vector3.new(20, range*3, range*3)
print(towerD.Instance)
physicalRange.Position = towerD.Instance.Position + Vector3.new(0, -1, 0)
physicalRange.CFrame = physicalRange.CFrame * CFrame.Angles(0, 0, math.rad(90))
return physicalRange
end
function tower:visibleRange(towerD, range)
local visibleRange = Instance.new("Part")
visibleRange.Parent = towerD.Instance
visibleRange.Anchored = true
visibleRange.Transparency = 1
visibleRange.CanCollide = false
visibleRange.Shape = "Cylinder"
visibleRange.BrickColor = BrickColor.new("Bright blue")
visibleRange.Size = Vector3.new(0.25, range*3, range*3)
print(towerD.Instance)
visibleRange.Position = towerD.Instance.Position + Vector3.new(0, -1, 0)
visibleRange.CFrame = visibleRange.CFrame * CFrame.Angles(0, 0, math.rad(90))
visibleRange.Name = "visibleRange"
return visibleRange
end
function tower:CreateStats(towerD, statVal, name)
local stat = Instance.new("NumberValue")
stat.Name = statVal
stat.Value = TowerStats[name][0][statVal]
stat.Parent = towerD.Instance
return stat
end
--[[ kinda universal ]]--
function tower:Sound(soundName, towerD)
local newSound = game.ReplicatedStorage.Sounds[soundName]:Clone()
newSound.Parent = towerD
newSound:Play()
return newSound
end
--[[ constant activation ]]--
function tower:Target(check)
local enemyChosen
local farthest = -1
for index, enemy in check do
local Node = enemy:GetAttribute("Node")
if game:GetService("Workspace").Nodes:FindFirstChild("Node"..tostring(Node+1)) == nil then
continue
end
local Distance = (enemy.Position - game:GetService("Workspace").Nodes:FindFirstChild("Node"..tostring(Node+1)).Position).Magnitude
print((Node)+(1/Distance))
if (Node)+(1/Distance) > farthest and enemy:GetAttribute("Health") > 0 then
farthest = (Node)+(1/Distance)
enemyChosen = enemy
end
end
return enemyChosen
end
function tower:BasicLaser(enemyChosen, towerSelected)
local Direction = (enemyChosen.Position - towerSelected.Position)
local Midpoint = (towerSelected.Position + Direction/2)
local Bullet = Instance.new("Part")
Bullet.Name = "Bullet"
Bullet.CFrame = CFrame.new(Midpoint, enemyChosen.Position)
Bullet.Size = Vector3.new(0.5, 0.5, (enemyChosen.Position - towerSelected.Position).Magnitude)
Bullet.Anchored = true
Bullet.CanCollide = false
Bullet.BrickColor = BrickColor.new("Neon orange")
Bullet.Material = Enum.Material.Neon
Bullet.Transparency = 0.5
Bullet.Parent = game:GetService("Workspace").Cache
return Bullet
end
function tower:Rotate(towerSelected, enemyChosen)
local rotatedCFrame = CFrame.new(towerSelected.Position, enemyChosen.Position)
towerSelected.CFrame = rotatedCFrame
end
function tower:BeginAttacking()
if TowerMods[self.towerType] then
local module = require(TowerMods[self.towerType])
module.attackLoop(self.Instance, self)
end
end
return tower
example tower using module:
local carrot = {}
local TowerStats = require(game:GetService("ReplicatedStorage").TowerStats)
local TowerModule = require(game:GetService("ServerScriptService").TowerModule)
local towerName = script.Name
function carrot.attackLoop(tower, self)
local range = TowerModule:CreateStats(self, "range", towerName)
local rate = TowerModule:CreateStats(self, "rate", towerName)
local damage = TowerModule:CreateStats(self, "damage", towerName)
local attacking = false
local physicalRange = TowerModule:newRange(self, range.Value)
local visibleRange = TowerModule:visibleRange(self, range.Value)
local Params = OverlapParams.new()
Params.FilterType = Enum.RaycastFilterType.Include
Params.FilterDescendantsInstances = {workspace.Enemies}
local on = false
tower.ClickDetector.MouseClick:Connect(function(person)
if on == false then
on = true
game:GetService("ReplicatedStorage").ShowRange:FireClient(person, self.Instance, on)
else
on = false
game:GetService("ReplicatedStorage").ShowRange:FireClient(person, self.Instance, on)
end
end)
local function attack()
if attacking then return end
local check = workspace:GetPartsInPart(physicalRange, Params)
if #check == 0 then return end
if check then
local enemyChosen = TowerModule:Target(check)
if enemyChosen and enemyChosen:GetAttribute("Health") > 0 then
TowerModule:Rotate(tower, enemyChosen)
local Health = enemyChosen:GetAttribute("Health")
local Bullet = TowerModule:BasicLaser(enemyChosen, tower)
enemyChosen:SetAttribute("Health", Health-damage.Value)
local AttackSound = TowerModule:Sound("carrot_attack.wav", self.Instance)
game:GetService("Debris"):AddItem(Bullet, 0.3)
game:GetService("Debris"):AddItem(AttackSound, 2)
task.spawn(function()
for i = 1, 3 do
Bullet.Transparency+=0.1
task.wait(0.08)
end
end)
end
end
task.wait(rate.Value)
attacking = false
end
while task.wait() do
if not attacking then
attack()
end
end
end
return carrot