i want my npc movement to be smoother but performant to handle 20 - 30 npcs at once
issue is that the npc moves, stops, moves and so on, very choppy
i tried increasing the update rate, but with more npcs it becomes a bit heavy on the server
-- ServerScriptService/NPCManager
local CollectionService = game:GetService("CollectionService")
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local Debris = game:GetService("Debris")
local GetPathModule = require(script:WaitForChild("GetPath"))
print("checkpoint")
local GetNearestPlayer = require(script:WaitForChild("GetNearestPlayer"))
local npcFolder = workspace:WaitForChild("Evils")
local activeNPCs = {}
-- Utility
local function destroyAfter(item, time)
task.delay(time, function()
if item then item:Destroy() end
end)
end
-- Registers an NPC
local function registerNPC(npc)
if not npc:IsDescendantOf(workspace) then return end
if not npc:FindFirstChild("Humanoid") or not npc:FindFirstChild("HumanoidRootPart") then return end
if not npc:FindFirstChild("Settings") then return end
local humanoid = npc.Humanoid
local root = npc.HumanoidRootPart
local settings = require(npc.Settings)
npc.PrimaryPart:SetNetworkOwner(nil)
root.Anchored = false
local data = {
model = npc,
humanoid = humanoid,
root = root,
settings = settings,
state = {
attacking = false,
stunned = false,
combo = 1,
parryCooldown = 0,
},
}
-- Optional: idle animation
if settings.idleAnim and settings.idleAnim ~= 0 then
local anim = Instance.new("Animation")
anim.AnimationId = "rbxassetid://" .. settings.idleAnim
humanoid:WaitForChild("Animator"):LoadAnimation(anim):Play()
end
activeNPCs[npc] = data
end
-- Deregisters an NPC
local function removeNPC(npc)
activeNPCs[npc] = nil
end
-- Initial scan
for _, npc in pairs(CollectionService:GetTagged("NPC")) do
registerNPC(npc)
end
-- Handle dynamic adds/removals
CollectionService:GetInstanceAddedSignal("NPC"):Connect(registerNPC)
CollectionService:GetInstanceRemovedSignal("NPC"):Connect(removeNPC)
-- Main loop
while task.wait(0.5) do
for npc, data in pairs(activeNPCs) do
local model = data.model
local humanoid = data.humanoid
local root = data.root
local settings = require(npc.Settings)
local state = data.state
-- Skip dead or invalid NPCs
if humanoid.Health <= 0 then
removeNPC(npc)
model.Parent = workspace:FindFirstChild("Corpses") or workspace
local weld = model:FindFirstChild("Right Arm") and model["Right Arm"]:FindFirstChildWhichIsA("WeldConstraint")
if weld then weld:Destroy() end
Debris:AddItem(model, 10)
-- Loot drop
if math.random(1, 4) == 1 then
local book = ReplicatedStorage.items:FindFirstChild("techbook"):Clone()
local roll = math.random(1, 100)
local vol = (roll <= 30 and math.random(1,3)) or (roll <= 60 and math.random(4,6)) or (roll <= 85 and math.random(7,9)) or math.random(10,11)
book.Name = "Book of Technology Vol " .. vol
book.PivotTo(model.Head.CFrame)
book.Handle.Name = "pickupPart"
book.Parent = workspace:WaitForChild("Loot")
end
continue
end
local target = GetNearestPlayer.GetNearestPlayer(npc)
if not target and model:FindFirstChild("Raider") and model.Raider.Value == true then
local fallback = workspace:FindFirstChild("Target")
if fallback and fallback:FindFirstChild("PrimaryPart") then
target = fallback.PrimaryPart
end
end
if not target then continue end
local distance = (root.Position - target.Position).Magnitude
local path = GetPathModule.GetPath(npc, target)
if not path or path.Status ~= Enum.PathStatus.Success then continue end
local waypoints = path:GetWaypoints()
if #waypoints < 2 then continue end
-- Attack range logic
if distance <= settings.MaxDistanceToKill and not state.attacking and not state.stunned then
state.attacking = true
local animID = settings.animations[state.combo]
if animID then
local anim = Instance.new("Animation")
anim.AnimationId = "rbxassetid://" .. animID
local track = humanoid.Animator:LoadAnimation(anim)
track:Play()
-- Placeholder: actual hit logic should use Touched events, Raycasting, etc.
task.delay(settings.TimeToParry + 0.1, function()
state.attacking = false
state.combo = (state.combo % #settings.animations) + 1
end)
end
continue
end
-- Movement
if distance > 6 then
humanoid.WalkSpeed = state.stunned and (settings.RunSpeed - 11) or settings.RunSpeed
humanoid:MoveTo(waypoints[2].Position)
else
humanoid.WalkSpeed = 0
end
end
end
any help or advice appreciated