Коротко: DataStore — хранилище на серверах Roblox, где игра запоминает прогресс между заходами. Без него монеты и уровни исчезают при выходе; с ним игрок возвращается туда, где остановился.

На определённом этапе каждый новичок в Roblox Studio упирается в одну и ту же стену: игрок вышел из игры, а завтра все его монеты, уровни и прогресс исчезли. Причина проста — по умолчанию Roblox ничего не сохраняет между сессиями. Для этого нужен DataStore.

Что такое DataStoreService на самом деле

DataStoreService — это встроенный сервис Roblox, который позволяет сохранять данные игрока на серверах Roblox и загружать их при следующем входе. Технически это что-то вроде облачного хранилища ключ-значение: в ключ записывается ID игрока, в значение — его данные.

local DataStoreService = game:GetService("DataStoreService")
local playerData = DataStoreService:GetDataStore("PlayerCoins")

game.Players.PlayerAdded:Connect(function(player)
  local savedCoins = playerData:GetAsync(player.UserId)
  player.leaderstats.Coins.Value = savedCoins or 0
end)

Три строки — и игра уже «помнит» игрока. Но тут и начинаются сложности.

Почему это работает только на сервере

Первое, обо что ребёнок спотыкается: DataStore недоступен из клиентского скрипта. И это не прихоть платформы, а защита. Если бы игрок мог сам записать себе количество монет, сохранялось бы любое значение — в том числе то, которое он придумал.

Поэтому правило простое: всё, что касается сохранения, живёт на сервере, а клиент лишь показывает результат. Для ребёнка это первое по-настоящему дорогое знакомство с мыслью «нельзя доверять тому, что приходит от игрока» — и она потом понадобится в любой сетевой разработке, не только в играх.

Ловушка №1: забыть сохранить данные при выходе

Загрузить данные — это половина работы. Вторая половина — сохранить их, когда игрок выходит:

game.Players.PlayerRemoving:Connect(function(player)
  playerData:SetAsync(player.UserId, player.leaderstats.Coins.Value)
end)

Без этой строки вся система работает лишь наполовину: данные загружаются, но никогда не обновляются.

Ловушка №2: превышение лимитов запросов

У Roblox есть лимит на количество запросов к DataStore в минуту. Если вызывать SetAsync на каждом шаге игры (например, при каждом изменении монет) — игра начнёт выдавать ошибки при большом количестве игроков. Правильный подход: сохранять изменения локально в игре и отправлять в DataStore реже — раз в несколько минут или при выходе игрока.

Ловушка №3: потеря данных при сбое сервера

Сервер может «упасть» до того, как сработает PlayerRemoving. В серьёзных проектах для этого используют автосохранение — отдельный цикл, который сохраняет прогресс каждые несколько минут независимо от выхода игрока.

Вторая половина этой же проблемы — плановая остановка сервера. Roblox даёт для этого отдельный крючок, game:BindToClose: в нём успевают сохранить данные всех, кто ещё в игре, перед тем как сервер закроется. Без него последние минуты игры регулярно исчезают, и выглядит это для игрока как «игра съела мой прогресс».

Ловушка №4: вызов без pcall

Обращение к DataStore — это запрос через сеть, а сеть иногда не отвечает. Если вызвать GetAsync напрямую, в момент сбоя скрипт просто упадёт — и дальше не выполнится ничего.

local ok, data = pcall(function()
  return playerData:GetAsync(player.UserId)
end)

if not ok then
  warn("Не удалось загрузить данные: ", data)
  return -- ничего не перезаписываем!
end

Именно здесь прячется самая болезненная ошибка новичков, и она в последней строке. Если данные не загрузились, а игра спокойно продолжила работу с нулём, то при выходе этот ноль запишется поверх реального прогресса. Игрок потеряет всё — не из-за сбоя сети, а из-за того, что игра решила сохранить пустоту.

Правило, которое спасает: не смогли прочитать — не пишем ничего. Лучше показать игроку предупреждение и попросить перезайти, чем молча стереть то, что он собирал неделями.

Одна таблица вместо десяти ключей

Когда данных становится больше одной цифры, появляется соблазн завести отдельное хранилище для монет, отдельное для уровня, отдельное для инвентаря. Это и бьёт по лимитам, и рассинхронизирует данные между собой.

Рабочий подход — хранить всё одной таблицей под одним ключом игрока:

local profile = {
  coins = 120,
  level = 3,
  items = { "sword", "key" },
}

Один запрос вместо трёх, и данные не могут разойтись между собой. Побочный урок для ребёнка — мысль о структуре данных: что именно имеет смысл хранить, а что можно посчитать заново при входе.

Игра изменилась — а сохранения старые

Через месяц после запуска ребёнок добавляет в игру новую вещь: скажем, энергию или вторую валюту. Код готов, в тесте всё работает — а у старых игроков игра ломается, потому что в их сохранении этого поля просто нет.

Лечится это двумя привычками. Первая — никогда не рассчитывать, что поле есть: берём значение, а если его нет, подставляем начальное. Вторая — держать в самих данных номер версии, чтобы игра понимала, по каким правилам записан профиль, и при необходимости дописала недостающее.

Тема выглядит взрослой, и она такой и является: ровно этим занимаются настоящие сервисы, когда обновляют формат данных миллионам пользователей. Ребёнок же делает это на своей игре с пятью друзьями — и навсегда запоминает, что старые данные никуда не деваются, когда код изменился.

Мнение преподавателя

«DataStore — одна из тех тем, где дети впервые сталкиваются с понятием надёжности системы, а не просто «сделал и работает». Это уже инженерное мышление, не просто программирование ради эффекта», — отмечает один из наших менторов курса.

Тестирование DataStore: подводный камень для новичков

DataStore не работает в режиме Studio по умолчанию — нужно отдельно включить опцию «Enable Studio Access to API Services» в настройках игры. Без этого шага код выглядит рабочим, но никакие данные никуда не сохраняются, и новичок долго ищет несуществующую ошибку.

Проверять сохранение тоже надо по правилам, иначе вывод будет ложным. Порядок такой: зайти, изменить значение, выйти полностью (а не перезапустить тест), зайти снова. И отдельно — проверить поведение первого входа, когда данных ещё нет вовсе: именно там прячется половина ошибок, потому что nil легко превращается в сбой вместо нуля.

Когда стоит изучать DataStore?

Мы обычно вводим эту тему не на первом занятии, а когда ученик уже уверенно работает с переменными, событиями и базовыми скриптами — примерно на втором месяце курса Roblox Studio. Раньше — просто слишком много новых понятий сразу.

Ещё одна причина не спешить: DataStore меняет сам разговор об игре. До него ребёнок спрашивает «как сделать, чтобы работало», после него — «что будет, если сломается». Это разные уровни мышления, и второй даётся легче, когда первый уже уверенный.

Если ребёнок уже сделал первый уровень и хочет, чтобы игра «помнила» игрока — это естественный следующий шаг. Приходите на занятие, покажем, как не наступить на эти грабли с первого раза.