Por qué la IA personalizada de NPCs importa
La población ambiental por defecto de GTA V está pensada para una experiencia de un jugador, no para rol inmersivo. Los NPCs caminan sin rumbo, reaccionan de forma impredecible a las acciones del jugador y no entienden el contexto del rol a su alrededor. Un dependiente se queda quieto, un camarero no hace nada tras la barra y los peatones ignoran escenas del crimen en plena actividad. La IA personalizada de NPCs convierte estas figuras de cartón en personajes creíbles que refuerzan la inmersión. Un tendero bien scripteado sí se queda detrás del mostrador y saluda a los clientes. Los guardias de seguridad patrullan rutas asignadas y reaccionan a los disparos. Los miembros de banda defienden su territorio y huyen cuando les superan en armas. En esta guía veremos las funciones native principales de GTA V para controlar el comportamiento de los NPCs, construiremos un sistema de secuencias de tareas, implementaremos rutas de patrulla, configuraremos grupos de relaciones para dinámicas de combate realistas y crearemos población ambiental basada en escenarios que dé vida al mundo de tu servidor.
Secuencias de tareas: la base del comportamiento de los NPCs
Las secuencias de tareas son listas ordenadas de acciones que un ped ejecuta en orden. A diferencia de las llamadas de tarea individuales, que se interrumpen entre sí, una secuencia garantiza que cada acción termine antes de que empiece la siguiente. Esto es esencial para comportamientos complejos, como un guardia de seguridad que camina hasta una puerta, la abre, entra a una sala y después adopta una posición de vigilancia de pie. Sin secuencias de tareas tendrías que hacer seguimiento manual del estado con temporizadores, algo frágil y difícil de mantener. Las natives OpenSequenceTask y CloseSequenceTask crean un handle de secuencia que puedes asignar a cualquier ped mediante TaskPerformSequence.
-- Crear una secuencia de tareas para un NPC tendero
local function CreateShopkeeperRoutine(ped, counterCoords, counterHeading)
local seq = 0
-- Abre una nueva secuencia (pasa 0 como referencia, devuelve un handle)
OpenSequenceTask(seq)
-- Camina hasta la posición del mostrador
TaskGoStraightToCoord(0, counterCoords.x, counterCoords.y, counterCoords.z, 1.0, -1, counterHeading, 0.5)
-- Gírate mirando a los clientes
TaskAchieveHeading(0, counterHeading, 2000)
-- Reproduce animación idle en el mostrador
TaskStartScenarioInPlace(0, 'PROP_HUMAN_SHOP_INTERACT', 0, true)
CloseSequenceTask(seq)
-- Asigna la secuencia al ped
TaskPerformSequence(ped, seq)
ClearSequenceTask(seq)
end
-- Uso
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)
El sistema de escenarios
Los escenarios son bucles de animación predefinidos que hacen que los NPCs parezcan realizar actividades reales. GTA V incluye cientos de escenarios integrados, desde fumar y beber hasta usar un cajero, trabajar en una mesa o hacer flexiones en un parque. Usando TaskStartScenarioInPlace para actividades estáticas o TaskStartScenarioAtPosition para las ligadas a una posición, puedes hacer que los NPCs realicen acciones contextualmente adecuadas. La clave de una IA ambiental creíble es casar los escenarios con los lugares. Un camarero debería usar WORLD_HUMAN_BARTENDER, un mecánico en un taller WORLD_HUMAN_WELDING y los NPCs frente a un club nocturno WORLD_HUMAN_SMOKING o WORLD_HUMAN_STAND_MOBILE.
-- NPCs ambientales basados en escenarios con conciencia de la ubicación
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
Rutas de patrulla y navegación por waypoints
Las rutas de patrulla hacen que los NPCs se muevan entre waypoints predefinidos en bucle, algo esencial para guardias de seguridad, policías y centinelas de banda. El enfoque más simple utiliza una secuencia de tareas repetitiva que camina al ped a cada waypoint, espera un poco y pasa al siguiente. Un enfoque más avanzado usa una máquina de estados que sigue el índice del waypoint actual y gestiona las interrupciones con soltura. Cuando un guardia en patrulla detecta una amenaza, debería salir de la patrulla, encargarse de la amenaza y luego retomar la patrulla desde su último waypoint conocido, en lugar de empezar de nuevo. El sistema de patrulla también debería admitir distintas velocidades de movimiento, acciones de escenario opcionales en cada waypoint y tiempos de espera configurables.
-- Sistema de rutas de patrulla con gestión de estado
local PatrolRoutes = {
police_station_exterior = {
speed = 1.0, -- velocidad de caminar
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]
-- Camina hasta el waypoint
TaskGoStraightToCoord(ped, wp.coords.x, wp.coords.y, wp.coords.z, route.speed, -1, wp.heading, 0.5)
-- Espera hasta que el ped llegue al destino
while DoesEntityExist(ped) and not IsEntityDead(ped) do
local dist = #(GetEntityCoords(ped) - wp.coords)
if dist < 1.5 then break end
Wait(500)
end
-- Encara la dirección correcta
TaskAchieveHeading(ped, wp.heading, 1500)
Wait(1500)
-- Reproduce el escenario en el waypoint si está definido
if wp.scenario then
TaskStartScenarioInPlace(ped, wp.scenario, 0, false)
Wait(wp.wait or 5000)
ClearPedTasks(ped)
else
Wait(wp.wait or 3000)
end
-- Pasa al siguiente waypoint (vuelve al 1 en bucle)
waypointIndex = waypointIndex % #route.waypoints + 1
end
end)
end
Grupos de relaciones y comportamiento de combate
Los grupos de relaciones determinan cómo reaccionan los NPCs entre sí y frente al jugador. Por defecto, todos los NPCs están en grupos de relación genéricos con estatus neutral, y por eso los miembros de banda no se pelean entre sí y la policía no persigue automáticamente a los criminales. Los grupos personalizados de relaciones te permiten crear dinámicas de facción en las que la policía es hostil a los criminales armados, las bandas rivales se atacan en cuanto se ven y los civiles huyen del combate. La native SetRelationshipBetweenGroups acepta niveles de relación del 0 (compañero) pasando por el 3 (neutral) hasta el 5 (odio), controlando si los peds pelean, huyen o se ignoran. Este sistema es la espina dorsal para crear guerras de facciones realistas, disputas territoriales y respuestas de las fuerzas del orden.
-- Configuración de grupos de relaciones para IA por facción
local RelGroups = {}
local function InitRelationshipGroups()
-- Crea grupos personalizados
AddRelationshipGroup('GANG_BALLAS', RelGroups)
AddRelationshipGroup('GANG_FAMILIES', RelGroups)
AddRelationshipGroup('GANG_VAGOS', RelGroups)
AddRelationshipGroup('POLICE_CUSTOM', RelGroups)
AddRelationshipGroup('CIVILIAN', RelGroups)
-- Las bandas odian a las bandas rivales
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'))
-- A la policía no le gusta ninguna banda
SetRelationshipBetweenGroups(4, GetHashKey('POLICE_CUSTOM'), GetHashKey('GANG_BALLAS'))
SetRelationshipBetweenGroups(4, GetHashKey('POLICE_CUSTOM'), GetHashKey('GANG_FAMILIES'))
SetRelationshipBetweenGroups(4, GetHashKey('POLICE_CUSTOM'), GetHashKey('GANG_VAGOS'))
-- Los civiles huyen de las bandas
SetRelationshipBetweenGroups(1, GetHashKey('CIVILIAN'), GetHashKey('GANG_BALLAS'))
SetRelationshipBetweenGroups(1, GetHashKey('CIVILIAN'), GetHashKey('GANG_FAMILIES'))
end
-- Asignar un ped a un grupo de relación
local function SetPedFaction(ped, faction)
local groupHash = GetHashKey(faction)
SetPedRelationshipGroupHash(ped, groupHash)
end
Comportamiento de huida y pelea
Controlar cómo responden los NPCs a las amenazas es clave para escenarios de combate inmersivos. El comportamiento por defecto de GTA V es caótico: algunos NPCs corren, otros se acurrucan y los armados pueden o no entrar al combate. El comportamiento de combate personalizado usa atributos de combate y flags de configuración para crear respuestas predecibles y acordes al rol. Un miembro de banda debería plantar cara con poca precisión, un guardia bien entrenado debería cubrirse y disparar con precisión media, un civil debería huir al momento y un VIP debería agacharse y pedir ayuda. La native SetPedCombatAttributes controla comportamientos individuales, como si el ped puede cubrirse, disparar a ciegas, investigar sonidos o huir cuando le superan en armas.
-- Presets de comportamiento de combate
local CombatPresets = {
gang_member = function(ped)
SetPedCombatAttributes(ped, 46, true) -- Puede pelear a peds armados a pie
SetPedCombatAttributes(ped, 5, true) -- Puede cubrirse
SetPedCombatAttributes(ped, 2, true) -- Puede hacer drivebys
SetPedCombatAbility(ped, 1) -- Habilidad de combate media
SetPedCombatRange(ped, 1) -- Distancia media
SetPedAccuracy(ped, 30) -- Poca precisión
SetPedFleeAttributes(ped, 0, false) -- No huye
SetPedCombatMovement(ped, 2) -- Movimiento ofensivo
end,
security_guard = function(ped)
SetPedCombatAttributes(ped, 46, true)
SetPedCombatAttributes(ped, 5, true) -- Usa cobertura
SetPedCombatAttributes(ped, 21, true) -- Investiga peds muertos
SetPedCombatAbility(ped, 2) -- Profesional
SetPedCombatRange(ped, 2) -- Distancia larga
SetPedAccuracy(ped, 60) -- Buena precisión
SetPedCombatMovement(ped, 1) -- Movimiento defensivo
end,
civilian = function(ped)
SetPedFleeAttributes(ped, 2, true) -- Huye al momento
SetPedCombatAttributes(ped, 17, true) -- Puede asustarse
SetPedCombatAttributes(ped, 46, false) -- No puede pelear
SetPedCombatAbility(ped, 0) -- Mal luchador
end,
}
-- Aplicar un preset de combate
local function ApplyCombatPreset(ped, presetName)
local preset = CombatPresets[presetName]
if preset then preset(ped) end
end
Gestionar el ciclo de vida y el rendimiento de los NPCs
Spawnear NPCs afecta directamente al rendimiento del servidor y del cliente. Cada ped activo consume ciclos de CPU para procesar IA, simulación de físicas y sincronización de red. Una implementación ingenua que spawnee todos los NPCs al arrancar el servidor y los mantenga vivos de forma permanente hundirá el rendimiento conforme crezca la población. El enfoque correcto usa spawn basado en proximidad: los NPCs solo se crean cuando un jugador está dentro de un rango configurable y se despawnean cuando todos los jugadores abandonan la zona. Utiliza un sistema de particionado espacial que divida el mapa en zonas y procese la lógica de NPCs solo para las zonas con jugadores cerca. Guarda los handles de los peds y reutilízalos siempre que puedas, en lugar de borrar y recrear NPCs cada vez que un jugador entra y sale de una zona. Configura SetEntityAsMissionEntity a false y usa flags de creación con conocimiento de red para que el motor del juego pueda gestionar con naturalidad el ciclo de vida de los peds dentro de su propio sistema de streaming.
-- Spawner de NPCs basado en proximidad con gestión de zonas
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
-- Spawnea los NPCs de la zona
activeZones[zoneName] = SpawnZoneNPCs(zone)
elseif dist > DESPAWN_RANGE and activeZones[zoneName] then
-- Despawnea los NPCs de la zona
for _, ped in ipairs(activeZones[zoneName]) do
if DoesEntityExist(ped) then
DeleteEntity(ped)
end
end
activeZones[zoneName] = nil
end
end
Wait(2000) -- Comprueba cada 2 segundos
end
end)
Juntándolo todo
Un sistema de IA de NPCs de producción combina todos estos elementos en un framework coherente dirigido por datos de configuración. Cada definición de NPC en tu config especifica el modelo, el lugar de spawn, el tipo de comportamiento (estático, patrullando, vigilando), el preset de combate, el grupo de relaciones y cualquier escenario o secuencia de tareas a ejecutar. El framework carga esas definiciones, gestiona spawn y despawn según la proximidad del jugador, maneja las transiciones de estado cuando los NPCs son interrumpidos por combate o interacción del jugador y libera recursos cuando ya no se necesitan. El principio de diseño más importante es separar datos de lógica: todo el comportamiento de los NPCs debería ser configurable sin cambios de código, para que los dueños de servidor puedan añadir NPCs nuevos, modificar rutas de patrulla y ajustar parámetros de combate solo tocando archivos de configuración. Este enfoque orientado a datos hace que tu sistema de IA escale de un puñado de NPCs de misión a cientos de personajes ambientales poblando una ciudad entera sin cambios arquitectónicos en el código subyacente.
