PNJ
Des personnages non joueurs qui se déplacent seuls sur la carte, voient et entendent, se battent, fuient ou se mettent à couvert. Ils ont le même mannequin que les joueurs. Le serveur calcule leur intelligence et leurs déplacements, les joueurs reçoivent leur position et leurs animations. Pour écrire toi-même leur intelligence en Lua, voir NextBot.
Dans le jeu
Les PNJ sont dans l'onglet PNJ du menu de spawn : un clic en fait apparaître un là où tu vises, tourné vers toi. Z (annuler) le retire, comme un prop, et l'outil Suppression du toolgun aussi. Le gamemode décide qui peut en faire apparaître (PlayerSpawnNPC : Sandbox autorise, Base refuse) et le serveur limite leur nombre (npc_limit, 50 par défaut, voir Serveur).
| Classe | PNJ | Faction | Comportement |
|---|---|---|---|
citizen | Citoyen | citizen | Se promène ; fuit les ennemis, les tirs proches et ceux qui le blessent. |
hostile_crowbar | Ennemi (pied-de-biche) | hostile | Repère les joueurs, les gardes et les citoyens, court vers eux et frappe. |
hostile_pistol | Ennemi (pistolet) | hostile | Tire à distance, se met à couvert quand il a peu de vie. |
hostile_rifle | Ennemi (fusil d'assaut) | hostile | Tire par rafales, de plus loin. |
ally_pistol | Garde (pistolet) | ally | Suit le joueur qui l'a fait apparaître et attaque les ennemis. |
ally_shotgun | Garde (fusil à pompe) | ally | Pareil, au fusil à pompe, de près. |
Avec Sandbox, le physgun attrape les PNJ comme les props : le PNJ reste debout, suit le faisceau, tourne avec toi et retombe quand tu le lâches (clic gauche relâché ou clic droit ; il ne se fige pas). En multijoueur, la protection des props décide qui peut attraper, toolgun (remover) et blesser les PNJ d'un autre joueur : par défaut son propriétaire et ses amis (les PNJ créés par les scripts suivent la règle « sans propriétaire »). Un gamemode peut aussi refuser avec le hook PhysgunPickup.
Toutes les armes du jeu, les armes personnalisées, les armes Lua et les explosions blessent les PNJ, avec la tête ×2 (sauf ceux d'un autre joueur que la protection des props refuse). Le réglage pvp ne les concerne pas. Tuer un PNJ donne un point (Frags) au joueur. Un PNJ mort tombe en ragdoll puis disparaît après 20 secondes.
Ce qu'ils savent faire
- Vue : champ de vision (120° par défaut) et ligne de vue (les murs et les props cachent). Une cible à moins de 8 m est remarquée même de dos ; un peu plus loin et hors du champ de vision, elle est aperçue de temps en temps.
- Ouïe : les tirs (45 m), les coups de pied-de-biche (12 m) et les explosions alertent les PNJ armés, qui vont voir ; les citoyens et les PNJ sans arme fuient.
- Combat : distance de tir selon l'arme, petits pas de côté, rafales au fusil d'assaut, rechargement, précision et dégâts réglables. Un PNJ réagit après un court délai (
reaction). - Couverture : sous un seuil de vie (
cover_health), un PNJ armé cherche un endroit proche que l'ennemi ne voit pas, recharge, puis reprend le combat. - Fuite : vers un point du navmesh à l'opposé de la menace.
- Déplacement : chemins sur le navmesh, montée des marches, saut quand quelque chose bloque, il pousse les props non figés (caisses, portes sur charnière) et garde ses distances avec les autres PNJ.
Le navmesh
Le navmesh est la carte des endroits où un PNJ peut marcher. Une map .pcmap peut l'avoir calculé dans l'éditeur (onglet Monde → Calculer le navmesh) : il est alors chargé instantanément. Sinon, il se calcule tout seul au chargement de la carte (intégrée ou .pcmap), en arrière-plan, par tuiles de 64 m, en commençant par celles qui entourent les joueurs et les points d'apparition (les PNJ proches sont prêts plus vite) : les PNJ marchent tout droit en attendant. En Lua, navmesh.IsLoaded() dit quand toute la carte est calculée. Le terrain des maps compte comme sol. Il tient compte du décor fixe, des props figés (recalculé autour d'eux deux secondes après qu'ils apparaissent, bougent ou disparaissent) et des zones interdites aux PNJ.
Dans l'éditeur de map : onglet Monde → Voir le navmesh affiche en vert le navmesh calculé, et l'entité Zone interdite aux PNJ (boîte invisible en jeu) retire une zone du navmesh. Un PNJ marche sur des pentes jusqu'à 46°, monte des marches de 45 cm et passe sous 1,8 m.
Une map .pcmap peut désactiver son navmesh (onglet Monde → Navmesh des PNJ) : rien n'est calculé et les PNJ restent sur place ; ils tournent, visent, tirent et frappent ce qui passe à portée.
Lua
La bibliothèque npc existe dans les deux réalités. Créer et modifier un PNJ, c'est sur le serveur ; les lire marche partout.
| Fonction | Réalité | Rôle |
|---|---|---|
npc.Register(classe, réglages) | partagé | Ajoute une classe de PNJ (aussi dans l'onglet PNJ). À appeler dans un fichier partagé de l'addon. |
npc.GetList() | partagé | Les classes : {Class, Name, Category, Faction, Weapon}. |
npc.Create(classe, pos, yaw) | serveur | Fait apparaître un PNJ (sans propriétaire ni hook PlayerSpawnNPC). Renvoie le PNJ, ou nil. |
npc.GetAll() | partagé | Tous les PNJ. |
npc.FindInSphere(pos, rayon) | partagé | Les PNJ à moins de rayon mètres. |
Réglages d'une classe
Tous facultatifs. Les armes sont celles du pack : "crowbar", "pistol", "shotgun", "rifle" ou "" (aucune).
| Clé | Par défaut | Rôle |
|---|---|---|
name, category | "PNJ", "PNJ" | Nom dans le menu et dans les messages de mort (« abattu par … »), rubrique de l'onglet PNJ. Rien n'est affiché au-dessus de la tête d'un PNJ. |
faction | "citizen" | "citizen", "hostile", "ally" ou une faction à toi (neutre envers tout le monde, à régler avec AddRelationship). |
health, weapon, color, scale | 100, "", gris, 1 | Vie, arme, couleur du t-shirt, taille (0,3 à 3). |
walk_speed, run_speed | 1,6, 4,6 | Vitesses en m/s. |
accuracy, damage, reaction | 0,6, 0,5, 0,45 | Précision (0 à 1), multiplicateur des dégâts de l'arme, délai de réaction (s). |
sight, fov, hearing | 40, 120, 1 | Distance de vue (m), champ de vision (°), multiplicateur de l'ouïe. |
cover_health | 0,35 | Part de vie sous laquelle il se met à couvert (0 = jamais). |
model | "" | Playermodel porté à la place du mannequin : "addon:<clé>/playermodels/<fichier>.json". Dans le menu de spawn, la liste Apparence de l'onglet PNJ le choisit pour chaque PNJ qu'on fait apparaître. |
skin, bodygroups | 0, {} | Skin du playermodel (0 = d'origine) et sa tenue : l'option de chaque bodygroup, à partir de 0, par exemple {1, 0, 2}. |
Apparence en multijoueur
Le serveur décide de l'apparence d'un PNJ et tous les joueurs voient la même : nom, couleur, taille, arme, playermodel, skin et tenue. Un joueur qui rejoint la partie après l'apparition du PNJ le voit tel qu'il est à ce moment-là, changements compris. Un joueur qui n'a pas le playermodel le télécharge depuis le Workshop s'il y est publié ; en attendant, il voit le mannequin. Une classe déclarée seulement côté serveur (npc.Register dans un fichier serveur) s'affiche aussi correctement chez les joueurs, mais n'apparaît pas dans leur onglet PNJ : déclare-la dans un fichier partagé.
Méthodes d'un PNJ
| Méthode | Réalité | Rôle |
|---|---|---|
IsValid(), IsNPC(), IsPlayer() | partagé | Toujours là ? Vrai / faux. |
GetClass(), GetName(), EntIndex() | partagé | Classe, nom affiché, identifiant. |
GetPos(), EyePos(), GetAngles(), GetForward(), GetVelocity() | partagé | Position, yeux, orientation, vitesse. |
Health(), GetMaxHealth(), Alive() | partagé | Vie. |
GetFaction(), GetWeapon(), GetColor(), GetOwner() | partagé | Faction, arme, couleur, joueur qui l'a fait apparaître (ou nil). |
GetPlayerModel(), GetPlayerSkin(), GetBodygroup(groupe) | partagé | Playermodel porté ("" = mannequin), son skin, l'option choisie d'un bodygroup (à partir de 0). |
GetState() | serveur | "idle", "wander", "investigate", "combat", "cover", "flee", "follow", "move" ou "guard". |
GetEnemy(), Disposition(ent), Visible(ent) | serveur | Ennemi actuel ; "hate", "fear", "like" ou "neutral" envers un joueur ou un PNJ ; le voit-il ? |
SetPos(v), SetAngles(a), SetName(s), SetColor(c) | serveur | Place, tourne, renomme, recolore. |
SetPlayerModel(id, skin), SetBodygroup(groupe, option) | serveur | Fait porter un playermodel ou un prop (nil ou "" : le mannequin) avec un skin, dans sa tenue par défaut ; change un bodygroup du playermodel porté. Tout le monde voit le changement. |
SetHealth(n), SetMaxHealth(n), TakeDamage(n, attaquant), Kill(), Remove() | serveur | Vie, dégâts, mort, suppression. |
SetFaction(f), AddRelationship(cible, disposition) | serveur | Change de camp ; règle son attitude envers un joueur, un PNJ ou une faction ("player" pour tous les joueurs). |
SetEnemy(ent) | serveur | L'attaque tout de suite (nil : oublie son ennemi). |
MoveTo(pos), StopMoving() | serveur | Va à un point en courant (hook NPCReachedGoal à l'arrivée) ; annule. |
Follow(ent), SetSchedule(nom) | serveur | Suit un joueur ou un PNJ (nil : arrête) ; ce qu'il fait au calme : "" (automatique), "idle", "wander", "guard" (reste sur place) ou "follow". |
Give(arme), StripWeapon() | serveur | Change ou retire son arme. |
SetAccuracy(n), SetDamageScale(n), SetSpeed(marche, course) | serveur | Précision, dégâts, vitesses de ce PNJ. |
Hooks
| Hook | Arguments | Retour |
|---|---|---|
PlayerSpawnNPC | ply, classe | true pour autoriser l'onglet PNJ |
PlayerSpawnedNPC | ply, pnj | — |
NPCSpawned | pnj | — (PNJ sans joueur : npc.Create) |
NPCTakeDamage | pnj, dégâts, attaquant, type | false annule, un nombre remplace les dégâts |
OnNPCKilled | pnj, attaquant, cause | — |
NPCReachedGoal | pnj, pos | — |
NPCRemoved | pnj | — |
Quand un PNJ blesse un joueur, PlayerShouldTakeDamage et PlayerHurt sont appelés avec un attaquant nil ; la cause du message est « a été tué par nom du PNJ ».
-- lua/autorun/zombies.lua (partagé) : une classe de PNJ.
npc.Register("zombie", {
name = "Zombie", category = "Monstres", faction = "hostile",
health = 60, weapon = "crowbar", color = Color(90, 140, 70),
walk_speed = 1.0, run_speed = 3.2, damage = 0.8, sight = 25, cover_health = 0,
})
if SERVER then
-- Une vague de zombies toutes les 30 secondes autour du point "spawn_zombies".
timer.Create("vagues", 30, 0, function()
local centre = map.GetPoint("spawn_zombies")
if not centre then return end
for i = 1, 4 do
local z = npc.Create("zombie", centre + Vector(math.random(-6, 6), 0.2, math.random(-6, 6)))
if IsValid(z) then z:SetSchedule("wander") end
end
end)
-- Les gardes du menu de spawn en uniforme : un playermodel de l'addon, skin 1, deuxième option du premier bodygroup.
hook.Add("PlayerSpawnedNPC", "uniforme_gardes", function(ply, pnj)
if pnj:GetClass() == "ally_pistol" then
pnj:SetPlayerModel("addon:mon_addon/playermodels/garde.json", 1)
pnj:SetBodygroup(0, 1)
end
end)
-- Points pour chaque zombie abattu.
hook.Add("OnNPCKilled", "points_zombies", function(pnj, attaquant)
if pnj:GetClass() == "zombie" and IsValid(attaquant) and attaquant:IsPlayer() then
attaquant:ChatPrint("Zombie abattu !")
end
end)
end
NextBot : une IA en Lua
Un NextBot est un PNJ dont l'intelligence est le code de sa classe, comme les NextBots de Garry's Mod. Il a le corps des PNJ (mannequin ou playermodel, collisions, dégâts, ragdoll, réseau), mais le jeu ne lui donne aucun comportement : c'est ton code qui le fait marcher, viser, attendre…
Déclarer et créer
C'est une entité scriptée (entities/<classe>.lua d'un gamemode ou lua/entities/<classe>.lua d'un addon) avec ENT.Base = "base_nextbot" (ou ENT.Type = "nextbot"). ENT.Model peut être un playermodel ("addon:<clé>/playermodels/<fichier>.json") ; sinon c'est le mannequin.
Côté serveur, ents.Create(classe) crée le corps (immobile, en attente) ; règle SetPos, SetAngles et éventuellement SetModel(playermodel), puis Spawn() appelle ENT:Initialize() et démarre ENT:RunBehaviour(). Il a toutes les méthodes d'un PNJ (GetPos, SetHealth, TakeDamage, Remove…), plus :
GetClass()renvoie sa classe,IsNextBot()etIsNPC()renvoienttrue;npc.GetAll()le liste.- Il compte dans la limite
npc_limit: au-delà,ents.Createrenvoienil(message dans la console). - Il n'a pas de propriétaire et ne lance ni
NPCSpawnedniPlayerSpawnedNPC. - Il part avec 50 points de vie (et le t-shirt bleu du citoyen sur le mannequin) : règle sa vie dans
Initializeavecself:SetMaxHealth(n)puisself:SetHealth(n).
RunBehaviour, une coroutine
ENT:RunBehaviour() tourne dans une coroutine : écris-la comme une boucle sans fin. coroutine.yield() rend la main au jeu jusqu'à l'image physique suivante ; coroutine.wait(s) attend s secondes. Une erreur dans la coroutine est affichée avec sa pile d'appels et arrête le comportement. À chaque image physique du serveur, le jeu appelle aussi ENT:BehaveUpdate(intervalle) (qui fait avancer la coroutine : ne la remplace que si tu sais ce que tu fais), ENT:BodyUpdate() et ENT:Think() (pas de NextThink pour un NextBot : elle est appelée à chaque image).
| Méthode | Rôle |
|---|---|
self:MoveToPos(pos, options) → texte | Calcule un chemin sur le navmesh et le suit jusqu'au bout, en rendant la main à chaque image (à appeler dans RunBehaviour). Renvoie "ok" (arrivé), "failed" (pas de chemin : point hors du navmesh), "stuck" (bloqué : self:HandleStuck() est appelée, par défaut loco:ClearStuck()) ou "timeout". Options (facultatives) : tolerance (distance d'arrivée, 1 m par défaut), maxage (abandon après tant de secondes), repath (recalcule le chemin toutes les tant de secondes), draw (true : dessine le chemin), lookahead (gardé pour les scripts). |
self:GetRangeTo(pos ou ent), self:GetRangeSquaredTo(…) | Distance (m) jusqu'à un point, un prop ou un PNJ, et son carré. Pour un joueur, passe sa position : self:GetRangeTo(ply:GetPos()). |
self:PlaySequenceAndWait(clip, vitesse) | Joue un clip d'animation du modèle (mannequin ou playermodel) vu par tous, attend sa durée (divisée par vitesse, 1 par défaut), puis rend les animations de marche. Un clip inconnu ne joue rien et n'attend pas. Dans RunBehaviour seulement. |
self:SetSequence(clip) / self:ResetSequence(clip) | Joue un clip sans attendre, jusqu'au prochain changement ; "" rend les animations de marche. |
self:StartActivity(act) / self:GetActivity() | Gardent une valeur pour les scripts (pas d'animation associée). |
self:BecomeRagdoll(dmg) | Le corps tombe en ragdoll ; il disparaît après 20 secondes et n'est plus listé par npc.GetAll(). |
Évènements
| Fonction de la classe (serveur) | Quand |
|---|---|
ENT:OnInjured(dmg) | Il perd de la vie (après le hook NPCTakeDamage). dmg : GetDamage(), GetAttacker(), GetDamageType() (DMG_BULLET, DMG_CLUB pour le corps à corps, DMG_BLAST, DMG_FALL)… |
ENT:OnKilled(dmg) | Sa vie tombe à 0. Par défaut : hook OnNPCKilled(bot, attaquant, "") puis self:BecomeRagdoll(dmg). Si tu la remplaces, appelle toi-même BecomeRagdoll ou Remove, sinon il reste debout à 0. |
ENT:OnStuck() / ENT:OnUnStuck() | Il essaie d'avancer sans y arriver depuis 1 seconde / il bouge de nouveau. |
ENT:OnLandOnGround() / ENT:OnLeaveGround() | Il touche le sol / le quitte (saut, chute). |
ENT:OnContact(ent) | Il heurte un prop, un joueur ou un PNJ (au plus toutes les 0,2 s pour chacun). |
Se déplacer : self.loco
self.loco (serveur) commande le corps. Unités : mètres, m/s, degrés.
| Méthode | Rôle |
|---|---|
loco:Approach(pos, poids) | Avance vers pos. L'ordre dure 0,15 s : appelle-la à chaque image (poids est ignoré). |
loco:FaceTowards(pos) | Se tourne vers pos (à chaque image aussi). Sans cela, il regarde où il va. |
loco:SetDesiredSpeed(v) / GetDesiredSpeed() | Vitesse de marche, 0 à 60 m/s (3 par défaut). |
loco:SetAcceleration(a) / GetAcceleration() | Accélération au sol (0,1 à 500 m/s², 18 par défaut). Elle sert aussi à freiner. |
loco:SetDeceleration(a) / GetDeceleration() | Gardée pour les scripts (le freinage suit SetAcceleration). |
loco:Jump() | Saute, s'il est au sol et en train d'avancer (pendant un Approach). |
loco:JumpAcrossGap(pos) | Bondit vers pos (au sol ; 8 m/s à l'horizontale au plus). |
loco:SetJumpHeight(m) / GetJumpHeight() | Hauteur du saut (0 à 20 m ; environ 1,2 m sans réglage). |
loco:SetStepHeight(m) / GetStepHeight() | Hauteur de marche franchie sans sauter (0 à 2 m, 0,5 par défaut ; multipliée par la taille du PNJ). |
loco:SetMaxYawRate(°/s) / GetMaxYawRate() | Vitesse de rotation maximale (1 à 3600). Sans réglage, il tourne en douceur, sans limite fixe (GetMaxYawRate renvoie alors 250). |
loco:SetDeathDropHeight(m) / GetDeathDropHeight() | Gardée pour les scripts (2 par défaut) : le jeu ne s'en sert pas. |
loco:IsStuck(), loco:ClearStuck() | Bloqué ? Oublie qu'il l'est. |
loco:IsOnGround(), loco:IsClimbingOrJumping(), loco:IsAttemptingToMove() | Au sol, en l'air, un Approach est en cours. |
loco:GetVelocity() / SetVelocity(v) | Vitesse (m/s, 100 au plus). |
loco:GetGroundMotionVector() | Direction de son déplacement au sol (vecteur unitaire). |
loco:IsAreaTraversable(aire) | Toujours true. |
Chemins : Path
Path("Follow") crée un chemin à suivre soi-même (c'est ce que fait MoveToPos). Côté serveur.
| Méthode | Rôle |
|---|---|
path:Compute(bot, pos) → booléen | Calcule le chemin sur le navmesh (512 points au plus). false si le départ ou l'arrivée est hors du navmesh. |
path:Update(bot) | À chaque image : passe au point suivant quand le bot l'a atteint et le fait avancer vers lui (FaceTowards + Approach). Le chemin devient invalide à l'arrivée. |
path:Chase(bot, ent) | Comme Update, en recalculant le chemin quand il a plus de 0,5 s ou que la cible a bougé de plus de 1 m. |
path:IsValid(), path:Invalidate() | Reste-t-il du chemin ? L'abandonne. |
path:GetAge(), path:ResetAge() | Secondes depuis le dernier calcul ; remet à 0. |
path:GetStart(), path:GetEnd(), path:GetLength() | Premier et dernier point (Vector), longueur (m). |
path:GetCurrentGoal() | Le point visé : {pos, type, distanceFromStart} (type et distanceFromStart valent 0), ou nil. |
path:GetAllSegments(), path:FirstSegment(), path:LastSegment(), path:GetCursorPosition() | Les points ({pos, type}), le premier, le dernier, le numéro du point visé. |
path:SetGoalTolerance(m) / GetGoalTolerance() | Distance à l'horizontale à laquelle un point compte comme atteint (0,4 m au moins). |
path:SetMinLookAheadDistance(m) / GetMinLookAheadDistance() | Gardée pour les scripts. |
path:Draw() | Dessine le chemin avec debugoverlay (0,1 s). |
Le navmesh en Lua
La bibliothèque navmesh lit le navmesh de la carte. Le navmesh de PhysiCore est fait de polygones, pas des carrés de Source : une « aire » est ici un point du navmesh, avec les méthodes de Garry's Mod qui ont un sens pour un point.
| Fonction | Renvoie |
|---|---|
navmesh.IsLoaded() | true quand le navmesh de toute la carte est prêt (il se calcule en arrière-plan après le chargement ; false si la map l'a désactivé). |
navmesh.GetNearestNavArea(pos, _, distMax) | L'aire au point du navmesh le plus proche (nil au-delà de distMax mètres, si donné). |
navmesh.GetNavArea(pos, dessous) | L'aire sous pos : le point le plus proche, s'il est entre 0,5 m au-dessus et dessous mètres (2 par défaut) en dessous ; sinon nil. |
navmesh.Find(pos, rayon, descente, montée) | Jusqu'à 16 aires autour de pos (rayon 200 m au plus), à moins de max(descente, montée) mètres de hauteur (2 par défaut). |
navmesh.GetAllNavAreas(), navmesh.GetNavAreaCount() | Les aires à moins de 50 m des joueurs (pas toute la carte), leur nombre. |
| Méthode d'une aire | Renvoie |
|---|---|
GetCenter(), GetZ(), GetID() | Le point, sa hauteur (Y), un identifiant tiré de sa position. |
GetRandomPoint() | Un point du navmesh au hasard, à moins de 2 m. |
ClosestPointOnArea(pos) | Le point du navmesh le plus proche de pos. |
Contains(pos) | pos est à moins de 1,5 m à l'horizontale et 2 m en hauteur. |
GetAdjacentAreas(), IsConnected(aire) | Les aires à moins de 4 m ; l'autre aire est-elle à moins de 6 m ? |
GetSizeX(), GetSizeY() | Toujours 3. |
IsValid(), IsUnderwater(), HasAttributes() | true, false, false. |
-- entities/garde_lua.lua : patrouille autour de son point d'apparition, poursuit les joueurs proches.
ENT.Base = "base_nextbot"
ENT.PrintName = "Garde (Lua)"
if SERVER then
function ENT:Initialize()
self:SetMaxHealth(80)
self:SetHealth(80)
self.origine = self:GetPos()
self.loco:SetDesiredSpeed(2)
self.loco:SetMaxYawRate(180)
end
function ENT:CibleProche()
for _, ply in ipairs(player.GetAll()) do
if ply:Alive() and self:GetRangeTo(ply:GetPos()) < 15 then return ply end
end
end
function ENT:RunBehaviour()
while true do
local cible = self:CibleProche()
if cible then
-- Poursuite : 5 m/s, chemin recalculé quand la cible bouge.
self.loco:SetDesiredSpeed(5)
local chemin = Path("Follow")
chemin:SetGoalTolerance(1.5)
while IsValid(cible) and cible:Alive() and self:GetRangeTo(cible:GetPos()) < 20 do
chemin:Chase(self, cible)
coroutine.yield()
end
self.loco:SetDesiredSpeed(2)
else
-- Patrouille : un point au hasard à moins de 10 m de l'origine.
local aire = navmesh.GetNearestNavArea(self.origine + Vector(math.random(-10, 10), 0, math.random(-10, 10)))
if aire then self:MoveToPos(aire:GetCenter(), { tolerance = 1, maxage = 10 }) end
coroutine.wait(2)
end
end
end
function ENT:OnInjured(dmg)
local attaquant = dmg:GetAttacker()
if IsValid(attaquant) and attaquant:IsPlayer() then attaquant:ChatPrint("Le garde t'a repéré !") end
end
end
-- Ailleurs, côté serveur, quand le navmesh est prêt :
-- if navmesh.IsLoaded() then
-- local bot = ents.Create("garde_lua")
-- if IsValid(bot) then bot:SetPos(Vector(0, 0.2, 5)) bot:Spawn() end
-- end
Limites
- Le navmesh est fait de polygones : pas d'attributs (accroupi, saut, eau…), pas de vrais carrés ni d'échelles ;
GetAllNavAreasne couvre que les environs des joueurs. - Un NextBot ne fait que ce que son code demande : il ne voit pas, n'entend pas et n'a pas d'arme par lui-même (
Disposition,GetEnemy… des PNJ du jeu ne lui servent pas). - Tant que le navmesh n'est pas prêt autour de lui,
MoveToPosrenvoie"failed": attendsnavmesh.IsLoaded()ou réessaie.