System taxi w FiveM: skryptowanie kierowców NPC i graczy
Dodaj działający system taxi do FiveM. Taksówki NPC, kierowcy gracze, logika opłat, taksometry i najlepsze skrypty taxi dla tętniących życiem miast roleplay.
Agency Scripts
Założyciel i główny deweloper w Agency Scripts
Architektura systemu pracy taksówkarza
System pracy taksówkarza to kluczowa funkcja dla każdego serwera roleplay, który chce zaoferować graczom przystępną pracę na start z prawdziwymi interakcjami między graczami. W przeciwieństwie do wielu innych prac, które bywają monotonne i powtarzalne, dobrze zaprojektowany system taksówek tworzy naturalne sytuacje roleplay, gdy kierowcy zabierają pasażerów, negocjują opłaty i poruszają się po mieście pod presją czasu. Architektura dzieli się na dwa odrębne tryby: transport gracz-gracz, w którym prawdziwi pasażerowie zamawiają przejazdy, oraz tryb misji NPC, w którym system generuje lokalizacje odbioru i dowozu do gry solo poza godzinami szczytu. Oba tryby współdzielą ten sam silnik obliczania opłat, UI taksometru i system zarządzania pojazdami. Kluczową decyzją projektową jest wybór pomiędzy scentralizowanym systemem dispatch, w którym zgłoszenia trafiają do wspólnej kolejki widocznej dla wszystkich kierowców na służbie, a systemem opartym na bliskości, gdzie pierwszeństwo ma najbliższy kierowca. Większość udanych wdrożeń wykorzystuje podejście hybrydowe z tablicą dispatch, która pokazuje wszystkie oczekujące zgłoszenia, ale wyróżnia te znajdujące się w pobliżu, dając kierowcom swobodę wyboru zleceń przy jednoczesnym zapewnieniu szybkiego odbioru pasażerów.
Implementacja taksometru
Taksometr to serce systemu opłat, które musi sprawiać autentyczne wrażenie, pozostając jednocześnie przejrzystym zarówno dla kierowcy, jak i pasażera. Odpowiedni taksometr śledzi dwa elementy: opłatę początkową naliczaną na starcie każdego kursu oraz stawkę za dystans, która rośnie w miarę poruszania się pojazdu. Niektóre serwery dodają także stawkę za czas oczekiwania, która nalicza niższą opłatę za każdą sekundę postoju taksówki, symulując rzeczywistą praktykę pobierania opłat za czas spędzony w korkach. Taksometr powinien być widoczny zarówno dla kierowcy, jak i wszystkich pasażerów w pojeździe za pośrednictwem stałego elementu NUI umieszczonego w rogu ekranu. Oto implementacja taksometru po stronie klienta, która oblicza opłatę na podstawie przebytego dystansu:
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
Komponent NUI taksometru powinien być kompaktowy, ale czytelny, eksponując aktualną opłatę wraz z przebytym dystansem i obowiązującą stawką. Użyj czcionki o stałej szerokości znaków (monospace) do wyświetlania opłaty, aby naśladować wygląd prawdziwego taksometru, oraz dodaj subtelną animację tykania przy każdym naliczeniu kwoty, dając wizualny sygnał, że taksometr działa. Rozważ dodanie funkcji szacowania kosztu, która przed rozpoczęciem kursu oblicza przybliżoną cenę na podstawie odległości w linii prostej do celu, aby pasażerowie mogli zdecydować, czy chcą kontynuować.
System misji odbioru NPC
Misje NPC zapewniają zajęcie taksówkarzom, gdy na serwerze brakuje prawdziwych pasażerów. System generuje losowe punkty odbioru i wysiadki z przygotowanej listy interesujących miejsc na mapie, tworzy NPC w miejscu odbioru i prowadzi kierowcę przez cały kurs. Jakość rozgrywki w misjach NPC zależy w dużej mierze od danych lokalizacji: używanie wyłącznie losowych współrzędnych powoduje nienaturalne odbiory na odludziu, dlatego ręcznie dobrana lista realistycznych miejsc, takich jak szpitale, restauracje, hotele i centra handlowe, sprawia, że misje wydają się autentyczne. Oto logika po stronie serwera do generowania i przydzielania misji NPC:
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)
Po stronie klienta zespawnuj NPC w miejscu odbioru z animacją oczekiwania, dodaj blip prowadzący kierowcę i odtwórz animację wsiadania do pojazdu, gdy kierowca dotrze na miejsce. Podczas jazdy NPC powinien zachowywać się naturalnie, na przykład rozglądać się i sporadycznie komentować jazdę w dymkach czatu. Gdy pojazd dotrze do celu, NPC wysiada, a opłata za przejazd jest pobierana automatycznie przez zdarzenie serwera. Urozmaicaj rozgrywkę, generując od czasu do czasu misje specjalne z wyższymi wypłatami, na przykład transfery na lotnisko lub klientów VIP, którzy wymagają ostrożnej jazdy bez żadnych kolizji.
Kalkulacja opłat i dynamiczne ceny
Poza podstawowym naliczaniem na podstawie odległości zaawansowany system opłat uwzględnia porę dnia, warunki drogowe oraz typ pojazdu. Wprowadź mnożnik cenowy (surge pricing) w godzinach szczytu na serwerze, gdy wielu graczy jest online, a popyt na taksówki przewyższa podaż. Mnożnik powinien być jasno komunikowany pasażerom przed potwierdzeniem przejazdu, aby uniknąć niespodzianek na koniec. Popyt możesz obliczać dynamicznie, monitorując stosunek oczekujących zgłoszeń do dostępnych kierowców. Oto silnik kalkulacji opłat uwzględniający te zmienne:
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
Wyświetlaj pasażerowi szczegółowe zestawienie opłat przy zamawianiu przejazdu, aby wiedział, za co płaci. Jeśli dynamiczne ustalanie cen (surge pricing) jest aktywne, wyraźnie pokaż mnożnik za pomocą innego koloru w UI. Taka przejrzystość buduje zaufanie między kierowcami a pasażerami i ogranicza spory o wysokość opłat. Pozwól pasażerom wybierać preferowaną klasę pojazdu podczas rezerwacji: opcje budżetowe z autami kompaktowymi oraz opcje luksusowe z wysokiej klasy sedanami lub SUV-ami w stawkach premium.
Wyświetlanie trasy i nawigacja GPS
Zapewnienie wizualnego prowadzenia po trasie ułatwia pracę taksówkarza zarówno nowym, jak i doświadczonym kierowcom. FiveM udostępnia SetNewWaypoint native do podstawowego wyznaczania trasy GPS, ale bardziej dopracowane podejście rysuje trasę bezpośrednio na minimapie i opcjonalnie renderuje ścieżkę 3D w świecie gry. Użyj SetBlipRoute funkcję na blipach docelowych, aby włączyć wbudowaną trasę GPS, i rozbuduj ją o własne blipy w kluczowych punktach trasy. Dla wygody pasażera wyświetlaj szacowany czas przybycia i pozostały dystans w UI taksometru, aktualizując je w czasie rzeczywistym w miarę jazdy pojazdu. Oto jak skonfigurować system wyświetlania trasy ze śledzeniem punktów trasy:
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
Dla dodatkowej immersji wdróż system ocen, w którym pasażerowie oceniają przejazd na podstawie takich czynników, jak czas podróży w porównaniu z szacowanym, liczba kolizji podczas trasy oraz to, czy kierowca przestrzegał przepisów ruchu drogowego. Wyświetlaj średnią ocenę kierowcy na jego profilu taksówki, aby pasażerowie mogli ją zobaczyć przed zaakceptowaniem kursu, co motywuje kierowców do świadczenia wysokiej jakości usług.
System napiwków i oceny kierowców
System napiwków dodaje satysfakcjonującą mechanikę społeczną, która zachęca kierowców do zapewniania doskonałej obsługi. Po opłaceniu przejazdu i opuszczeniu pojazdu przez pasażera wyświetl mu monit o napiwek z gotowymi opcjami, takimi jak 10%, 15%, 20% lub własna kwota. Napiwek trafia bezpośrednio do kierowcy niezależnie od opłaty bazowej i ewentualnej prowizji firmy taksówkarskiej. Zliczaj łączne napiwki dla każdego kierowcy w bazie danych, aby tworzyć rankingi i odblokowywać premie dla najlepiej ocenianych kierowców. Po stronie serwera przetwarzaj napiwki przez ten sam system transakcji co opłaty za przejazd, aby zapewnić spójność i zapobiec duplikacji. Kierowcy, którzy utrzymują wysokie oceny i średnią napiwków, mogliby z czasem odblokowywać przywileje, takie jak dostęp do pojazdów premium, niższe stawki prowizji firmy czy priorytet w kolejce dispatch. Połącz system napiwków z systemem opinii, w którym pasażerowie zostawiają krótką recenzję tekstową widoczną dla kierowcy, tworząc pętlę informacji zwrotnej podnoszącą ogólną jakość usług taksówkarskich na serwerze.
Panel dispatch i zlecenia przejazdów
Tablica dyspozytorska to centralny punkt, w którym taksówkarze znajdują kolejne zlecenia. Zbuduj ją jako panel NUI dostępny w siedzibie firmy taksówkarskiej lub pod klawiszem skrótu podczas służby. Tablica powinna wyświetlać wszystkie oczekujące zgłoszenia przejazdów posortowane według odległości, gdzie każdy wpis pokazuje imię pasażera, miejsce odbioru, cel podróży, szacowaną opłatę oraz szacowaną odległość. Gdy kierowca przyjmie zlecenie, usuń je z tablicy dla pozostałych kierowców i uruchom odliczanie czasu: jeśli kierowca nie dotrze na miejsce odbioru w wyznaczonym czasie, zlecenie wraca na tablicę, a kierowca otrzymuje karę do wskaźnika niezawodności. Od strony pasażera stwórz prostą aplikację na telefon lub komendę umożliwiającą zamówienie przejazdu przez podanie aktualnej lokalizacji i celu podróży. Zgłoszenie trafia do kolejki dyspozytorskiej, a pasażer otrzymuje aktualizacje, gdy kierowca zaakceptuje kurs i będzie się zbliżać. Wdróż system anulowania z niewielką opłatą za późną rezygnację, aby uniemożliwić pasażerom ciągłe zamawianie i anulowanie kursów, co marnuje czas kierowcy i frustruje.
Zarządzanie pojazdami i system floty
Kompletny system taksówek wymaga odpowiedniego zarządzania pojazdami, aby kierowcy korzystali z właściwych aut, a firma dbała o flotę. Stwórz system wydawania pojazdów w bazie taksówek, gdzie kierowcy wybierają spośród dostępnych aut firmowych. Każdy pojazd powinien mieć licznik przebiegu, poziom paliwa i stan techniczny, a kierowcy odpowiadają za zwrot pojazdów w akceptowalnym stanie lub ponoszą potrącenia za naprawy ze swoich zarobków. Baza powinna oferować różne klasy pojazdów: ekonomiczne sedany do standardowych kursów, komfortowe SUV-y do usług premium oraz luksusowe auta do zleceń VIP. Śledź statystyki użytkowania każdego pojazdu, w tym zrealizowane kursy, wygenerowany przychód i koszty utrzymania. Wdróż system zużycia paliwa wymagający tankowania na stacjach benzynowych podczas zmiany, co wprowadza realistyczny koszt operacyjny, którym doświadczeni kierowcy uczą się efektywnie zarządzać. Na koniec zmiany kierowca zwraca pojazd do bazy, jego łączne zarobki są obliczane po potrąceniu prowizji firmy i ewentualnych uszkodzeń, a wynagrodzenie netto trafia na konto bankowe. Taka pełna pętla rozgrywki zapewnia realistyczne wrażenia z pracy, odzwierciedlające działanie prawdziwej korporacji taksówkarskiej.