Описанные ранее способы монетизации конечно работают. Но вот пришла беда, откуда не ждали! Они «теряются» если персонаж умирает! Или когда игрок жмёт «Reset character». Вот уж действительно — нежданчик.

Пришлось пересмотреть подход к сохранению информации о покупки конкретным игроком.
Во первых данная информация должна хранится на сервере.
Во вторых данная информация должна загружаться после смерти персонажа.
В третьих там должна храниться информация и о временных покупках, если они накладывают какой-то эффект на самого персонажа.
Варианты были разные. Но у всех были какие-либо недостатки и тут меня осенило! Единственное что не изменяется пока игрок не вышел это сам игрок! Провёл небольшое тестирование и вот оно — решение найдено = нужно хранить данные о покупках в переменных, которые будут лежать в самом игроке!

Но, так как при входе игрока этих переменных нет, то их надо создать. А также нужно их читать, когда игрок воскрешается.
Для начала, переделал код для GamePass-а:
--[[
https://developer.roblox.com/en-us/api-reference/event/Player/CharacterAdded
https://developer.roblox.com/en-us/api-reference/event/Player/CharacterRemoving/index.html
https://developer.roblox.com/en-us/api-reference/property/Player/Character/index.html
https://developer.roblox.com/en-us/api-reference/event/IntValue/Changed
]]--
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local tmp
local id1 = 8626045 -- id gamepass - 5s
-- ищем игрока
-- добавляем ему флаг, что оно куплено
-- убираем с экрана возможность покупки
local function buy(tmp)
local plr = game.Players:FindFirstChild(tmp)
local char = plr.Character:WaitForChild("HumanoidRootPart")
RunService.Stepped:wait()
char = plr.Character
if char ~= nil then -- игрок не покинул игру
if game:GetService("MarketplaceService"):UserOwnsGamePassAsync(game.Players[char.Name].UserId, id1) then
-- что тут нужно сделать?!
char.Humanoid.WalkSpeed = 30
if plr:FindFirstChild("Buy") == nil then -- проверяем наличие переменной, если нет - создаём
local Buy = Instance.new("IntValue")
Buy.Parent = plr
Buy.Name = "Buy"
Buy.Value = 5
end
-- а теперь убираем Frame покупки
local pl = game.Players:FindFirstChild(plr.Name)
if pl ~= nil then
pl.PlayerGui.Main.GamePass.Visible=false
pl.PlayerGui.Main.Answer.Visible=true
end
end
end
end
-- проверка уже приобретённого пропуска при подключении игрока
game.Players.PlayerAdded:Connect(function(plr)
plr.CharacterAdded:connect(function(char)
buy(char.Name)
end)
plr.CharacterRemoving:Connect(function(char)
buy(char.Name)
local pl = plr -- Players:GetPlayerFromCharacter(char)
if pl:FindFirstChild("Buy") ~= nil then -- проверяем наличие переменной
if pl.Buy.Value == 3 then
pl.PlayerGui.Main.GameDev.Visible=false
pl.PlayerGui.Main.Answer.Visible=true
end
end
end)
end)
-- проверка на совершении покупки
game:GetService("MarketplaceService").PromptGamePassPurchaseFinished:Connect(function(plr,ido,purchased)
if purchased and ido == id1 then -- пропуск с id1
-- local char = plr.Character
buy(plr.Name)
end
end)
Как можно заметить, я немножко оптимизировал. Вынес код необходимых действий из блоков на вход игрока и покупку игроком GamePass в самой игре в отдельный блок, которому в качестве параметра просто передаётся имя игрока.
В подключении игрока теперь не только создание чара — CharacterAdded, но и его удаление — CharacterRemoving. Это удаление и происходит при смерти персонажа или когда нажата вышеупомянутая кнопка.
Так же добавил «уборку» с экрана возможности покупки GamePass-а, если он у нас уже куплен. Нечего ему глаза мозолить.
Так как у GamePass-а значение 5 чуть выше, чем у временной покупки — 3 , то обошёлся одной переменной.
local MS = game:GetService("MarketplaceService") -- сокращение для вызова сервиса
local function processReceipt(ReceiptInfo)
local player = game:GetService("Players"):GetPlayerByUserId(ReceiptInfo.PlayerId) -- получаем Id игрока
if not player then -- если не игрок, то вылетаем
return Enum.ProductPurchaseDecision.NotProcessedYet
end
-- если игрок найден, выполняем действия
-- print(ReceiptInfo.PlayerId .. " buy " .. ReceiptInfo.ProductId)
local char=player.Character
if player:FindFirstChild("Buy") == nil then -- проверяем наличие переменной, если нет - создаём
local Buy = Instance.new("IntValue")
Buy.Parent = player
Buy.Name = "Buy"
Buy.Value=3
else
player.Buy.Value = 3
end
char.Humanoid.WalkSpeed = 30
-- а теперь убираем Frame покупки
local pl = game.Players:FindFirstChild(player.Name)
if pl ~= nil then
pl.PlayerGui.Main.GameDev.Visible=false
pl.PlayerGui.Main.Answer.Visible=true
end
-- возвращаем, что покупка произведена
return Enum.ProductPurchaseDecision.PurchaseGranted
end
MS.ProcessReceipt= processReceipt
Как видим, всё оказалось не так уж и страшно, если знать что искать.
Остальные скрипты (для кнопок) остались без изменений.
И как всегда, результат можно увидеть и опробовать на странице арифметического тренажёра:
https://www.roblox.com/games/4789977549/Math-Rocket