Zurück zum Blog
Tutorial10 Min. Lesezeit

FiveM-Taxisystem: NPC- und Spieler-Taxifahrer per Script

Füge FiveM ein funktionierendes Taxisystem hinzu. NPC-Taxis, Spieler als Fahrer, Fahrpreislogik, Taxameter und beliebte Scripts für lebendige Roleplay-Städte.

Agency Scripts

Gründer und leitender Entwickler bei Agency Scripts

Architektur eines Taxi-Jobsystems

Ein Taxi-Jobsystem ist ein zentrales Feature für jeden Roleplay-Server, der Spielern einen zugänglichen Einstiegsjob mit echten Interaktionen zwischen Spielern bieten möchte. Anders als viele andere Jobs, die sich mühsam und eintönig anfühlen können, sorgt ein gut gestaltetes Taxi-System für organische Roleplay-Begegnungen, wenn Fahrer Fahrgäste abholen, Fahrpreise aushandeln und unter Zeitdruck durch die Stadt navigieren. Die Architektur ist in zwei verschiedene Modi aufgeteilt: den Transport von Spieler zu Spieler, bei dem echte Fahrgäste Fahrten anfragen, und den NPC-Missionsmodus, bei dem das System Abhol- und Zielorte für das Solospiel in ruhigen Stunden generiert. Beide Modi verwenden dieselbe Fahrpreisberechnung, dieselbe Taxi-Meter-Oberfläche und dasselbe Fahrzeugverwaltungssystem. Die wichtigste Designentscheidung ist, ob du ein zentrales Vermittlungssystem verwendest, bei dem Fahrtanfragen in einer gemeinsamen Warteschlange landen, die für alle diensthabenden Fahrer sichtbar ist, oder ein entfernungsbasiertes System, bei dem der nächstgelegene Fahrer Vorrang erhält. Die erfolgreichsten Umsetzungen verwenden einen hybriden Ansatz mit einem Vermittlungsboard, das alle offenen Anfragen zeigt, aber nahegelegene hervorhebt. So können Fahrer ihre Aufträge frei wählen und Fahrgäste werden trotzdem schnell abgeholt.

Implementierung des Taxi-Meters

Das Taxi-Meter ist das Herzstück des Fahrpreissystems und muss authentisch wirken, während es für Fahrer und Fahrgäste transparent bleibt. Ein richtiges Meter erfasst zwei Komponenten: einen Grundpreis, der zu Beginn jeder Fahrt berechnet wird, und einen Preis pro Strecke, der mit der gefahrenen Distanz steigt. Manche Server erheben außerdem einen Wartezeittarif, der eine niedrigere Gebühr pro Sekunde berechnet, wenn das Taxi steht. So wird die übliche Praxis simuliert, die Zeit im Verkehr in Rechnung zu stellen. Das Meter sollte sowohl für den Fahrer als auch für alle Fahrgäste im Fahrzeug über ein dauerhaftes NUI-Element in einer Bildschirmecke sichtbar sein. Hier ist eine clientseitige Meter-Implementierung, die den Fahrpreis anhand der zurückgelegten Strecke berechnet:

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

Die NUI-Komponente für das Meter sollte kompakt, aber gut lesbar sein und den aktuellen Fahrpreis deutlich zusammen mit der zurückgelegten Strecke und dem geltenden Tarif anzeigen. Verwende für die Fahrpreisanzeige eine Monospace-Schrift, um das Aussehen eines echten Taxi-Meters nachzuahmen, und füge bei jeder Erhöhung des Fahrpreises eine dezente Tick-Animation hinzu, damit sichtbar ist, dass das Meter läuft. Ergänze eine Fahrpreisschätzung, die vor Fahrtbeginn die ungefähren Kosten anhand der direkten Entfernung zum Ziel berechnet. So können Fahrgäste entscheiden, ob sie die Fahrt antreten möchten.

NPC-Abholmissionssystem

NPC-Missionen halten Taxifahrer beschäftigt, wenn nicht genug echte Fahrgäste auf dem Server sind. Das System generiert zufällige Abhol- und Zielorte aus einer ausgewählten Liste von Sehenswürdigkeiten auf der Karte, erstellt am Abholort einen NPC und führt den Fahrer durch den gesamten Ablauf der Fahrt. Die Qualität der NPC-Missionen hängt stark von den Standortdaten ab: Werden nur zufällige Koordinaten verwendet, entstehen unnatürliche Abholorte mitten im Nirgendwo. Eine von Hand zusammengestellte Liste realistischer Orte wie Krankenhäuser, Restaurants, Hotels und Einkaufszentren sorgt dafür, dass sich die Missionen glaubwürdig anfühlen. Hier ist die serverseitige Logik zum Generieren und Zuweisen von NPC-Missionen:

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)

Erstelle clientseitig am Abholort einen NPC mit einer Warteanimation, füge einen Blip zur Navigation für den Fahrer hinzu und spiele bei dessen Ankunft eine Einsteigeanimation ab. Während der Fahrt sollte sich der NPC natürlich verhalten, zum Beispiel umherschauen und gelegentlich in Chat-Sprechblasen das Fahrverhalten kommentieren. Am Zielort steigt der NPC aus und der Fahrpreis wird automatisch über ein Server-Event bezahlt. Sorge für Abwechslung, indem du gelegentlich besondere Missionen mit höheren Auszahlungen generierst, etwa Flughafentransfers oder VIP-Kunden, die eine vorsichtige Fahrweise ohne Zusammenstöße verlangen.

Fahrpreisberechnung und Preiserhöhung

Neben einer einfachen streckenbasierten Berechnung berücksichtigt ein ausgefeiltes Fahrpreissystem auch die Tageszeit, die Verkehrslage und den Fahrzeugtyp. Aktiviere während der Stoßzeiten auf dem Server Preiserhöhungen, wenn viele Spieler online sind und die Taxinachfrage das Angebot übersteigt. Der Multiplikator muss den Fahrgästen vor der Bestätigung der Fahrt transparent mitgeteilt werden, damit es am Ende keine Überraschungen gibt. Du kannst die Nachfrage dynamisch berechnen, indem du das Verhältnis zwischen offenen Fahrtanfragen und verfügbaren Fahrern erfasst. Hier ist eine Fahrpreisberechnung, die diese Faktoren einbezieht:

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

Zeige dem Fahrgast bei der Fahrtanfrage eine Aufschlüsselung des Fahrpreises, damit klar ist, wofür bezahlt wird. Wenn eine Preiserhöhung aktiv ist, zeige den Multiplikator deutlich mit einer andersfarbigen Anzeige in der UI. Diese Transparenz schafft Vertrauen zwischen Fahrern und Fahrgästen und verringert Streitigkeiten über den Fahrpreis. Lass Fahrgäste bei der Buchung ihre bevorzugte Fahrzeugklasse auswählen. Günstige Optionen verwenden Kompaktwagen, während Luxusoptionen hochklassige Limousinen oder SUVs zu Premiumtarifen bieten.

Routenanzeige und GPS-Navigation

Eine visuelle Routenführung macht das Taxi-Erlebnis für neue und erfahrene Fahrer angenehmer. FiveM bietet die SetNewWaypoint native für grundlegende GPS-Routenführung, eine ausgereiftere Lösung zeichnet die Route jedoch direkt auf der Minikarte ein und kann zusätzlich einen 3D-Pfad in der Spielwelt darstellen. Verwende die SetBlipRoute Funktion für Ziel-Blips, um den integrierten GPS-Pfad zu aktivieren, und ergänze ihn durch eigene Blips an wichtigen Wegpunkten entlang der Route. Zeige dem Fahrgast zu seiner Information die voraussichtliche Ankunftszeit und die verbleibende Strecke in der Meter-UI an und aktualisiere sie während der Fahrt in Echtzeit. So richtest du die Routenanzeige mit Wegpunktverfolgung ein:

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

Für noch mehr Immersion kannst du ein Bewertungssystem einbauen, bei dem Fahrgäste ihre Fahrt anhand von Faktoren wie der Fahrzeit im Vergleich zur Schätzung, der Anzahl der Zusammenstöße während der Fahrt und der Einhaltung der Verkehrsregeln durch den Fahrer bewerten. Zeige die durchschnittliche Bewertung des Fahrers in seinem Taxi-Profil an, damit Fahrgäste sie vor der Annahme der Fahrt sehen können. Das motiviert Fahrer zu einem guten Service.

Trinkgeldsystem und Fahrerbewertungen

Ein Trinkgeldsystem ergänzt eine motivierende soziale Komponente, die Fahrer zu ausgezeichnetem Service ermutigt. Nachdem der Fahrpreis bezahlt wurde und der Fahrgast ausgestiegen ist, zeige ihm eine Trinkgeldabfrage mit vorausgewählten Optionen wie 10%, 15%, 20% oder einem benutzerdefinierten Betrag an. Das Trinkgeld geht zusätzlich zum Grundpreis und zu einer möglichen Provision des Taxiunternehmens direkt an den Fahrer. Erfasse die Trinkgelder jedes Fahrers in der Datenbank, um Ranglisten zu erstellen und Boni für besonders gut bewertete Fahrer freizuschalten. Verarbeite Trinkgelder serverseitig über dasselbe Transaktionssystem wie Fahrpreise, um Konsistenz zu gewährleisten und doppelte Zahlungen zu verhindern. Fahrer, die über längere Zeit eine hohe Bewertung und einen hohen Trinkgeld-Durchschnitt halten, könnten Vorteile wie Zugang zu Premiumfahrzeugen, niedrigere Provisionssätze des Unternehmens oder Vorrang in der Vermittlungswarteschlange freischalten. Kombiniere das Trinkgeldsystem mit einem Bewertungssystem, in dem Fahrgäste eine kurze Textbewertung hinterlassen, die der Fahrer sehen kann. So entsteht eine Feedbackschleife, die die Qualität des Taxidienstes auf dem Server insgesamt verbessert.

Vermittlungsboard und Fahrtanfragen

Das Vermittlungsboard ist die zentrale Anlaufstelle, an der Taxifahrer ihren nächsten Auftrag finden. Erstelle es als NUI-Panel, das am Hauptsitz des Taxiunternehmens oder während des Dienstes über eine Tastenkombination geöffnet werden kann. Das Board sollte alle offenen Fahrtanfragen nach Entfernung sortiert anzeigen. Jeder Eintrag zeigt den Namen des Fahrgasts, den Abholort, das Ziel, den geschätzten Fahrpreis und die geschätzte Entfernung. Wenn ein Fahrer einen Auftrag annimmt, entferne ihn für alle anderen Fahrer vom Board und starte einen Countdown. Erreicht der Fahrer den Abholort nicht innerhalb des Zeitlimits, wird der Auftrag wieder ins Board aufgenommen und der Fahrer erhält eine Zuverlässigkeitsstrafe. Erstelle für die Fahrgäste eine einfache Handy-App oder einen Befehl, mit dem sie eine Fahrt unter Angabe ihres aktuellen Standorts und des Ziels anfragen können. Die Anfrage kommt in die Vermittlungswarteschlange und der Fahrgast erhält Updates, sobald ein Fahrer den Auftrag annimmt und sich nähert. Füge für verspätete Stornierungen eine geringe Gebühr hinzu, damit Fahrgäste nicht wiederholt Fahrten anfragen und stornieren. Das verschwendet die Zeit der Fahrer und sorgt für Frust.

Fahrzeugverwaltung und Fuhrparksystem

Ein vollständiges Taxi-System braucht eine ordentliche Fahrzeugverwaltung, damit Fahrer geeignete Fahrzeuge nutzen und das Unternehmen seinen Fuhrpark verwalten kann. Erstelle am Taxi-Depot ein Fahrzeugausleihsystem, über das Fahrer aus den verfügbaren Firmenfahrzeugen wählen können. Erfasse für jedes Fahrzeug den Kilometerstand, den Tankfüllstand und den Zustand. Fahrer müssen die Fahrzeuge in akzeptablem Zustand zurückgeben, andernfalls werden Reparaturkosten von ihren Einnahmen abgezogen. Das Depot sollte verschiedene Fahrzeugklassen anbieten: Economy-Limousinen für Standardfahrten, komfortable SUVs für Premium-Service und Luxusfahrzeuge für VIP-Buchungen. Erfasse Nutzungsstatistiken für jedes Fahrzeug, darunter die Anzahl abgeschlossener Fahrten, erzielte Einnahmen und Wartungskosten. Implementiere einen Kraftstoffverbrauch, der Fahrer dazu zwingt, während ihrer Schicht an Tankstellen aufzutanken. Dadurch kommen realistische Betriebskosten hinzu, mit denen erfahrene Fahrer effizient umzugehen lernen. Am Ende einer Schicht gibt der Fahrer das Fahrzeug am Depot zurück. Seine Gesamteinnahmen werden abzüglich der Firmenprovision und möglicher Fahrzeugschäden berechnet, der Nettolohn wird anschließend auf sein Bankkonto überwiesen. Dieser vollständige Ablauf schafft ein realistisches Job-Erlebnis, das den Betrieb echter Taxiunternehmen widerspiegelt.

Bereit loszulegen?

Hol dir die Scripts in unserem Shop, oder komm auf den Discord für Support, Updates und einen Blick auf das, was kommt.