FiveM Parking System: Реалистичные скрипты парковки транспортных средств
Добавьте реалистичную парковку в ваш город FiveM. Уличная парковка, платные стоянки, правила эвакуации и лучшие скрипты парковки для борьбы с захламлением транспорта.
Agency Scripts
Основатель и ведущий разработчик Agency Scripts
Понимание архитектуры системы парковки
Система парковки добавляет глубину и реализм любому FiveM ролевому серверу, предоставляя игрокам выделенные места для оставления транспорта с последствиями за нарушение правил. В основе система отслеживает занятость парковочных мест, время стоянки и обрабатывает оплату через счётчики или разрешения. Система состоит из серверной логики для отслеживания состояния и обработки платежей, клиентского обнаружения входа и выхода из парковочной зоны и опционального NUI интерфейса для отображения статуса счётчика и вариантов оплаты. Хорошо реализованная система парковки интегрируется с вашими скриптами полиции и эвакуации, чтобы незаконно припаркованные машины могли штрафоваться или эвакуироваться уполномоченными игроками.
Определение зон и мест для парковки
Парковочные зоны определяются как полигоны или прямоугольные области на карте, каждая из которых содержит отдельные парковочные места с конкретными координатами и ориентацией. Используйте конфигурационный файл для определения этих зон, чтобы администраторы сервера могли добавлять новые парковочные области без изменения кода. Каждая зона может иметь разные правила, такие как бесплатная парковка, парковка с оплатой по счетчику или только по разрешениям. Вот практическая структура конфигурации зон:
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,
},
}
Параметр vector4 формат хранит координаты x, y, z вместе с углом направления, обеспечивая правильную парковку транспортных средств. The occupied флаг управляется во время выполнения сервером и не сохраняется между перезапусками, так как транспорт отслеживается отдельно через базу данных гаражной системы. Держите количество мест реалистичным для каждого локации, потому что слишком много мест на маленькой площади приводит к проблемам с клиппингом транспорта.
Система таймера парковочного счетчика
Система счётчиков отслеживает, как долго каждый автомобиль припаркован, и взимает плату соответственно. Когда игрок паркуется в зоне с платным счётчиком, он взаимодействует с ним, чтобы начать сессию, выбирая длительность и оплачивая вперёд. Сервер сохраняет время начала сессии, оплаченный период, номерной знак и ID зоны. Серверный таймер периодически проверяет все активные сессии и помечает истёкшие для применения штрафов. Вот основная логика счётчика:
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)
Разрешение таймера в 60 секунд достаточно для контроля парковки без излишней нагрузки на CPU. Сохраняйте сессии таксометра в памяти, а не в базе данных, так как они временные и не должны сохраняться при перезапуске сервера. При перезапуске все таксометры сбрасываются, что приемлемо, поскольку транспортные средства также возвращаются в гаражи при перезапуске в большинстве фреймворков.
Обнаружение транспортных средств и назначение мест
Обнаружение въезда или выезда транспортного средства с парковочного места требует периодических проверок близости на стороне клиента. Используйте проверку расстояния ко всем местам в близлежащих зонах, а не проверяйте каждое место на всей карте. Запускайте цикл обнаружения только когда игрок находится в транспортном средстве и рядом с настроенной зоной парковки. Это минимизирует использование CPU во время обычной игры:
-- 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 гарантирует, что подсказка парковки появляется только когда транспорт почти неподвижен, предотвращая мерцание UI при проезде через зону. Адаптивный интервал сна 200 мс возле зон и 1000 мс в других местах балансирует отзывчивость и производительность. Для серверов с большим количеством парковочных зон рассмотрите использование пространственного хэша или квадродерева, чтобы не перебирать каждую зону на каждом тике.
Система штрафов за парковку и контроля
Когда срок действия счетчика истекает или транспорт припаркован незаконно, система выписывает штраф, привязанный к владельцу машины. Штрафы хранятся в таблице базы данных и отображаются при следующем входе владельца или при получении автомобиля. Игроки полиции также могут вручную выписывать штрафы за парковку с помощью команды или взаимодействия через target. Запись о штрафе включает номерной знак, сумму, причину и временную метку:
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)
Интегрируйте систему штрафов с процессом выдачи транспортных средств, чтобы игроки должны были оплатить задолженности перед тем, как забрать машину из гаража. Это создаёт естественный механизм контроля, где игнорирование правил парковки имеет реальные последствия в игре. Рассмотрите возможность добавления льготного периода от пяти до десяти минут после окончания времени на парковочном счётчике перед выдачей штрафа, так как это отражает реальное соблюдение правил парковки и снижает раздражение игроков из-за идеально рассчитанных штрафов.
Отображение счетчика NUI и интерфейс оплаты
NUI парковочного счетчика должен быть чистым и информативным, показывать текущее название зоны, ставку за час, оставшееся время, если уже оплачено, и кнопки для выбора длительности и оплаты. Отображайте обратный отсчет, обновляющийся каждую секунду при активном счетчике, чтобы игрок точно знал, сколько времени осталось. Используйте компактный дизайн, не закрывающий обзор игры, похожий на экран реального парковочного счетчика. Анимируйте таймер с переходом цвета от зеленого к желтому и красному по мере истечения времени, обеспечивая интуитивный визуальный сигнал без необходимости читать точное количество минут. Сохраняйте NUI легким, используя ванильный JavaScript и минимальный CSS, избегая тяжелых фреймворков, которые увеличивают время загрузки для простого интерфейса.
Интеграция буксировки и брошенных транспортных средств
Свяжите вашу парковочную систему с механикой эвакуации, чтобы просроченные или неправильно припаркованные автомобили могли быть эвакуированы на штрафстоянку. Когда транспортное средство нарушает правила дольше настроенного порога, отметьте его как доступное для эвакуации в состоянии сервера. Игроки с работой эвакуатора смогут видеть эти машины на карте и получать оплату за эвакуацию. Процесс эвакуации должен обновлять состояние машины в базе данных с "out" на "impounded" и фиксировать причину эвакуации как нарушение парковки. Для брошенных автомобилей, которые долго стоят без взаимодействия игроков, реализуйте автоматическую очистку, перемещающую их на штрафстоянку после настраиваемого таймаута, освобождая места для активных игроков. Это предотвращает заполнение парковок машинами игроков, которые не заходили в игру днями или неделями.
Производительность и вопросы масштабируемости
Системы парковки работают в непрерывных циклах обнаружения, что может влиять на производительность при неправильной оптимизации. Обнаружение транспортных средств на клиенте должно активироваться только для зон в пределах дистанции отрисовки, а серверный поток проверки счетчика должен обрабатывать все активные сессии пакетно, а не создавать отдельные таймеры для каждого транспортного средства. Индексируйте таблицу штрафов за парковку по колонкам plate и citizenid для быстрого поиска при возврате транспортных средств игроками или запросах администраторов истории штрафов. Для серверов с десятками парковочных зон и сотнями одновременных транспортных средств рассмотрите кэширование данных зон в Lua-таблице вместо чтения из конфигурации при каждой проверке, и используйте нативную функцию GetClosestVehicle используйте экономно, так как сканирует все транспортные средства в радиусе. Профилируйте ваш ресурс парковки с помощью встроенного профайлера FiveM при максимальной нагрузке, чтобы выявить узкие места, и помните, что система парковки должна использовать менее 0.1 мс серверного тика, чтобы не влиять на общую производительность сервера.