>
Tutoriel 2026-04-04

Système de garage pour FiveM : guide complet

OntelMonke

OntelMonke

Administrateur et développeur chez Agency Scripts

Comprendre l'architecture du système de garage

Un système de garage est l'une des fonctionnalités les plus essentielles de tout serveur de jeu de rôle FiveM, servant de principal moyen pour les joueurs de stocker, récupérer et gérer leurs véhicules. À la base, un système de garage se compose de trois couches interconnectées : une couche de base de données qui conserve la propriété et l'état du véhicule, une couche logique côté serveur qui gère les opérations d'apparition et de disparition avec validation, et une couche d'interface utilisateur côté client qui permet aux joueurs d'interagir avec leurs véhicules stockés. Avant d'écrire un code, tu dois décider des choix architecturaux clés, comme si les garages sont basés sur la localisation ou globaux, si les joueurs peuvent accéder à n'importe quel garage ou seulement à des garages spécifiques, et comment tu souhaites gérer les propriétés du véhicule telles que les modifications, le niveau de carburant et l'état des dégâts. Les meilleurs systèmes de garage stockent l'objet complet des propriétés du véhicule afin que lorsqu'un joueur récupère sa voiture, elle revienne exactement telle qu'il l'a laissée, y compris les travaux de peinture personnalisés, les améliorations de performances et même le niveau de saleté sur la carrosserie.

Schéma de base de données et persistance du véhicule

Le schéma de ton base de données constitue la base de l’ensemble du système de garage. Tu as besoin d'un tableau qui suit la propriété du véhicule, son état actuel et les propriétés stockées. La colonne d'état est essentielle car elle détermine si un véhicule est actuellement généré dans le monde, stocké dans un garage ou stocké dans une fourrière. Voici un schéma pratique qui couvre les domaines essentiels :

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)
);

Le mods La colonne stocke un objet codé JSON contenant toutes les modifications du véhicule renvoyées par des fonctions telles que QBCore.Functions.GetVehicleProperties(vehicle) ou l'équivalent dans ESX. Indexer le citizenid, plate, et state Les colonnes garantissent que les recherches restent rapides même si ton base de joueurs atteint des milliers. Utilisez toujours des requêtes paramétrées lorsque tu interagisses avec cette table pour empêcher les attaques par injection SQL.

Logique d'apparition et de disparition côté serveur

Le côté serveur est l’endroit où se déroule toute la validation critique. Lorsqu'un joueur demande à sortir un véhicule du garage, le serveur doit vérifier que le joueur possède réellement ce véhicule, que le véhicule est actuellement dans l'état de garage et qu'il existe un point d'apparition valide disponible. Ne laissez jamais le client dicter directement la position d'apparition, car les tricheurs pourraient faire apparaître des véhicules n'importe où sur la carte. Au lieu de cela, définissez les points d'apparition sur le serveur et sélectionnez celui disponible le plus proche. Voici un exemple de gestionnaire de retrait sécurisé côté serveur :

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)

Pour le processus de disparition, le serveur doit capturer les propriétés actuelles du véhicule avant de le supprimer du monde. Cela garantit que les modifications apportées depuis le dernier stockage sont enregistrées. Mettez toujours à jour le carburant, la santé du moteur et la santé du corps avec les mods JSON afin que tout persiste correctement. Implémentez un contrôle de distance côté serveur pour tu assurer que le joueur se trouve bien à proximité d'un garage avant d'autoriser les opérations de stockage.

Interface utilisateur du garage côté client avec NUI

L'interface utilisateur du garage est l'endroit où les joueurs interagissent avec le système, et une interface bien conçue fait la différence entre une expérience frustrante et une expérience transparente. Utilisez NUI avec HTML, CSS et JavaScript pour créer un panneau réactif qui affiche tous les véhicules stockés dans le garage actuel. Chaque entrée de véhicule doit afficher en un coup d'œil le nom du véhicule, la plaque d'immatriculation, le niveau de carburant et l'état général. Incluez un système de prévisualisation qui fait apparaître temporairement le modèle de véhicule afin que les joueurs puissent voir ce qu'ils sélectionnent, particulièrement utile lorsqu'un joueur possède plusieurs véhicules du même type. Voici la logique côté client pour ouvrir le menu du garage et collecter les données du véhicule :

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)

Côté JavaScript, affichez chaque véhicule sous forme de carte avec des boutons d'action pour sortir le véhicule ou le transférer dans un autre garage. Pensez à ajouter des options de tri et de filtrage afin que les joueurs possédant de grandes collections puissent trouver rapidement le véhicule dont ils ont besoin. Une barre de recherche qui filtre par numéro de plaque ou par nom de véhicule est un petit ajout qui améliore considérablement la convivialité sur les serveurs où les joueurs accumulent de nombreux véhicules au fil du temps.

Entreposage et restauration des biens automobiles

Sauvegarder et restaurer correctement les propriétés du véhicule est l’une des parties les plus délicates du développement d’un système de garage. L'objet Propriétés contient des dizaines de champs, notamment les couleurs, les livrées, les néons, les teintes des vitres, la couleur de la fumée des pneus, les extras et toutes les modifications de performances. Lorsque tu stockes un véhicule, capturez les propriétés immédiatement avant de supprimer l'entité pour tu assurer d'obtenir l'état le plus récent. Lors de la réapparition d'un véhicule, tu dois attendre que l'entité soit complètement chargée avant d'appliquer les propriétés, sinon les modifications telles que les roues personnalisées ou les mises à niveau du moteur échoueront silencieusement. Utilisez un petit délai ou une boucle de vérification de l'existence d'une entité appropriée :

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

Portez une attention particulière aux véhicules complémentaires, car ils ont parfois des extras personnalisés ou des indices de livrée qui se comportent différemment des véhicules Vanilla GTA. Testez minutieusement le cycle de sauvegarde et de restauration de ton propriété avec une variété de types de véhicules pour détecter rapidement les cas extrêmes.

Intégration du système de fourrière

Un système de fourrière fonctionne main dans la main avec ton garage et ajoute une couche de réalisme exigée par les serveurs de jeu de rôle. Les véhicules se retrouvent à la fourrière pour plusieurs raisons : saisie par la police lors d'une arrestation, nettoyage automatique des véhicules abandonnés après un redémarrage du serveur ou action de l'administrateur en cas de violation des règles. Lorsqu'un véhicule est mis en fourrière, mettez à jour son état à 2 dans la base de données et définissez éventuellement un prix de dépôt que le joueur doit payer pour le récupérer. La fourrière devrait fonctionner de la même manière qu'un garage, mais avec l'exigence supplémentaire d'un paiement avant la libération. Créez un emplacement de mise en fourrière distinct sur la carte avec ses propres points d'apparition et une interface NUI qui affiche les frais de mise en fourrière bien en évidence.

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)

Envisagez de mettre en place un système de tarification par paliers dans lequel les frais de mise en fourrière augmentent à chaque fois que le même véhicule est mis en fourrière, décourageant ainsi les joueurs de considérer la mise en fourrière comme un stationnement gratuit. Tu peux également ajouter un mécanisme basé sur le temps où les véhicules laissés en fourrière pendant plus d'un nombre configurable de jours réels sont automatiquement remis au garage sans frais, évitant ainsi les scénarios de perte permanente qui frustrent les joueurs.

Blips de garage et intégration de cibles

Rendre les garages visibles et faciles à interagir nécessite un placement approprié des spots et des zones d'interaction. Ajoutez des points de carte pour chaque emplacement de garage afin que les joueurs puissent les trouver sur la mini-carte, et utilisez soit des marqueurs basés sur la proximité, soit l'intégration du système cible pour le déclencheur d'interaction. Les systèmes cibles comme ox_target ou qb-target offrent une expérience plus propre car ils n'affichent les options d'interaction que lorsque le joueur vise un point spécifique, réduisant ainsi l'encombrement de l'écran. Définissez les emplacements de tes garages dans un fichier de configuration partagé que le client et le serveur peuvent référencer, en gardant les coordonnées, les points d'apparition et les paramètres synchronisés :

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
    },
}

Prenez en charge plusieurs types de véhicules en créant des garages séparés pour les bateaux et les avions avec des emplacements d'apparition appropriés près de l'eau ou dans les aéroports. Le vehicleType Le filtre garantit que les joueurs ne voient que les véhicules terrestres dans un garage de rue et ne voient que les bateaux dans une marina, évitant ainsi toute confusion et tout problème d'apparition. Lorsqu'un joueur s'approche d'un garage, vérifiez s'il y a des véhicules stockés avant d'afficher l'invite d'interaction afin d'éviter des ouvertures de menu inutiles pour les joueurs qui n'ont pas de véhicule à cet endroit.

Conseils d'optimisation des performances

Les systèmes de garage peuvent devenir un goulot d'étranglement en termes de performances s'ils ne sont pas mis en œuvre avec soin, en particulier sur des serveurs comptant des centaines de joueurs simultanés possédant chacun plusieurs véhicules. Mettez en cache les listes de véhicules côté serveur au lieu d'interroger la base de données à chaque fois qu'un joueur ouvre un menu de garage, et invalidez le cache uniquement lorsque l'état d'un véhicule change. Côté client, évitez de garder les trames NUI ouvertes lorsqu'elles ne sont pas nécessaires, car même les trames NUI masquées consomment des ressources si elles exécutent des minuteries ou des animations JavaScript. Lors de la génération de véhicules, assurez-tu de nettoyer correctement les entités en les définissant comme n'étant plus nécessaires une fois que le joueur les a stockées, et implémentez une routine de nettoyage de secours qui s'exécute périodiquement pour détecter toutes les entités de véhicules orphelines qui n'ont pas été correctement supprimées en raison de crashs ou de déconnexions. Utilisez des fonctions natives comme GetGamePool('CVehicle') avec parcimonie et mettez en cache les résultats lorsque tu dois vérifier les véhicules de joueurs existants dans le monde. Enfin, envisagez de mettre en place une limite maximale de véhicules par garage pour limiter les requêtes dans la base de données et empêcher un seul joueur de stocker des centaines de véhicules, ce qui pourrait ralentir les opérations de récupération.

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.