Назад к блогу
Tutorial5 мин чтения

Управление сущностями FiveM: работа с пешеходами, транспортом, объектами

Освойте управление сущностями FiveM. Спавн, владение, удаление и очистка пэдов, транспортных средств и объектов для стабильной и быстрой работы сервера.

Agency Scripts

Основатель и ведущий разработчик Agency Scripts

Понимание сущностей в FiveM

В FiveM сущность , это любой объект в игровом мире: транспорт, пешеходы, объекты, предметы и даже персонаж игрока. У каждой сущности есть дескриптор (целочисленный ID), с помощью которого вы взаимодействуете через native-функции. Управление сущностями , одна из базовых навыков в разработке FiveM, так как почти каждый скрипт взаимодействует с сущностями. Плохое управление приводит к утечкам памяти, «призрачным» объектам, сохраняющимся после перезапуска ресурса, рассинхронизации между игроками и нестабильности сервера. Это руководство описывает правильные шаблоны создания, отслеживания и очистки сущностей.

Спавн объектов и пропов

Основной натив для спавна объектов , 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 обработчик, который их удаляет.

-- 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, продавцов в магазинах, дающих задания и фоновых персонажей. Они требуют особого обращения, так как имеют ИИ, который может заставлять их блуждать, реагировать на окружение или нападать на игроков при неправильной настройке. Всегда замораживайте и отключайте ИИ для статичных NPC.

-- 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 state bags для метаданных. Вместо поддержки отдельных таблиц поиска для данных сущностей используйте Entity(entity).state для хранения пользовательских свойств непосредственно на сущности.

Готовы начать?

Возьмите скрипты в нашем магазине или заходите в Discord за поддержкой, обновлениями и анонсами.