FiveM-Parksystem: Realistische Skripte zum Parken von Fahrzeugen
Ergänze realistisches Parken in deiner FiveM-Stadt: Straßenparkplätze, gebührenpflichtige Parkplätze und Abschleppregeln gegen wild abgestellte Fahrzeuge.
Agency Scripts
Gründer und leitender Entwickler bei Agency Scripts
Die Architektur eines Parksystems verstehen
Ein Parksystem verleiht jedem FiveM-Rollenspielserver mehr Tiefe und Realismus, indem es Spielern ausgewiesene Bereiche bietet, in denen sie ihre Fahrzeuge abstellen können, und Konsequenzen für Regelverstöße vorsieht. Im Kern erfasst ein Parksystem, welche Parkplätze belegt sind, überwacht, wie lange Fahrzeuge geparkt bleiben, und wickelt Zahlungen über Parkuhren oder Genehmigungen ab. Das System besteht aus serverseitiger Logik zur Erfassung des Zustands und zur Abwicklung von Zahlungen, clientseitiger Erkennung beim Betreten und Verlassen von Parkzonen sowie einer optionalen NUI-Oberfläche zur Anzeige des Parkuhrenstatus und der Zahlungsoptionen. Ein gut umgesetztes Parksystem ist außerdem in deine bestehenden Polizei- und Abschlepp-Scripts integriert, damit unrechtmäßig geparkte Fahrzeuge von befugten Spielern mit Strafzetteln versehen oder abgeschleppt werden können.
Parkzonen und Stellplätze definieren
Parkzonen werden als polygonale oder rechteckige Bereiche auf der Karte definiert. Jede Zone enthält einzelne Stellplätze mit bestimmten Koordinaten und Ausrichtungen. Verwende eine Konfigurationsdatei, um diese Zonen festzulegen, damit Serveradministratoren neue Parkbereiche hinzufügen können, ohne den Code zu ändern. Für jede Zone lassen sich unterschiedliche Regeln festlegen, zum Beispiel kostenloses Parken, Parken mit Parkuhr oder Bereiche, die nur mit einer Genehmigung genutzt werden dürfen. Hier ist eine praktische Struktur für die Zonenkonfiguration:
Config.ParkingZones = {
['pillbox_lot'] = {
label = 'Pillbox Medical Parking',
type = 'metered', -- 'free', 'metered', 'permit'
rate = 50, -- $ per hour (metered only)
maxTime = 120, -- minutes max parking
blip = {sprite = 357, color = 3, scale = 0.6},
spots = {
{coords = vector4(310.5, -590.2, 43.3, 70.0), occupied = false},
{coords = vector4(313.8, -591.6, 43.3, 70.0), occupied = false},
{coords = vector4(317.1, -593.0, 43.3, 70.0), occupied = false},
{coords = vector4(320.4, -594.4, 43.3, 70.0), occupied = false},
},
fineAmount = 250, -- fine for overtime or illegal parking
},
['legion_street'] = {
label = 'Legion Square Parking',
type = 'metered',
rate = 75,
maxTime = 60,
blip = {sprite = 357, color = 3, scale = 0.6},
spots = {
{coords = vector4(215.2, -810.5, 30.7, 160.0), occupied = false},
{coords = vector4(212.6, -810.5, 30.7, 160.0), occupied = false},
},
fineAmount = 350,
},
}
Die vector4 Das Format speichert die Koordinaten x, y, z sowie den Ausrichtungswinkel, damit Fahrzeuge in die richtige Richtung geparkt werden. Das occupied Flag wird zur Laufzeit vom Server verwaltet und bei Neustarts nicht gespeichert, da Fahrzeuge separat über die Datenbank des Garagensystems erfasst werden. Achte an jedem Standort auf eine realistische Anzahl an Stellplätzen, denn zu viele Plätze auf engem Raum führen dazu, dass Fahrzeuge ineinander clippen.
Parkuhr-Timersystem
Das Parkuhrsystem erfasst, wie lange jedes Fahrzeug geparkt ist, und berechnet die entsprechenden Gebühren. Wenn ein Spieler in einer Parkzone mit Parkuhr parkt, interagiert er mit einer Parkuhr, um eine Sitzung zu starten, wählt eine Dauer aus und bezahlt im Voraus. Der Server speichert den Startzeitpunkt der Sitzung, die bezahlte Dauer, das Kennzeichen und die Zonen-ID. Ein serverseitiger Timer-Thread prüft regelmäßig alle aktiven Parkuhrsitzungen und markiert abgelaufene Sitzungen zur Ahndung. Hier ist die grundlegende Parkuhrlogik:
local ActiveMeters = {} -- keyed by plate
RegisterNetEvent('parking:server:startMeter', function(plate, zoneId, duration)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
local zone = Config.ParkingZones[zoneId]
if not zone or zone.type ~= 'metered' then return end
-- Calculate cost based on duration in minutes
local hours = duration / 60
local cost = math.ceil(hours * zone.rate)
if Player.Functions.RemoveMoney('cash', cost, 'parking-meter') then
ActiveMeters[plate] = {
zone = zoneId,
startTime = os.time(),
paidUntil = os.time() + (duration * 60),
plate = plate,
owner = Player.PlayerData.citizenid,
}
TriggerClientEvent('parking:client:meterStarted', src, duration, cost)
else
TriggerClientEvent('QBCore:Notify', src, 'Not enough cash', 'error')
end
end)
-- Check for expired meters every 60 seconds
CreateThread(function()
while true do
Wait(60000)
local now = os.time()
for plate, meter in pairs(ActiveMeters) do
if now > meter.paidUntil then
-- Meter expired, issue fine
IssueParkingFine(plate, meter.zone, meter.owner)
ActiveMeters[plate] = nil
end
end
end
end)
Ein Timerintervall von 60 Sekunden reicht für die Durchsetzung der Parkregeln aus, ohne unnötige CPU-Last zu verursachen. Speichere Parkuhrsitzungen im Arbeitsspeicher statt in der Datenbank, da sie nur vorübergehend sind und Serverneustarts nicht überstehen müssen. Bei einem Serverneustart werden alle Parkuhren zurückgesetzt. Das ist akzeptabel, da Fahrzeuge in den meisten Frameworks beim Neustart ebenfalls in die Garagen zurückkehren.
Fahrzeugerkennung und Stellplatzzuweisung
Um zu erkennen, wann ein Fahrzeug einen Stellplatz befährt oder verlässt, sind regelmäßige Näherungsprüfungen auf der Client-Seite nötig. Prüfe anhand der Entfernung alle Stellplätze in nahegelegenen Zonen, statt jeden Stellplatz auf der gesamten Karte zu überprüfen. Führe die Erkennungsschleife nur aus, wenn sich der Spieler in einem Fahrzeug und in der Nähe einer konfigurierten Parkzone befindet. So bleibt die CPU-Auslastung beim normalen Spielen minimal:
-- client/detection.lua
local nearestZone = nil
local currentSpot = nil
CreateThread(function()
while true do
local sleep = 1000
local ped = PlayerPedId()
local inVehicle = IsPedInAnyVehicle(ped, false)
if inVehicle then
local veh = GetVehiclePedIsIn(ped, false)
local vehCoords = GetEntityCoords(veh)
for zoneId, zone in pairs(Config.ParkingZones) do
local firstSpot = zone.spots[1]
local zoneDist = #(vehCoords - vector3(firstSpot.coords.x,
firstSpot.coords.y, firstSpot.coords.z))
if zoneDist < 50.0 then
sleep = 200
nearestZone = zoneId
for i, spot in ipairs(zone.spots) do
local spotPos = vector3(spot.coords.x, spot.coords.y, spot.coords.z)
local dist = #(vehCoords - spotPos)
if dist < 3.0 then
local speed = GetEntitySpeed(veh)
if speed < 0.5 then
currentSpot = {zone = zoneId, index = i}
ShowParkingPrompt(zone)
end
end
end
break
end
end
else
nearestZone = nil
currentSpot = nil
end
Wait(sleep)
end
end)
Die Geschwindigkeitsprüfung von 0.5 sorgt dafür, dass die Parkaufforderung nur erscheint, wenn das Fahrzeug fast stillsteht. Dadurch flackert die UI nicht, während der Spieler durch einen Parkplatz fährt. Das adaptive Schlafintervall von 200ms in der Nähe von Zonen und 1000ms an anderen Orten sorgt für ein ausgewogenes Verhältnis zwischen Reaktionsfähigkeit und Leistung. Bei Servern mit vielen Parkzonen kannst du einen räumlichen Hash oder Quadbaum verwenden, damit nicht bei jedem Tick jede Zone durchlaufen werden muss.
Parksünder und Ahndung
Wenn eine Parkuhr abläuft oder ein Fahrzeug falsch geparkt ist, stellt das System ein Bußgeld aus, das dem Fahrzeughalter zugeordnet wird. Bußgelder werden in einer Datenbanktabelle gespeichert und angezeigt, wenn sich der Halter das nächste Mal anmeldet oder sein Fahrzeug abholt. Spieler bei der Polizei können außerdem manuell Strafzettel per Befehl oder Target-Interaktion ausstellen. Der Bußgeldeintrag enthält das Kennzeichen, den Betrag, den Grund für die Ausstellung und den Zeitstempel:
function IssueParkingFine(plate, zoneId, citizenid)
local zone = Config.ParkingZones[zoneId]
local amount = zone and zone.fineAmount or 250
MySQL.insert([[
INSERT INTO parking_fines (plate, citizenid, amount, reason, zone, issued_at)
VALUES (?, ?, ?, ?, ?, NOW())
]], {plate, citizenid, amount, 'Expired parking meter', zoneId})
-- Notify owner if online
local Player = QBCore.Functions.GetPlayerByCitizenId(citizenid)
if Player then
TriggerClientEvent('QBCore:Notify', Player.PlayerData.source,
'Parking fine issued: $' .. amount, 'error')
end
end
-- Police manual ticket command
RegisterNetEvent('parking:server:issueTicket', function(plate, reason)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
if Player.PlayerData.job.name ~= 'police' then return end
local vehicle = MySQL.query.await(
'SELECT citizenid FROM player_vehicles WHERE plate = ?', {plate}
)
if vehicle and vehicle[1] then
IssueParkingFine(plate, 'manual', vehicle[1].citizenid)
TriggerClientEvent('QBCore:Notify', src, 'Ticket issued for ' .. plate, 'success')
end
end)
Integriere das Bußgeldsystem in den Fahrzeugabholprozess, damit Spieler ausstehende Bußgelder bezahlen müssen, bevor sie ein Fahrzeug aus der Garage holen können. So entsteht ein natürlicher Ahndungskreislauf, bei dem das Missachten der Parkregeln echte Konsequenzen im Spiel hat. Erwäge, nach Ablauf der Parkuhr eine Karenzzeit von fünf bis zehn Minuten einzurichten, bevor ein Bußgeld ausgestellt wird. Das entspricht der Ahndung im echten Leben und verringert den Frust über Bußgelder, die auf die Minute genau ausgestellt werden.
NUI-Anzeige und Bezahloberfläche der Parkuhr
Die NUI der Parkuhr sollte übersichtlich und informativ sein. Sie sollte den Namen der aktuellen Zone, den Stundensatz, die verbleibende Zeit bei bereits bezahltem Parken sowie Schaltflächen zur Auswahl der Parkdauer und zur Zahlung anzeigen. Wenn eine Parkuhr aktiv ist, zeige einen Countdown an, der jede Sekunde aktualisiert wird, damit der Spieler genau weiß, wie viel Zeit ihm noch bleibt. Verwende ein kompaktes Design, das die Spielansicht nicht verdeckt und einem echten Parkautomaten ähnelt. Verändere die Farbe der Timeranzeige mit einem Farbübergang von Grün über Gelb zu Rot, wenn die Zeit abläuft. So erhält der Spieler einen intuitiven visuellen Hinweis, ohne die verbleibenden Minuten genau ablesen zu müssen. Halte die NUI schlank, indem du Vanilla JavaScript und minimales CSS verwendest. Vermeide umfangreiche Frameworks, die bei einer einfachen Oberfläche die Ladezeit verlängern.
Abschleppen und verlassene Fahrzeuge
Verbinde dein Parksystem mit einer Abschleppmechanik, damit abgelaufene oder falsch geparkte Fahrzeuge zum Verwahrplatz abgeschleppt werden können. Wenn ein Fahrzeug länger als der konfigurierte Grenzwert gegen die Parkregeln verstößt, markiere es im Serverstatus als abschleppbar. Spieler mit einem Abschleppwagen-Job können diese Fahrzeuge dann auf ihrer Karte hervorgehoben sehen und für das Abschleppen bezahlt werden. Beim Abschleppen sollte der Fahrzeugstatus in der Datenbank von "out" auf "impounded" gesetzt und der Verwahrgrund als Parkverstoß erfasst werden. Implementiere für verlassene Fahrzeuge, die längere Zeit ohne Interaktion eines Spielers auf Stellplätzen stehen, eine automatische Bereinigungsroutine. Sie verschiebt diese Fahrzeuge nach einer konfigurierbaren Zeit zum Verwahrplatz und macht die Stellplätze für aktive Spieler frei. So füllen sich Parkplätze nicht mit Fahrzeugen von Spielern, die sich seit Tagen oder Wochen nicht angemeldet haben.
Leistung und Skalierbarkeit
Laufende Erkennungsschleifen können die Leistung von Parksystemen beeinträchtigen, wenn sie nicht sorgfältig optimiert sind. Die clientseitige Fahrzeugerkennung sollte nur für Zonen innerhalb der Renderdistanz aktiviert werden. Der serverseitige Parkuhr-Thread sollte außerdem alle aktiven Sitzungen in Stapeln verarbeiten, statt für jedes Fahrzeug einen eigenen Timer zu erstellen. Lege in der Datenbanktabelle für Bußgelder Indizes für die Spalten plate und citizenid an, damit Abfragen schnell ausgeführt werden, wenn Spieler Fahrzeuge abholen oder Administratoren den Bußgeldverlauf abfragen. Bei Servern mit Dutzenden Parkzonen und Hunderten gleichzeitig aktiven Fahrzeugen solltest du die Zonendaten in einer Lua-Tabelle zwischenspeichern, statt sie bei jeder Prüfung aus der Konfiguration auszulesen. Verwende die native Funktion GetClosestVehicle sparsam, da sie alle Fahrzeuge im Umkreis durchsucht. Profiliere deine Parkressource mit dem integrierten FiveM-Profiler unter Spitzenlast, um Engpässe zu erkennen. Ein Parksystem sollte weniger als 0.1ms Server-Tick-Zeit benötigen, damit die Gesamtleistung des Servers nicht beeinträchtigt wird.