Guide complet : PathfindingService pour créer un NPC qui suit le joueur sur Roblox
TL;DR
Tu veux un NPC capable de naviguer dans ton map, contourner les obstacles et suivre un joueur en temps réel ? Ce guide couvre tout : PathfindingService, ComputeAsync, les waypoints et la gestion de l'événement Blocked. Niveau intermédiaire, Script serveur uniquement.
Pourquoi PathfindingService ?
Déplacer un NPC avec un simple Humanoid:MoveTo() fonctionne en ligne droite, mais dès qu'un mur s'interpose, le personnage reste bloqué. PathfindingService permet aux NPCs de suivre des chemins, d'interagir avec les joueurs et d'exécuter toutes sortes de tâches grâce au scripting.
Le flux de travail est toujours le même :
- Créer un
PathviaCreatePath() - Calculer le chemin avec
ComputeAsync() - Récupérer les waypoints avec
GetWaypoints() - Faire avancer le NPC de waypoint en waypoint
- Gérer les blocages dynamiques avec
path.Blocked
Étape 1 — Préparer la scène
- Place un modèle de personnage (avec
Humanoid+HumanoidRootPart) dans le Workspace, nomméNPC. - Mets un
Scriptserveur directement dans le modèle. Cette approche est pleinement compatible avec les Scripts serveur, car le serveur a une vue complète du monde à tout moment ; lesLocalScriptpeuvent échouer si l'objet cible n'est pas encore streamé côté client. - Active la Navigation Mesh pour visualiser les zones traversables : dans l'onglet Studio, section Visualization, active Show Navigation Mesh.
Étape 2 — Paramètres de CreatePath
PathfindingService:CreatePath() accepte une table optionnelle de paramètres qui affinent la façon dont le personnage (agent) se déplace sur le chemin.
| Paramètre | Défaut | Description |
|---|---|---|
AgentRadius | 2 | Rayon en studs — espace minimal par rapport aux obstacles |
AgentHeight | 5 | Hauteur de l'agent : les espaces vides plus petits que cette valeur seront marqués non traversables. |
AgentCanJump | true | Autorise le saut |
AgentCanClimb | false | Autorise la montée sur les TrussPart. |
WaypointSpacing | 4 | Espacement entre waypoints intermédiaires |
Costs | nil | Table de matériaux/PathfindingModifier et leur coût — utile pour que l'agent préfère certaines zones. |
Conseil AgentRadius : le syndrome du "bloqué dans les coins" survient quand l'
AgentRadiusest trop petit — le pathfinder croit que le NPC peut passer, mais ses parties physiques sont plus larges que le chemin calculé. Ajouter un petit buffer (ex. 2,5 ou 3) évite que le NPC frôle les murs.
Étape 3 — ComputeAsync et waypoints
Une fois un chemin valide créé, il doit être calculé en appelant Path:ComputeAsync() avec un Vector3 pour le point de départ et la destination. Le chemin contient ensuite une série de waypoints du début à la fin, récupérables avec Path:GetWaypoints() — le tableau est ordonné du premier au dernier waypoint.
local PathfindingService = game:GetService("PathfindingService")
local NPC = script.Parent
local humanoid = NPC:WaitForChild("Humanoid")
local rootPart = NPC:WaitForChild("HumanoidRootPart")
local path = PathfindingService:CreatePath({
AgentRadius = 3,
AgentHeight = 6,
AgentCanJump = true,
})
local function allerVers(destination)
path:ComputeAsync(rootPart.Position, destination)
if path.Status ~= Enum.PathStatus.Success then
warn("Chemin introuvable :", path.Status)
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
Préfère
MoveToFinished:Wait()à une bouclewhilemanuelle : l'événement se déclenche automatiquement quand le NPC atteint la cible, ou après un timeout de 8 secondes si le NPC est bloqué.
Étape 4 — Gestion des blocages dynamiques
Le chemin peut devenir invalide en cours de route (porte qui se ferme, objet posé par un joueur…). Path.Blocked:Connect() permet de réagir automatiquement : dès qu'un obstacle bloque le chemin calculé, le NPC peut recalculer un nouveau trajet, ce qui le rend bien plus réactif.
local estBloque = false
path.Blocked:Connect(function()
estBloque = true
end)
Connecte cet événement avant d'appeler ComputeAsync, et sur le même objet path réutilisé tout au long de la vie du NPC — si tu recrées path à chaque itération, tu perds les connexions précédentes.
Script complet — NPC qui suit le joueur le plus proche
local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local NPC = script.Parent
local humanoid = NPC:WaitForChild("Humanoid")
local rootPart = NPC:WaitForChild("HumanoidRootPart")
local RAYON_DETECTION = 100
local DELAI_RECALCUL = 0.5
local path = PathfindingService:CreatePath({
AgentRadius = 3,
AgentHeight = 6,
AgentCanJump = true,
})
local estBloque = false
path.Blocked:Connect(function()
estBloque = true
end)
local function joueurLePlusProche()
local meilleur, distMin = nil, RAYON_DETECTION
for _, joueur in ipairs(Players:GetPlayers()) do
local perso = joueur.Character
if perso and perso:FindFirstChild("HumanoidRootPart") then
local dist = (rootPart.Position - perso.HumanoidRootPart.Position).Magnitude
if dist < distMin then
meilleur, distMin = perso, dist
end
end
end
return meilleur
end
while task.wait(DELAI_RECALCUL) do
local cible = joueurLePlusProche()
if not cible then continue end
estBloque = false
path:ComputeAsync(rootPart.Position, cible.HumanoidRootPart.Position)
if path.Status ~= Enum.PathStatus.Success then continue end
for _, waypoint in ipairs(path:GetWaypoints()) do
if estBloque then break end
if waypoint.Action == Enum.PathWaypointAction.Jump then
humanoid.Jump = true
end
humanoid:MoveTo(waypoint.Position)
if not humanoid.MoveToFinished:Wait() then
estBloque = true
break
end
end
end
Pièges courants
NPC bloqué sur les coins / petits murs
Augmente AgentRadius de 0,5 à 1 stud par rapport à la moitié de la largeur réelle du modèle.
NPC parfait en mode Run mais bloqué en mode Play
Ce symptôme classique est souvent lié au personnage du joueur lui-même qui obstrue le chemin calculé. Réduire DELAI_RECALCUL et activer path.Blocked règle généralement le problème.
ComputeAsync appelé trop fréquemment = lag
Appeler ComputeAsync en permanence pour chaque NPC peut tuer les performances — mieux vaut ne recalculer que toutes les quelques secondes, ou quand le NPC s'écarte nettement de son chemin.
path.Blocked qui ne se déclenche jamais
Vérifie que la connexion est faite avant ComputeAsync et que tu réutilises le même objet path (ne le recrée pas dans la boucle).
Aller plus loin
PathfindingModifier: ces modificateurs permettent de définir des zones où le comportement de pathfinding change — pratique pour des zones restreintes ou à règles de déplacement spécifiques.PathfindingLink: pour traverser un espace normalement infranchissable (un ravin, par exemple) et exécuter une action personnalisée, utilise l'objetPathfindingLink.- Debug des waypoints : afficher le chemin avec des parts placées à chaque waypoint pendant le développement aide à comprendre pourquoi un NPC se bloque ou prend un itinéraire inattendu.
Tu trouveras aussi des systèmes d'IA et de NPC prêts à l'emploi sur le marketplace GM Market si tu cherches une base solide à personnaliser.
FAQ
Q : Puis-je utiliser ce système dans un LocalScript ? Techniquement oui pour un NPC client, mais c'est déconseillé : les LocalScript peuvent échouer si l'objet cible n'est pas encore streamé côté client. Préfère toujours le serveur.
Q : Comment faire sauter le NPC ? Vérifie waypoint.Action == Enum.PathWaypointAction.Jump avant chaque MoveTo, comme dans le script complet ci-dessus.
Q : Quelle valeur pour WaypointSpacing ? La valeur par défaut de 4 studs convient pour la majorité des maps. Réduis-la pour des couloirs très étroits ; augmente-la pour des espaces ouverts afin de limiter le nombre de waypoints et améliorer les performances.
Q : Mon NPC ne saute pas les obstacles alors que AgentCanJump = true. Assure-toi que ta map est correctement détectée par la Navigation Mesh. Les parties avec CanCollide = false ne sont généralement pas considérées comme obstacles — vérifie les propriétés de collision de tes parts.