Bloga Geri Dön
Tutorial7 dk. okuma

FiveM Park Sistemi: Gerçekçi Araç Park Scriptleri

FiveM şehrinize gerçekçi park sistemi ekleyin. Sokak parkı, ücretli otoparklar, çekilme kuralları ve araç karmaşasını önlemek için en iyi park scriptleri.

Agency Scripts

Agency Scripts Kurucusu ve Baş Geliştiricisi

Park Sistemi Mimarisini Anlamak

Bir park sistemi, oyunculara araçlarını bırakabilecekleri belirlenmiş alanlar sunarak ve kurallara uymamanın sonuçlarını uygulayarak herhangi bir FiveM roleplay sunucusuna derinlik ve gerçekçilik katar. Temelinde bir park sistemi, hangi park yerlerinin dolu olduğunu takip eder, araçların ne kadar süredir park edildiğini izler ve parkmetreler veya izinler aracılığıyla ödemeleri yönetir. Sistem, durumu izlemek ve ödemeleri işlemek için sunucu tarafı mantığından, park alanına giriş ve çıkış için istemci tarafı tespitinden ve parkmetre durumunu ile ödeme seçeneklerini görüntülemek için isteğe bağlı bir NUI arayüzünden oluşur. İyi uygulanmış bir park sistemi, yasadışı park edilmiş araçların yetkili oyuncular tarafından ceza kesilebilmesi veya çekilebilmesi için mevcut polis ve çekici scriptlerinizle de entegre olur.

Park Bölgelerini ve Noktalarını Tanımlama

Park bölgeleri, haritada her biri belirli koordinatlara ve heading değerlerine sahip bağımsız park yerleri içeren çokgen veya kutu şeklinde alanlar olarak tanımlanır. Sunucu yöneticilerinin kodu değiştirmeden yeni park alanları ekleyebilmesi için bu bölgeleri tanımlamak üzere bir yapılandırma dosyası kullanın. Her bölgenin ücretsiz park, ücretli parkmetre veya yalnızca izinli alanlar gibi farklı kuralları olabilir. İşte pratik bir bölge yapılandırma yapısı:

Config.ParkingZones = {
    ['pillbox_lot'] = {
        label = 'Pillbox Medical Parking',
        type = 'metered',       -- 'free', 'metered', 'permit'
        rate = 50,              -- $ per hour (metered only)
        maxTime = 120,          -- minutes max parking
        blip = {sprite = 357, color = 3, scale = 0.6},
        spots = {
            {coords = vector4(310.5, -590.2, 43.3, 70.0), occupied = false},
            {coords = vector4(313.8, -591.6, 43.3, 70.0), occupied = false},
            {coords = vector4(317.1, -593.0, 43.3, 70.0), occupied = false},
            {coords = vector4(320.4, -594.4, 43.3, 70.0), occupied = false},
        },
        fineAmount = 250,       -- fine for overtime or illegal parking
    },
    ['legion_street'] = {
        label = 'Legion Square Parking',
        type = 'metered',
        rate = 75,
        maxTime = 60,
        blip = {sprite = 357, color = 3, scale = 0.6},
        spots = {
            {coords = vector4(215.2, -810.5, 30.7, 160.0), occupied = false},
            {coords = vector4(212.6, -810.5, 30.7, 160.0), occupied = false},
        },
        fineAmount = 350,
    },
}

The vector4 formatı, x, y, z koordinatlarını yön açısıyla (heading) birlikte saklayarak araçların doğru yöne bakacak şekilde park edilmesini sağlar. occupied bayrağı çalışma zamanında sunucu tarafından yönetilir ve araçlar garaj sistemi veritabanı üzerinden ayrı olarak takip edildiğinden yeniden başlatmalar arasında kaydedilmez. Her konum için park yeri sayısını gerçekçi tutun, çünkü küçük bir alanda çok fazla park yeri olması araç clipping sorunlarına yol açar.

Parkmetre Zamanlayıcı Sistemi

Parkmetreli sistem, her aracın ne kadar süre park edildiğini takip eder ve buna göre ücretlendirir. Bir oyuncu parkmetreli bölgeye park ettiğinde, bir parkmetreyle etkileşime geçerek süresini seçer ve ücreti peşin ödeyip oturumunu başlatır. Sunucu oturum başlangıç zamanını, ödenen süreyi, plaka numarasını ve zone ID bilgisini saklar. Sunucu taraflı bir zamanlayıcı iş parçacığı tüm aktif parkmetre oturumlarını periyodik olarak kontrol eder ve süresi dolanları yaptırım için işaretler. İşte temel parkmetre mantığı:

local ActiveMeters = {} -- keyed by plate

RegisterNetEvent('parking:server:startMeter', function(plate, zoneId, duration)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end

    local zone = Config.ParkingZones[zoneId]
    if not zone or zone.type ~= 'metered' then return end

    -- Calculate cost based on duration in minutes
    local hours = duration / 60
    local cost = math.ceil(hours * zone.rate)

    if Player.Functions.RemoveMoney('cash', cost, 'parking-meter') then
        ActiveMeters[plate] = {
            zone = zoneId,
            startTime = os.time(),
            paidUntil = os.time() + (duration * 60),
            plate = plate,
            owner = Player.PlayerData.citizenid,
        }
        TriggerClientEvent('parking:client:meterStarted', src, duration, cost)
    else
        TriggerClientEvent('QBCore:Notify', src, 'Not enough cash', 'error')
    end
end)

-- Check for expired meters every 60 seconds
CreateThread(function()
    while true do
        Wait(60000)
        local now = os.time()
        for plate, meter in pairs(ActiveMeters) do
            if now > meter.paidUntil then
                -- Meter expired, issue fine
                IssueParkingFine(plate, meter.zone, meter.owner)
                ActiveMeters[plate] = nil
            end
        end
    end
end)

Gereksiz CPU yükü oluşturmadan park denetimi sağlamak için 60 saniyelik zamanlayıcı çözünürlüğü yeterlidir. Sayaç oturumları geçici olduğundan ve sunucu yeniden başlatmalarından sonra korunması gerekmediğinden veritabanı yerine bellekte saklayın. Sunucu yeniden başlatıldığında tüm sayaçlar sıfırlanır; çoğu frameworkte araçlar da yeniden başlatmada garajlara döndüğü için bu durum kabul edilebilirdir.

Araç Algılama ve Park Yeri Atama

Bir aracın park yerine girdiğini veya ayrıldığını tespit etmek, istemci tarafında düzenli yakınlık kontrolleri gerektirir. Tüm haritadaki her noktayı kontrol etmek yerine yakındaki bölgelerdeki tüm noktalara yönelik mesafe tabanlı bir kontrol kullanın. Tespit döngüsünü yalnızca oyuncu bir araçtayken ve yapılandırılmış bir park bölgesinin yakınındayken çalıştırın. Bu, normal oynanış sırasında CPU kullanımını minimum düzeyde tutar:

-- client/detection.lua
local nearestZone = nil
local currentSpot = nil

CreateThread(function()
    while true do
        local sleep = 1000
        local ped = PlayerPedId()
        local inVehicle = IsPedInAnyVehicle(ped, false)

        if inVehicle then
            local veh = GetVehiclePedIsIn(ped, false)
            local vehCoords = GetEntityCoords(veh)

            for zoneId, zone in pairs(Config.ParkingZones) do
                local firstSpot = zone.spots[1]
                local zoneDist = #(vehCoords - vector3(firstSpot.coords.x,
                    firstSpot.coords.y, firstSpot.coords.z))

                if zoneDist < 50.0 then
                    sleep = 200
                    nearestZone = zoneId

                    for i, spot in ipairs(zone.spots) do
                        local spotPos = vector3(spot.coords.x, spot.coords.y, spot.coords.z)
                        local dist = #(vehCoords - spotPos)
                        if dist < 3.0 then
                            local speed = GetEntitySpeed(veh)
                            if speed < 0.5 then
                                currentSpot = {zone = zoneId, index = i}
                                ShowParkingPrompt(zone)
                            end
                        end
                    end
                    break
                end
            end
        else
            nearestZone = nil
            currentSpot = nil
        end

        Wait(sleep)
    end
end)

0.5 hız kontrolü, park isteminin yalnızca araç neredeyse durduğunda görünmesini sağlayarak oyuncu otoparktan geçerken arayüzün titremesini önler. Bölgelerin yakınında 200 ms, diğer yerlerde 1000 ms olan uyarlanabilir bekleme aralığı, yanıt hızı ile performansı dengeler. Çok sayıda park bölgesi bulunan sunucularda, her tick sırasında her bölgeyi yinelemekten kaçınmak için uzamsal karma (spatial hash) veya quadtree kullanmayı değerlendirin.

Park Cezası ve Uygulama Sistemi

Park süresi dolduğunda veya bir araç hatalı park edildiğinde, sistem araç sahibine bağlı bir ceza keser. Cezalar bir veritabanı tablosunda saklanır ve araç sahibi bir sonraki girişinde ya da aracını teslim aldığında görüntülenir. Polis oyuncuları da bir komut veya hedef etkileşimi kullanarak manuel olarak park cezası kesebilir. Ceza kaydı plakayı, tutarı, düzenlenme nedenini ve zaman damgasını içerir:

function IssueParkingFine(plate, zoneId, citizenid)
    local zone = Config.ParkingZones[zoneId]
    local amount = zone and zone.fineAmount or 250

    MySQL.insert([[
        INSERT INTO parking_fines (plate, citizenid, amount, reason, zone, issued_at)
        VALUES (?, ?, ?, ?, ?, NOW())
    ]], {plate, citizenid, amount, 'Expired parking meter', zoneId})

    -- Notify owner if online
    local Player = QBCore.Functions.GetPlayerByCitizenId(citizenid)
    if Player then
        TriggerClientEvent('QBCore:Notify', Player.PlayerData.source,
            'Parking fine issued: $' .. amount, 'error')
    end
end

-- Police manual ticket command
RegisterNetEvent('parking:server:issueTicket', function(plate, reason)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end

    if Player.PlayerData.job.name ~= 'police' then return end

    local vehicle = MySQL.query.await(
        'SELECT citizenid FROM player_vehicles WHERE plate = ?', {plate}
    )

    if vehicle and vehicle[1] then
        IssueParkingFine(plate, 'manual', vehicle[1].citizenid)
        TriggerClientEvent('QBCore:Notify', src, 'Ticket issued for ' .. plate, 'success')
    end
end)

Ceza sistemini araç çıkarma sürecinizle entegre edin, böylece oyuncular garajdan araç çıkarmadan önce ödenmemiş cezalarını ödemek zorunda kalır. Bu, park kurallarını ihlal etmenin oyun içinde gerçek sonuçlar doğurduğu doğal bir yaptırım döngüsü oluşturur. Ceza kesilmeden önce park süresi dolduktan sonra beş ila on dakikalık bir ek süre tanımayı değerlendirin; bu durum gerçek dünyadaki park denetimini yansıtır ve tam zamanında kesilen cezaların oyuncuda yarattığı hayal kırıklığını azaltır.

NUI Taksimetre Göstergesi ve Ödeme Arayüzü

Parkmetre NUI arayüzü; mevcut bölge adını, saatlik ücreti, önceden ödeme yapılmışsa kalan süreyi, süre seçme ve ödeme yapma butonlarını göstererek temiz ve bilgilendirici olmalıdır. Bir sayaç aktif olduğunda saniyede bir güncellenen bir geri sayım sayacı görüntüleyin, böylece oyuncu tam olarak ne kadar zamanı kaldığını bilir. Gerçek dünyadaki bir parkmetre ekranına benzer şekilde, oyun görünümünü engellemeyen kompakt bir tasarım kullanın. Süre doldukça sayaç ekranını yeşilden sarıya ve kırmızıya geçen bir renk geçişiyle hareketlendirerek, oyuncunun kalan dakikaları tam olarak okumasına gerek kalmadan sezgisel bir görsel ipucu sağlayın. Basit bir arayüz için yükleme süresi ekleyen ağır framework'lerden kaçınıp vanilla JavaScript ve minimum CSS kullanarak NUI'yi hafif tutun.

Çekici Entegrasyonu ve Terk Edilmiş Araçlar

Süresi dolmuş veya hatalı park edilmiş araçların çekilmiş araç otoparkına (impound) çekilebilmesi için park sisteminizi bir çekici mekaniği ile bağlayın. Bir araç yapılandırılan eşikten daha uzun süre ihlalde kaldığında, sunucu durumunda çekilebilir olarak işaretleyin. Çekici mesleğindeki oyuncular daha sonra bu araçları haritalarında vurgulanmış olarak görebilir ve onları çekerek ödeme alabilir. Çekme işlemi, veritabanındaki araç durumunu "out" yerine "impounded" olarak güncellemeli ve çekilme nedenini park ihlali olarak kaydetmelidir. Herhangi bir oyuncu etkileşimi olmadan uzun süre park yerlerinde duran terk edilmiş araçlar için, yapılandırılabilir bir zaman aşımından sonra bunları impound alanına taşıyan ve aktif oyuncular için yer açan otomatik bir temizleme rutini uygulayın. Bu, otoparkların günlerce veya haftalarca giriş yapmamış oyunculara ait araçlarla dolmasını önler.

Performans ve Ölçeklenebilirlik Hususları

Park sistemleri, dikkatli bir şekilde optimize edilmediğinde performansı etkileyebilecek sürekli algılama döngüleri çalıştırır. İstemci tarafı araç algılaması yalnızca render mesafesindeki bölgeler için etkinleşmeli ve sunucu tarafı parkmetre kontrol thread'i her araç için ayrı zamanlayıcılar oluşturmak yerine tüm aktif oturumları toplu olarak işlemelidir. Oyuncular araçları teslim alırken veya adminler ceza geçmişini sorgularken hızlı aramalar sağlamak için park cezaları veritabanı tablosunu hem plate hem de citizenid sütunlarında dizinleyin. Düzinelerce park bölgesi ve yüzlerce eşzamanlı aracın bulunduğu sunucularda, bölge verilerini her kontrolde yapılandırmadan okumak yerine bir Lua tablosunda önbelleğe almayı düşünün ve native fonksiyonunu kullanın GetClosestVehicle menzildeki tüm araçları taradığı için idareli kullanın. Yoğun yük altında sıcak noktaları tespit etmek için park kaynağınızı FiveM dahili profil oluşturucusuyla tepe yük altında profilleyin ve genel sunucu performansını etkilememek için bir park sisteminin sunucu tick süresinin 0.1ms'sinden daha azını kullanması gerektiğini unutmayın.

Eşleşen script

Sunucunuz için hazır, güncellemeler dahil.

Başlamaya Hazır mısınız?

Scriptleri mağazamızdan edinin veya destek, güncellemeler ve gelecek yeniliklere göz atmak için Discord'a katılın.