FiveM-KI-NPC-Verhalten: Klügere NPCs skripten
Erwecke deine FiveM-NPCs zum Leben. Lerne Aufgabenfolgen, Wegfindung, reaktive Dialoge und KI-Tricks für immersivere Rollenspielwelten kennen.
Agency Scripts
Gründer und leitender Entwickler bei Agency Scripts
Warum benutzerdefinierte NPC-KI wichtig ist
Die standardmäßige Umgebungsbevölkerung in GTA V ist für ein Einzelspieler-Erlebnis ausgelegt, nicht für immersives Roleplay. NPCs laufen ziellos umher, reagieren unvorhersehbar auf Aktionen von Spielern und berücksichtigen den Roleplay-Kontext um sie herum nicht. Ein Ladenangestellter steht regungslos da, ein Barkeeper tut nichts hinter dem Tresen und Fußgänger ignorieren laufende Tatorte. Benutzerdefinierte NPC-KI macht aus diesen Pappfiguren glaubwürdige Charaktere, die die Immersion verbessern. Ein gut skriptierter Ladenbesitzer steht tatsächlich hinter der Kasse und begrüßt Kunden. Sicherheitskräfte patrouillieren auf festgelegten Routen und reagieren auf Schüsse. Gangmitglieder verteidigen ihr Gebiet und fliehen, wenn sie in der Unterzahl sind. In dieser Anleitung behandeln wir die wichtigsten nativen Funktionen von GTA V zur Steuerung des NPC-Verhaltens, erstellen ein Task-Sequenzsystem, implementieren Patrouillenrouten, konfigurieren Beziehungsgruppen für realistische Kampfdynamiken und erstellen eine szenariobasierte Umgebungsbevölkerung, die die Welt deines Servers zum Leben erweckt.
Task-Sequenzen: die Grundlage des NPC-Verhaltens
Task-Sequenzen sind geordnete Aktionslisten, die ein Ped nacheinander ausführt. Anders als einzelne Task-Aufrufe, die einander unterbrechen, stellt eine Task-Sequenz sicher, dass jede Aktion abgeschlossen wird, bevor die nächste beginnt. Das ist für komplexe Verhaltensweisen unerlässlich, etwa wenn ein Sicherheitsmitarbeiter zu einer Tür geht, sie öffnet, einen Raum betritt und anschließend Wache steht. Ohne Task-Sequenzen müsstest du den Status manuell mit Timern verfolgen, was fehleranfällig und schwer zu warten ist. Die native OpenSequenceTask und CloseSequenceTask Funktionen erstellen einen Sequenz-Handle, der jedem Ped über 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)
Das Szenariosystem
Szenarien sind vordefinierte Animationsschleifen, durch die NPCs aussehen, als würden sie echte Tätigkeiten ausführen. GTA V enthält Hunderte integrierter Szenarien, vom Rauchen und Trinken bis zur Benutzung eines Geldautomaten, zur Arbeit am Schreibtisch oder zu Liegestützen im Park. Mit TaskStartScenarioInPlace für Tätigkeiten an einem festen Ort oder TaskStartScenarioAtPosition für Szenarien an bestimmten Positionen kannst du NPCs passende Aktionen ausführen lassen. Für eine glaubwürdige Umgebungs-KI ist es entscheidend, Szenarien an die jeweiligen Orte anzupassen. Ein Barkeeper sollte WORLD_HUMAN_BARTENDERverwenden, ein Mechaniker in einer Garage sollte WORLD_HUMAN_WELDINGverwenden und NPCs vor einem Nachtclub sollten WORLD_HUMAN_SMOKING oder 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
Patrouillenrouten und Wegpunktnavigation
Patrouillenrouten lassen NPCs in einer Schleife zwischen vordefinierten Wegpunkten wechseln. Das ist für Sicherheitskräfte, Polizisten und Gangwachen unerlässlich. Am einfachsten geht das mit einer sich wiederholenden Task-Sequenz, die das Ped zu jedem Wegpunkt gehen lässt, kurz warten lässt und dann zum nächsten führt. Eine fortschrittlichere Variante verwendet eine Zustandsmaschine, die den aktuellen Wegpunktindex verfolgt und Unterbrechungen sauber behandelt. Wenn eine patrouillierende Wache eine Bedrohung erkennt, sollte sie die Patrouille unterbrechen, sich um die Bedrohung kümmern und anschließend von ihrem zuletzt bekannten Wegpunkt aus weiterpatrouillieren, statt von vorne zu beginnen. Das Patrouillensystem sollte außerdem unterschiedliche Bewegungsgeschwindigkeiten, optionale Szenarioaktionen an jedem Wegpunkt und konfigurierbare Wartezeiten unterstützen.
-- 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
Beziehungsgruppen und Kampfverhalten
Beziehungsgruppen bestimmen, wie NPCs aufeinander und auf Spieler reagieren. Standardmäßig befinden sich alle NPCs in allgemeinen Beziehungsgruppen mit neutraler Einstellung. Deshalb bekämpfen sich Gangmitglieder nicht gegenseitig und Cops verfolgen Kriminelle nicht automatisch. Mit benutzerdefinierten Beziehungsgruppen kannst du Dynamiken zwischen Fraktionen erstellen, bei denen die Polizei bewaffneten Kriminellen feindlich gesinnt ist, rivalisierende Gangs sich sofort angreifen und Zivilisten vor Kämpfen fliehen. Der SetRelationshipBetweenGroups Native akzeptiert Beziehungsstufen von 0 (Verbündeter) über 3 (neutral) bis 5 (hasserfüllt). Damit wird festgelegt, ob Peds einander bekämpfen, vor einander fliehen oder einander ignorieren. Dieses System bildet die Grundlage für realistische Kämpfe zwischen Fraktionen, Gebietsstreitigkeiten und Reaktionen der Strafverfolgungsbehörden.
-- 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
Flucht- und Kampfverhalten
Für immersive Kampfszenarien ist es entscheidend, zu steuern, wie NPCs auf Bedrohungen reagieren. Das Standardverhalten in GTA V ist chaotisch: Einige NPCs laufen weg, andere ducken sich und bewaffnete NPCs greifen möglicherweise an oder auch nicht. Benutzerdefiniertes Kampfverhalten nutzt Kampfattribute und Konfigurationsflags, um vorhersehbare, der jeweiligen Rolle entsprechende Reaktionen zu erzeugen. Ein Gangmitglied sollte stehen bleiben und mit geringer Genauigkeit kämpfen, ein ausgebildeter Sicherheitsmitarbeiter sollte Deckung suchen und mit mittlerer Genauigkeit angreifen, ein Zivilist sollte sofort fliehen und eine VIP-Figur sollte sich ducken und um Hilfe rufen. Der SetPedCombatAttributes Native steuert individuelles Verhalten, zum Beispiel ob der Ped Deckung nutzen, blind feuern, Geräusche untersuchen oder fliehen kann, wenn er in der Unterzahl ist.
-- 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
NPC-Lebenszyklus und Performance verwalten
Das Spawnen von NPCs wirkt sich direkt auf die Server- und Client-Performance aus. Jeder aktive Ped beansprucht CPU-Leistung für KI-Verarbeitung, Physiksimulation und Netzwerksynchronisierung. Eine naive Implementierung, bei der alle NPCs beim Serverstart gespawnt und dauerhaft am Leben gehalten werden, beeinträchtigt die Performance stark, sobald die Population wächst. Verwende stattdessen ein entfernungsbasiertes Spawn-System, bei dem NPCs erst erstellt werden, wenn sich ein Spieler innerhalb einer konfigurierbaren Reichweite befindet, und wieder despawnen, wenn alle Spieler das Gebiet verlassen. Nutze ein räumliches Partitionierungssystem, das die Karte in Zonen unterteilt und NPC-Logik nur für Zonen mit Spielern in der Nähe verarbeitet. Speichere Ped-Handles zwischen und verwende sie wieder, wenn möglich, anstatt NPCs jedes Mal zu löschen und neu zu erstellen, wenn ein Spieler eine Zone betritt und verlässt. Setze SetEntityAsMissionEntity auf false und verwende netzwerkbewusste Erstellungsflags, damit die Game-Engine den NPC-Lebenszyklus im eigenen Streaming-System auf natürliche Weise verwalten kann.
-- 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)
Alles zusammenführen
Ein produktionsreifes NPC-KI-System vereint all diese Elemente in einem zusammenhängenden, durch Konfigurationsdaten gesteuerten Framework. Die Definition jedes NPCs in deiner Config legt das Modell, den Spawn-Ort, den Verhaltenstyp (stationär, Patrouille, Bewachung), das Kampfprofil, die Beziehungsgruppe und alle auszuführenden Szenarien oder Aufgabenfolgen fest. Das Framework lädt diese Definitionen, verwaltet das Spawnen und Despawnen basierend auf der Nähe von Spielern, verarbeitet Statusänderungen, wenn NPCs durch Kämpfe oder Spielerinteraktionen unterbrochen werden, und gibt Ressourcen frei, wenn NPCs nicht mehr benötigt werden. Das wichtigste Designprinzip ist die Trennung von Daten und Logik: Das gesamte NPC-Verhalten sollte sich ohne Codeänderungen konfigurieren lassen. So können Serverbetreiber neue NPCs hinzufügen, Patrouillenrouten ändern und Kampfparameter ausschließlich über Config-Dateien anpassen. Dank dieses datengetriebenen Ansatzes kann dein KI-System von einer Handvoll Quest-NPCs auf Hunderte von Charakteren in der ganzen Stadt anwachsen, ohne dass Änderungen an der Architektur des zugrunde liegenden Codes nötig sind.