>
Tutoriel 2026-04-29

Système de parking pour FiveM

OntelMonke

OntelMonke

Administrateur et développeur chez Agency Scripts

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.

Partager cet article

Prêt à améliorer votre serveur ?

Découvrez nos scripts FiveM premium dans la boutique Agency Scripts ou rejoignez notre communauté Discord pour le support et les mises à jour.