Wróć do bloga
Tutorial5 min czytania

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).state do przechowywania niestandardowych właściwości bezpośrednio w encji.

Gotowy, aby zacząć?

Kup skrypty w naszym sklepie lub dołącz do Discorda, aby uzyskać wsparcie, aktualizacje i sprawdzić nadchodzące nowości.