Система гаражей FiveM: хранение, спаун и эвакуация транспорта
Разработайте надёжную систему гаражей для FiveM. Личные и служебные гаражи, процесс эвакуации, ключи, связь с инвентарём и лучшие бесплатные и премиальные скрипты гаражей.
Agency Scripts
Основатель и ведущий разработчик Agency Scripts
Понимание архитектуры системы гаражей
Система гаражей , одна из самых важных функций на любом FiveM ролевом сервере, служащая основным способом хранения, получения и управления транспортом игроков. В основе системы гаражей три взаимосвязанных слоя: слой базы данных, сохраняющий владение и состояние транспорта; серверная логика, обрабатывающая спавн и деспаун с проверками; и клиентский UI, позволяющий игрокам взаимодействовать с их транспортом. Перед написанием кода нужно принять ключевые архитектурные решения: будут ли гаражи привязаны к локациям или глобальными, смогут ли игроки заходить в любой гараж или только в определённые, и как обрабатывать свойства транспорта, такие как модификации, уровень топлива и состояние повреждений. Лучшие системы гаражей сохраняют полный объект свойств транспорта, чтобы при получении машина возвращалась точно в том виде, в каком была оставлена, включая кастомные окраски, улучшения производительности и даже уровень грязи на кузове.
Схема базы данных и сохранение транспортных средств
Схема вашей базы данных является основой всей системы гаража. Вам нужна таблица, которая отслеживает владение транспортом, текущее состояние и свойства хранения. Столбец состояния критичен, так как определяет, находится ли транспорт в мире, в гараже или на штрафстоянке. Вот практическая схема, охватывающая основные поля:
CREATE TABLE IF NOT EXISTS player_vehicles (
id INT AUTO_INCREMENT PRIMARY KEY,
citizenid VARCHAR(50) NOT NULL,
vehicle VARCHAR(50) NOT NULL,
hash VARCHAR(50) NOT NULL,
mods LONGTEXT DEFAULT '{}',
plate VARCHAR(8) NOT NULL,
fakeplate VARCHAR(8) DEFAULT NULL,
garage VARCHAR(50) DEFAULT 'pillboxgarage',
fuel INT DEFAULT 100,
engine FLOAT DEFAULT 1000.0,
body FLOAT DEFAULT 1000.0,
state INT DEFAULT 1, -- 0 = out, 1 = garaged, 2 = impounded
depotprice INT DEFAULT 0,
drivingdistance INT DEFAULT 0,
INDEX idx_citizenid (citizenid),
INDEX idx_plate (plate),
INDEX idx_state (state)
);
Параметр mods столбец хранит JSON-кодированный объект со всеми модификациями транспортного средства, возвращаемыми функциями типа QBCore.Functions.GetVehicleProperties(vehicle) или эквивалент в ESX. Индексация citizenid, plate , и state колонки обеспечивают быструю работу запросов даже при росте базы игроков до тысяч. Всегда используйте параметризованные запросы при работе с этой таблицей, чтобы предотвратить SQL-инъекции.
Логика спавна и деспавна на стороне сервера
Серверная часть отвечает за все критические проверки. Когда игрок запрашивает выезд транспортного средства из гаража, сервер должен проверить, что игрок действительно владеет этим транспортом, что транспорт находится в состоянии гаража и что доступна валидная точка спавна. Никогда не позволяйте клиенту напрямую задавать позицию спавна, так как читеры могут заспавнить транспорт в любом месте карты. Вместо этого определите точки спавна на сервере и выберите ближайшую доступную. Вот пример безопасного серверного обработчика выезда:
RegisterNetEvent('garage:server:takeVehicle', function(vehicleId, garageId)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
local citizenid = Player.PlayerData.citizenid
local result = MySQL.query.await(
'SELECT * FROM player_vehicles WHERE id = ? AND citizenid = ? AND state = 1',
{vehicleId, citizenid}
)
if not result or not result[1] then
TriggerClientEvent('QBCore:Notify', src, 'Vehicle not found', 'error')
return
end
local vehData = result[1]
local spawnPoint = GetAvailableSpawnPoint(garageId)
if not spawnPoint then
TriggerClientEvent('QBCore:Notify', src, 'No parking spots available', 'error')
return
end
MySQL.update('UPDATE player_vehicles SET state = 0 WHERE id = ?', {vehicleId})
TriggerClientEvent('garage:client:spawnVehicle', src, vehData, spawnPoint)
end)
Для процесса удаления транспортного средства сервер должен зафиксировать текущие свойства машины перед её удалением из мира. Это гарантирует сохранение изменений, сделанных с момента последнего хранения. Всегда обновляйте уровень топлива, здоровье двигателя и кузова вместе с JSON модификаций, чтобы всё сохранялось корректно. Реализуйте проверку расстояния на стороне сервера, чтобы убедиться, что игрок действительно находится рядом с гаражом перед разрешением операций хранения.
Клиентский интерфейс гаража с NUI
Интерфейс гаража , это место, где игроки взаимодействуют с системой, и хорошо продуманный интерфейс делает разницу между раздражающим опытом и плавным. Используйте NUI с HTML, CSS и JavaScript для создания адаптивной панели, которая отображает все транспортные средства, хранящиеся в текущем гараже. Каждая запись о транспортном средстве должна показывать название, номерной знак, уровень топлива и общее состояние с первого взгляда. Включите систему предпросмотра, которая временно спавнит модель транспортного средства, чтобы игроки могли видеть, что они выбирают, особенно полезно, когда у игрока несколько транспортных средств одного типа. Вот клиентская логика для открытия меню гаража и сбора данных о транспортных средствах:
RegisterNetEvent('garage:client:openMenu', function(garageId)
QBCore.Functions.TriggerCallback('garage:server:getVehicles', function(vehicles)
if not vehicles or #vehicles == 0 then
QBCore.Functions.Notify('No vehicles stored here', 'info')
return
end
SetNuiFocus(true, true)
SendNUIMessage({
action = 'openGarage',
vehicles = vehicles,
garageName = Config.Garages[garageId].label
})
end, garageId)
end)
RegisterNUICallback('takeVehicle', function(data, cb)
SetNuiFocus(false, false)
TriggerServerEvent('garage:server:takeVehicle', data.vehicleId, currentGarage)
cb('ok')
end)
На стороне JavaScript отображайте каждое транспортное средство как карточку с кнопками действий для выезда или передачи в другой гараж. Рассмотрите возможность добавления сортировки и фильтрации, чтобы игроки с большими коллекциями быстро находили нужное транспортное средство. Поисковая строка, фильтрующая по номеру или названию машины, , небольшое дополнение, значительно повышающее удобство на серверах, где игроки со временем накапливают много транспорта.
Хранение и восстановление свойств транспортных средств
Правильное сохранение и восстановление свойств транспортных средств , одна из самых сложных частей разработки гаражной системы. Объект свойств содержит десятки полей, включая цвета, ливреи, неоновые огни, тонировку стекол, цвет дыма шин, дополнительные опции и все модификации производительности. При сохранении транспортного средства фиксируйте свойства непосредственно перед удалением сущности, чтобы получить актуальное состояние. При спавне транспортного средства нужно дождаться полной загрузки сущности перед применением свойств, иначе модификации, такие как кастомные колеса или улучшения двигателя, не применятся. Используйте небольшую задержку или правильный цикл проверки существования сущности:
function SpawnAndApplyMods(vehData, spawnPoint)
local model = GetHashKey(vehData.vehicle)
RequestModel(model)
while not HasModelLoaded(model) do
Wait(10)
end
local veh = CreateVehicle(model, spawnPoint.x, spawnPoint.y, spawnPoint.z,
spawnPoint.w, true, false)
while not DoesEntityExist(veh) do
Wait(10)
end
local props = json.decode(vehData.mods)
if props then
QBCore.Functions.SetVehicleProperties(veh, props)
end
SetVehicleFuelLevel(veh, vehData.fuel + 0.0)
SetVehicleEngineHealth(veh, vehData.engine + 0.0)
SetVehicleBodyHealth(veh, vehData.body + 0.0)
SetEntityAsMissionEntity(veh, true, true)
SetModelAsNoLongerNeeded(model)
TaskWarpPedIntoVehicle(PlayerPedId(), veh, -1)
end
Обратите особое внимание на транспортные средства из аддонов, так как у них иногда есть кастомные дополнительные опции или индексы ливреи, которые ведут себя иначе, чем стандартные транспортные средства GTA. Тщательно тестируйте цикл сохранения и восстановления собственности с разными типами транспортных средств, чтобы выявить крайние случаи на раннем этапе.
Интеграция системы штрафстоянки
Система эвакуации работает в связке с вашим гаражом и добавляет уровень реализма, который требуют ролевые серверы. Транспортные средства оказываются на эвакуаторе по нескольким причинам: изъятие полицией при аресте, автоматическая очистка заброшенных транспортных средств после перезапуска сервера или действия админа за нарушение правил. Когда транспортное средство эвакуировано, обновите его состояние на 2 в базе данных и при необходимости установите цену депо, которую игрок должен заплатить для его возврата. Площадка эвакуатора должна функционировать аналогично гаражу, но с дополнительным требованием оплаты перед выдачей. Создайте отдельное место эвакуатора на карте с собственными точками спавна и интерфейсом NUI, который явно отображает плату за эвакуацию.
RegisterNetEvent('police:server:impoundVehicle', function(plate, price)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
-- Verify the player has police job authorization
if Player.PlayerData.job.name ~= 'police' then return end
local result = MySQL.update.await(
'UPDATE player_vehicles SET state = 2, depotprice = ? WHERE plate = ?',
{price or 500, plate}
)
if result > 0 then
TriggerClientEvent('QBCore:Notify', src, 'Vehicle impounded', 'success')
end
end)
Рассмотрите внедрение системы ступенчатого ценообразования, где плата за штрафстоянку увеличивается каждый раз при повторном эвакуировании одного и того же автомобиля, чтобы отговорить игроков использовать штрафстоянку как бесплатную парковку. Также можно добавить механику по времени, когда машины, находящиеся на штрафстоянке дольше настраиваемого количества реальных дней, автоматически возвращаются в гараж бесплатно, предотвращая ситуации с постоянной потерей, которые раздражают игроков.
Интеграция Garage Blips и Target
Сделать гаражи обнаруживаемыми и удобными для взаимодействия требует правильного размещения блипсов и зон взаимодействия. Добавьте блипсы на карту для каждого гаража, чтобы игроки могли их найти на миникарте, и используйте либо маркеры на основе близости, либо интеграцию с системой target для триггера взаимодействия. Системы target, такие как ox_target или qb-target, обеспечивают более чистый опыт, так как показывают опции взаимодействия только когда игрок целится в конкретную точку, уменьшая загромождение экрана. Определите местоположения гаражей в общем конфигурационном файле, к которому могут обращаться и клиент, и сервер, сохраняя координаты, точки спавна и настройки синхронизированными:
Config.Garages = {
['pillboxgarage'] = {
label = 'Pillbox Garage',
coords = vector3(215.83, -810.18, 30.73),
spawnPoints = {
vector4(218.32, -803.28, 30.73, 248.5),
vector4(222.41, -799.84, 30.73, 248.5),
vector4(226.52, -796.41, 30.73, 248.5),
},
blip = { sprite = 357, color = 3, scale = 0.7 },
vehicleType = 'car', -- car, boat, aircraft
},
}
Поддерживайте несколько типов транспорта, создавая отдельные гаражи для лодок и самолётов с подходящими точками спавна рядом с водой или на аэропортах. vehicleType фильтр гарантирует, что игроки видят только наземные транспортные средства на уличном гараже и только лодки в марине, предотвращая путаницу и проблемы со спавном. Когда игрок подходит к гаражу, проверьте, есть ли у него транспорт там, прежде чем показывать подсказку взаимодействия, чтобы избежать ненужного открытия меню для игроков без транспорта в этом месте.
Советы по оптимизации производительности
Системы гаражей могут стать узким местом по производительности, если их реализовать неаккуратно, особенно на серверах с сотнями одновременных игроков, каждый из которых владеет несколькими транспортными средствами. Кэшируйте списки транспортных средств на стороне сервера вместо запроса к базе данных каждый раз при открытии меню гаража игроком и инвалидируйте кэш только при изменении состояния транспортного средства. На стороне клиента избегайте держать открытыми NUI окна, когда они не нужны, так как даже скрытые NUI окна потребляют ресурсы, если в них работают таймеры JavaScript или анимации. При спавне транспортных средств убедитесь, что вы правильно очищаете сущности, помечая их как больше не нужные после того, как игрок их убирает, и реализуйте резервную процедуру очистки, которая периодически запускается для удаления оставшихся сущностей транспортных средств, которые не были корректно удалены из-за сбоев или отключений. Используйте native функции, такие как GetGamePool('CVehicle') используйте экономно и кэшируйте результаты, когда нужно проверить наличие транспортных средств игрока в мире. Наконец, рассмотрите возможность введения максимального лимита транспортных средств на гараж, чтобы ограничить запросы к базе данных и предотвратить хранение сотен транспортных средств одним игроком, что может замедлить операции извлечения.