Данные и сохранения: DataStore без потерь

Прогресс игрока живёт в DataStore — облачном хранилище Roblox. Работает оно по сети, а значит иногда не отвечает: код, который этого не ждёт, однажды сотрёт чужие сто часов игры.

Что нужно знать до первого сохранения

  • DataStore работает только на сервере и только в опубликованной игре
  • В настройках игры должен быть включён доступ к API-сервисам
  • Любой запрос может упасть — оборачивай его в pcall
  • У запросов есть лимиты по частоте: сохранять каждый кадр нельзя

Скелет сохранения

ServerScriptService/DataService.lua
  1. local DataStoreService = game:GetService("DataStoreService")
  2. local store = DataStoreService:GetDataStore("PlayerData_v1")
  3.  
  4. local function load(userId)
  5. local ok, result = pcall(function()
  6. return store:GetAsync("user_" .. userId)
  7. end)
  8.  
  9. if not ok then
  10. warn("Не удалось загрузить данные: " .. tostring(result))
  11. return nil
  12. end
  13.  
  14. return result or { coins = 0, level = 1 }
  15. end
  16.  
  17. local function save(userId, data)
  18. local ok, err = pcall(function()
  19. store:UpdateAsync("user_" .. userId, function()
  20. return data
  21. end)
  22. end)
  23.  
  24. if not ok then
  25. warn("Не удалось сохранить данные: " .. tostring(err))
  26. end
  27. end
  28.  
  29. return { load = load, save = save }

Имя стора с версией: когда формат данных изменится, старые записи не сломаются.

Почему UpdateAsync, а не SetAsync

SetAsync просто перезаписывает значение. Если игрок успел зайти на второй сервер, запись с первого затрёт всё, что он сделал на втором.

UpdateAsync получает текущее значение и возвращает новое — так можно принять решение на основе того, что уже лежит в базе. Не потерять данные важнее, чем сохранить быстро.

Когда сохранять

  • При выходе игрока — Players.PlayerRemoving
  • При закрытии сервера — game:BindToClose, иначе последние минуты пропадут
  • Периодически, раз в несколько минут, а не после каждого действия
  • Никогда — по запросу от клиента: клиент не решает, что сохранять