Zarządzanie entity FiveM: Obsługuj pedy, pojazdy i obiekty
Opanuj zarządzanie bytami w FiveM. Spawnowanie, ownership, culling oraz czyszczenie pedów, pojazdów i obiektów, aby Twój serwer działał stabilnie i szybko.
Agency Scripts
Założyciel i główny deweloper w Agency Scripts
Zrozumienie entities w FiveM
W FiveM entity to dowolny obiekt istniejący w świecie gry: pojazdy, pedy, propy, pickupy, a nawet postać gracza. Każdy entity posiada uchwyt (handle, całkowitoliczbowe ID), którego używasz do interakcji za pomocą funkcji natywnych. Zarządzanie entity to jedna z najbardziej fundamentalnych umiejętności w developmentcie FiveM, ponieważ niemal każdy skrypt w jakiś sposób wchodzi z nimi w interakcję. Złe zarządzanie entity prowadzi do wycieków pamięci, obiektów widm (ghost objects) pozostających po restarcie zasobu, desynchronizacji między graczami i w efekcie niestabilności serwera. Ten przewodnik omawia prawidłowe wzorce tworzenia, śledzenia i usuwania entity.
Spawnowanie obiektów i propsów
Główny native do spawnowania propów to CreateObject (lub CreateObjectNoOffset). Przed zrespawnowaniem dowolnego obiektu musisz zażądać modelu i poczekać na jego załadowanie. Brak wcześniejszego zażądania modelu poskutkuje niewidzialnym lub brakującym bytem. Zawsze zwalniaj model po utworzeniu obiektu, aby zwolnić pamięć.
-- 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)
Spawnowanie pojazdów
Spawnowanie pojazdów przebiega według podobnego schematu, ale wykorzystuje CreateVehicle oraz wymaga uwzględnienia network ownership, poziomu paliwa i tekstu tablicy rejestracyjnej. Zawsze twórz pojazdy po stronie serwera, jeśli to możliwe, lub używaj callbacków po stronie serwera do walidacji żądań utworzenia pojazdu od klientów.
-- 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
Śledzenie i czyszczenie encji
Najbardziej kluczowym aspektem zarządzania bytami jest śledzenie każdego bytu tworzonego przez twój skrypt i usuwanie ich przy zatrzymaniu zasobu. Bez odpowiedniego czyszczenia byty pozostają w świecie gry jako osierocone, marnując pamięć i zasoby renderowania. Użyj tabeli śledzenia do przechowywania wszystkich uchwytów bytów i zarejestruj onResourceStop handler, który je usuwa.
-- 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)
Własność encji sieciowych
W środowisku sieciowym FiveM każdy entity ma właściciela, czyli klienta odpowiedzialnego za symulację jego fizyki i pozycji. Network ownership określa, który klient gracza kontroluje ruch i kolizję entity. Zrozumienie oraz zarządzanie network ownership jest kluczowe dla zsynchronizowanego zachowania entity, szczególnie w skryptach sterujących zdalnie NPC lub pojazdami.
-- 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
Spawnowanie i zarządzanie pedami
Jednostki pedów (pieszych) są używane dla NPC, sprzedawców w sklepach, zleceniodawców zadań i postaci w tle. Wymagają one specjalnego traktowania, ponieważ mają AI, które może spowodować ich oddalenie się, reagowanie na otoczenie lub atakowanie graczy, jeśli nie są odpowiednio skonfigurowane. Zawsze zamrażaj i wyłączaj AI dla statycznych 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'
)
Wskazówki wydajnościowe dotyczące zarządzania bytami
- Ogranicz aktywne byty. Każdy entity zużywa pamięć i cykle renderowania. Używaj systemów streamingu, aby ładować entity, gdy gracz jest w pobliżu, i wyładowywać je, gdy opuści dany obszar.
- Używaj pul obiektów. W przypadku bytów często tworzonych i usuwanych, takich jak łuski pocisków czy efekty cząsteczkowe, używaj ponownie entity handles zamiast ciągłego tworzenia i usuwania nowych.
- Preferuj encje po stronie klienta. Jeśli dany obiekt (entity) musi być widoczny tylko dla jednego gracza (jak rekwizyt UI lub obiekt podglądu), utwórz go jako non-networked, aby zmniejszyć obciążenie serwera.
- Zawsze wywołuj SetModelAsNoLongerNeeded. Załadowane modele pozostają w pamięci do momentu ich jawnego zwolnienia. Pominięcie tego wywołania to jedna z najczęstszych przyczyn wycieków pamięci w skryptach FiveM.
- Sprawdź DoesEntityExist przed wykonaniem operacji. Encje mogą zostać usunięte przez silnik gry, inne skrypty lub desynchronizację. Zawsze weryfikuj, czy encja nadal istnieje, zanim spróbujesz ją zmodyfikować.
- Używaj entity state bags do metadanych. Zamiast utrzymywać osobne tabele wyszukiwania dla danych entity, użyj
Entity(entity).statedo przechowywania niestandardowych właściwości bezpośrednio w encji.