I’ve been working on better pathfinding for monsters for my game, 2 functions really:
- Wander around every x seconds (Every 5-7 seconds)
- Follow the player if nearby (~100 Studs, executed every frame)
I have 480 Monsters, which is causing extreme lag making the game next to unplayable.
I’m using a very modified version of SimplePath removing all of it’s events and all other extra stuff I don’t need (the pathfinding does work perfectly).
Here are the things I’ve tried:
-
If there are no players nearby the check for nearby players to then follow them then we’ll wait 1 second in the Follow function before doing checks and trying to follow again instead of doing it every frame.
-
Same as above but for Wandering, making the wait a lot longer too.
Making the follow player function wait more doesn’t improve in performance and just makes the following look choppy and it’s unprecise.
Scripts incase you want to check them out:
PathfindingModule
local Path = {}
Path.__index = Path
local PathfindingService = game:GetService("PathfindingService")
function Path.new(Agent, AgentParameters)
local NewPath = {}
setmetatable(NewPath, Path)
NewPath["Path"] = PathfindingService:CreatePath(AgentParameters)
NewPath["Agent"] = Agent
NewPath["Humanoid"] = Agent:FindFirstChildWhichIsA("Humanoid")
NewPath["AgentParameters"] = AgentParameters
NewPath["LastTime"] = os.clock()
NewPath["Position"] = {
Count = 0,
Last = Vector3.new(),
}
NewPath["Connections"] = {}
NewPath["VisualWaypoints"] = {}
return NewPath
end
local function GetNonHumanoidWaypoint(self)
if not self.Humanoid then return end
for Waypoint = 2, #self.Waypoints do
if (self.Waypoints[Waypoint].Position - self.Waypoints[Waypoint - 1].Position).Magnitude > 0.1 then
return Waypoint
end
end
return 2
end
local function Jump(self)
pcall(function()
if self.Humanoid:GetState() ~= Enum.HumanoidStateType.Jumping and self.Humanoid:GetState() ~= Enum.HumanoidStateType.Freefall then
self.Humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
end
end)
end
local function ComparePosition(self)
if self.CurrentWaypoint == #self.Waypoints then return end
self.Position.Count = ((self.Agent.PrimaryPart.Position - self.Position.Last).Magnitude <= 0.07 and (self.Position.Count + 1)) or 0
self.Position.Last = self.Agent.PrimaryPart.Position
if self.Position.Count >= 1 then
Jump(self)
end
end
local function Move(self)
if self.Waypoints[self.CurrentWaypoint].Action == Enum.PathWaypointAction.Jump then
Jump(self)
end
self.Humanoid:MoveTo(self.Waypoints[self.CurrentWaypoint].Position)
end
local function MoveToFinished(self, Reached)
if Reached and self.CurrentWaypoint + 1 <= #self.Waypoints then
self.CurrentWaypoint += 1
Move(self)
elseif Reached then
self.Connections["MoveConnection"]:Disconnect()
self.Connections["MoveConnection"] = nil
else
self.Connections["MoveConnection"]:Disconnect()
self.Connections["MoveConnection"] = nil
end
end
local function Visualize(self)
for _, Waypoint in pairs(self.VisualWaypoints) do
Waypoint:Destroy()
end
table.clear(self.VisualWaypoints)
for _, Waypoint in pairs(self.Waypoints) do
local Part = Instance.new("Part")
Part.Size = Vector3.new(1, 1, 1)
Part.Anchored = true
Part.CanCollide = false
Part.Position = Waypoint.Position
Part.Parent = game.Workspace.Map
if Waypoint.Action == Enum.PathWaypointAction.Jump then
Part.Color = Color3.fromRGB(0, 200, 255)
end
table.insert(self.VisualWaypoints, Part)
end
end
function Path:Run(Target)
if not self.Humanoid then return end
if os.clock() - self.LastTime <= 0.07 and self.Humanoid then
task.wait(os.clock() - self.LastTime)
return false
elseif self.Humanoid then
self.LastTime = os.clock()
end
local ComputedPath, _ = pcall(function()
self.Path:ComputeAsync(self.Agent.PrimaryPart.Position, Target)
end)
if not ComputedPath
or self.Path.Status == Enum.PathStatus.NoPath
or #self.Path:GetWaypoints() < 2
or (self.Humanoid and self.Humanoid:GetState() == Enum.HumanoidStateType.Freefall) then
task.wait()
return
end
self.Target = Target
pcall(function()
self.Agent.PrimaryPart:SetNetworkOwner(nil)
end)
self.Waypoints = self.Path:GetWaypoints()
self.CurrentWaypoint = 2
for _, Waypoint in pairs(self.VisualWaypoints) do
Waypoint:Destroy()
end
ComparePosition(self)
self.Connections["MoveConnection"] = self.Humanoid and (self.Connections["MoveConnection"] or self.Humanoid.MoveToFinished:Connect(function(...)
MoveToFinished(self, ...)
end))
if self.Humanoid then
self.Humanoid:MoveTo(self.Waypoints[self.CurrentWaypoint].Position)
elseif #self.Waypoints == 2 then
self.Target = nil
else
self.CurrentWaypoint = GetNonHumanoidWaypoint(self)
MoveToFinished(self, true)
end
return true
end
function Path:Destroy()
setmetatable(self, nil)
table.clear(self)
table.freeze(self)
self = nil
end
return Path
Wander/Follow/Pathfinding function for the Monsters
local function WanderScript(Monster)
local Humanoid = Monster:WaitForChild("MonsterHumanoid")
local StartPosition = Monster:WaitForChild("StartPosition")
local Status = Monster:WaitForChild("Status")
local HumanoidRootPart = Monster:WaitForChild("HumanoidRootPart")
local ValuesFolder = ValuesModule[Monster.Name]
local AgentParameters = {
AgentHeight = 10,
AgentRadius = 3,
AgentCanJump = true,
AgentCanClimb = true,
Costs = {
Water = 20,
}
}
local Path = PathfindingModule.new(Monster, AgentParameters)
local IsDead = false
local HasPlayerNearby = false
local HasPlayersInZone = false
local LastHitTick = 0
if ValuesFolder["CanFollow"] then
if ValuesFolder["CanFollow"] == true then
coroutine.wrap(function()
while true do
if HasPlayerNearby == false then
task.wait(1)
end
task.wait()
if IsDead == true then break end
if not Monster:FindFirstChild("HumanoidRootPart") then continue end
local Target = FindNearestTorso(Monster, HumanoidRootPart.Position, 100)
if Target then
Status.Value = "Following"
Path:Run(Target.Position)
HasPlayerNearby = false
elseif Target == nil and Status.Value == "Following" then
Status.Value = "Wandering"
Path:Run(StartPosition.Position)
HasPlayerNearby = false
end
end
end)()
end
end
if ValuesFolder["CanWander"] then
if ValuesFolder["CanWander"] == true then
coroutine.wrap(function()
while true do
task.wait(math.random(5, 7))
if HasPlayerNearby == false then
task.wait(10)
end
if IsDead == true then break end
if not Monster:FindFirstChild("HumanoidRootPart") then continue end
if Status == "Following" then continue end
Path:Run(Vector3.new(HumanoidRootPart.Position.X + math.random(-50, 50), HumanoidRootPart.Position.Y, HumanoidRootPart.Position.Z + math.random(-50, 50)))
end
end)()
end
end
coroutine.wrap(function()
while task.wait(1) do
if IsDead == true then break end
if (tick() - LastHitTick) < 15 then continue end
Humanoid.Health = Humanoid.Health + (Humanoid.MaxHealth * 0.01)
end
end)()
local CurrentHealth = Humanoid.Health
Humanoid.HealthChanged:Connect(function()
if Humanoid.Health < CurrentHealth then
LastHitTick = tick()
end
CurrentHealth = Humanoid.Health
end)
Humanoid.Died:Connect(function()
Path:Destroy()
IsDead = true
end)
end
What can I do to get rid of the lag? What do you recommend? How could it be optimised?
I’ve been trying for 3 days and I could really use the ideas/recomendations.
Please have mercy on my soul.