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.