Pourquoi l'IA des PNJ personnalisés est importante
La population ambiante par défaut dans GTA V est conçue pour une expérience solo, pas pour un jeu de rôle immersif. Les PNJ marchent sans but, réagissent de manière imprévisible aux actions des joueurs et n'ont aucune idée du contexte de jeu de rôle qui les entoure. Un employé de magasin reste immobile, un barman ne fait rien derrière le comptoir et les piétons ignorent les scènes de crime actives. L'IA PNJ personnalisée transforme ces découpes en carton en personnages crédibles qui améliorent l'immersion. Un commerçant bien scénarisé se tient derrière la caisse et accueille les clients. Les agents de sécurité patrouillent sur les itinéraires désignés et réagissent aux coups de feu. Les membres de gangs défendent leur territoire et fuient lorsqu'ils sont dépassés par les armes. Dans ce guide, nous couvrirons les fonctions natives de base du GTA V pour contrôler le comportement des PNJ, créer un système de séquence de tâches, mettre en œuvre des itinéraires de patrouille, configurer des groupes de relations pour une dynamique de combat réaliste et créer une population ambiante basée sur des scénarios qui donne vie au monde de ton serveur.
Séquences de tâches : le fondement du comportement des PNJ
Les séquences de tâches sont des listes ordonnées d'actions qu'un pédophile exécute en séquence. Contrairement aux appels de tâches individuels qui s'interrompent les uns les autres, une séquence de tâches garantit que chaque action se termine avant le début de la suivante. Ceci est essentiel pour les comportements complexes, comme un agent de sécurité qui se dirige vers une porte, l’ouvre, entre dans une pièce, puis prend un poste de garde. Sans séquences de tâches, tu auries besoin d’un suivi manuel de l’état avec des minuteries, ce qui est fragile et difficile à maintenir. Le natif OpenSequenceTask et CloseSequenceTask les fonctions créent un handle de séquence qui peut être attribué à n’importe quel pédiatre via TaskPerformSequence.
-- Create a task sequence for a shopkeeper NPC
local function CreateShopkeeperRoutine(ped, counterCoords, counterHeading)
local seq = 0
-- Open a new sequence (pass 0 as reference, it returns a handle)
OpenSequenceTask(seq)
-- Walk to the counter position
TaskGoStraightToCoord(0, counterCoords.x, counterCoords.y, counterCoords.z, 1.0, -1, counterHeading, 0.5)
-- Turn to face customers
TaskAchieveHeading(0, counterHeading, 2000)
-- Play idle animation at counter
TaskStartScenarioInPlace(0, 'PROP_HUMAN_SHOP_INTERACT', 0, true)
CloseSequenceTask(seq)
-- Assign sequence to the ped
TaskPerformSequence(ped, seq)
ClearSequenceTask(seq)
end
-- Usage
local shopkeeper = CreateNPC('s_m_m_shopkeep_01', vector3(25.7, -1347.3, 29.5))
CreateShopkeeperRoutine(shopkeeper, vector3(25.7, -1347.3, 29.5), 270.0)
Le système de scénarios
Les scénarios sont des boucles d'animation prédéfinies qui donnent l'impression que les PNJ effectuent de véritables activités. GTA V comprend des centaines de scénarios intégrés allant de fumer et boire à l'utilisation d'un guichet automatique, en passant par le travail à un bureau ou les pompes dans un parc. En utilisant TaskStartScenarioInPlace pour les activités stationnaires ou TaskStartScenarioAtPosition pour ceux spécifiques à une position, tu peux demander aux PNJ d'effectuer des actions contextuellement appropriées. La clé d’une IA ambiante crédible consiste à faire correspondre les scénarios aux emplacements. Un barman devrait utiliser WORLD_HUMAN_BARTENDER, un mécanicien dans un garage devrait utiliser WORLD_HUMAN_WELDING, et les PNJ à l'extérieur d'une boîte de nuit devraient utiliser WORLD_HUMAN_SMOKING ou WORLD_HUMAN_STAND_MOBILE.
-- Scenario-based ambient NPCs with location awareness
local AmbientNPCs = {
{
model = 'a_m_y_barman_01',
coords = vector4(-561.2, 286.3, 82.2, 270.0),
scenario = 'WORLD_HUMAN_BARTENDER',
invincible = true,
},
{
model = 's_m_y_doorman_01',
coords = vector4(-560.8, 293.5, 82.2, 180.0),
scenario = 'WORLD_HUMAN_GUARD_STAND',
weapon = 'WEAPON_PISTOL',
},
{
model = 'a_f_y_business_01',
coords = vector4(-555.0, 290.1, 82.2, 90.0),
scenario = 'WORLD_HUMAN_STAND_MOBILE',
},
{
model = 's_m_y_ammucity_01',
coords = vector4(22.0, -1105.0, 29.8, 160.0),
scenario = 'PROP_HUMAN_SHOP_INTERACT',
invincible = true,
},
}
local spawnedPeds = {}
local function SpawnAmbientNPCs()
for _, data in ipairs(AmbientNPCs) do
local hash = GetHashKey(data.model)
RequestModel(hash)
while not HasModelLoaded(hash) do Wait(10) end
local ped = CreatePed(4, hash, data.coords.x, data.coords.y, data.coords.z, data.coords.w, false, true)
SetEntityInvincible(ped, data.invincible or false)
SetBlockingOfNonTemporaryEvents(ped, true)
FreezeEntityPosition(ped, false)
if data.weapon then
GiveWeaponToPed(ped, GetHashKey(data.weapon), 250, false, true)
end
TaskStartScenarioInPlace(ped, data.scenario, 0, true)
SetModelAsNoLongerNeeded(hash)
table.insert(spawnedPeds, ped)
end
end
Itinéraires de patrouille et navigation par points de cheminement
Les itinéraires de patrouille permettent aux PNJ de se déplacer entre des points de cheminement prédéfinis en boucle, ce qui est essentiel pour les gardes de sécurité, les policiers et les sentinelles des gangs. L'approche la plus simple utilise une séquence de tâches répétitives qui conduit le pédiatre jusqu'à chaque point de cheminement, attend brièvement, puis passe au suivant. Une approche plus avancée utilise une machine à états qui suit l'index du point de cheminement actuel et gère les interruptions avec élégance. Lorsqu'un garde en patrouille détecte une menace, il doit quitter la patrouille, gérer la menace, puis reprendre la patrouille à partir de son dernier waypoint connu plutôt que de recommencer depuis le début. Le système de patrouille doit également prendre en charge différentes vitesses de déplacement, des actions de scénario facultatives à chaque point de cheminement et des temps d'attente configurables.
-- Patrol route system with state management
local PatrolRoutes = {
police_station_exterior = {
speed = 1.0, -- walking speed
waypoints = {
{ coords = vector3(441.0, -982.0, 30.7), heading = 90.0, wait = 5000, scenario = 'WORLD_HUMAN_GUARD_STAND' },
{ coords = vector3(451.0, -982.0, 30.7), heading = 0.0, wait = 3000 },
{ coords = vector3(451.0, -993.0, 30.7), heading = 270.0, wait = 5000, scenario = 'WORLD_HUMAN_COP_IDLES' },
{ coords = vector3(441.0, -993.0, 30.7), heading = 180.0, wait = 3000 },
}
},
}
local function StartPatrol(ped, routeName)
local route = PatrolRoutes[routeName]
if not route then return end
CreateThread(function()
local waypointIndex = 1
while DoesEntityExist(ped) and not IsEntityDead(ped) do
local wp = route.waypoints[waypointIndex]
-- Walk to waypoint
TaskGoStraightToCoord(ped, wp.coords.x, wp.coords.y, wp.coords.z, route.speed, -1, wp.heading, 0.5)
-- Wait until ped reaches destination
while DoesEntityExist(ped) and not IsEntityDead(ped) do
local dist = #(GetEntityCoords(ped) - wp.coords)
if dist < 1.5 then break end
Wait(500)
end
-- Face the correct direction
TaskAchieveHeading(ped, wp.heading, 1500)
Wait(1500)
-- Play scenario at waypoint if defined
if wp.scenario then
TaskStartScenarioInPlace(ped, wp.scenario, 0, false)
Wait(wp.wait or 5000)
ClearPedTasks(ped)
else
Wait(wp.wait or 3000)
end
-- Move to next waypoint (loop back to 1)
waypointIndex = waypointIndex % #route.waypoints + 1
end
end)
end
Groupes relationnels et comportement de combat
Les groupes de relations déterminent la façon dont les PNJ réagissent les uns aux autres et aux joueurs. Par défaut, tous les PNJ appartiennent à des groupes de relations génériques avec un statut neutre, c'est pourquoi les membres des gangs ne se battent pas et les flics ne poursuivent pas automatiquement les criminels. Les groupes de relations personnalisés tu permettent de créer une dynamique de faction dans laquelle la police est hostile aux criminels armés, les gangs rivaux s'attaquent à vue et les civils fuient le combat. Le SetRelationshipBetweenGroups le natif accepte les niveaux de relation de 0 (compagnon) à 3 (neutre) jusqu'à 5 (haine), contrôlant si les pédiatres se battront, fuiront ou s'ignoreront. Ce système est l’épine dorsale de la création de guerres de factions réalistes, de conflits territoriaux et de réponses policières.
-- Relationship group setup for faction-based AI
local RelGroups = {}
local function InitRelationshipGroups()
-- Create custom groups
AddRelationshipGroup('GANG_BALLAS', RelGroups)
AddRelationshipGroup('GANG_FAMILIES', RelGroups)
AddRelationshipGroup('GANG_VAGOS', RelGroups)
AddRelationshipGroup('POLICE_CUSTOM', RelGroups)
AddRelationshipGroup('CIVILIAN', RelGroups)
-- Gangs hate rival gangs
SetRelationshipBetweenGroups(5, GetHashKey('GANG_BALLAS'), GetHashKey('GANG_FAMILIES'))
SetRelationshipBetweenGroups(5, GetHashKey('GANG_FAMILIES'), GetHashKey('GANG_BALLAS'))
SetRelationshipBetweenGroups(5, GetHashKey('GANG_BALLAS'), GetHashKey('GANG_VAGOS'))
SetRelationshipBetweenGroups(5, GetHashKey('GANG_VAGOS'), GetHashKey('GANG_BALLAS'))
-- Police dislike all gangs
SetRelationshipBetweenGroups(4, GetHashKey('POLICE_CUSTOM'), GetHashKey('GANG_BALLAS'))
SetRelationshipBetweenGroups(4, GetHashKey('POLICE_CUSTOM'), GetHashKey('GANG_FAMILIES'))
SetRelationshipBetweenGroups(4, GetHashKey('POLICE_CUSTOM'), GetHashKey('GANG_VAGOS'))
-- Civilians flee from gangs
SetRelationshipBetweenGroups(1, GetHashKey('CIVILIAN'), GetHashKey('GANG_BALLAS'))
SetRelationshipBetweenGroups(1, GetHashKey('CIVILIAN'), GetHashKey('GANG_FAMILIES'))
end
-- Assign a ped to a relationship group
local function SetPedFaction(ped, faction)
local groupHash = GetHashKey(faction)
SetPedRelationshipGroupHash(ped, groupHash)
end
Comportement de fuite et de combat
Contrôler la manière dont les PNJ réagissent aux menaces est essentiel pour les scénarios de combat immersifs. Le comportement par défaut du GTA V est chaotique : certains PNJ courent, d'autres se recroquevillent et des PNJ armés peuvent ou non s'engager. Le comportement de combat personnalisé utilise des attributs de combat et des indicateurs de configuration pour créer des réponses prévisibles et adaptées au rôle. Un membre d'un gang doit se lever et se battre avec une précision médiocre, un agent de sécurité qualifié doit se mettre à couvert et engager un combat avec une précision modérée, un civil doit fuir immédiatement et un personnage VIP doit s'accroupir et appeler à l'aide. Le SetPedCombatAttributes le natif contrôle les comportements individuels, par exemple si le pédophile peut se mettre à couvert, effectuer un tir aveugle, enquêter sur les sons ou fuir lorsqu'il est dépassé.
-- Combat behavior presets
local CombatPresets = {
gang_member = function(ped)
SetPedCombatAttributes(ped, 46, true) -- Can fight armed peds on foot
SetPedCombatAttributes(ped, 5, true) -- Can use cover
SetPedCombatAttributes(ped, 2, true) -- Can do drivebys
SetPedCombatAbility(ped, 1) -- Average combat ability
SetPedCombatRange(ped, 1) -- Medium range
SetPedAccuracy(ped, 30) -- Poor accuracy
SetPedFleeAttributes(ped, 0, false) -- Don't flee
SetPedCombatMovement(ped, 2) -- Offensive movement
end,
security_guard = function(ped)
SetPedCombatAttributes(ped, 46, true)
SetPedCombatAttributes(ped, 5, true) -- Use cover
SetPedCombatAttributes(ped, 21, true) -- Investigate dead peds
SetPedCombatAbility(ped, 2) -- Professional
SetPedCombatRange(ped, 2) -- Long range
SetPedAccuracy(ped, 60) -- Good accuracy
SetPedCombatMovement(ped, 1) -- Defensive movement
end,
civilian = function(ped)
SetPedFleeAttributes(ped, 2, true) -- Flee immediately
SetPedCombatAttributes(ped, 17, true) -- Can be scared
SetPedCombatAttributes(ped, 46, false) -- Cannot fight
SetPedCombatAbility(ped, 0) -- Poor fighter
end,
}
-- Apply a combat preset
local function ApplyCombatPreset(ped, presetName)
local preset = CombatPresets[presetName]
if preset then preset(ped) end
end
Gestion du cycle de vie et des performances des PNJ
L'apparition de PNJ a un impact direct sur les performances du serveur et du client. Chaque pédalier actif consomme des cycles CPU pour le traitement de l'IA, la simulation physique et la synchronisation du réseau. Une implémentation naïve qui génère tous les PNJ au démarrage du serveur et les maintient en vie en permanence réduira les performances à mesure que la population augmente. L'approche correcte utilise une génération basée sur la proximité où les PNJ ne sont créés que lorsqu'un joueur se trouve dans une plage configurable et disparaissent lorsque tous les joueurs quittent la zone. Utilisez un système de partitionnement spatial qui divise la carte en zones et ne traite la logique des PNJ que pour les zones avec des joueurs à proximité. Mettez en cache les poignées et réutilisez-les lorsque cela est possible plutôt que de supprimer et de recréer des PNJ à chaque fois qu'un joueur entre et sort d'une zone. Ensemble SetEntityAsMissionEntity à false et utilisez des indicateurs de création sensibles au réseau afin que le moteur de jeu puisse gérer naturellement le cycle de vie du ped au sein de son propre système de streaming.
-- Proximity-based NPC spawner with zone management
local SPAWN_RANGE = 80.0
local DESPAWN_RANGE = 120.0
local activeZones = {}
CreateThread(function()
while true do
local playerCoords = GetEntityCoords(PlayerPedId())
for zoneName, zone in pairs(NPCZones) do
local dist = #(playerCoords - zone.center)
if dist < SPAWN_RANGE and not activeZones[zoneName] then
-- Spawn zone NPCs
activeZones[zoneName] = SpawnZoneNPCs(zone)
elseif dist > DESPAWN_RANGE and activeZones[zoneName] then
-- Despawn zone NPCs
for _, ped in ipairs(activeZones[zoneName]) do
if DoesEntityExist(ped) then
DeleteEntity(ped)
end
end
activeZones[zoneName] = nil
end
end
Wait(2000) -- Check every 2 seconds
end
end)
Rassembler tout cela
Un système d’IA NPC de production combine tous ces éléments dans un cadre cohérent piloté par des données de configuration. Chaque définition de PNJ dans ton configuration spécifie le modèle, l'emplacement d'apparition, le type de comportement (stationnaire, patrouille, garde), le préréglage de combat, le groupe de relations et tout scénario ou séquence de tâches à exécuter. Le framework charge ces définitions, gère l'apparition et la disparition en fonction de la proximité du joueur, gère les transitions d'état lorsque les PNJ sont interrompus par le combat ou l'interaction du joueur, et nettoie les ressources lorsque les PNJ ne sont plus nécessaires. Le principe de conception le plus important consiste à séparer les données de la logique : tous les comportements des PNJ doivent être configurables sans modification de code, permettant ainsi aux propriétaires de serveurs d'ajouter de nouveaux PNJ, de modifier les itinéraires de patrouille et d'ajuster les paramètres de combat uniquement via les fichiers de configuration. Cette approche basée sur les données signifie que ton système d'IA peut évoluer d'une poignée de PNJ de quête à des centaines de personnages ambiants peuplant une ville entière sans modification architecturale du code sous-jacent.
