>
Tutoriel 2026-04-07

Système de taxi pour FiveM

OntelMonke

OntelMonke

Administrateur et développeur chez Agency Scripts

Architecture du système de travail de taxi

Un système de travail de taxi est une fonctionnalité fondamentale pour tout serveur de jeu de rôle qui souhaite offrir aux joueurs un travail d'entrée de gamme accessible avec une véritable interaction entre les joueurs. Contrairement à de nombreux autres emplois qui peuvent sembler fastidieux et répétitifs, un système de taxi bien conçu crée des rencontres de jeu de rôle organiques alors que les conducteurs prennent des passagers, négocient les tarifs et naviguent dans la ville sous pression. L'architecture se divise en deux modes distincts : le transport de joueur à joueur où de vrais passagers demandent des trajets, et le mode mission PNJ où le système génère des emplacements de prise en charge et de dépôt pour un jeu en solo pendant les heures creuses. Les deux modes partagent le même moteur de calcul des tarifs, la même interface utilisateur du compteur de taxi et le même système de gestion des véhicules. La décision clé en matière de conception est de savoir s'il faut utiliser un système de répartition centralisé où les demandes de trajet sont envoyées dans une file d'attente partagée visible par tous les conducteurs en service, ou un système basé sur la proximité où le conducteur le plus proche a la priorité. Les mises en œuvre les plus réussies utilisent une approche hybride avec un tableau de répartition qui affiche toutes les demandes en attente mais met en évidence celles à proximité, donnant aux conducteurs la liberté de choisir leur travail tout en garantissant que les passagers soient récupérés rapidement.

Implémentation de taximètres

Le compteur de taxi est au cœur du système tarifaire et doit être authentique tout en restant transparent pour le conducteur et le passager. Un compteur approprié suit deux éléments : un tarif de base facturé au début de chaque trajet et un tarif par distance qui augmente au fur et à mesure que le véhicule se déplace. Certains serveurs ajoutent également un tarif de temps d'attente qui facture des frais par seconde inférieurs lorsque le taxi est à l'arrêt, simulant la pratique réelle consistant à facturer le temps passé dans le trafic. Le compteur doit être visible à la fois par le conducteur et par tous les passagers du véhicule grâce à un élément NUI persistant positionné dans le coin de l'écran. Voici une implémentation de compteur côté client qui calcule le tarif en fonction de la distance parcourue :

local meterActive = false
local currentFare = 0.0
local lastPosition = nil
local baseFare = 50
local ratePerMile = 15
local waitRatePerSecond = 0.5
local totalDistance = 0.0

function StartMeter()
    meterActive = true
    currentFare = baseFare
    totalDistance = 0.0
    lastPosition = GetEntityCoords(PlayerPedId())

    SendNUIMessage({action = 'showMeter', fare = currentFare, distance = 0.0})

    CreateThread(function()
        while meterActive do
            Wait(500)
            local currentPos = GetEntityCoords(PlayerPedId())
            local speed = GetEntitySpeed(PlayerPedId())

            if speed > 1.0 then
                local dist = #(currentPos - lastPosition)
                totalDistance = totalDistance + dist
                local miles = totalDistance / 1609.34
                currentFare = baseFare + (miles * ratePerMile)
            else
                currentFare = currentFare + (waitRatePerSecond * 0.5)
            end

            lastPosition = currentPos

            SendNUIMessage({
                action = 'updateMeter',
                fare = math.floor(currentFare),
                distance = math.floor(totalDistance),
                speed = math.floor(speed * 2.236936)
            })
        end
    end)
end

function StopMeter()
    meterActive = false
    local finalFare = math.floor(currentFare)
    SendNUIMessage({action = 'stopMeter', fare = finalFare})
    return finalFare
end

Le composant NUI du compteur doit être compact mais lisible, indiquant le tarif actuel bien en évidence ainsi que la distance parcourue et le tarif actuel appliqué. Utilisez une police à espacement fixe pour l'affichage du tarif afin d'imiter l'apparence d'un véritable compteur de taxi, et ajoutez une subtile animation de tic-tac à chaque fois que le tarif augmente pour donner un retour visuel indiquant que le compteur fonctionne. Pensez à ajouter une fonction d'estimation du tarif qui calcule le coût approximatif en fonction de la distance en ligne droite jusqu'à la destination avant le début du trajet, afin que les passagers puissent décider s'ils souhaitent continuer.

Système de mission de ramassage des PNJ

Les missions de PNJ occupent les chauffeurs de taxi lorsqu'il n'y a pas assez de vrais passagers sur le serveur. Le système génère des lieux de prise en charge et de dépose aléatoires à partir d'une liste organisée de points d'intérêt autour de la carte, crée un PNJ au lieu de prise en charge et guide le conducteur tout au long du cycle tarifaire complet. La qualité de l'expérience de mission des PNJ dépend fortement des données de localisation : l'utilisation uniquement de coordonnées aléatoires produit des collectes peu naturelles au milieu de nulle part, donc le maintien d'une liste soigneusement sélectionnée d'emplacements réalistes comme des hôpitaux, des restaurants, des hôtels et des centres commerciaux donne l'impression que les missions sont authentiques. Voici la logique côté serveur pour générer et répartir les missions PNJ :

local MissionLocations = {
    {coords = vector3(-240.49, -987.78, 29.29), label = 'Pillbox Medical Center'},
    {coords = vector3(228.28, -790.70, 30.58), label = 'Legion Square'},
    {coords = vector3(-1044.57, -2739.76, 13.85), label = 'Los Santos Airport'},
    {coords = vector3(126.19, -1282.72, 29.27), label = 'Strawberry Avenue'},
    {coords = vector3(-556.18, -196.17, 38.22), label = 'Rockford Hills'},
    {coords = vector3(1159.56, -314.45, 69.21), label = 'Vinewood Hills'},
    {coords = vector3(-1222.08, -906.82, 12.32), label = 'Del Perro Pier'},
    {coords = vector3(373.58, 325.71, 103.57), label = 'Downtown Vinewood'},
}

RegisterNetEvent('taxi:server:requestNPCMission', function()
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player or Player.PlayerData.job.name ~= 'taxi' then return end

    local pickup = MissionLocations[math.random(#MissionLocations)]
    local dropoff = MissionLocations[math.random(#MissionLocations)]

    while pickup.label == dropoff.label do
        dropoff = MissionLocations[math.random(#MissionLocations)]
    end

    local estimatedDistance = #(pickup.coords - dropoff.coords)
    local basePay = 50
    local distancePay = math.floor(estimatedDistance / 1609.34 * 15)
    local totalPay = basePay + distancePay

    TriggerClientEvent('taxi:client:startNPCMission', src, {
        pickup = pickup,
        dropoff = dropoff,
        estimatedPay = totalPay,
        npcModel = Config.NPCModels[math.random(#Config.NPCModels)],
        missionId = 'TAXI-' .. os.time() .. '-' .. math.random(1000, 9999)
    })
end)

Du côté client, générez le PNJ au lieu de prise en charge avec une animation d'attente, ajoutez un signal pour guider le conducteur et jouez une animation d'entrée dans le véhicule lorsque le conducteur arrive. Pendant le trajet, le PNJ doit adopter des comportements naturels, comme regarder autour de lui et occasionnellement commenter la conduite via des bulles de discussion. Lorsque le véhicule arrive à destination, le PNJ sort et le tarif est payé automatiquement via un événement sur le serveur. Ajoutez de la variété en générant occasionnellement des missions spéciales avec des gains plus élevés, comme des transferts aéroport ou des clients VIP qui exigent une conduite prudente sans aucune collision.

Calcul des tarifs et majoration des prix

Au-delà du calcul de base basé sur la distance, un système tarifaire sophistiqué prend en compte l'heure de la journée, les conditions de circulation et le type de véhicule. Implémentez une tarification accrue pendant les heures de pointe sur le serveur, lorsque de nombreux joueurs sont en ligne et que la demande de taxi dépasse l'offre. Le multiplicateur de surtension doit être communiqué de manière transparente aux passagers avant qu'ils ne confirment le trajet afin qu'il n'y ait pas de surprises à la fin. Tu peux calculer la demande de manière dynamique en suivant le ratio des demandes de courses en attente par rapport aux conducteurs disponibles. Voici un moteur de calcul tarifaire qui intègre ces variables :

function CalculateFare(pickupCoords, dropoffCoords, vehicleClass)
    local distance = #(pickupCoords - dropoffCoords)
    local miles = distance / 1609.34

    local rates = Config.FareRates
    local baseFare = rates.base
    local perMile = rates.perMile

    -- Vehicle class multiplier
    local classMultiplier = {
        [0] = 0.8,   -- Compacts (budget)
        [1] = 1.0,   -- Sedans (standard)
        [2] = 1.0,   -- SUVs
        [3] = 1.2,   -- Coupes (comfort)
        [18] = 1.5,  -- Super (luxury)
    }
    local vehMultiplier = classMultiplier[vehicleClass] or 1.0

    -- Time-of-day multiplier (night premium)
    local hour = GetClockHours()
    local timeMultiplier = 1.0
    if hour >= 22 or hour <= 5 then
        timeMultiplier = 1.3  -- 30% night surcharge
    end

    -- Surge pricing based on demand
    local surgeMultiplier = GetCurrentSurgeMultiplier()

    local totalFare = (baseFare + (miles * perMile)) * vehMultiplier * timeMultiplier * surgeMultiplier

    return {
        total = math.floor(totalFare),
        breakdown = {
            base = baseFare,
            distance = math.floor(miles * perMile),
            vehicleClass = vehMultiplier,
            nightSurcharge = timeMultiplier > 1.0,
            surgeActive = surgeMultiplier > 1.0,
            surgeRate = surgeMultiplier
        }
    }
end

Affichez la répartition des tarifs au passager lorsqu'il demande un trajet afin qu'il comprenne ce qu'il paie. Si la majoration des prix est active, affichez clairement le multiplicateur avec un indicateur de couleur différente dans l'interface utilisateur. Cette transparence renforce la confiance entre les conducteurs et les passagers et réduit les litiges sur le montant des tarifs. Permettez aux passagers de sélectionner leur classe de véhicule préférée lors de la réservation, avec des options économiques utilisant des voitures compactes et des options de luxe utilisant des berlines ou des SUV haut de gamme à des tarifs plus élevés.

Affichage de l'itinéraire et navigation GPS

Le guidage visuel de l'itinéraire rend l'expérience de taxi plus fluide pour les conducteurs débutants et expérimentés. FiveM fournit le SetNewWaypoint natif pour le routage GPS de base, mais une approche plus raffinée dessine l'itinéraire directement sur la mini-carte et restitue éventuellement un chemin 3D dans le monde. Utilisez le SetBlipRoute fonction sur les points de destination pour activer le chemin GPS intégré et l'améliorer avec des points personnalisés aux points de cheminement clés le long de l'itinéraire. Pour le bénéfice du passager, affichez l'heure d'arrivée estimée et la distance restante dans l'interface utilisateur du compteur, mise à jour en temps réel au fur et à mesure de la progression du véhicule. Voici comment configurer le système d’affichage d’itinéraire avec suivi des waypoints :

local routeBlip = nil
local checkpointBlips = {}

function SetTaxiRoute(destination, checkpoints)
    ClearTaxiRoute()

    routeBlip = AddBlipForCoord(destination.x, destination.y, destination.z)
    SetBlipSprite(routeBlip, 198)
    SetBlipColour(routeBlip, 5)
    SetBlipScale(routeBlip, 1.0)
    SetBlipRoute(routeBlip, true)
    SetBlipRouteColour(routeBlip, 5)
    BeginTextCommandSetBlipName('STRING')
    AddTextComponentSubstringPlayerName('Destination')
    EndTextCommandSetBlipName(routeBlip)

    if checkpoints then
        for i, cp in ipairs(checkpoints) do
            local cpBlip = AddBlipForCoord(cp.x, cp.y, cp.z)
            SetBlipSprite(cpBlip, 1)
            SetBlipColour(cpBlip, 3)
            SetBlipScale(cpBlip, 0.6)
            table.insert(checkpointBlips, cpBlip)
        end
    end
end

function ClearTaxiRoute()
    if routeBlip then
        SetBlipRoute(routeBlip, false)
        RemoveBlip(routeBlip)
        routeBlip = nil
    end
    for _, blip in ipairs(checkpointBlips) do
        RemoveBlip(blip)
    end
    checkpointBlips = {}
end

function UpdateETADisplay(destination)
    CreateThread(function()
        while routeBlip do
            Wait(2000)
            local pos = GetEntityCoords(PlayerPedId())
            local remaining = #(pos - destination)
            local speed = GetEntitySpeed(PlayerPedId())

            local etaSeconds = speed > 2.0 and (remaining / speed) or 0
            local etaMinutes = math.floor(etaSeconds / 60)

            SendNUIMessage({
                action = 'updateETA',
                distance = math.floor(remaining),
                eta = etaMinutes > 0 and (etaMinutes .. ' min') or 'Arriving'
            })
        end
    end)
end

Pour une couche d'immersion supplémentaire, mettez en œuvre un système d'évaluation dans lequel les passagers évaluent leur trajet en fonction de facteurs tels que le temps de trajet par rapport à l'estimation, le nombre de collisions pendant le voyage et le respect par le conducteur du code de la route. Affichez la note moyenne du chauffeur sur son profil de taxi afin que les passagers puissent la voir avant d'accepter le trajet, ce qui incite les chauffeurs à fournir un bon service.

Système de pourboires et évaluations des conducteurs

Un système de pourboires ajoute une mécanique sociale enrichissante qui encourage les conducteurs à fournir un excellent service. Une fois le tarif payé et le passager sorti du véhicule, affichez une invite de pourboire au passager avec des options prédéfinies telles que 10 %, 15 %, 20 % ou un montant personnalisé. Le pourboire va directement au chauffeur en plus du tarif de base et de toute commission de la compagnie de taxi. Suivez les conseils cumulés par conducteur dans la base de données pour créer des classements et débloquer des bonus pour les conducteurs les mieux notés. Côté serveur, traitez les pourboires via le même système de transaction que les tarifs pour garantir la cohérence et éviter la duplication. Les conducteurs qui maintiennent une note élevée et un pourboire moyen au fil du temps pourraient débloquer des avantages tels que l'accès à des véhicules haut de gamme, des taux de commission réduits de l'entreprise ou une priorité dans la file d'attente de répartition. Combinez le système de pourboires avec un système d'avis dans lequel les passagers laissent un bref avis textuel visible au conducteur, créant ainsi une boucle de rétroaction qui améliore la qualité globale du service de taxi sur le serveur.

Tableau de répartition et demandes de trajet

Le tableau de répartition est la plaque tournante centrale où les chauffeurs de taxi trouvent leur prochain emploi. Construisez-le comme un panneau NUI accessible depuis le siège de la compagnie de taxi ou via un raccourci clavier pendant ton service. Le tableau doit afficher toutes les demandes de trajet en attente triées par proximité, chaque entrée indiquant le nom du passager, le lieu de prise en charge, la destination, le tarif estimé et la distance estimée. Lorsqu'un conducteur accepte une tâche, supprimez-la du tableau pour tous les autres conducteurs et démarrez un compte à rebours. Si le conducteur n'atteint pas le pick-up dans le délai imparti, la tâche revient au tableau et le conducteur reçoit une pénalité de fiabilité. Pour le côté passager, créez une application ou une commande téléphonique simple qui leur permet de demander un trajet en spécifiant leur emplacement actuel et leur destination. La demande entre dans la file d'attente de répartition et le passager reçoit des mises à jour lorsqu'un conducteur accepte et s'approche. Mettez en œuvre un système d'annulation avec des frais minimes pour les annulations tardives afin d'empêcher les passagers de demander et d'annuler à plusieurs reprises des courses, ce qui fait perdre du temps au conducteur et crée une expérience frustrante.

Système de gestion des véhicules et de flotte

Un système de taxi complet nécessite une gestion appropriée des véhicules afin que les conducteurs utilisent des véhicules appropriés et que l'entreprise entretienne sa flotte. Créez un système de caisse de véhicules au dépôt de taxis où les chauffeurs choisissent parmi les véhicules de société disponibles. Chaque véhicule doit avoir un compteur de kilométrage suivi, un niveau de carburant et un état d'état, et les conducteurs sont responsables de restituer les véhicules dans un état acceptable ou de faire face à des déductions pour réparation sur leurs revenus. Le dépôt devrait stocker différents niveaux de véhicules : des berlines économiques pour les tarifs standard, des SUV confortables pour un service haut de gamme et des véhicules de luxe pour les réservations VIP. Suivez les statistiques d'utilisation de chaque véhicule, y compris le total des tarifs effectués, les revenus générés et les coûts de maintenance. Mettez en œuvre un système de consommation de carburant qui oblige les conducteurs à faire le plein dans les stations-service pendant leur quart de travail, ajoutant ainsi un coût opérationnel réaliste que les conducteurs expérimentés apprennent à gérer efficacement. À la fin d'un quart de travail, le conducteur ramène le véhicule au dépôt, son salaire total est calculé moins la commission de l'entreprise et les éventuels frais de dommages au véhicule, et le salaire net est déposé sur son compte bancaire. Cette boucle complète crée une expérience de travail réaliste qui reflète les opérations réelles de la compagnie de taxi.

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.