Самая частая беда в «Разработке» — игроки теряют монеты после выхода. Почти всегда причина в одном из трёх мест: нет повторов при ошибке, сохранение поверх неудачной загрузки или сервер выключается раньше, чем всё сохранилось.
Вот минимальная схема, которая закрывает все три:
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local store = DataStoreService:GetDataStore("PlayerData_v1")
local cache = {}
local function load(player)
local key = "u_" .. player.UserId
for attempt = 1, 3 do
local ok, data = pcall(store.GetAsync, store, key)
if ok then
cache[player] = data or { coins = 0 }
return
end
task.wait(2 ^ attempt)
end
-- Не удалось загрузить: не пускаем в игру, иначе пустые данные
-- потом сохранятся поверх настоящих.
player:Kick("Не удалось загрузить данные — зайди ещё раз")
end
local function save(player)
local data = cache[player]
if not data then return end
local key = "u_" .. player.UserId
for attempt = 1, 3 do
local ok = pcall(store.UpdateAsync, store, key, function()
return data
end)
if ok then return end
task.wait(2 ^ attempt)
end
end
Players.PlayerAdded:Connect(load)
Players.PlayerRemoving:Connect(function(player)
save(player)
cache[player] = nil
end)
game:BindToClose(function()
for _, player in Players:GetPlayers() do
task.spawn(save, player)
end
task.wait(3)
end)Что здесь важно:
1. pcall и повторы с паузой 2, 4, 8 секунд — DataStore иногда отвечает ошибкой, и одна неудача не должна стоить игроку прогресса. 2. Если загрузка не удалась, игрока кикает, а save() ничего не пишет: в кэше нет данных. Это главное правило — не сохранять то, что не загрузилось. 3. BindToClose даёт серверу до 30 секунд перед выключением. Без него при обновлении игры последние изменения теряются.
Когда игра вырастет, бери ProfileStore: он запрещает двум серверам писать один профиль одновременно (session locking) и решает остальные крайние случаи.
Вопросы и свои схемы — в ответах.