Por que a IA personalizada do NPC é importante
A população ambiental padrão em GTA V foi projetada para uma experiência de jogador único, não para um RPG envolvente. Os NPCs andam sem rumo, reagem de forma imprevisível às ações dos jogadores e não têm noção do contexto do RPG ao seu redor. Um balconista fica imóvel, um barman não faz nada atrás do balcão e os pedestres ignoram cenas de crimes ativas. A IA personalizada do NPC transforma esses recortes de papelão em personagens verossímeis que melhoram a imersão. Um lojista bem preparado fica atrás da caixa registradora e cumprimenta os clientes. Os guardas de segurança patrulham rotas designadas e reagem aos tiros. Os membros de gangues defendem seu território e fogem quando estão em desvantagem. Neste guia, cobriremos as principais funções nativas do GTA V para controlar o comportamento dos NPCs, construir um sistema de sequência de tarefas, implementar rotas de patrulha, configurar grupos de relacionamento para dinâmicas de combate realistas e criar uma população ambiental baseada em cenários que dá vida ao mundo do seu servidor.
Sequências de tarefas: a base do comportamento do NPC
As sequências de tarefas são listas ordenadas de ações que um ped executa em sequência. Ao contrário das chamadas de tarefas individuais que se interrompem, uma sequência de tarefas garante que cada ação seja concluída antes do início da próxima. Isto é essencial para comportamentos complexos, como um guarda de segurança que caminha até uma porta, abre-a, entra numa sala e depois assume uma posição de guarda permanente. Sem sequências de tarefas, você precisaria de rastreamento manual de estado com temporizadores, que é frágil e difícil de manter. As funções nativas OpenSequenceTask e CloseSequenceTask criam um identificador de sequência que pode ser atribuído a qualquer ped 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)O Sistema de Cenário
Cenários são loops de animação predefinidos que fazem com que os NPCs pareçam estar realizando atividades reais. GTA V inclui centenas de cenários integrados, desde fumar e beber até usar um caixa eletrônico, trabalhar em uma mesa ou fazer flexões em um parque. Usando TaskStartScenarioInPlace para atividades estacionárias ou TaskStartScenarioAtPosition para atividades específicas de posição, você pode fazer com que os NPCs executem ações contextualmente apropriadas. A chave para uma IA ambiental confiável é combinar cenários com locais. Um bartender deve usar WORLD_HUMAN_BARTENDER, um mecânico em uma garagem deve usar WORLD_HUMAN_WELDING e NPCs fora de uma boate devem usar 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
endRotas de patrulha e navegação por pontos de referência
As rotas de patrulha fazem com que os NPCs se movam entre pontos de referência predefinidos em um loop, o que é essencial para guardas de segurança, policiais e sentinelas de gangues. A abordagem mais simples usa uma sequência de tarefas repetida que leva o ped até cada waypoint, espera brevemente e depois passa para o próximo. Uma abordagem mais avançada usa uma máquina de estado que rastreia o índice do waypoint atual e lida com as interrupções normalmente. Quando um guarda patrulha detecta uma ameaça, ele deve sair da patrulha, lidar com a ameaça e então retomar a patrulha a partir do seu último waypoint conhecido, em vez de reiniciar desde o início. O sistema de patrulha também deve suportar diferentes velocidades de movimento, ações de cenário opcionais em cada waypoint e tempos de espera configuráveis.
-- 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)
endGrupos de Relacionamento e Comportamento de Combate
Os grupos de relacionamento determinam como os NPCs reagem uns aos outros e aos jogadores. Por padrão, todos os NPCs estão em grupos de relacionamento genéricos com posição neutra, e é por isso que os membros de gangues não lutam entre si e os policiais não perseguem automaticamente os criminosos. Grupos de relacionamento personalizados permitem criar dinâmicas de facção onde a polícia é hostil a criminosos armados, gangues rivais atacam umas às outras e os civis fogem do combate. O nativo SetRelationshipBetweenGroups aceita níveis de relacionamento de 0 (companheiro) a 3 (neutro) a 5 (ódio), controlando se os peds vão brigar, fugir ou ignorar uns aos outros. Este sistema é a espinha dorsal da criação de guerras entre facções, disputas territoriais e respostas de aplicação da lei realistas.
-- 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)
endComportamento de fuga e luta
Controlar como os NPCs respondem às ameaças é fundamental para cenários de combate envolventes. O comportamento padrão do GTA V é caótico: alguns NPCs correm, alguns se encolhem e NPCs armados podem ou não se envolver. O comportamento de combate personalizado usa atributos de combate e sinalizadores de configuração para criar respostas previsíveis e adequadas à função. Um membro de uma gangue deve ficar de pé e lutar com pouca precisão, um guarda de segurança treinado deve se proteger e atacar com precisão moderada, um civil deve fugir imediatamente e um personagem VIP deve se agachar e pedir ajuda. O nativo SetPedCombatAttributes controla comportamentos individuais, como se o ped pode usar cobertura, disparar às cegas, investigar sons ou fugir quando estiver desarmado.
-- 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
endGerenciando o ciclo de vida e o desempenho do NPC
A geração de NPCs tem um impacto direto no desempenho do servidor e do cliente. Cada ped ativo consome ciclos de CPU para processamento de IA, simulação física e sincronização de rede. Uma implementação ingênua que gera todos os NPCs na inicialização do servidor e os mantém vivos permanentemente prejudicará o desempenho à medida que a população cresce. A abordagem correta usa o spawn baseado em proximidade, onde os NPCs só são criados quando um jogador está dentro de um alcance configurável e desaparecem quando todos os jogadores deixam a área. Use um sistema de particionamento espacial que divida o mapa em zonas e processe apenas a lógica do NPC para zonas com jogadores próximos. Armazene em cache os identificadores e reutilize-os quando possível, em vez de excluir e recriar NPCs toda vez que um jogador entra e sai de uma zona. Defina SetEntityAsMissionEntity como falso e use sinalizadores de criação com reconhecimento de rede para que o mecanismo de jogo possa gerenciar naturalmente o ciclo de vida do ped dentro de seu próprio sistema 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)Juntando tudo
Um sistema NPC AI de produção combina todos esses elementos em uma estrutura coesa orientada por dados de configuração. Cada definição de NPC em sua configuração especifica o modelo, local de spawn, tipo de comportamento (estacionário, patrulhamento, guarda), predefinição de combate, grupo de relacionamento e qualquer cenário ou sequência de tarefas a ser executada. A estrutura carrega essas definições, gerencia o surgimento e o desaparecimento com base na proximidade do jogador, lida com transições de estado quando os NPCs são interrompidos por combate ou interação do jogador e limpa recursos quando os NPCs não são mais necessários. O princípio de design mais importante é separar os dados da lógica: todo o comportamento do NPC deve ser configurável sem alterações de código, permitindo que os proprietários de servidores adicionem novos NPCs, modifiquem rotas de patrulha e ajustem parâmetros de combate apenas através de arquivos de configuração. Essa abordagem baseada em dados significa que seu sistema de IA pode escalar desde um punhado de NPCs de missão até centenas de personagens ambientais que povoam uma cidade inteira sem alterações arquitetônicas no código subjacente.
