Tower Defence Enemies Server/Client

Would this be the right category
If this is too long please read TL;DR

Currently on the server. To make an enemy I have a class. Skipping to the :Move function the movement can seem a bit off when I increase the speed. Currently, the speed is set to 1 which works perfectly fine but let’s say I want to increase the speed to 3 or more it will get stuck on a node due to the magnitude being greater than one. Is there any way to possibly fix this?

function Enemy:Move()
    if self.State == "Dead" then return end 
    if self.CurrentNode >= #Path:GetChildren() then return end 

    local nextNode = Path[self.CurrentNode + 1] -- Get's next node in path

    self.Position = self.Position + ((nextNode.Position - self.Position.Position).Unit * self.Speed) -- Not sure how I could improve this

    if (self.Position.Position - nextNode.Position).Magnitude < 1 then -- If it's reached the node
        self.Position = nextNode.CFrame -- Correct its position to the node
        self.CurrentNode += 1 -- Increase current node
    end
end

Now I also have an EnemyService which will handle the information the client will receive. This is a server script and my main concern is the coroutine. Let’s say there are 100 enemies (very extreme case) on-screen how would this affect performance. Would there be a better way to do this? I get 100 enemies and will almost definitely have performance issues but could this be made more efficient so the performance is relatively.

--[[
   Creates an enemy via the enemy class and moves it
   @ Param: enemyName [string] -- Name of Enemy 
]]
function EnemyService:CreateEnemy(enemyName: string)
    local newEnemy = Enemy.new(enemyName) -- Creates the enemy
    self:AddData(newEnemy) -- Adds itself to a table
    coroutine.wrap(function() -- Not sure the best way to go about moving this 
        while true do 
           newEnemy:Move() 
           self:AddData(newEnemy)
           task.wait()
        end
    end)()
end

Lastly, and sorry for asking a lot, I just want to be sure before proceeding since I’ve spent a couple of hours on this so I may be missing something completely or overlooking something. So the client-side, when I receive the event from the

--[[
   Loops through the current enemies and checks if the data being given doesn't already exist
   @ Param: data [table]
   @ Returns Boolean
]]
function EnemyController:_isDuplicate(data: table): boolean
    for _, enemyData in ipairs(CurrentEnemies) do
        if enemyData["ID"] ~= data["ID"] then continue end
        return true
    end
    return false
end

--[[
   Adds enemy to the CurrentEnemies table
   @ Param: data [table]
   @ Returns Boolean
]]
function EnemyController:_addEnemy(data: table)
    local isDuplcate = self:_isDuplicate(data)
    if isDuplcate then return end

    table.insert(CurrentEnemies, data)

    local EnemyModel = ReplicatedStorage:WaitForChild("Assets"):WaitForChild("Enemies"):FindFirstChild(data.Name)
    if not EnemyModel then 
        return warn("Enemy model doesn't exist!")
    end

    -- What would be the best way to constantly update position?
    -- RenderStep?
    -- Is it best to make another class on the client?
    local Clone = EnemyModel:Clone()
    Clone.Parent = workspace:WaitForChild("Enemies")
    Clone.Position = Vector3.new(data.Position)
end

-- Using Knit
function EnemyController:KnitStart()
    local EnemyService = Knit.GetService("EnemyService")
    EnemyService.EnemyInfo:Connect(function(data) 
        for _, EnemyData: table in ipairs(data) do -- Loops through data | data is given every 0.1 seconds using heartbeat on the server
            self:_addEnemy(EnemyData)
        end
    end)
end

TL;DR
First Codeblock - is there a better way to calculate position?
Second Codeblock - is there a better way to move the enemy?
Last Codeblock- look at _addEnemy function. What would be the best way to update/add an enemy?

The last code block is my main concern.

Hopefully, you are able to assist me and I do have more question if anyone is up to answer please dm me, if not have a great day I’m sure I’ll figure it out eventually :sweat_smile:

1 Like

Maybe EnemyService could have a table called Enemies. Each enemy gets inserted to this. Then, in a heartbeat loop, you loop through EnemyService.Enemies or whatever you call it and call :Move and :AddData. So rather than 100 while true do loops for 100 enemies you have 1 loop for 100 enemies. Much faster when the number of enemies increase

This is all I could come up with. Goodluck!

Thank you for the idea! I just tried it out and as long as I have it run every 10 heartbeats it works just fine. I’ll do a bit more testing to see but it’s looking great so far.