Roblox NPC script: an enemy that chases and attacks
A zombie, guard or monster that walks to the nearest player and hits them. One server script inside the NPC; variations add pathfinding around walls, respawning and friendly talking NPCs.
ScriptUpdated November 9, 2026
Where to put it
Script — Inside the NPC model (Workspace > Zombie > ChaseScript). It runs on the server, so players can't change what it does.
- Insert a character rig: in Studio's Avatar tab open the rig builder (Character), pick R15 and insert a rig. Rename the model, for example
Zombie. - Check that no part of the rig is Anchored (select the parts and untick Anchored), or it can't walk.
- Insert a Script into the model (not into a part) and paste the code.
The script
local Players = game:GetService("Players")
local npc = script.Parent
local humanoid = npc:WaitForChild("Humanoid")
local root = npc:WaitForChild("HumanoidRootPart")
local CHASE_RANGE = 60 -- studs
local ATTACK_RANGE = 4.5
local DAMAGE = 15
local ATTACK_COOLDOWN = 1 -- seconds between hits
-- The server moves the NPC, so it doesn't stutter near players.
if not root.Anchored then
root:SetNetworkOwner(nil)
end
local home = root.Position
local lastAttack = 0
local function findTarget()
local closest, closestHumanoid, closestDistance = nil, nil, CHASE_RANGE
for _, player in Players:GetPlayers() do
local character = player.Character
local targetHumanoid = character and character:FindFirstChildOfClass("Humanoid")
local targetRoot = character and character:FindFirstChild("HumanoidRootPart")
if targetHumanoid and targetRoot and targetHumanoid.Health > 0 then
local distance = (targetRoot.Position - root.Position).Magnitude
if distance < closestDistance then
closest, closestHumanoid, closestDistance = targetRoot, targetHumanoid, distance
end
end
end
return closest, closestHumanoid, closestDistance
end
while humanoid.Health > 0 do
local targetRoot, targetHumanoid, distance = findTarget()
if targetRoot then
humanoid:MoveTo(targetRoot.Position)
if distance <= ATTACK_RANGE and os.clock() - lastAttack >= ATTACK_COOLDOWN then
lastAttack = os.clock()
targetHumanoid:TakeDamage(DAMAGE)
end
else
humanoid:MoveTo(home) -- nobody nearby: walk back
end
task.wait(0.2)
endHow it works
- Five times a second the NPC looks for the closest living player within
CHASE_RANGEand walks toward them withHumanoid:MoveTo. - Damage happens in the same server script with a cooldown, so a player standing next to it loses 15 health a second instead of dying instantly.
TakeDamagealso respects a ForceField, so freshly spawned players are protected. SetNetworkOwner(nil)keeps the NPC's physics on the server. Without it, Roblox may hand the NPC to a nearby player's device and it starts to jitter.- When nobody is in range the NPC walks back to where it started; when it dies, the loop ends.
Variations
Walk around walls with pathfinding
MoveTo walks in a straight line and gets stuck on walls. PathfindingService computes a route. Add this to the script and call walkTo(targetRoot.Position) instead of humanoid:MoveTo(...). It recomputes every few waypoints because the player keeps moving.
local PathfindingService = game:GetService("PathfindingService")
local path = PathfindingService:CreatePath({ AgentRadius = 2, AgentHeight = 5, AgentCanJump = true })
local function walkTo(position)
local ok = pcall(function()
path:ComputeAsync(root.Position, position)
end)
if not ok or path.Status ~= Enum.PathStatus.Success then
humanoid:MoveTo(position) -- no route found: try a straight line
return
end
local waypoints = path:GetWaypoints()
for i = 2, math.min(#waypoints, 4) do
local waypoint = waypoints[i]
if waypoint.Action == Enum.PathWaypointAction.Jump then
humanoid.Jump = true
end
humanoid:MoveTo(waypoint.Position)
humanoid.MoveToFinished:Wait()
end
endRespawn the NPC after it dies
Move the finished NPC into ServerStorage (scripts there don't run), add an anchored, invisible part named ZombieSpawn in Workspace, and put this Script in ServerScriptService. Each clone carries its own chase script.
local ServerStorage = game:GetService("ServerStorage")
local template = ServerStorage:WaitForChild("Zombie")
local spawnPart = workspace:WaitForChild("ZombieSpawn")
local RESPAWN_SECONDS = 5
local function spawnZombie()
local zombie = template:Clone()
zombie:PivotTo(spawnPart.CFrame + Vector3.new(0, 3, 0))
zombie.Parent = workspace
zombie.Humanoid.Died:Connect(function()
task.wait(RESPAWN_SECONDS)
zombie:Destroy()
spawnZombie()
end)
end
spawnZombie()A friendly NPC that talks
For a shopkeeper or quest giver, skip the chase script. Anchor the NPC's HumanoidRootPart, put a ProximityPrompt in it and a BillboardGui with a TextLabel named Speech in its Head, then add this Script to the model.
local npc = script.Parent
local prompt = npc.HumanoidRootPart:WaitForChild("ProximityPrompt")
local speech = npc.Head:WaitForChild("BillboardGui"):WaitForChild("Speech")
local LINES = {
"Welcome, traveler!",
"The cave to the north is full of zombies.",
"Come back when you have 100 coins.",
}
local index = 0
prompt.ActionText = "Talk"
speech.Text = ""
prompt.Triggered:Connect(function()
index = index % #LINES + 1
speech.Text = LINES[index]
task.delay(4, function()
if speech.Text == LINES[index] then
speech.Text = ""
end
end)
end)Common mistakes
- Dealing damage from a LocalScript or trusting a RemoteEvent from the client: exploiters can make themselves immune or hit others. Damage belongs on the server, as here.
- Damage in
Touchedwith no cooldown: Touched fires many times a second, so the player dies instantly. - An anchored HumanoidRootPart: the NPC plays its walk animation but never moves (and
SetNetworkOwnererrors on anchored parts). - Calling
MoveToin a loop with notask.wait: the script freezes the server. - Dozens of NPCs each looping over all players every frame. Five checks a second per NPC is plenty; for big waves, use one script that drives every NPC.
FAQ
How do I make an NPC in Roblox Studio?
Open the Avatar tab, use the rig builder (Character) to insert an R15 rig, unanchor its parts, and add a Script to the model. The script on this page makes it chase and attack players.
How do I make an NPC follow the player in Roblox?
Find the player's HumanoidRootPart on the server and call the NPC's Humanoid:MoveTo with its position a few times a second, as this script does. Add PathfindingService if there are walls in the way.
Why is my NPC not moving?
The most common causes: a part of the rig is Anchored, the Humanoid's WalkSpeed is 0, the script is a LocalScript (it must be a Script), or MoveTo is given a position the NPC can't reach in a straight line.
How do I make an NPC talk in Roblox?
Put a ProximityPrompt and a BillboardGui with a TextLabel on the NPC, and change the label's text when the prompt is triggered — see the friendly NPC variation above.