I’m trying to get my zombie OOP module to have smooth pathfinding without lagging too much.
The problem is that when the zombies are actively tracking for players, these zombies stop in between every second when following the moving player. Even with this system, it is still lagging badly with about 20 to 30 zombies.
I tried modifying the pathfinding functions countless times but I just can’t really figure out a way to make the movement not look like a slideshow…
Where I'm creating zombie objects (Server Script)
for i, v in pairs(game.Workspace.CurrentZombies:GetChildren()) do
task.wait(0.5)
local zombie = zombieClass.new(v, 100, 0.7, 15, 3, 500, 10, CFrame.new((math.random(200)-250), 2, (math.random(300)-150)))
CollisionModule.charCollisionGroup(zombie.char, "Zombies")
zombie:MoveToNearestEntrance(entrances)
zombie:TrackingPathfind()
end
Zombie Module (There's a section for pathfinding, it's commented)
---------------------------------------------------------------------
--[[
This Server Module handles Zombies
Author: Void_Trader
Last Edit: 7/15/2024
Version: 1.0
]]
---------------------------------------------------------------------
--Services
local CollectionService = game:GetService("CollectionService")
local PathfindingService = game:GetService("PathfindingService")
local ServerStorage = game:GetService("ServerStorage")
local Players = game:GetService("Players")
--Modules
local Modules = ServerStorage:WaitForChild("Modules")
local GeneralModules = Modules:WaitForChild("General")
local EntranceClass = require(GeneralModules:WaitForChild("Entrance"))
--Main module
local zombie = {}
zombie.__index = zombie
--Setting up zombie
function zombie.new(char: Model, health: number, breakSpeed: number, damage: number, attackRange: number, detectRange: number, smoothness: number,position: CFrame)
--Setting up Metatable
local self = setmetatable({}, zombie)
--Setting up values and variables
self.char = char
self.root = char:FindFirstChild("HumanoidRootPart")
self.humanoid = char:FindFirstChild("Humanoid") :: Humanoid
self.head = char:FindFirstChild("Head")
self.torso = char:FindFirstChild("Torso")
self.target = nil
--Animations
self.animator = self.humanoid:FindFirstChild("Animator")
self.grabAnim = self.animator:LoadAnimation(self.char:FindFirstChild("Grab"))
--Properties
self.health = health
self.humanoid.MaxHealth = health
self.humanoid.Health = health
self.breakSpeed = breakSpeed
self.damage = damage
self.attackRange = attackRange
self.detectRange = detectRange
self.smoothness = smoothness
--Thread variables
self.threads = {}
self.__type = "Zombie"
--Run code
self.char.PrimaryPart:SetNetworkOwner(nil)
self.humanoid:AddTag("Zombie")
self:position(position)
-------AI-------
--Entrance is called in main script
--Tracking
return self
end
------------------------------------------------------------
--METAMETHODS
------------------------------------------------------------
--MOVEMENT FUNCTIONS
--Move zombie to position instantly
function zombie:position(position: CFrame)
if self.__type ~= "Zombie" then return end
self.root:PivotTo(position)
return self
end
--------------------------------------------
--AI FUNCTIONS--
--Entrance Functions--
--Break plank
function zombie:BreakPlanks(Entrance)
repeat task.wait(self.breakSpeed) Entrance:Break()
until (Entrance.currentPlanks == 0) or (self.humanoid.Health <= 0)
return self
end
--Find nearest entrance
function zombie:MoveToNearestEntrance(Entrances)
if self.__type ~= "Zombie" then return end
--Variables
local target = nil
local dist = self.detectRange
local entrance = nil
--Finding entrance
for _, entranceObject in pairs(Entrances) do
local breakZone = entranceObject.breakZone
if (breakZone) and (self.checkDist(breakZone, self.root) < dist) then
dist = self.checkDist(breakZone, self.root)
target = breakZone
entrance = entranceObject
end
end
self.target = target
--Moving to entrance--
--Creating path
if self.target then
self:threadSpawn(function()
local path = PathfindingService:CreatePath()
path:ComputeAsync(self.root.Position, self.target.Position)
local waypoints = path:GetWaypoints()
local currentTarget = self.target
--Setting up pathfinding
if path.Status == Enum.PathStatus.Success then
for i, waypoint in pairs(waypoints) do
--[[
-----------------
local part = Instance.new("Part")
part.CanCollide = false
part.Anchored = true
part.Material = "Neon"
part.BrickColor = BrickColor.new("White")
part.Position = waypoint.Position + Vector3.new(0, 2, 0)
part.Shape = "Ball"
part.Size = Vector3.new(1,1,1)
part.Parent = game.Workspace
]]-----------------
--Creating waypoints and moving zombie there
if waypoint.Action == Enum.PathWaypointAction.Jump then
self.humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
else
self.humanoid:MoveTo(waypoint.Position)
self:threadSpawn(function()
task.wait(0.5)
if self.humanoid.WalkToPoint.Y > self.root.Position.Y then
self.humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
end
end)
end
--Makes sure zombie walking is correct
self.humanoid.MoveToFinished:Wait()
--Making sure tracking works
if not self.target then
print("Lost target")
break
elseif (self.checkDist(currentTarget, waypoints[#waypoints]) > self.smoothness) or currentTarget ~= self.target then
print("?")
self:MoveToNearestEntrance()
break
end
end
--Breaking Planks--
self:BreakPlanks(entrance)
else
print("Path unable to be computed")
self:removeZombie()
end
end)
end
return self
end
--------------------------------------------
--TRACKING FUNCTIONS--
--Tracking Pathfind
function zombie:TrackingPathfind()
if self.__type ~= "Zombie" then return end
self:threadSpawn(function()
task.wait(15)
while task.wait(0.1) do
--Find Target
self:findTarget()
--Create Path
if self.target then
local path = PathfindingService:CreatePath()
path:ComputeAsync(self.root.Position, self.target.Position)
local waypoints = path:GetWaypoints()
for _, waypoint in pairs(waypoints) do
if self.target and self.target.Parent:FindFirstChildWhichIsA("Humanoid").Health > 0 then
print(self.target.Parent.Name)
--Move towards next waypoint
if waypoint.Action == Enum.PathWaypointAction.Jump then
self.humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
else
self.humanoid:MoveTo(waypoint.Position)
self:threadSpawn(function()
task.wait(0.5)
if self.humanoid.WalkToPoint.Y > self.root.Position.Y then
self.humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
end
end)
end
--Makes sure zombie walking is correct
self.humanoid.MoveToFinished:Wait()
--Attack
self:attack()
--Making sure tracking works
if not self.target then
print("Lost target")
break
--elseif (self.checkDist(self.target, waypoints[#waypoints]) > 20) then
-- print("?")
-- self:TrackingPathfind()
-- break
end
else
print("!!!!!!!!!!!!!!!!!!")
end
end
end
end
end)
return self
end
--Find Target
function zombie:findTarget()
if self.__type ~= "Zombie" then return end
--Variables
local players = Players:GetPlayers()
local maxDistance = self.detectRange
self.target = nil
--Check for closest player
for i, plr in pairs(players) do
if plr.Character then
local target = plr.Character
--local distance = (self.root.Position - target.HumanoidRootPart.Position).Magnitude
local distance = self.checkDist(self.root, target:FindFirstChild("HumanoidRootPart"))
if distance < maxDistance then
self.target = target:FindFirstChild("HumanoidRootPart")
maxDistance = distance
end
end
end
return self
end
--------------------------------------------
--ATTACK FUNCTIONS--
--Attack
function zombie:attack()
if self.__type ~= "Zombie" then return end
if self.target == nil then return end
--Get distance
local distance = self.checkDist(self.root, self.target)
--if distance > self.attackRange then
--Move zombie to player
-- self.humanoid:MoveTo(self.target.Position)
--else
if distance < self.attackRange then
--Attack
self.target.Parent:FindFirstChildWhichIsA("Humanoid").Health -= self.damage
end
return self
end
--------------------------------------------
--UTILITY FUNCTIONS--
--Remove zombie
function zombie:removeZombie()
if self.__type ~= "Zombie" then return end
task.wait(5)
--Close all threads
for _, thread in pairs(self.threads) do
coroutine.close(thread)
thread = nil
end
self.char:Destroy()
self = nil
return self
end
--Check distance(NORMAL FUNCTION)
function zombie.checkDist(part1: Part | Vector3, part2: Part | Vector3): number
local part_1
local part_2
if typeof(part1) ~= Vector3 then part_1 = part1.Position end
if typeof(part2) ~= Vector3 then part_2 = part2.Position end
return (part_1 - part_2).Magnitude
end
--Spawn thread
function zombie:threadSpawn(functionObject, ...)
if self.__type ~= "Zombie" then return end
local co = coroutine.wrap(functionObject)
table.insert(self.threads, co)
co(...)
end
------------------------------------------------------------
return zombie
I’m guessing someone else knows an easy method to this, but I’m far from an expert with pathfinding.
Thanks