返回博客
Guide4 分钟阅读

FiveM AI NPC 行为:编写更智能的 NPC

让你的 FiveM NPC 表现得更有生命力。任务序列、路径寻找、反应式对话和 AI 技巧,打造更沉浸的角色扮演世界。

Agency Scripts

Agency Scripts 创始人兼首席开发者

为什么自定义 NPC AI 很重要

默认 GTA V 环境人口设计为单人体验,不适合沉浸式角色扮演。NPC 漫无目的行走,对玩家行为反应不可预测,且无角色扮演上下文意识。店员站立不动,酒保柜台后无动作,行人忽视活跃犯罪现场。自定义 NPC AI 将这些纸板人变为可信角色,增强沉浸感。精心编写的店主会站在收银台后招呼顾客。保安巡逻指定路线并对枪击作出反应。帮派成员保卫领地,兵力不足时逃跑。本指南涵盖 GTA V 原生函数控制 NPC 行为,构建任务序列系统,实现巡逻路线,配置关系组以实现真实战斗动态,并创建基于场景的环境人口,使服务器世界生动起来。

任务序列:NPC行为的基础

任务序列是NPC按顺序执行的动作列表。与会相互中断的单个任务调用不同,任务序列保证每个动作完成后再开始下一个。这对于复杂行为至关重要,比如保安走向门、打开门、进入房间,然后站岗。没有任务序列,您需要使用计时器手动跟踪状态,这既脆弱又难维护。原生 OpenSequenceTaskCloseSequenceTask 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_SMOKINGWORLD_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 扩展到数百个环境角色,覆盖整个城市,而无需更改底层代码架构。

准备好开始了吗?

在我们的商店获取脚本,或加入 Discord 获取支持、更新以及新功能预告。