Wróć do bloga
Tutorial7 min czytania

System wyścigów FiveM: Trasy, tabele wyników i zakłady

Dodaj wyścigi uliczne do swojego serwera FiveM. Kreatory tras, punkty kontrolne, zakłady, tabele wyników i najlepsze skrypty wyścigowe dla wymagających społeczności tunerów.

Agency Scripts

Założyciel i główny deweloper w Agency Scripts

Projektowanie systemu tworzenia wyścigów

Dobry system wyścigów ulicznych zaczyna się od umożliwienia graczom tworzenia i zarządzania własnymi trasami. Zamiast polegać wyłącznie na gotowych torach, najlepsze skrypty wyścigów pozwalają organizatorom dynamicznie stawiać punkty kontrolne poprzez jazdę trasą i oznaczanie pozycji. Zapisuj każdy wyścig jako strukturę danych zawierającą unikalny identyfikator, ID gracza tworzącego, nazwę, uporządkowaną listę współrzędnych punktów kontrolnych z kątem zwrotu oraz metadane, takie jak dystans i szacowany czas okrążenia. Proces tworzenia powinien być intuicyjny: gracz wchodzi w tryb edytora wyścigów, dojeżdża do każdego punktu kontrolnego, zatwierdza pozycję naciśnięciem klawisza i kończy, wracając na linię startu. Zapisuj ukończone wyścigi w bazie danych, aby zachowały się po restartach serwera i mogły być udostępniane społeczności. Wdróż system weryfikacji, w którym administratorzy mogą zatwierdzać trasy stworzone przez graczy przed ich pojawieniem się na publicznej liście wyścigów, zapobiegając griefingowi przez niemożliwe lub nieuczciwe układy tras.

System checkpointów i logika wyścigu

Checkpointy to kluczowa mechanika, która weryfikuje postęp w wyścigu i zapobiega skracaniu trasy. Każdy checkpoint powinien być zdefiniowany jako strefa 3D z konfigurowalnym promieniem, zwykle od 8 do 15 metrów w zależności od szerokości drogi. Użyj natywnych funkcji rysowania markerów z GTA, aby wizualnie wyświetlać checkpointy, z aktualnym checkpointem wyróżnionym jasnym kolorem i kolejnym widocznym jako przezroczysty podgląd. Śledź postęp każdego zawodnika na indeksowanej liście checkpointów, przesuwając go dalej tylko wtedy, gdy wejdzie w kolejną prawidłową strefę checkpointu w odpowiedniej kolejności. Wdróż zabezpieczenia anti-cheat poprzez śledzenie czasu między checkpointami i oznaczanie lub dyskwalifikowanie zawodników, którzy pokonują odcinki w niemożliwie szybkim czasie, co wskazuje na teleportację lub speed hacki.

-- Client-side checkpoint detection
local currentCheckpoint = 1
local raceCheckpoints = {} -- populated when race starts

CreateThread(function()
    while isRacing do
        local playerCoords = GetEntityCoords(PlayerPedId())
        local checkpoint = raceCheckpoints[currentCheckpoint]

        if checkpoint then
            local dist = #(playerCoords - checkpoint.coords)

            -- Draw current checkpoint marker
            DrawMarker(1, checkpoint.coords.x, checkpoint.coords.y,
                checkpoint.coords.z - 1.0, 0, 0, 0, 0, 0, 0,
                checkpoint.radius * 2, checkpoint.radius * 2, 2.0,
                45, 212, 191, 120, false, true, 2, false, nil, nil, false)

            if dist < checkpoint.radius then
                PlaySoundFrontend(-1, 'CHECKPOINT_NORMAL', 'HUD_MINI_GAME_SOUNDSET', true)
                currentCheckpoint = currentCheckpoint + 1

                if currentCheckpoint > #raceCheckpoints then
                    -- Race finished
                    local finishTime = GetGameTimer() - raceStartTime
                    TriggerServerEvent('racing:finished', raceId, finishTime)
                    isRacing = false
                else
                    -- Notify server of checkpoint progress
                    TriggerServerEvent('racing:checkpoint', raceId, currentCheckpoint)
                end
            end
        end
        Wait(50)
    end
end)

Tabela wyników i system rankingowy

Tabele wyników napędzają rywalizację i sprawiają, że gracze wracają, aby poprawiać swoje czasy. Zapisuj wyniki wyścigów w tabeli bazy danych, która rejestruje identyfikator gracza, ID wyścigu, czas ukończenia w milisekundach, użyty model pojazdu, datę oraz informację, czy przejazd był rekordem życiowym. Wyświetlaj tabele wyników przez interfejs NUI, który pokazuje najlepsze czasy dla każdego toru, z możliwością filtrowania według klasy pojazdu, aby gracze mogli sprawiedliwie porównywać się w kategoriach takich jak sports, super, muscle czy compact. Wdróż sezonowy system rankingowy, który resetuje się okresowo, aby utrzymać świeżość rywalizacji, zachowując jednocześnie tablicę rekordów wszech czasów dla prestiżu. Obliczaj ocenę wyścigową w stylu ELO na podstawie bezpośrednich pojedynków w wyścigach wieloosobowych, dając zręcznym kierowcom widoczną rangę, która rośnie po pokonaniu wyżej notowanych rywali i spada po przegranej ze słabszymi. Wyświetlaj tę ocenę w UI wyścigu i używaj jej do opcjonalnego dobierania graczy przy organizacji losowych wyścigów.

System zakładów i nagród

System zakładów dodaje stawkę finansową do wyścigów i integruje scenę wyścigową z szerszą gospodarką twojego serwera. Pozwól organizatorom wyścigów ustalać wpisowe, które muszą opłacić wszyscy uczestnicy, przy czym cała pula jest dzielona między najlepszych zawodników według konfigurowalnego podziału, na przykład 70-20-10 za pierwsze, drugie i trzecie miejsce. Wdróż system zakładów dla widzów, w którym gracze niebiorący udziału w wyścigu mogą obstawiać wyniki, wybierając kierowcę, na którego postawią pieniądze. Wykorzystuj wyłącznie logikę po stronie serwera dla wszystkich transakcji finansowych, aby zapobiec exploitom. Dodaj prowizję dla domu w wysokości 5-10% od wszystkich zakładów, która posłuży jako money sink w twojej ekonomii. W przypadku większych zorganizowanych wydarzeń pozwól administratorom dodawać bonusowe pule nagród finansowane ze skarbca serwera. Śledź statystyki zakładów każdego gracza, aby wykrywać i zapobiegać ustawianiu wyścigów, na przykład sytuacjom, gdy kierowca stale przegrywa wyścigi, w których postawiono przeciwko niemu duże kwoty.

-- Server-side betting handler
local raceBets = {}

RegisterNetEvent('racing:placeBet')
AddEventHandler('racing:placeBet', function(raceId, targetRacerId, amount)
    local src = source
    if not activeRaces[raceId] then return end
    if activeRaces[raceId].state ~= 'lobby' then
        return notify(src, 'Betting is closed once the race starts.', 'error')
    end

    -- Validate the target racer is in this race
    local racerFound = false
    for _, racer in ipairs(activeRaces[raceId].participants) do
        if racer.src == targetRacerId then racerFound = true break end
    end
    if not racerFound then return end

    -- Check and deduct funds
    local playerMoney = GetPlayerMoney(src)
    if playerMoney < amount or amount < Config.MinBet then
        return notify(src, 'Insufficient funds or below minimum bet.', 'error')
    end

    RemoveMoney(src, amount)

    if not raceBets[raceId] then raceBets[raceId] = {} end
    table.insert(raceBets[raceId], {
        bettor = src,
        target = targetRacerId,
        amount = amount,
    })

    notify(src, ('Bet $%s on %s'):format(amount, GetPlayerName(targetRacerId)), 'success')
end)

Tworzenie UI wyścigów z NUI

Interfejs wyścigowy musi przekazywać kluczowe informacje w czasie rzeczywistym bez zaśmiecania ekranu i rozpraszania kierowcy. Zaprojektuj minimalistyczny HUD pokazujący aktualną pozycję wśród zawodników, numer okrążenia (jeśli dotyczy), split timer porównujący bieżący przejazd z rekordem osobistym lub rekordem toru oraz nakładkę minimapy wskazującą kierunek kolejnego punktu kontrolnego. Użyj prędkościomierza dopasowanego do motywu wyścigu, wyświetlającego prędkość w mph lub km/h zależnie od preferencji gracza. Ekran lobby przed wyścigiem powinien wyświetlać wszystkich zarejestrowanych uczestników wraz z ich pojazdami, nazwę i długość trasy, podział puli nagród oraz licznik odliczający czas. Zbuduj UI za pomocą HTML, CSS i JavaScript przy użyciu systemu NUI oraz przesyłaj aktualizacje stanu wyścigu z klienckiego kodu Lua do ramki NUI za pośrednictwem SendNUIMessage. Zadbaj o responsywność UI, ograniczając aktualizacje pozycji do 4-5 razy na sekundę zamiast w każdej klatce, co znacznie zmniejsza narzut NUI bez widocznego wpływu na wyświetlane informacje. Rozważ dodanie ekranu wyników po wyścigu ze szczegółowymi statystykami, takimi jak osiągnięta prędkość maksymalna, średnia prędkość oraz porównanie czasów na poszczególnych odcinkach checkpointów.

Kategorie wyścigów i klasy pojazdów

Organizuj wyścigi w osobne kategorie, aby zapewnić uczciwą rywalizację i różnorodność. Zdefiniuj klasy pojazdów na podstawie wskaźników wydajności, takich jak prędkość maksymalna, przyspieszenie i oceny prowadzenia pobierane z natywnych danych handlingu gry. Twórz ograniczenia klasowe dla konkretnych wyścigów, aby gracz w Dominatorze nie mógł dołączyć do wyścigu ograniczonego do aut kompaktowych, i odwrotnie. Poza klasą pojazdu oferuj różne formaty wyścigów: sprinty z punktu A do B przez miasto, wyścigi torowe z wieloma okrążeniami na pętli, wyścigi drag na prostych drogach ze startem na czas reakcji oraz zawody driftu punktowane za kąt, prędkość i bliskość punktów clipping point. Każdy format wymaga nieco innej logiki punktacji i wykrywania ukończenia. W szczególności punktacja driftu wymaga niestandardowego algorytmu, który ocenia kąt poślizgu pojazdu względem wektora jego prędkości, prędkość utrzymywaną podczas driftu oraz mnożniki bonusowe za łączenie kolejnych driftów bez utraty kontroli. Pozwól organizatorom wyścigów przełączać opcje, takie jak mechanika catch-up, ghosting przez innych zawodników zapobiegający kolizjom typu griefing, a także ustawienia pogody lub pory dnia dla klimatu.

Integracja z policją i nielegalne wyścigi

Wyścigi uliczne powinny być nielegalną aktywnością, która generuje naturalne interakcje ze służbami porządkowymi. Gdy wyścig się rozpoczyna, generuj zgłoszenia o hałasie od NPC w okolicy, które pojawiają się w systemie dyspozytorskim policji po konfigurowalnym opóźnieniu, dając ścigającym się przewagę na starcie, ale sprawiając, że dłuższe wyścigi przyciągają uwagę. Policjanci reagujący na zgłoszenie mogą próbować zatrzymać wyścig, rozkładając kolczatki, stawiając blokady drogowe lub ścigając poszczególnych kierowców. Jeśli kierowca zostanie złapany przez policję, grożą mu mandaty, konfiskata pojazdu i potencjalnie kara więzienia, co dodaje realnych konsekwencji i sprawia, że scena wyścigowa staje się niebezpieczna oraz ekscytująca. Wdróż system poziomu poszukiwania dedykowany wyścigom, który eskaluje wraz z kolejnymi wykroczeniami, dzięki czemu notoryczni kierowcy z czasem ściągają na siebie bardziej agresywne reakcje policji. Pozwól policji na korzystanie z kamer monitoringu w znanych lokalizacjach wyścigowych w celu budowania spraw przeciwko organizatorom, tworząc warstwę śledczą, która nagradza cierpliwą pracę policji większymi akcjami rozbijającymi całe ekipy wyścigowe zamiast pojedynczych kierowców.

Optymalizacja wydajności dla wyścigów

Systemy wyścigów wymagają płynnego działania, ponieważ nawet niewielki lag może zepsuć rywalizację. Zminimalizuj komunikację klient-serwer podczas trwających wyścigów, obsługując wykrywanie checkpointów i śledzenie pozycji po stronie klienta, a aktualizacje wysyłaj na serwer tylko wtedy, gdy checkpoint zostanie osiągnięty lub wyścig się zakończy. Używaj entity state bags do rozgłaszania pozycji zamiast własnych zdarzeń sieciowych, ponieważ state bags są zoptymalizowane pod kątem częstych aktualizacji i efektywniej zarządzają przepustowością. Przy renderowaniu checkpointów rysuj markery tylko dla bieżącego i następnego punktu, a nie dla wszystkich jednocześnie, oraz stosuj logikę LOD (Level of Detail), aby zmniejszyć złożoność markerów na większych dystansach. Łącz zapytania o dane wyścigów, pobierając tabele wyników i listy wyścigów w operacjach wsadowych zamiast pojedynczych zapytań, oraz cachuj wyniki po stronie klienta z krótkim TTL, aby ograniczyć obciążenie bazy danych w godzinach szczytu. Przetestuj swój system przy maksymalnej przewidywanej liczbie jednoczesnych zawodników, zazwyczaj 8-16 na wyścig, aby upewnić się, że synchronizacja pozycji i walidacja checkpointów skalują się prawidłowo bez spadków klatek ani problemów z desynchronizacją.

Gotowy, aby zacząć?

Kup skrypty w naszym sklepie lub dołącz do Discorda, aby uzyskać wsparcie, aktualizacje i sprawdzić nadchodzące nowości.