Почему твоя игра лагает: разбор по причинам
Что на самом деле съедает FPS в Roblox: детали, свет, частицы, физика и скрипты. Как найти виновника инструментами Studio и что делать с каждым из них.
Сложно 24 минуты проверен 1 час назад
Что понадобится
Свой проект в Studio и понимание, где лежат скрипты. Гайд «Клиент и сервер» поможет, но не обязателен.
Текст гайда «Почему твоя игра лагает: разбор по причинам»
Содержание
«Игра лагает» — это не диагноз, а симптом. Причин ровно пять, и лечатся они по-разному. Прежде чем что-то удалять, надо понять, что именно тормозит: картинка, физика, скрипты, сеть или память. Иначе можно вырезать пол-карты и не получить ни одного лишнего кадра.
Сначала измерить, потом чинить#
В Studio на вкладке View есть три вещи, которые отвечают на вопрос «куда уходит время».
| Инструмент | Что показывает |
|---|---|
| MicroProfiler | Подробную раскладку кадра: сколько времени ушло на отрисовку, физику и скрипты |
| Консоль разработчика (F9) | Память, сеть, ошибки скриптов — уже в запущенной игре |
| Explorer с фильтром | Сколько в сцене деталей, частиц и источников света |
Смотри на телефоне, а не на своём компьютере
У тебя игра идёт плавно почти всегда. Реальный игрок сидит на телефоне трёхлетней давности, и первым у него ложится именно то, чего ты не замечаешь: свет с тенями, прозрачность и частицы. Проверяй в эмуляторе устройства и по возможности на настоящем телефоне.
Причина первая: картинка#
Отрисовка — самый частый виновник. Дорого стоит не количество деталей само по себе, а количество того, что видно одновременно, и сложность каждого объекта.
- Прозрачность дороже непрозрачности: стекло поверх стекла поверх дыма — верный способ уронить FPS
- Тени от каждой лампы в режиме Future красивы и дороги; на мобильной игре чаще выбирают ShadowMap
- Частицы (ParticleEmitter) с большим Rate и крупным размером бьют по слабым устройствам сильнее всего
- Мелкие детали, которых не видно с расстояния, всё равно рисуются — если они не нужны, их не должно быть
- Много разных текстур и материалов — больше памяти; повторяющийся набор лучше уникального на каждый дом
Главный рычаг для больших карт — StreamingEnabled у Workspace. С ним клиент загружает только ближайшую часть мира, а дальнюю подтягивает по мере движения. Для карты размером с город это разница между «играет» и «не запускается на телефоне».
-- LocalScript. При включённом StreamingEnabled деталь может
-- ещё не приехать к клиенту, и обращение к ней уронит скрипт.
local Workspace = game:GetService("Workspace")
local function whenReady(name, callback)
local part = Workspace:FindFirstChild(name)
if part then
callback(part)
return
end
-- Ждём появления, но не вечно: пять секунд и честная запись в Output.
task.spawn(function()
local found = Workspace:WaitForChild(name, 5)
if found then
callback(found)
else
warn("Не дождались объекта: " .. name)
end
end)
end
whenReady("Fountain", function(fountain)
print("Фонтан на месте:", fountain:GetFullName())
end)Причина вторая: физика#
Каждая незакреплённая деталь считается физическим движком каждый кадр. Сотня коробок, которые «просто лежат», — это сотня объектов, за которыми сервер следит постоянно.
- Ставь Anchored = true всему, что не должно двигаться. Это самое дешёвое ускорение из существующих
- У декораций, сквозь которые всё равно ходят, выключай CanCollide и CanTouch
- Не оставляй кучи мусора после боёв: осколки и гильзы надо убирать через Debris или таймер
- Сложная форма считается дороже простой: там, где хватит невидимого куба, не нужен коллайдер по мешу
Причина третья: скрипты#
Скрипт тормозит игру не размером, а частотой. Код, который выполняется каждый кадр для каждого игрока, обязан быть коротким.
local RunService = game:GetService("RunService")
RunService.Heartbeat:Connect(function()
-- Обход всей сцены шестьдесят раз в секунду.
for _, item in ipairs(workspace:GetDescendants()) do
if item:IsA("BasePart") and item.Name == "Coin" then
item.CFrame = item.CFrame * CFrame.Angles(0, 0.05, 0)
end
end
end)local CollectionService = game:GetService("CollectionService")
local RunService = game:GetService("RunService")
-- Монетам заранее выдан тег «Coin» — в Studio это делает
-- окно Tag Editor, из кода AddTag.
local coins = CollectionService:GetTagged("Coin")
CollectionService:GetInstanceAddedSignal("Coin"):Connect(function(coin)
table.insert(coins, coin)
end)
RunService.Heartbeat:Connect(function(delta)
for index = #coins, 1, -1 do
local coin = coins[index]
if coin.Parent == nil then
table.remove(coins, index) -- монету забрали, следить не за чем
else
coin.CFrame = coin.CFrame * CFrame.Angles(0, delta * 3, 0)
end
end
end)Вращение — работа клиента
Если монета крутится только для красоты, вращай её в LocalScript. Тогда сервер вообще не участвует, а по сети не идёт ни одного лишнего пакета. Сервер должен знать только факт: монету забрали.
Причина четвёртая: сеть#
Сетевые лаги выглядят иначе, чем просадка FPS: картинка плавная, но персонаж дёргается, а награда приходит с задержкой. Причина почти всегда одна — слишком много сообщений между клиентом и сервером.
- Не шли FireServer каждый кадр: собери изменения и отправь пачкой раз в несколько кадров
- FireAllClients с большой таблицей на полном сервере умножается на число игроков
- Передавай минимум: идентификатор предмета вместо всей его карточки
- Часто меняющиеся значения (полоска здоровья) обновляй по факту изменения, а не по таймеру
Причина пятая: память и утечки#
Игра, которая идёт нормально первые пять минут и умирает к двадцатой, почти всегда течёт. Классика — подписки на события, которые никто не отключает, и таблицы, куда только добавляют.
local Players = game:GetService("Players")
local connections = {}
Players.PlayerAdded:Connect(function(player)
local connection = player.CharacterAdded:Connect(function(character)
print(character.Name .. " появился")
end)
connections[player] = connection
end)
Players.PlayerRemoving:Connect(function(player)
local connection = connections[player]
if connection then
connection:Disconnect() -- иначе она живёт до конца сервера
connections[player] = nil
end
end)Проверить утечку просто: открой консоль разработчика на F9, посмотри память в начале и через двадцать минут игры. Если число растёт и не падает, значит что-то накапливается.
Порядок действий, если уже лагает#
- Определи вид лага: низкий FPS, рывки раз в несколько секунд или задержка действий
- Низкий FPS — смотри картинку: свет, прозрачность, частицы, количество видимого
- Рывки — ищи скрипт, который делает тяжёлую работу редко, но всю сразу; раздели её по кадрам
- Задержки — считай сообщения по сети и убирай лишние
- Растущая память — ищи подписки и таблицы, которые никто не чистит
- После каждой правки измеряй заново: без замера легко «оптимизировать» то, что и так работало
Если тормозит не твоя игра, а сам Roblox, лечение другое — оно собрано в гайде «Roblox лагает: что делать». Ошибки из Output разобраны здесь, а проверить правки на разных устройствах поможет гайд про тестирование.
Обсуждение
Пиши по делу и без личных данных. Ссылки на сторонние сайты, обмен и «бесплатные робуксы» мы скрываем — почему.
Комментарии пишут те, кто вошёл — так спокойнее всем.
ВойтиПока никто ничего не написал. Будь первым.