§ How to · with Picoo
How to make a Roblox pathfinding NPC (with AI)
By Sametcan Tasgiran, Founder & Developer·Published ·Updated
A Roblox pathfinding NPC needs five pieces: PathfindingService waypoint generation, a state machine (idle/chase/attack), obstacle handling, stuck detection, and animation hooks. Describe the NPC you want and Picoo writes the scripts for it. The build below is one example of that output.
Example build: 5 files · 156 lines. Picoo writes this code for each prompt, so file names and line counts vary from run to run.
PathfindingService waypoints
While chasing, each pass of the brain loop computes a path to the target's current position with PathfindingService:CreatePath, walks its waypoints with Humanoid:MoveTo, then waits 0.5 seconds before the next pass.
State machine
Idle, chase and attack, stored in a State attribute on the NPC model. A player inside sight range starts a chase; a player inside attack range switches it to attack.
Obstacle handling
AgentRadius and AgentHeight come from each NPC's config, so the path fits its body and routes around walls. AgentCanJump is on, and the NPC jumps at waypoints marked Jump.
Stuck-detection recovery
A separate script watches each chasing NPC. If it moves less than 2 studs in 3 seconds, the NPC walks to a random nearby point and computes a new path from there.
Nearest-player targeting
findTarget picks the closest player whose character is inside the NPC's sightRange. For a vision cone or a line-of-sight raycast, ask for it in your prompt.
Animation hooks
State changes go out on an NPCStateChange RemoteEvent, so a client script can play walk, attack and idle animations to match.
Example output from a build like this
5 files in this example. Picoo writes this code for your prompt, so file names and sizes vary.
ServerScriptService/NPCBrain.lua
State machine, pathfinding, target selection
ServerScriptService/NPCStuckDetector.lua
Position tracking, recovery waypoints
ReplicatedStorage/Modules/NPCConfig.lua
Per-NPC stats (speed, range, damage, sight)
ServerScriptService/NPCSpawner.lua
Tag-based NPC initialization
ReplicatedStorage/Remotes/NPCStateChange
State broadcast to clients for animation sync
Sample output: ServerScriptService/NPCBrain.lua
--!strict
-- ServerScriptService/NPCBrain.lua (Picoo · excerpt)
local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local NPCConfig = require(ReplicatedStorage.Modules.NPCConfig)
type State = "idle" | "chase" | "attack"
local function findTarget(npc: Model, sightRange: number): Player?
local pos = npc.PrimaryPart.Position
local closest, closestDist = nil, sightRange
for _, player in ipairs(Players:GetPlayers()) do
if not player.Character or not player.Character.PrimaryPart then continue end
local d = (player.Character.PrimaryPart.Position - pos).Magnitude
if d < closestDist then closest, closestDist = player, d end
end
return closest
end
local function moveToward(npc: Model, target: Vector3, config)
local hum = npc:FindFirstChildOfClass("Humanoid") :: Humanoid
local path = PathfindingService:CreatePath({
AgentRadius = config.radius,
AgentHeight = config.height,
AgentCanJump = true,
})
local ok = pcall(function() path:ComputeAsync(npc.PrimaryPart.Position, target) end)
if not ok or path.Status ~= Enum.PathStatus.Success then return end
for _, wp in ipairs(path:GetWaypoints()) do
if wp.Action == Enum.PathWaypointAction.Jump then hum.Jump = true end
hum:MoveTo(wp.Position)
hum.MoveToFinished:Wait()
end
end
local function brain(npc: Model)
local config = NPCConfig[npc:GetAttribute("NPCType") or "default"]
npc:SetAttribute("State", "idle" :: State)
while npc.Parent do
local state = npc:GetAttribute("State") :: State
local target = findTarget(npc, config.sightRange)
if state == "idle" and target then npc:SetAttribute("State", "chase") end
if state == "chase" and target then
moveToward(npc, target.Character.PrimaryPart.Position, config)
local dist = (target.Character.PrimaryPart.Position - npc.PrimaryPart.Position).Magnitude
if dist < config.attackRange then npc:SetAttribute("State", "attack") end
end
task.wait(0.5)
end
endBuilding a Roblox pathfinding NPC
NPCs that pathfind correctly are the difference between an immersive Roblox game and a janky one. The two most common failures are NPCs that get stuck on corners and NPCs that walk straight into walls because nothing computes a route around them. The example below handles both.
In this example Picoo wrote five files. NPCBrain.lua runs a state machine per NPC (idle, chase, attack) and stores the current state in an attribute on the model. A player inside sight range starts a chase, and a player inside attack range switches the NPC to attack.
PathfindingService is the core movement primitive. The brain computes a path from the NPC's position to the target's with AgentRadius and AgentHeight taken from the NPC's config, so the route fits the NPC's body and goes around walls. AgentCanJump is on, and at any waypoint marked Jump the NPC jumps before walking on.
Stuck detection is the resilience layer. Physics can push an NPC into a wall, and a path can hug a corner too tightly. The example's stuck detector watches position over a 3-second window; if the NPC moved less than 2 studs while chasing, it walks to a random nearby point and re-routes from there.
Targeting in this example is distance only: findTarget picks the closest player inside sightRange, walls or not. For stealth gameplay, ask for a vision cone with a raycast line-of-sight check, so players behind cover or behind the NPC's back go unseen. Because Picoo writes this code for your prompt, you can put those details in the first request.
See more on the Luau generator, the AI Luau script writer, the 3D model generator and the game builder, read how Picoo compares with ChatGPT, or browse the full blog.
Frequently asked
Why does the NPC sometimes get stuck on corners?+
A path can hug a corner tightly enough for the NPC's body to catch on it, and physics can push an NPC off its route. The example's stuck detector watches each chasing NPC: if it has moved less than 2 studs in 3 seconds, it walks the NPC to a random nearby point and computes a new path from there.
Can I have NPCs that attack other NPCs (not just players)?+
Yes. findTarget can look for models with a CollectionService tag such as 'Enemy' or 'Friendly' instead of looping over Players. Ask for faction-based targeting in a follow-up prompt.
How do I make the NPC patrol when idle?+
Ask for a patrol route: a list of Vector3 points in NPCConfig that the idle state walks through in order, picking the route back up when a chase ends.
What about line-of-sight checks?+
The example's findTarget checks distance only, so an NPC can notice a player through a wall. For line of sight, ask for a raycast from the NPC's head to the player that ignores the NPC's own model: if the ray hits something else first, the player is hidden. A vision cone adds an angle check against the NPC's LookVector, so players behind it go unseen.
Does Picoo have a recipe for NPCs?+
Yes, npc_spawner. Prompts such as 'make a zombie that chases me' or 'spawn some patrolling NPCs' start from it. It spawns real R15 NPCs (5 by default) in Workspace.NPCs that wander around their spawn point with Humanoid:MoveTo, within a 40-stud patrol radius. Chasing, with or without PathfindingService, goes in a separate script on top of those NPCs.
Related Picoo prompts
Roblox boss fight
1 file · 199 lines · one recipe call. Five color themes and a scale setting for titan-sized bosses.
Roblox tower defense game
Example build: 11 files · 390 lines. Per-team economy, wave-based progression.
Roblox quest system
9 files · 1219 lines · one recipe call. 852 of those lines are PicooFeedback, the shared module behind the reward popups.