roblox-npc-ai
NPCs and enemy AI - PathfindingService (CreatePath, ComputeAsync, costs, modifiers, links, blocked paths), chase/follow loops, state machines, perception and line of sight, spawning/pooling, scaling to many NPCs. Use when creating enemies, pets, followers, shopkeepers, wave spawners, or fixing NPCs that get stuck, jitter, or lag the server.
How do I install this agent skill?
npx skills add https://github.com/el4cteo/roblox-skills --skill roblox-npc-aiIs this agent skill safe to install?
- Gen Agent Trust Hubpass
The skill provides standard Roblox Luau code for implementing NPC AI, including pathfinding, state machines, and performance-oriented object pooling. It follows platform security best practices and contains no malicious patterns.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
What does this agent skill do?
NPCs and AI
Architecture
- NPC logic runs on the server (authoritative targets, damage, drops). Visual extras (idle animations, nameplates, effects) can run on clients.
- One NPC service drives all NPCs from a single loop (e.g. 5–10 Hz "think" rate, staggered), not one
script +
while trueper NPC. Tag NPC models (CollectionService) and keep per-NPC state in a table. - Server-owned physics:
npc.PrimaryPart:SetNetworkOwner(nil)after spawning, so NPCs don't stutter when ownership moves between nearby players (and exploiters can't push them). - Use
Humanoidfor NPCs that need walking/jumping/climbing; for simple movers (turrets, floating enemies, crowds) use anAnimationController+Animatorand move with constraints or CFrames.
Pathfinding
--!strict
local PathfindingService = game:GetService("PathfindingService")
local path = PathfindingService:CreatePath({
AgentRadius = 2,
AgentHeight = 5,
AgentCanJump = true,
AgentCanClimb = false,
WaypointSpacing = 4,
Costs = {
Water = 20, -- avoid water (Enum.Material name)
DangerZone = math.huge, -- PathfindingModifier.Label: never cross
},
})
-- Walk `humanoid` to `goal`. Returns true on arrival, false if unreachable or interrupted.
local function walkTo(humanoid: Humanoid, goal: Vector3): boolean
local root = humanoid.RootPart
if not root then
return false
end
local ok = pcall(function()
path:ComputeAsync(root.Position, goal)
end)
if not ok or path.Status ~= Enum.PathStatus.Success then
return false -- NoPath: try a fallback (direct MoveTo, new goal, teleport if stuck long)
end
local waypoints = path:GetWaypoints()
local blocked = false
local blockedConnection = path.Blocked:Connect(function(blockedIndex)
blocked = true -- an obstacle appeared on a waypoint ahead; caller recomputes
print("path blocked at waypoint", blockedIndex)
end)
for index = 2, #waypoints do
local waypoint = waypoints[index]
if waypoint.Action == Enum.PathWaypointAction.Jump then
humanoid.Jump = true
end
humanoid:MoveTo(waypoint.Position)
local reached = humanoid.MoveToFinished:Wait() -- false after 8 s timeout
if not reached or blocked or humanoid.Health <= 0 then
blockedConnection:Disconnect()
return false
end
end
blockedConnection:Disconnect()
return true
end
return walkTo
CreatePath+ComputeAsyncis the current API (FindPathAsyncis superseded). ReusePathobjects per agent type;ComputeAsyncyields — never call it every frame for every NPC.- Chasing moving targets: recompute when the target moved more than a few studs since the last
compute or every ~0.5–1 s, and skip pathfinding entirely when there's a clear line of sight
(
workspace:Raycastfrom NPC to target) — justMoveTothe target. PathfindingModifier(child of a part/region,Labelmatching aCostskey,PassThroughfor doors) andPathfindingLink(between two attachments, e.g. ladders, teleporters, boats; the waypoint hasLabelandAction = Custom) shape paths.- Limits: start→goal straight-line distance ≤ 3,000 studs, 20,000-node budget; far goals need intermediate targets. Visualize the navmesh with Studio's Navigation Mesh visualization option.
- On clients, pathfinding sees only streamed-in geometry: compute paths on the server.
Behavior: state machine
--!strict
type State = "Idle" | "Chase" | "Attack" | "Return"
type Npc = {
model: Model,
humanoid: Humanoid,
home: Vector3,
state: State,
target: Model?,
lastAttack: number,
}
local AGGRO_RANGE = 40
local ATTACK_RANGE = 5
local LEASH_RANGE = 80
local ATTACK_COOLDOWN = 1.2
local losParams = RaycastParams.new()
losParams.FilterType = Enum.RaycastFilterType.Exclude
local function hasLineOfSight(from: Vector3, target: BasePart, ignore: { Instance }): boolean
losParams.FilterDescendantsInstances = ignore
local result = workspace:Raycast(from, target.Position - from, losParams)
return result == nil or result.Instance:IsDescendantOf(target.Parent :: Instance)
end
local function think(npc: Npc, findTarget: (Vector3, number) -> Model?)
local root = npc.humanoid.RootPart
if not root or npc.humanoid.Health <= 0 then
return
end
local targetRoot = npc.target and npc.target:FindFirstChild("HumanoidRootPart") :: BasePart?
local distance = if targetRoot then (targetRoot.Position - root.Position).Magnitude else math.huge
if npc.state == "Idle" then
npc.target = findTarget(root.Position, AGGRO_RANGE)
if npc.target then
npc.state = "Chase"
end
elseif npc.state == "Chase" then
if not targetRoot or (root.Position - npc.home).Magnitude > LEASH_RANGE then
npc.state = "Return"
elseif distance <= ATTACK_RANGE then
npc.state = "Attack"
elseif hasLineOfSight(root.Position, targetRoot, { npc.model }) then
npc.humanoid:MoveTo(targetRoot.Position)
end -- else: request a path (throttled) via walkTo in a separate thread
elseif npc.state == "Attack" then
if not targetRoot or distance > ATTACK_RANGE * 1.5 then
npc.state = "Chase"
elseif os.clock() - npc.lastAttack >= ATTACK_COOLDOWN then
npc.lastAttack = os.clock()
local targetHumanoid = npc.target and npc.target:FindFirstChildOfClass("Humanoid")
if targetHumanoid then
targetHumanoid:TakeDamage(10)
end
end
elseif npc.state == "Return" then
npc.target = nil
npc.humanoid:MoveTo(npc.home)
if (root.Position - npc.home).Magnitude < 4 then
npc.state = "Idle"
end
end
end
return think
For complex AI (many behaviors, priorities), use a behavior tree or utility AI; keep the same "think at a fixed rate, act through small primitives" structure. See references/scaling-npcs.md for spawning, pooling, and performance with hundreds of NPCs.
Perception tips
- Find targets with
workspace:GetPartBoundsInRadiusagainst a collision group/filter containing only characters, or iteratePlayers:GetPlayers()(cheaper for small counts) and compare squared distances. - Add reaction delays and field-of-view checks (
root.CFrame.LookVector:Dot(direction.Unit) > 0.5) so NPCs feel fair. - Stagger thinking:
npcIndex % 5 == frame % 5spreads work across frames.
Related skills
roblox-characters-animation (humanoids, animation), roblox-physics (ownership, raycasts),
roblox-performance (profiling, parallel Luau), roblox-game-systems (waves, combat, drops).
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/el4cteo/roblox-skills/roblox-npc-ai">View roblox-npc-ai on skillZs</a>