Управление сущностями 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для хранения пользовательских свойств непосредственно на сущности.