返回博客
Tutorial3 分钟阅读

FiveM 实体管理:处理行人、车辆、物体

掌握 FiveM 实体管理。生成、所有权、剔除和清理行人、车辆及物体,保持服务器稳定快速。

Agency Scripts

Agency Scripts 创始人兼首席开发者

理解FiveM中的实体

在FiveM中,实体是游戏世界中存在的任何对象:车辆、行人、道具、拾取物,甚至玩家角色。每个实体都有一个句柄(整数ID),用于通过本地函数与其交互。实体管理是FiveM开发中最基本的技能之一,因为几乎所有脚本都会以某种方式与实体交互。实体管理不当会导致内存泄漏、资源重启后残留的幽灵对象、玩家间不同步,最终导致服务器不稳定。本指南涵盖创建、跟踪和清理实体的正确模式。

生成物体和道具

生成道具的主要native是 CreateObject (或 CreateObjectNoOffset)。在生成任何对象之前,您必须请求模型并等待加载。未先请求模型将导致实体不可见或缺失。创建对象后务必释放模型以释放内存。

-- client/entity_spawner.lua
local function SpawnProp(modelName, coords, rotation, isNetwork)
    local model = joaat(modelName)

    -- Request the model and wait for it to load
    RequestModel(model)
    local timeout = 0
    while not HasModelLoaded(model) do
        Wait(10)
        timeout = timeout + 10
        if timeout > 5000 then
            print(('[ERROR] Model %s failed to load after 5s'):format(modelName))
            return nil
        end
    end

    local obj = CreateObject(model, coords.x, coords.y, coords.z, isNetwork, true, false)

    if rotation then
        SetEntityRotation(obj, rotation.x, rotation.y, rotation.z, 2, true)
    end

    -- Freeze the object so it does not fall through the ground
    FreezeEntityPosition(obj, true)

    -- Release the model from memory
    SetModelAsNoLongerNeeded(model)

    return obj
end

-- Usage
local chair = SpawnProp('prop_chair_01a', vector3(200.0, -800.0, 31.0), nil, false)

生成车辆

车辆生成遵循类似模式,但使用 CreateVehicle 并且对网络所有权、燃油水平和车牌文字有额外考虑。尽可能在服务器端生成车辆,或使用服务器端回调验证客户端的车辆创建请求。

-- client/vehicle_spawner.lua
local function SpawnVehicle(modelName, coords, heading)
    local model = joaat(modelName)

    RequestModel(model)
    while not HasModelLoaded(model) do
        Wait(10)
    end

    local vehicle = CreateVehicle(model, coords.x, coords.y, coords.z, heading, true, false)

    -- Basic vehicle setup
    SetVehicleOnGroundProperly(vehicle)
    SetEntityAsMissionEntity(vehicle, true, true)
    SetVehicleHasBeenOwnedByPlayer(vehicle, true)
    SetVehicleNeedsToBeHotwired(vehicle, false)
    SetVehRadioStation(vehicle, 'OFF')

    -- Set fuel if using a fuel system
    Entity(vehicle).state:set('fuel', 100.0, true)

    SetModelAsNoLongerNeeded(model)

    return vehicle
end

实体跟踪与清理

实体管理最关键的是跟踪脚本创建的每个实体,并在资源停止时清理它们。若无适当清理,实体将作为孤儿持续存在游戏世界,浪费内存和渲染资源。使用跟踪表存储所有实体句柄,并注册一个 onResourceStop handler 用于移除它们。

-- client/entity_manager.lua
local ManagedEntities = {}

function RegisterEntity(entity, category)
    if not DoesEntityExist(entity) then return end

    ManagedEntities[entity] = {
        category = category or 'default',
        created = GetGameTimer(),
        model = GetEntityModel(entity),
    }
end

function UnregisterEntity(entity)
    if ManagedEntities[entity] then
        if DoesEntityExist(entity) then
            SetEntityAsMissionEntity(entity, false, true)
            DeleteEntity(entity)
        end
        ManagedEntities[entity] = nil
    end
end

function CleanupCategory(category)
    for entity, data in pairs(ManagedEntities) do
        if data.category == category then
            UnregisterEntity(entity)
        end
    end
end

function CleanupAllEntities()
    for entity, _ in pairs(ManagedEntities) do
        UnregisterEntity(entity)
    end
    ManagedEntities = {}
end

-- Critical: Clean up when resource stops
AddEventHandler('onResourceStop', function(resourceName)
    if GetCurrentResourceName() ~= resourceName then return end
    CleanupAllEntities()
end)

网络实体所有权

在FiveM的网络环境中,每个实体都有一个所有者,即负责模拟其物理和位置的客户端。网络所有权决定哪个玩家客户端控制实体的移动和碰撞。理解和管理网络所有权对于同步实体行为至关重要,尤其是需要远程控制NPC或车辆的脚本。

-- server/ownership.lua
-- Request control of a networked entity
local function RequestEntityOwnership(src, netId)
    local entity = NetworkGetEntityFromNetworkId(netId)
    if not DoesEntityExist(entity) then return false end

    -- Set the requesting player as the owner
    SetEntityRoutingBucket(entity, GetPlayerRoutingBucket(src))

    return true
end

-- client/ownership.lua
-- Request network control of an entity
local function TakeEntityControl(entity, timeout)
    timeout = timeout or 2000
    local start = GetGameTimer()

    NetworkRequestControlOfEntity(entity)

    while not NetworkHasControlOfEntity(entity) do
        Wait(100)
        NetworkRequestControlOfEntity(entity)
        if GetGameTimer() - start > timeout then
            return false
        end
    end

    return true
end

-- Usage: Move an entity you need control of
local function MoveEntity(entity, targetCoords)
    if TakeEntityControl(entity) then
        SetEntityCoords(entity, targetCoords.x, targetCoords.y, targetCoords.z, false, false, false, false)
        return true
    end

    print('[WARN] Could not get control of entity')
    return false
end

生成和管理行人

Ped(行人)实体用于 NPC、商店供应商、任务发布者和环境角色。它们需要特殊处理,因为它们具有 AI,可能会漫游、对环境做出反应,或在配置不当时攻击玩家。静态 NPC 应始终冻结并禁用 AI。

-- client/ped_spawner.lua
local function SpawnStaticPed(modelName, coords, heading, scenario)
    local model = joaat(modelName)

    RequestModel(model)
    while not HasModelLoaded(model) do
        Wait(10)
    end

    local ped = CreatePed(0, model, coords.x, coords.y, coords.z - 1.0, heading, false, true)

    -- Make the ped static and non-interactive with ambient AI
    SetEntityAsMissionEntity(ped, true, true)
    SetBlockingOfNonTemporaryEvents(ped, true)
    SetPedFleeAttributes(ped, 0, false)
    SetPedCombatAttributes(ped, 17, true)
    SetPedDiesWhenInjured(ped, false)
    SetEntityInvincible(ped, true)
    FreezeEntityPosition(ped, true)

    -- Play a scenario animation if specified
    if scenario then
        TaskStartScenarioInPlace(ped, scenario, 0, true)
    end

    SetModelAsNoLongerNeeded(model)
    RegisterEntity(ped, 'npc')

    return ped
end

-- Spawn a shop vendor
local vendor = SpawnStaticPed(
    's_m_y_ammucity_01',
    vector3(22.0, -1105.0, 29.8),
    160.0,
    'WORLD_HUMAN_STAND_IMPATIENT'
)

实体管理性能提示

  • 限制活动实体数。 每个实体都会消耗内存和渲染周期。使用流式系统在玩家接近时加载实体,离开区域时卸载它们。
  • 使用对象池。 对于频繁生成和销毁的实体,如弹壳或粒子效果,重用实体句柄,避免不断创建和删除新实体。
  • 优先使用客户端实体。 如果实体只需对单个玩家可见(如UI道具或预览对象),请创建为非网络实体以减少服务器负载。
  • 始终调用 SetModelAsNoLongerNeeded。 加载的模型会一直保留在内存中,直到显式释放。忘记调用此操作是FiveM脚本中内存泄漏最常见的原因之一。
  • 在操作前检查 DoesEntityExist。 实体可能被游戏引擎、其他脚本或不同步删除。修改前务必确认实体仍存在。
  • 使用实体状态包存储元数据。 无需维护单独的实体数据查找表,使用 Entity(entity).state 直接在实体上存储自定义属性。

准备好开始了吗?

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