Comprendre l'architecture du système de stationnement
Un système de stationnement ajoute de la profondeur et du réalisme à n'importe quel serveur de jeu de rôle FiveM en donnant aux joueurs des zones désignées pour laisser leur véhicule, avec des conséquences en cas de non-respect des règles. À la base, un système de stationnement suit les places de stationnement occupées, surveille la durée de stationnement des véhicules et gère le paiement au moyen de compteurs ou de permis. Le système se compose d'une logique côté serveur pour suivre l'état et traiter les paiements, d'une détection côté client pour l'entrée et la sortie de la zone de stationnement, et d'une interface NUI en option pour afficher l'état du compteur et les options de paiement. Un système de stationnement bien mis en œuvre s'intègre également à tes scripts de police et de remorquage existants afin que les véhicules stationnés illégalement puissent être verbalisés ou mis en fourrière par les joueurs autorisés.
Définir des zones et des emplacements de stationnement
Les zones de stationnement sont définies comme des zones polygonales ou en forme de boîte sur la carte, chacune contenant des places de stationnement individuelles avec des coordonnées et des titres spécifiques. Utilisez un fichier de configuration pour définir ces zones afin que les administrateurs du serveur puissent ajouter de nouvelles zones de stationnement sans modifier le code. Chaque zone peut avoir des règles différentes, telles que le stationnement gratuit, le stationnement payant ou les zones réservées aux permis. Voici une structure pratique de configuration de zone :
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,
},
}
Le vector4 Le format stocke les coordonnées x, y, z ainsi que l'angle de cap, garantissant que les véhicules sont garés dans la bonne direction. Le occupied L'indicateur est géré au moment de l'exécution par le serveur et n'est pas enregistré entre les redémarrages puisque les véhicules sont suivis séparément via la base de données du système de garage. Gardez le nombre de spots réaliste pour chaque emplacement, car avoir trop de spots dans une petite zone entraîne des problèmes d'écrêtage des véhicules.
Système de minuterie de parcomètre
Le système de compteur enregistre la durée de stationnement de chaque véhicule et facture en conséquence. Lorsqu'un joueur se gare dans une zone payante, il interagit avec un compteur pour démarrer sa session, sélectionnant une durée et payant d'avance. Le serveur stocke l'heure de début de la session, la durée payée, le numéro de plaque et l'ID de zone. Un thread de minuterie côté serveur vérifie périodiquement toutes les sessions de compteur actives et marque celles expirées pour les appliquer. Voici la logique du compteur de base :
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)
La résolution du minuteur de 60 secondes est suffisante pour le contrôle du stationnement sans créer de charge inutile sur le processeur. Stockez les sessions de compteur dans la mémoire plutôt que dans la base de données, car elles sont temporaires et n'ont pas besoin de survivre aux redémarrages du serveur. Lorsque le serveur redémarre, tous les compteurs se réinitialisent, ce qui est acceptable car les véhicules retournent également aux garages au redémarrage dans la plupart des frameworks.
Détection de véhicules et attribution de points
Détecter lorsqu’un véhicule entre ou sort d’une place de stationnement nécessite des contrôles de proximité périodiques côté client. Utilisez une vérification basée sur la distance par rapport à tous les points dans les zones voisines plutôt que de vérifier chaque point sur l'ensemble de la carte. Exécutez la boucle de détection uniquement lorsque le joueur est dans un véhicule et à proximité d'une zone de stationnement configurée. Cela permet de minimiser l'utilisation du processeur pendant un jeu normal :
-- 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)
Le contrôle de vitesse de 0,5 garantit que l'invite de stationnement n'apparaît que lorsque le véhicule est presque à l'arrêt, empêchant ainsi l'interface utilisateur de scintiller lorsque le joueur traverse beaucoup de rues. L'intervalle de sommeil adaptatif de 200 ms à proximité des zones contre 1 000 ms ailleurs équilibre réactivité et performances. Pour les serveurs comportant de nombreuses zones de stationnement, envisagez d'utiliser un hachage spatial ou un quadtree pour éviter de parcourir chaque zone à chaque tick.
Système d'amende de stationnement et d'application
Lorsqu'un compteur expire ou qu'un véhicule est garé illégalement, le système émet une amende liée au propriétaire du véhicule. Les amendes sont stockées dans une table de base de données et affichées la prochaine fois que le propriétaire se connecte ou récupère son véhicule. Les acteurs de la police peuvent également émettre manuellement des contraventions de stationnement à l'aide d'une commande ou d'une interaction cible. Le dossier d'amende comprend la plaque, le montant, le motif d'émission et l'horodatage :
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)
Intégrez le système d'amendes à ton processus de récupération de véhicules afin que les joueurs doivent payer des amendes impayées avant de sortir un véhicule du garage. Cela crée une boucle naturelle d’application dans laquelle ignorer les règles de stationnement a de réelles conséquences dans le jeu. Envisagez d'ajouter une période de grâce de cinq à dix minutes après l'expiration du compteur avant d'émettre l'amende, car cela reflète l'application réelle du stationnement et réduit la frustration des joueurs due à des amendes parfaitement chronométrées.
Affichage du compteur et interface de paiement NUI
L'horodateur NUI doit être propre et informatif, indiquant le nom de la zone actuelle, le tarif horaire, le temps restant si déjà payé et les boutons permettant de sélectionner la durée et d'effectuer le paiement. Affichez un compte à rebours qui se met à jour chaque seconde lorsqu'un compteur est actif afin que le joueur sache exactement combien de temps il lui reste. Utilisez un design compact qui n’obstrue pas la vue du jeu, semblable à un écran d’horodateur du monde réel. Animez l'affichage de la minuterie avec une transition de couleur du vert au jaune puis au rouge au fur et à mesure que le temps s'écoule, fournissant un repère visuel intuitif sans obliger le joueur à lire les minutes exactes restantes. Gardez le NUI léger en utilisant Vanilla JavaScript et un minimum de CSS, en évitant les frameworks lourds qui ajoutent du temps de chargement pour une interface simple.
Intégration du remorquage et véhicules abandonnés
Connectez ton système de stationnement à un mécanicien de remorquage afin que les véhicules expirés ou garés illégalement puissent être remorqués jusqu'à la fourrière. Lorsqu'un véhicule est en violation depuis plus longtemps que le seuil configuré, marquez-le comme remorquable dans l'état du serveur. Les joueurs travaillant en dépanneuse peuvent alors voir ces véhicules mis en évidence sur leur carte et gagner un paiement pour leur remorquage. Le processus de remorquage doit mettre à jour l'état du véhicule dans la base de données de « sorti » à « mis en fourrière » et enregistrer la raison de la mise en fourrière comme une infraction de stationnement. Pour les véhicules abandonnés qui sont restés à des endroits pendant de longues périodes sans aucune interaction des joueurs, implémentez une routine de nettoyage automatique qui les met en fourrière après un délai d'attente configurable, libérant ainsi des places pour les joueurs actifs. Cela évite que les parkings ne se remplissent de véhicules appartenant à des joueurs qui ne se sont pas connectés depuis des jours ou des semaines.
Considérations relatives aux performances et à l'évolutivité
Les systèmes de stationnement exécutent des boucles de détection continues qui peuvent avoir un impact sur les performances si elles ne sont pas soigneusement optimisées. La détection des véhicules côté client ne doit s'activer que pour les zones situées à portée de rendu, et le fil de vérification des compteurs côté serveur doit traiter par lots toutes les sessions actives plutôt que de créer des minuteries individuelles par véhicule. Indexez le tableau de la base de données des amendes de stationnement dans les colonnes plaque et citoyen pour garantir des recherches rapides lorsque les joueurs récupèrent des véhicules ou lorsque les administrateurs interrogent l'historique des amendes. Pour les serveurs avec des dizaines de zones de stationnement et des centaines de véhicules simultanés, envisagez de mettre en cache les données de zone dans une table Lua plutôt que de lire la configuration à chaque contrôle, et utilisez la fonction native. GetClosestVehicle avec parcimonie puisqu’il scanne tous les véhicules à portée. Profilez ton ressource de stationnement avec le profileur intégré FiveM en cas de charge de pointe pour identifier les points chauds, et rappelez-tu qu'un système de stationnement doit utiliser moins de 0,1 ms de temps de tick du serveur pour éviter d'avoir un impact sur les performances globales du serveur.

