FiveM AI NPC 行为:编写更智能的 NPC
让你的 FiveM NPC 表现得更有生命力。任务序列、路径寻找、反应式对话和 AI 技巧,打造更沉浸的角色扮演世界。
Agency Scripts
Agency Scripts 创始人兼首席开发者
为什么自定义 NPC AI 很重要
默认 GTA V 环境人口设计为单人体验,不适合沉浸式角色扮演。NPC 漫无目的行走,对玩家行为反应不可预测,且无角色扮演上下文意识。店员站立不动,酒保柜台后无动作,行人忽视活跃犯罪现场。自定义 NPC AI 将这些纸板人变为可信角色,增强沉浸感。精心编写的店主会站在收银台后招呼顾客。保安巡逻指定路线并对枪击作出反应。帮派成员保卫领地,兵力不足时逃跑。本指南涵盖 GTA V 原生函数控制 NPC 行为,构建任务序列系统,实现巡逻路线,配置关系组以实现真实战斗动态,并创建基于场景的环境人口,使服务器世界生动起来。
任务序列:NPC行为的基础
任务序列是NPC按顺序执行的动作列表。与会相互中断的单个任务调用不同,任务序列保证每个动作完成后再开始下一个。这对于复杂行为至关重要,比如保安走向门、打开门、进入房间,然后站岗。没有任务序列,您需要使用计时器手动跟踪状态,这既脆弱又难维护。原生 OpenSequenceTask 和 CloseSequenceTask functions 创建一个序列句柄,可通过 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)
场景系统
场景是预定义的动画循环,使 NPC 看起来像在执行真实活动。GTA V 包含数百个内置场景,涵盖吸烟、饮酒、使用 ATM、办公桌工作或在公园做俯卧撑等。使用 TaskStartScenarioInPlace 用于固定活动或 TaskStartScenarioAtPosition 用于位置特定动作,可让NPC执行符合情境的动作。逼真环境AI的关键是场景与位置匹配。酒保应使用 WORLD_HUMAN_BARTENDER,车库中的机械师应使用 WORLD_HUMAN_WELDING,夜总会外的NPC应使用 WORLD_HUMAN_SMOKING 或 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
巡逻路线与路径导航
巡逻路线使 NPC 在预定义的路径点之间循环移动,这对于保安、警察和帮派哨兵至关重要。最简单的方法是使用重复任务序列,让行人依次走到每个路径点,短暂停留,然后移动到下一个。更高级的方法使用状态机跟踪当前路径点索引并优雅地处理中断。当巡逻守卫发现威胁时,应中断巡逻,处理威胁,然后从上次已知的路径点继续巡逻,而不是从头开始。巡逻系统还应支持不同的移动速度、每个路径点的可选场景动作和可配置的等待时间。
-- 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
关系组和战斗行为
关系组决定NPC之间以及NPC与玩家之间的反应。默认情况下,所有NPC都属于通用关系组,处于中立状态,这就是帮派成员不会互相攻击,警察不会自动追捕罪犯的原因。自定义关系组让你创建派系动态,例如警察对持械罪犯敌对,敌对帮派见面即开战,平民在战斗中逃离。 SetRelationshipBetweenGroups native 接受从 0(同伴)到 3(中立)再到 5(仇恨)的关系等级,控制行人是否会战斗、逃跑或忽视彼此。该系统是创建真实派系战争、领土争端和执法响应的基础。
-- 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
逃跑与战斗行为
控制NPC对威胁的反应对于沉浸式战斗场景至关重要。GTA V默认行为混乱:部分NPC逃跑,部分畏缩,持枪NPC可能会或可能不会交战。自定义战斗行为使用战斗属性和配置标志创建可预测、符合角色的反应。帮派成员应站立战斗,命中率较低;训练有素的保安应掩护并以中等命中率交战;平民应立即逃跑;VIP角色应蹲下并呼救。 SetPedCombatAttributes native 控制个体行为,如行人是否能利用掩体、进行盲射、调查声音或在劣势时逃跑。
-- 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 生命周期和性能
生成NPC对服务器和客户端性能有直接影响。每个活跃的行人都消耗CPU周期用于AI处理、物理模拟和网络同步。简单实现是在服务器启动时生成所有NPC并永久保持活跃,随着人口增长性能会急剧下降。正确做法是基于接近度生成,只有当玩家在可配置范围内时才创建NPC,所有玩家离开区域时销毁。使用空间分区系统将地图划分为区域,仅处理有玩家附近的区域的NPC逻辑。缓存行人句柄并尽可能重用,而非每次玩家进出区域都删除重建NPC。设置 SetEntityAsMissionEntity 设为 false 并使用网络感知的创建标志,这样游戏引擎可以在其自身的流式系统内自然管理行人生命周期。
-- 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)
综合整合
生产 NPC AI 系统将所有这些元素结合成由配置数据驱动的统一框架。配置中的每个 NPC 定义指定模型、生成位置、行为类型(静止、巡逻、守卫)、战斗预设、关系组以及任何要执行的场景或任务序列。框架加载这些定义,根据玩家接近度管理生成和销毁,处理 NPC 被战斗或玩家互动打断时的状态转换,并在不再需要时清理资源。最重要的设计原则是数据与逻辑分离:所有 NPC 行为应可通过配置调整,无需代码更改,让服务器所有者仅通过配置文件添加新 NPC、修改巡逻路线和调整战斗参数。此数据驱动方法使您的 AI 系统可从少数任务 NPC 扩展到数百个环境角色,覆盖整个城市,而无需更改底层代码架构。