Hey there!
So i am currently working on a Tower Defense Game. I am making the enemy movement and it really just doesn’t work. There are multiple bugs, when they go around a corner, they just go crazy and do weird stuff.
There is no errors and i don’t see the problem in my code, that’s why i’m here to get help.
The way my Enemy System Works:
So we got the Main Script calling functions from a module script to spawn the enemies.
Here we got the Function for spawning the Enemies:
local possibleEntityIds = {}
for index = 1, 1000, 1 do
table.insert(possibleEntityIds, index)
end
function EntityService:GenerateEntityId()
local random = math.random(1, #possibleEntityIds)
local id = possibleEntityIds[random]
table.remove(possibleEntityIds, random)
return id
end
function EntityService:CreateEnemy(enemyName, position)
local Id = EntityService:GenerateEntityId()
local enemyData = {
["Id"] = Id,
["Enemy"] = enemyName,
["Position"] = position,
["Health"] = EnemyData.MainData[enemyName].Health,
["Rotating"] = false,
["Reverse"] = false,
["Path"] = 1,
["NodeProgress"] = 0
}
table.insert(EnemyData.CurrentEnemies, enemyData)
spawn(function()
while true do
task.wait()
if table.find(EnemyData.CurrentEnemies, enemyData) == nil then
table.insert(possibleEntityIds, Id)
break
end
end
end)
end
Alright, so we got a Module Script containing all the enemy data. It contains the Enemy Data for each enemy, also the Currently Spawned Enemies.
Here the Module Script is:
local enemyData = {}
enemyData.CurrentEnemies = {}
enemyData.MainData = {
["Flower"] = {
["Health"] = 5,
["Speed"] = 3,
["Camoflage"] = false,
["HipHeight"] = 1.75,
["Damage"] = 3,
["Reward"] = 5,
["Animations"] = {
["Walk"] = "rbxassetid://7327516095",
["Death"] = "rbxassetid://7327594298"
}
},
["Fast Rock"] = {
["Health"] = 5,
["Speed"] = 10,
["Camoflage"] = false,
["HipHeight"] = 1.75,
["Damage"] = 2,
["Reward"] = 5,
["Animations"] = {
["Walk"] = "rbxassetid://7573965164",
["Death"] = "rbxassetid://7574044604"
}
},
}
return enemyData
So whenever a enemy spawns, it gets added to the module current enemies table. So the movement for all the enemies is handled by a Server Script, the rendering is on the client tho.
Here is the Enemy Handler Server Script:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Modules = ReplicatedStorage:WaitForChild("Modules")
local Remotes = ReplicatedStorage:WaitForChild("Remotes")
local Events = Remotes:WaitForChild("Events")
local Functions = Remotes:WaitForChild("Functions")
local EnemyData = require(Modules:WaitForChild("EnemyData"))
local EnemyEventThreads = {}
RunService.Heartbeat:Connect(function()
for index, enemyData in pairs(EnemyData.CurrentEnemies) do
spawn(function()
local CurrentNode = workspace.Map.Paths[enemyData.Path].Node
local NextNode = nil
local PreviousNode = nil
if tonumber(enemyData.Path) == #workspace.Map.Paths:GetChildren() - 1 then
NextNode = workspace.Map.Paths.End
else
NextNode = workspace.Map.Paths[tostring(tonumber(enemyData.Path) + 1)].Node
end
if tonumber(enemyData.Path) > 1 then
PreviousNode = workspace.Map.Paths[tostring(tonumber(enemyData.Path) - 1)].Node
end
local LerpPosition = nil
if enemyData.Reverse then
if enemyData.NodeProgress > 0 then
if enemyData.NodeProgress - ((0.01 / (CurrentNode.Position - NextNode.Position).magnitude) * EnemyData.MainData[enemyData.Enemy].Speed) < 0 then
LerpPosition = CurrentNode.Position:Lerp(NextNode.Position, 1)
enemyData.NodeProgress = 0
else
LerpPosition = CurrentNode.Position:Lerp(NextNode.Position, enemyData.NodeProgress - ((0.01 / (CurrentNode.Position - NextNode.Position).magnitude) * EnemyData.MainData[enemyData.Enemy].Speed)) + Vector3.new(0, EnemyData.MainData[enemyData.Enemy].HipHeight, 0)
enemyData.NodeProgress = enemyData.NodeProgress - ((0.01 / (CurrentNode.Position - NextNode.Position).magnitude) * EnemyData.MainData[enemyData.Enemy].Speed)
end
else
LerpPosition = CurrentNode.Position:Lerp(NextNode.Position, 0)
enemyData.NodeProgress = 0
end
else
if enemyData.NodeProgress < 1 then
if enemyData.NodeProgress + ((0.01 / (CurrentNode.Position - NextNode.Position).magnitude) * EnemyData.MainData[enemyData.Enemy].Speed) > 1 then
LerpPosition = CurrentNode.Position:Lerp(NextNode.Position, 1)
enemyData.NodeProgress = 1
else
LerpPosition = CurrentNode.Position:Lerp(NextNode.Position, enemyData.NodeProgress + ((0.01 / (CurrentNode.Position - NextNode.Position).magnitude) * EnemyData.MainData[enemyData.Enemy].Speed)) + Vector3.new(0, EnemyData.MainData[enemyData.Enemy].HipHeight, 0)
enemyData.NodeProgress = enemyData.NodeProgress + ((0.01 / (CurrentNode.Position - NextNode.Position).magnitude) * EnemyData.MainData[enemyData.Enemy].Speed)
end
else
LerpPosition = CurrentNode.Position:Lerp(NextNode.Position, 1)
enemyData.NodeProgress = 1
end
end
--enemyData.Position = Vector3int16.new(math.floor(50 * enemyData.Position.X + (.5 * EnemyData.MainData[enemyData.Enemy].Speed)), math.floor(50 * enemyData.Position.Y + (.5 * EnemyData.MainData[enemyData.Enemy].Speed)), math.floor(50 * enemyData.Position.Z + (.5 * EnemyData.MainData[enemyData.Enemy].Speed)))
enemyData.Position = LerpPosition
if enemyData.Reverse then
if enemyData.NodeProgress == 0 then
if enemyData.Path > 1 then
enemyData.Rotating = true
spawn(function()
task.wait(2 / EnemyData.MainData[enemyData.Enemy].Speed)
enemyData.Rotating = false
end)
enemyData.Path -= 1
enemyData.NodeProgress = 1
else
table.remove(EnemyData.CurrentEnemies, index)
end
end
else
if enemyData.NodeProgress == 1 then
if enemyData.Path < #workspace.Map.Paths:GetChildren() - 1 then
enemyData.Rotating = true
spawn(function()
task.wait(2 / EnemyData.MainData[enemyData.Enemy].Speed)
enemyData.Rotating = false
end)
enemyData.Path += 1
enemyData.NodeProgress = 0
else
table.remove(EnemyData.CurrentEnemies, index)
end
end
end
if EnemyEventThreads[enemyData.Id] == nil then
table.insert(EnemyEventThreads, enemyData.Id)
local clientSendData = enemyData
task.wait(0.1)
Events.EnemyData:FireAllClients(enemyData)
table.remove(EnemyEventThreads, table.find(EnemyEventThreads, enemyData.Id))
end
end)
end
end)
Also here’s the map in the Explorer:
So we got a Local Script rendering everything on the Client. It is located in the StarterPlayerScripts.
Here it is:
local TweenService = game:GetService("TweenService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local PhysicsService = game:GetService("PhysicsService")
local Remotes = ReplicatedStorage:WaitForChild("Remotes")
local Events = Remotes:WaitForChild("Events")
local Functions = Remotes:WaitForChild("Functions")
local Modules = ReplicatedStorage:WaitForChild("Modules")
local Enemies = ReplicatedStorage:WaitForChild("Enemies")
local EnemyData = require(Modules:WaitForChild("EnemyData"))
local CurrentEnemies = {}
local function GetOrientationOfCFrame(Cframe)
local Orientation = Vector3.new(math.floor(math.deg(Vector3.new(Cframe:ToOrientation()).X)), math.floor(math.deg(Vector3.new(Cframe:ToOrientation()).Y)), math.floor(math.deg(Vector3.new(Cframe:ToOrientation()).Z)))
return Orientation
end
Events:WaitForChild("EnemyData").OnClientEvent:Connect(function(enemyData)
local CurrentNode = workspace.Map.Paths[enemyData.Path].Node
local NextNode = nil
if tonumber(enemyData.Path) == #workspace.Map.Paths:GetChildren() - 1 then
NextNode = workspace.Map.Paths.End
else
NextNode = workspace.Map.Paths[tostring(tonumber(enemyData.Path) + 1)].Node
end
local EnemyPosition = CFrame.lookAt(enemyData.Position, NextNode.Position + Vector3.new(0, EnemyData.MainData[enemyData.Enemy].HipHeight, 0))
if workspace.Map.EnemiesFolder:FindFirstChild(tostring(enemyData.Id)) then
local EnemyModel = workspace.Map.EnemiesFolder[tostring(enemyData.Id)]
if enemyData.Rotating then
TweenService:Create(EnemyModel.HumanoidRootPart, TweenInfo.new(0.1, Enum.EasingStyle.Linear, Enum.EasingDirection.In, 0, false, 0), {Position = EnemyPosition.Position}):Play()
TweenService:Create(EnemyModel.HumanoidRootPart, TweenInfo.new(0.5, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut, 0, false, 0), {Orientation = GetOrientationOfCFrame(EnemyPosition)}):Play()
else
TweenService:Create(EnemyModel.HumanoidRootPart, TweenInfo.new(0.1, Enum.EasingStyle.Linear, Enum.EasingDirection.In, 0, false, 0), {CFrame = EnemyPosition}):Play()
end
else
local EnemyModel = Enemies:FindFirstChild(enemyData.Enemy):Clone()
EnemyModel.Name = tostring(enemyData.Id)
EnemyModel:SetPrimaryPartCFrame(EnemyPosition)
EnemyModel.Parent = workspace.Map.EnemiesFolder
for index, object in pairs(EnemyModel:GetDescendants()) do
if object:IsA("BasePart") then
PhysicsService:SetPartCollisionGroup(object, "Entity")
end
end
end
end)
Don’t mind the UI, The Map or even the Enemies, it is all just for testing and will definitely be deleted
Here is a Video of what happens when i play the game:
Also the Performance is so bad, it literally uses 1.6GB Memory and the Received is also at like 85KB with just 5 Enemies bro. Pls help what did i do wrong?
Sorry for not explaining all the scripts, go through them and hopefully you will understand what i am trying to do.
I have tried to get help on Discord Servers but didn’t get much help, hopefully i will here.
Every response is absolutely appreciated!