Learn how to script Roblox NPCs that walk around, talk to players, and react to the world. Complete guide with Luau code examples using PathfindingService.
NPCs (Non-Player Characters) are one of the most requested scripting topics for Roblox developers. There are a few main types:
Wandering NPCs โ walk around randomly using PathfindingService
Dialogue NPCs โ stand still and talk when a player interacts
Enemy NPCs โ chase and attack players
Vendor NPCs โ open a shop when approached
This guide covers the first two, which are the foundation for everything else.
Your NPC needs:
- A HumanoidRootPart (the main part Roblox uses for movement)
- A Humanoid (controls movement speed, health, etc.)
- A Head with a face (optional but standard)
- An Animator inside the Humanoid
The easiest way is to use a Roblox dummy: Avatar โ Insert โ Dummy in the toolbar.
PathfindingService makes NPCs navigate around obstacles automatically. Without it your NPC walks into walls.
-- Script inside the NPC Model
local PathfindingService = game:GetService("PathfindingService")
local npc = script.Parent
local humanoid = npc:WaitForChild("Humanoid")
local root = npc:WaitForChild("HumanoidRootPart")
local WANDER_RADIUS = 30
local function getRandomTarget()
local p = root.Position
return Vector3.new(
p.X + math.random(-WANDER_RADIUS, WANDER_RADIUS),
p.Y,
p.Z + math.random(-WANDER_RADIUS, WANDER_RADIUS)
)
end
local function walkTo(target)
local path = PathfindingService:CreatePath({
AgentRadius = 2,
AgentHeight = 5,
})
local ok = pcall(function()
path:ComputeAsync(root.Position, target)
end)
if not ok or path.Status ~= Enum.PathStatus.Success then return end
for _, waypoint in ipairs(path:GetWaypoints()) do
if waypoint.Action == Enum.PathWaypointAction.Jump then
humanoid.Jump = true
end
humanoid:MoveTo(waypoint.Position)
humanoid.MoveToFinished:Wait()
end
end
while task.wait(3) do
if humanoid.Health > 0 then
walkTo(getRandomTarget())
end
endProximityPrompt is the cleanest way to trigger NPC interaction โ no custom detection needed.
First, add a ProximityPrompt inside the NPC's Head in the Explorer. Set its ActionText to "Talk".
Then in a LocalScript in StarterPlayerScripts:
local Players = game:GetService("Players")
local player = Players.LocalPlayer
local gui = player.PlayerGui:WaitForChild("DialogueGui")
local frame = gui:WaitForChild("Frame")
local label = frame:WaitForChild("TextLabel")
local LINES = {
"Hello, traveler.",
"Have you seen the lost sword?",
"It was last seen in the Dark Forest...",
}
local function typewrite(text)
label.Text = ""
for i = 1, #text do
label.Text = text:sub(1, i)
task.wait(0.04)
end
end
local function runDialogue()
frame.Visible = true
for _, line in ipairs(LINES) do
typewrite(line)
task.wait(2.5)
end
frame.Visible = false
end
-- Find the NPC's ProximityPrompt and connect it
local npc = workspace:WaitForChild("NPC")
local prompt = npc:FindFirstChildOfClass("ProximityPrompt", true)
if prompt then
prompt.Triggered:Connect(runDialogue)
endMake them look at the player โ When dialogue starts, use CFrame.lookAt to rotate the NPC toward the player. Much more immersive.
Add idle animations โ A standing NPC with no animation looks dead. Use an AnimationController to play a looping idle animation.
Limit wander range โ Set your WANDER_RADIUS based on the size of the area. Too large and the NPC wanders off the map.
Use pcall on pathfinding โ PathfindingService can fail if the target is unreachable. Always wrap ComputeAsync in pcall.
Grab our complete scripts for both systems: NPC Wander (Pathfinding) and NPC Typewriter Dialogue.
Browse our free library of copy-paste Luau scripts โ no setup needed.
Browse Script Library โ