Инвентарь — сердцевина RPG и симуляторов выживания в Roblox. Неправильная архитектура приводит к дюпу предметов, рассинхрону и задержкам на смартфонах.
Используя строгую валидацию на сервере, составные структуры данных и блокировку сессий через ProfileService, разработчики создают надежную экономику.
1. Модели инвентаря: Слоты против Сетки и Системы Веса
Выбор оптимальной структуры под механику игры:
- Фиксированные слоты: Классический хотбар с индексированным массивом (например, 20 слотов). Быстрый поиск за O(1).
- Сетка (Тетрис): Предметы разного размера (2x3, 1x4) в стиле Diablo. Требует 2D-матрицы коллизий.
- Ограничение по весу: Превышение лимита перегружает персонажа и замедляет его передвижение.
2. Серверный авторитет и Оптимистичный интерфейс
Мгновенный отклик без брешей в безопасности:
- Оптимистичный UI: Иконка перемещается сразу, не ожидая ответа от сервера.
- Проверка на сервере: Контроль прав владения, веса и лимитов перед сохранением состояния.
- Откат назад (Rollback): При отклонении запроса предмет плавно возвращается на исходную позицию.
3. Структура данных и сериализация стаков
Разделение статических и динамических свойств в Luau:
- Базовый шаблон и экземпляр: Названия и вес хранятся в ReplicatedStorage, уникальный UUID и прочность — у игрока.
- Логика слияния стаков: Атомарный перенос предметов с учетом максимальной вместимости слота.
- Оптимизация хранения: Удаление пустых слотов перед записью в DataStore для экономии лимита в 4 МБ.
-- Серверный менеджер инвентаря
local InventoryManager = {}
InventoryManager.__index = InventoryManager
local ItemDatabase = require(game.ReplicatedStorage.ItemDatabase)
function InventoryManager.new(player, maxSlots, maxWeight)
local self = setmetatable({}, InventoryManager)
self.Player = player
self.MaxSlots = maxSlots or 30
self.MaxWeight = maxWeight or 100
self.Slots = table.create(self.MaxSlots, false)
self.CurrentWeight = 0
self.IsLocked = false
return self
end
function InventoryManager:AddItem(itemId, amount)
if self.IsLocked then return false, "INVENTORY_LOCKED" end
local itemData = ItemDatabase[itemId]
if not itemData then return false, "INVALID_ITEM" end
local addedWeight = itemData.Weight * amount
if self.CurrentWeight + addedWeight > self.MaxWeight then
return false, "OVER_ENCUMBERED"
end
for slotIndex = 1, self.MaxSlots do
local slot = self.Slots[slotIndex]
if slot and slot.Id == itemId and slot.Count < itemData.MaxStack then
local space = itemData.MaxStack - slot.Count
local toAdd = math.min(space, amount)
slot.Count = slot.Count + toAdd
amount = amount - toAdd
self.CurrentWeight = self.CurrentWeight + (itemData.Weight * toAdd)
if amount <= 0 then return true end
end
end
for slotIndex = 1, self.MaxSlots do
if not self.Slots[slotIndex] then
self.Slots[slotIndex] = {
UUID = game:GetService("HttpService"):GenerateGUID(false),
Id = itemId,
Count = amount,
Durability = itemData.MaxDurability
}
self.CurrentWeight = self.CurrentWeight + (itemData.Weight * amount)
return true
end
end
return false, "INVENTORY_FULL"
end
return InventoryManager
4. Защита от дюпа предметов и Блокировка сессий
Устранение уязвимостей при торговле и вылетах игры:
- MemoryStore Session Lock: Временная блокировка профиля во время обмена для защиты от повторной траты.
- Атомарный трейд: Одновременный обмен предметами за один кадр сервера или полный откат при сбое связи.
- Ограничение частоты: Кулдаун в 100 мс на перемещение предметов защищает от пакетного спама.
5. Адаптация под сенсорные экраны смартфонов
Удобство управления на мобильных устройствах:
- Зоны касания от 44px: Динамический UIScale предотвращает случайные промахи пальцем.
- Контекстное меню при зажатии: Замена правого клика мыши круговым меню выбора действий.
- Тактильный отклик: Вибрация через HapticService при экипировке снаряжения.
Frequently Asked Questions
Почему логика инвентаря должна выполняться на сервере?
Клиентские вычисления позволяют нечестным игрокам создавать предметы из воздуха. Сервер гарантирует честность.
Как оптимистичный интерфейс убирает задержку?
Интерфейс перемещает иконку мгновенно, а в случае запрета от сервера возвращает ее обратно.
Зачем нужна блокировка сессии DataStore?
Она запрещает нескольким серверам одновременно изменять профиль игрока, исключая дюп через принудительный выход.
Как сделать плавный драг-энд-дроп на смартфонах?
Отображать легкий клон иконки под пальцем, а расчет попадания в слот производить только при отпускании.