Zurück zum Blog
Tutorial8 Min. Lesezeit

FiveM Garagensystem: Fahrzeuge einlagern, ausparken und beschlagnahmen

Entwirf ein zuverlässiges FiveM-Garagensystem mit Privat- und Jobgaragen, Abschlepphof, Schlüsseln, Inventaranbindung und Top-Scripts.

Agency Scripts

Gründer und leitender Entwickler bei Agency Scripts

Die Architektur eines Garagensystems verstehen

Ein Garagensystem gehört zu den wichtigsten Funktionen jedes FiveM-Rollenspielservers. Es ist die zentrale Möglichkeit für Spieler, ihre Fahrzeuge aufzubewahren, abzuholen und zu verwalten. Im Kern besteht ein Garagensystem aus drei miteinander verbundenen Ebenen: einer Datenbankebene, die Fahrzeugbesitz und Fahrzeugstatus speichert, einer serverseitigen Logikebene, die Spawn- und Despawn-Vorgänge mit Prüfungen verwaltet, und einer clientseitigen UI-Ebene, über die Spieler mit ihren gespeicherten Fahrzeugen interagieren. Bevor du Code schreibst, musst du wichtige Architekturentscheidungen treffen: ob Garagen standortgebunden oder global sind, ob Spieler auf jede Garage oder nur auf bestimmte zugreifen können und wie du Fahrzeugeigenschaften wie Modifikationen, Kraftstoffstand und Schadenszustand handhaben möchtest. Die besten Garagensysteme speichern das vollständige Fahrzeug-Eigenschaftenobjekt, damit ein abgeholtes Fahrzeug genau so zurückkommt, wie es abgestellt wurde, einschließlich individueller Lackierungen, Leistungsverbesserungen und sogar des Verschmutzungsgrads der Karosserie.

Datenbankschema und Speicherung von Fahrzeugen

Dein Datenbankschema bildet das Fundament des gesamten Garagensystems. Du brauchst eine Tabelle, in der Fahrzeugbesitz, aktueller Status und gespeicherte Eigenschaften festgehalten werden. Die Statusspalte ist entscheidend, da sie angibt, ob ein Fahrzeug gerade in der Welt gespawnt ist, in einer Garage steht oder auf dem Verwahrplatz ist. Hier ist ein praktisches Schema mit den wichtigsten Feldern:

CREATE TABLE IF NOT EXISTS player_vehicles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    citizenid VARCHAR(50) NOT NULL,
    vehicle VARCHAR(50) NOT NULL,
    hash VARCHAR(50) NOT NULL,
    mods LONGTEXT DEFAULT '{}',
    plate VARCHAR(8) NOT NULL,
    fakeplate VARCHAR(8) DEFAULT NULL,
    garage VARCHAR(50) DEFAULT 'pillboxgarage',
    fuel INT DEFAULT 100,
    engine FLOAT DEFAULT 1000.0,
    body FLOAT DEFAULT 1000.0,
    state INT DEFAULT 1,  -- 0 = out, 1 = garaged, 2 = impounded
    depotprice INT DEFAULT 0,
    drivingdistance INT DEFAULT 0,
    INDEX idx_citizenid (citizenid),
    INDEX idx_plate (plate),
    INDEX idx_state (state)
);

Die mods Die Spalte speichert ein JSON-kodiertes Objekt mit allen Fahrzeugmodifikationen, das von Funktionen wie QBCore.Functions.GetVehicleProperties(vehicle) oder dem entsprechenden Gegenstück in ESX zurückgegeben wird. Durch Indizes auf den citizenid, plate , und state Spalten bleiben Abfragen schnell, auch wenn deine Spielerbasis auf mehrere Tausend anwächst. Verwende bei Abfragen dieser Tabelle immer parametrisierte Queries, um SQL-Injection-Angriffe zu verhindern.

Serverseitige Spawn- und Despawn-Logik

Auf der Serverseite finden alle wichtigen Prüfungen statt. Wenn ein Spieler anfordert, ein Fahrzeug aus der Garage zu holen, muss der Server überprüfen, ob der Spieler tatsächlich der Besitzer des Fahrzeugs ist, ob sich das Fahrzeug gerade im Garagenstatus befindet und ob ein gültiger Spawn-Punkt verfügbar ist. Lass den Client niemals die Spawn-Position direkt festlegen, da Cheater sonst Fahrzeuge an beliebigen Stellen auf der Karte spawnen könnten. Definiere die Spawn-Punkte stattdessen auf dem Server und wähle den nächstgelegenen verfügbaren aus. Hier ist ein Beispiel für einen sicheren serverseitigen Handler zum Ausparken:

RegisterNetEvent('garage:server:takeVehicle', function(vehicleId, garageId)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end

    local citizenid = Player.PlayerData.citizenid
    local result = MySQL.query.await(
        'SELECT * FROM player_vehicles WHERE id = ? AND citizenid = ? AND state = 1',
        {vehicleId, citizenid}
    )

    if not result or not result[1] then
        TriggerClientEvent('QBCore:Notify', src, 'Vehicle not found', 'error')
        return
    end

    local vehData = result[1]
    local spawnPoint = GetAvailableSpawnPoint(garageId)

    if not spawnPoint then
        TriggerClientEvent('QBCore:Notify', src, 'No parking spots available', 'error')
        return
    end

    MySQL.update('UPDATE player_vehicles SET state = 0 WHERE id = ?', {vehicleId})
    TriggerClientEvent('garage:client:spawnVehicle', src, vehData, spawnPoint)
end)

Beim Despawn-Vorgang muss der Server die aktuellen Fahrzeugeigenschaften erfassen, bevor das Fahrzeug aus der Welt entfernt wird. So werden Änderungen seit dem letzten Einparken gespeichert. Aktualisiere neben dem JSON der Modifikationen immer auch den Kraftstoffstand, die Motorgesundheit und den Karosseriezustand, damit alle Daten korrekt gespeichert werden. Führe serverseitig eine Distanzprüfung durch, um sicherzustellen, dass sich der Spieler tatsächlich in der Nähe einer Garage befindet, bevor Einparkaktionen zugelassen werden.

Clientseitige Garagen-UI mit NUI

Über die Garagen-UI interagieren Spieler mit dem System. Eine gut gestaltete Oberfläche macht den Unterschied zwischen einer frustrierenden und einer reibungslosen Erfahrung. Verwende NUI mit HTML, CSS und JavaScript, um ein responsives Panel zu erstellen, das alle Fahrzeuge in der aktuellen Garage anzeigt. Jeder Fahrzeugeintrag sollte auf einen Blick den Fahrzeugnamen, das Kennzeichen, den Kraftstoffstand und den Gesamtzustand anzeigen. Ergänze eine Vorschaufunktion, die das Fahrzeugmodell vorübergehend spawnt, damit Spieler sehen können, welches Fahrzeug sie auswählen. Das ist besonders nützlich, wenn ein Spieler mehrere Fahrzeuge desselben Typs besitzt. Hier ist die clientseitige Logik zum Öffnen des Garagenmenüs und Abrufen der Fahrzeugdaten:

RegisterNetEvent('garage:client:openMenu', function(garageId)
    QBCore.Functions.TriggerCallback('garage:server:getVehicles', function(vehicles)
        if not vehicles or #vehicles == 0 then
            QBCore.Functions.Notify('No vehicles stored here', 'info')
            return
        end

        SetNuiFocus(true, true)
        SendNUIMessage({
            action = 'openGarage',
            vehicles = vehicles,
            garageName = Config.Garages[garageId].label
        })
    end, garageId)
end)

RegisterNUICallback('takeVehicle', function(data, cb)
    SetNuiFocus(false, false)
    TriggerServerEvent('garage:server:takeVehicle', data.vehicleId, currentGarage)
    cb('ok')
end)

Rendere auf der JavaScript-Seite jedes Fahrzeug als Karte mit Aktionsbuttons, um das Fahrzeug auszuparken oder in eine andere Garage zu verlegen. Ergänze Sortier- und Filteroptionen, damit Spieler mit großen Sammlungen das gewünschte Fahrzeug schnell finden. Eine Suchleiste, die nach Kennzeichen oder Fahrzeugnamen filtert, ist eine kleine Ergänzung, die die Bedienbarkeit auf Servern deutlich verbessert, auf denen Spieler mit der Zeit viele Fahrzeuge ansammeln.

Speicherung und Wiederherstellung von Fahrzeugeigenschaften

Das korrekte Speichern und Wiederherstellen von Fahrzeugeigenschaften gehört zu den anspruchsvollsten Teilen der Entwicklung eines Garagensystems. Das Eigenschaftsobjekt enthält Dutzende Felder, darunter Farben, Lackierungen, Neonlichter, getönte Scheiben, Reifenrauchfarbe, Extras und alle Leistungsmodifikationen. Erfasse beim Einparken die Eigenschaften unmittelbar vor dem Löschen der Entität, damit du den aktuellsten Zustand erhältst. Warte beim erneuten Spawnen des Fahrzeugs, bis die Entität vollständig geladen ist, bevor du Eigenschaften anwendest. Andernfalls schlagen Modifikationen wie individuelle Felgen oder Motorverbesserungen unbemerkt fehl. Verwende eine kurze Verzögerung oder eine geeignete Schleife zur Prüfung, ob die Entität existiert:

function SpawnAndApplyMods(vehData, spawnPoint)
    local model = GetHashKey(vehData.vehicle)
    RequestModel(model)

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

    local veh = CreateVehicle(model, spawnPoint.x, spawnPoint.y, spawnPoint.z,
        spawnPoint.w, true, false)

    while not DoesEntityExist(veh) do
        Wait(10)
    end

    local props = json.decode(vehData.mods)
    if props then
        QBCore.Functions.SetVehicleProperties(veh, props)
    end

    SetVehicleFuelLevel(veh, vehData.fuel + 0.0)
    SetVehicleEngineHealth(veh, vehData.engine + 0.0)
    SetVehicleBodyHealth(veh, vehData.body + 0.0)
    SetEntityAsMissionEntity(veh, true, true)
    SetModelAsNoLongerNeeded(model)
    TaskWarpPedIntoVehicle(PlayerPedId(), veh, -1)
end

Achte besonders auf Add-on-Fahrzeuge, da ihre benutzerdefinierten Extras oder Lackierungsindizes manchmal anders funktionieren als bei Standardfahrzeugen aus GTA. Teste den gesamten Ablauf zum Speichern und Wiederherstellen gründlich mit verschiedenen Fahrzeugtypen, damit du Sonderfälle früh erkennst.

Integration des Verwahrplatzsystems

Ein Verwahrstellensystem arbeitet Hand in Hand mit deiner Garage und sorgt für den Realismus, den Roleplay-Server verlangen. Fahrzeuge landen aus mehreren Gründen in der Verwahrstelle: Die Polizei beschlagnahmt sie bei einer Festnahme, verlassene Fahrzeuge werden nach einem Serverneustart automatisch entfernt oder ein Admin greift wegen Regelverstößen ein. Wird ein Fahrzeug sichergestellt, setze seinen Status in der Datenbank auf 2 und lege optional eine Depotgebühr fest, die der Spieler für die Abholung zahlen muss. Die Verwahrstelle sollte ähnlich wie eine Garage funktionieren, allerdings muss vor der Freigabe bezahlt werden. Erstelle auf der Karte einen eigenen Standort mit separaten Spawnpunkten und einer NUI-Oberfläche, auf der die Verwahrgebühr gut sichtbar angezeigt wird.

RegisterNetEvent('police:server:impoundVehicle', function(plate, price)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end

    -- Verify the player has police job authorization
    if Player.PlayerData.job.name ~= 'police' then return end

    local result = MySQL.update.await(
        'UPDATE player_vehicles SET state = 2, depotprice = ? WHERE plate = ?',
        {price or 500, plate}
    )

    if result > 0 then
        TriggerClientEvent('QBCore:Notify', src, 'Vehicle impounded', 'success')
    end
end)

Du kannst ein gestaffeltes Preissystem einführen, bei dem die Verwahrgebühr jedes Mal steigt, wenn dasselbe Fahrzeug erneut sichergestellt wird. So gewöhnst du Spieler daran, die Verwahrstelle nicht als kostenlosen Parkplatz zu nutzen. Du kannst außerdem eine zeitabhängige Funktion hinzufügen, bei der Fahrzeuge, die länger als eine konfigurierbare Anzahl an Tagen in Echtzeit in der Verwahrstelle stehen, automatisch und kostenlos in die Garage zurückgebracht werden. So verhinderst du, dass Fahrzeuge dauerhaft verloren gehen und Spieler frustriert zurückbleiben.

Garagen-Blips und Target-Integration

Damit Garagen leicht zu finden und einfach zu nutzen sind, musst du Blips und Interaktionszonen richtig platzieren. Füge für jeden Garagenstandort einen Blip zur Karte hinzu, damit Spieler ihn auf der Minimap finden können, und nutze entweder markerbasierte Interaktionen in der Nähe oder die Integration eines Target-Systems als Interaktionstrigger. Target-Systeme wie ox_target oder qb-target sorgen für ein übersichtlicheres Spielerlebnis, da Interaktionsoptionen nur angezeigt werden, wenn der Spieler auf einen bestimmten Punkt zielt. Definiere deine Garagenstandorte in einer gemeinsamen Config-Datei, auf die Client und Server zugreifen können, damit Koordinaten, Spawnpunkte und Einstellungen synchron bleiben:

Config.Garages = {
    ['pillboxgarage'] = {
        label = 'Pillbox Garage',
        coords = vector3(215.83, -810.18, 30.73),
        spawnPoints = {
            vector4(218.32, -803.28, 30.73, 248.5),
            vector4(222.41, -799.84, 30.73, 248.5),
            vector4(226.52, -796.41, 30.73, 248.5),
        },
        blip = { sprite = 357, color = 3, scale = 0.7 },
        vehicleType = 'car',  -- car, boat, aircraft
    },
}

Unterstütze mehrere Fahrzeugtypen, indem du separate Garagen für Boote und Luftfahrzeuge mit passenden Spawnpunkten am Wasser oder auf Flughäfen erstellst. Der vehicleType Filter stellt sicher, dass Spieler in einer Straßengarage nur Landfahrzeuge und in einem Jachthafen nur Boote sehen. So vermeidest du Verwirrung und Probleme beim Spawnen. Wenn sich ein Spieler einer Garage nähert, prüfe, ob dort Fahrzeuge für ihn eingelagert sind, bevor du die Interaktionsaufforderung anzeigst. So verhinderst du, dass Spieler ohne Fahrzeuge an diesem Standort unnötig Menüs öffnen.

Tipps zur Performance-Optimierung

Garagensysteme können die Performance beeinträchtigen, wenn sie nicht sorgfältig umgesetzt werden. Das gilt besonders für Server mit Hunderten gleichzeitig aktiven Spielern, von denen jeder mehrere Fahrzeuge besitzt. Halte Fahrzeuglisten serverseitig im Cache, anstatt bei jedem Öffnen des Garagenmenüs die Datenbank abzufragen, und invalidiere den Cache nur, wenn sich der Status eines Fahrzeugs ändert. Vermeide es clientseitig, NUI-Frames geöffnet zu lassen, wenn sie nicht benötigt werden, denn auch ausgeblendete NUI-Frames verbrauchen Ressourcen, wenn darin JavaScript-Timer oder Animationen laufen. Achte beim Spawnen von Fahrzeugen darauf, Entitäten ordnungsgemäß aufzuräumen, indem du sie als nicht mehr benötigt markierst, nachdem der Spieler sie eingelagert hat. Implementiere außerdem eine regelmäßige Ausweichroutine zur Bereinigung, die verwaiste Fahrzeugentitäten entfernt, die aufgrund von Abstürzen oder Verbindungsabbrüchen nicht ordnungsgemäß despawnt wurden. Verwende native Funktionen wie GetGamePool('CVehicle') sparsam und cache die Ergebnisse, wenn du prüfen musst, ob sich Fahrzeuge von Spielern in der Spielwelt befinden. Lege außerdem ein maximales Fahrzeuglimit pro Garage fest, damit Datenbankabfragen begrenzt bleiben und einzelne Spieler nicht Hunderte Fahrzeuge einlagern können, was den Abruf verlangsamen könnte.

Das passende Script

Fertig für deinen Server, Updates inklusive.

Bereit loszulegen?

Hol dir die Scripts in unserem Shop, oder komm auf den Discord für Support, Updates und einen Blick auf das, was kommt.