{}const=>[]async()letfn</>var
LuaРазработка

DataStore в Roblox: сохранение прогресса игрока без потери данных

Собираем безопасное сохранение прогресса в Roblox через DataStoreService, UpdateAsync и pcall, разбираем загрузку, выход игрока и тестовую версию игры.

К

Кодик

Автор

7 мин чтения

Для сохранения прогресса Roblox используйте DataStoreService только в серверном Script, ключ на основе Player.UserId, сетевые вызовы внутри pcall() и UpdateAsync() для записи. Загружайте данные при входе, сохраняйте при выходе и тестируйте на отдельной опубликованной версии.

DataStore хранит значения между игровыми сессиями и доступен всем places одной experience. Это подходит для монет, уровней, инвентаря и настроек. Вызовы идут по сети и иногда завершаются ошибкой, поэтому код обязан иметь ветку неудачи. LocalScript не может обращаться к DataStore. Сервер остаётся владельцем прогресса и не принимает готовый баланс от клиента.

1Ключ

User_ плюс UserId, чтобы имя не менялось вместе с ником.

2Запись

UpdateAsync меняет актуальную версию значения через функцию.

3Ошибка

pcall отделяет сетевой сбой от логики игры и сохраняет сообщение.

Цикл монет: GetAsync при входе, атрибут Coins в игре, UpdateAsync при выходе
Жизненный цикл монет: PlayerAdded читает ключ User_ плюс UserId через GetAsync, игра меняет атрибут Coins в памяти сервера, PlayerRemoving возвращает число в хранилище Coins_v1 через UpdateAsync.

DataStore Roblox: код загрузки и сохранения прогресса

Пример хранит только число монет, поэтому его легко проверить. Script поместите в ServerScriptService. Перед тестом опубликуйте отдельную тестовую experience и разрешите Studio Access to API Services именно для неё. Не включайте Studio-доступ к боевым данным: случайный тест может перезаписать настоящий профиль.

local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local coinsStore = DataStoreService:GetDataStore("Coins_v1")

local function getKey(player)
    return "User_" .. player.UserId
end

local function savePlayer(player)
    local coins = player:GetAttribute("Coins") or 0
    local success, message = pcall(function()
        coinsStore:UpdateAsync(getKey(player), function()
            return coins
        end)
    end)

    if not success then
        warn("Save failed for", player.UserId, message)
    end
end

Players.PlayerAdded:Connect(function(player)
    local success, savedCoins = pcall(function()
        return coinsStore:GetAsync(getKey(player))
    end)

    player:SetAttribute("Coins", success and savedCoins or 0)
end)

Players.PlayerRemoving:Connect(savePlayer)
Ожидаемый результат
  • Первый вход: Coins равен 0
  • Следующий вход: загружается сохранённое число

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

🔥 100 000+ учеников уже с нами

Устал читать теорию?
Пора кодить!

Кодик — приложение, где ты учишься программировать через практику. AI-наставник, интерактивные уроки, реальные проекты.

🤖 AI 24/7
🎓 Сертификаты
💰 Бесплатно
🚀 Начать учиться
Присоединились сегодня

DataStore, MemoryStore или Attributes

ЭлементЧто означаетЧто делать
DataStoreДанные между сессиямиМонеты, инвентарь, уровень
OrderedDataStoreЧисла с сортировкойГлобальный рейтинг
MemoryStoreВременные быстрые данныеОчередь матчмейкинга, кэш
AttributesТекущее состояние InstanceМонеты в активной сессии
Обычная таблица LuauПамять одного сервераВременный профиль до сохранения

Не сохраняйте данные после каждого подобранного предмета. Запись имеет лимиты, а UpdateAsync расходует бюджет чтения и записи. Меняйте профиль в памяти, запускайте разумное автосохранение и делайте финальную попытку при выходе. Для одной горячей записи со множеством серверов продумайте конкуренцию и не храните весь мир под одним ключом.

Точки отказа: ошибка GetAsync, лимиты записи, два сервера на один ключ
Три места, где сохранение обрывается: GetAsync вернул ошибку и профиль подменился нулём, UpdateAsync упёрся в лимиты записи, два сервера переписали один ключ без контроля версии.

Практика: сохраните Coins через UpdateAsync и проверьте полный цикл

Соберите минимальный профиль из одного числа: монеты игрока в хранилище Coins_v1. Скрипт кладите в ServerScriptService и тестируйте на отдельной опубликованной experience, где включён Studio Access to API Services. Успех: вы вышли из сессии с 25 монетами, зашли снова и увидели те же 25, а не ноль.

  1. Script в ServerScriptService. Создайте обычный Script в ServerScriptService и получите хранилище через DataStoreService:GetDataStore с именем Coins_v1. LocalScript тут не подойдёт: клиенту доступ к DataStore закрыт.
  2. Ключ на основе UserId. Напишите функцию getKey(player), которая возвращает User_ плюс player.UserId. Выведите ключ через print и убедитесь, что он не зависит от отображаемого имени игрока.
  3. Загрузка в PlayerAdded. В обработчике Players.PlayerAdded оберните GetAsync(getKey(player)) в pcall и положите результат в player:SetAttribute с именем Coins. При первом входе в Explorer у игрока появится атрибут Coins со значением 0.
  4. Запись в PlayerRemoving. Поставьте Coins равным 25 прямо в свойствах игрока и повесьте savePlayer на Players.PlayerRemoving, где UpdateAsync возвращает текущее число. Остановите тест и проверьте, что в Output нет предупреждения Save failed for.
  5. Второй вход и сверка. Запустите сессию заново и посмотрите на атрибут Coins сразу после входа. Там должно быть сохранённое 25, а не ноль: значит, цикл чтения и записи замкнулся.

Теперь сломайте ветку отказа специально: временно выключите Studio Access to API Services или передайте в UpdateAsync значение, которое хранилище не умеет сериализовать, например таблицу с функцией внутри. pcall вернёт success равным false, и в Output появится ваш warn с UserId и текстом ошибки. Дальше главное: убедитесь, что после неудачного GetAsync игра не подставила ноль и не записала его поверх настоящих 25. Верните доступ, повторите вход и проверьте, что число на месте.

Критерий готовности. Готово, когда вы можете сказать, кто владеет числом монет в каждый момент: до входа хранилище Coins_v1, во время игры атрибут Coins на объекте игрока, после выхода снова хранилище. Подтверждение: два запуска подряд с одним и тем же значением и чистый Output без строки Save failed for. Отдельно объясните, что произойдёт при ошибке GetAsync и почему сохранять в этот момент нельзя.

Дальше замените одно число таблицей профиля с полями для монет, уровня и версии данных, чтобы позже можно было мигрировать формат. Добавьте автосохранение по таймеру и последнюю попытку записи при закрытии сервера, но не пишите после каждой подобранной монеты: у записи есть лимиты, а UpdateAsync расходует бюджет чтения и записи. Для глобального рейтинга заведите отдельный OrderedDataStore, а текущие монеты покажите игроку через leaderstats. Кнопку покупки делайте через RemoteEvent, где цену проверяет сервер.

Частые ошибки и почему они появляются

Потери данных чаще возникают не из-за одной опечатки, а из-за неверного сценария отказа. Игрок вошёл, GetAsync вернул ошибку, игра поставила ноль, а при выходе сохранила его поверх старого значения. Другая проблема появляется, когда два сервера меняют один профиль без контроля версии. Третья связана с тестами прямо на боевом хранилище.

Ключ по Name

Ник может измениться. UserId остаётся стабильным идентификатором игрока.

DataStore в LocalScript

Клиентский доступ запрещён и небезопасен. Все чтения и записи выполняет сервер.

Игнорировать success

После pcall всегда проверяйте флаг и решайте, можно ли продолжать, повторять или остановить сохранение.

Самопроверка

Зачем оборачивать GetAsync и UpdateAsync в pcall?

pcall нужен потому, что DataStore ходит по сети и вызов иногда завершается ошибкой без вины вашего кода. Без pcall скрипт остановится прямо на этой строке, а с ним вы получите флаг success и сообщение и сами решите: повторить попытку, отложить её или запретить сохранение.

Почему ключ строят на UserId, а не на имени игрока?

Ключ строят на UserId, потому что этот номер закреплён за аккаунтом и не меняется. Ник и отображаемое имя игрок может сменить в любой момент, и ключ по Name после переименования укажет на пустой профиль, а запись вида User_ плюс UserId найдёт те же данные.

Можно ли обращаться к DataStore из LocalScript?

Из LocalScript обращаться к DataStore нельзя: доступ есть только у серверного кода. Прогресс принадлежит серверу, и он же решает, можно ли изменить баланс, иначе клиент сможет прислать себе любое число монет.

Почему прогресс игрока обнулился после ошибки загрузки?

Обнуление обычно происходит по такой цепочке: GetAsync вернул ошибку, скрипт молча подставил 0, а при выходе сохранил этот ноль поверх настоящего профиля. Лечится отдельным состоянием загрузки: пока данные не прочитаны успешно, сохранение для этого игрока запрещено.

Как часто можно сохранять данные в DataStore?

Сохранять после каждого подобранного предмета не нужно: у записи есть лимиты, а каждый UpdateAsync тратит бюджет чтения и записи. Держите профиль в памяти сервера, запускайте автосохранение по таймеру и делайте финальную запись при выходе игрока и закрытии сервера.

Чем DataStore отличается от Attributes и MemoryStore?

DataStore хранит данные между сессиями и доступен всем places одной experience: монеты, инвентарь, уровень. Attributes держат текущее состояние Instance только внутри активной сессии, MemoryStore подходит для быстрых временных данных вроде очереди матчмейкинга, а числа с сортировкой для глобального рейтинга ведут в OrderedDataStore.

Что делать дальше

Сначала сохраните одно число и проверьте полный цикл: вход, изменение, выход, новый сервер. Затем добавьте таблицу профиля и поле версии. Логику таблиц и функций отработайте на курсе Lua. Для отображения монет подключите leaderstats, а для кнопки покупки используйте безопасный RemoteEvent, где сервер сам проверяет цену и меняет профиль.

🎯Хватит откладывать

Понравилась статья?
Пора применять на практике!

В Кодик ты не просто читаешь — ты сразу пишешь код. Теория + практика = реальный скилл.

Мгновенная практика
🧠AI объяснит код
🏆Сертификат

Без регистрации • Без карты