Игровые пропуска и покупки: как игра начинает приносить робуксы
Чем Game Pass отличается от Developer Product, как обработать покупку без потери денег игрока и почему обработчик чека обязан выдерживать повтор.
Сложно 25 минут проверен 1 час назад
Что понадобится
Опубликованная игра и основы Luau. Гайд «Клиент и сервер в Roblox» стоит прочитать до этого.
Текст гайда «Игровые пропуска и покупки: как игра начинает приносить робуксы»
Содержание
В Roblox есть два способа взять с игрока робуксы внутри своей игры, и путать их нельзя. Один — покупка навсегда, второй — покупка, которую можно повторять. От выбора зависит и код, и то, как игра будет ощущаться.
| Game Pass | Developer Product | |
|---|---|---|
| Сколько раз покупается | Один раз навсегда | Сколько угодно |
| Типичный пример | VIP-зона, второй прыжок, скин | Пачка монет, воскрешение, ускорение |
| Где создаётся | Creator Hub → опыт → Passes | Creator Hub → опыт → Developer Products |
| Как проверить владение | UserOwnsGamePassAsync | Владения нет — есть факт оплаты |
| Что вызывает покупку | PromptGamePassPurchase | PromptProductPurchase |
| Где обрабатывается | Проверкой при входе и по событию | Только в ProcessReceipt |
Всё, что связано с деньгами, живёт на сервере
Клиент может только показать окно покупки. Проверять владение пропуском, выдавать предмет и записывать покупку обязан сервер. Иначе «покупка» превращается в одну строчку, которую игрок отправляет сам себе.
Game Pass: покупка навсегда#
Пропуск создаётся в Creator Hub: открываешь свой опыт, раздел Monetization → Passes, задаёшь название, картинку и цену. После создания у пропуска появляется числовой идентификатор — он и нужен коду.
Проверять владение надо в двух местах: когда игрок заходит и когда он покупает пропуск прямо во время игры. Второе забывают чаще всего — и человек, только что заплативший, не получает ничего до перезахода.
-- Script в ServerScriptService.
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local PASS_ID = 000000 -- идентификатор из Creator Hub
local function applyPass(player)
local character = player.Character
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
if humanoid then
humanoid.WalkSpeed = 32
end
end
local function ownsPass(player)
-- Запрос уходит наружу и может не ответить: без pcall
-- один сбой сети уронит вход в игру.
local ok, owns = pcall(function()
return MarketplaceService:UserOwnsGamePassAsync(player.UserId, PASS_ID)
end)
return ok and owns
end
Players.PlayerAdded:Connect(function(player)
if not ownsPass(player) then
return
end
player.CharacterAdded:Connect(function()
task.wait() -- персонаж собирается не мгновенно
applyPass(player)
end)
if player.Character then
applyPass(player)
end
end)
-- Покупка прямо во время игры: ждать перезахода игрок не должен.
MarketplaceService.PromptGamePassPurchaseFinished:Connect(function(player, passId, purchased)
if purchased and passId == PASS_ID then
applyPass(player)
end
end)Не кэшируй владение навсегда
Ответ UserOwnsGamePassAsync можно запомнить на время сессии, но не на весь сервер и не в общем списке. Пропуск можно купить в середине игры, и жёсткий кэш оставит покупателя без покупки.
Developer Product: покупка, которую повторяют#
Товар создаётся там же, в разделе Developer Products. Покупка вызывается с клиента или с сервера через PromptProductPurchase, а выдача происходит в одном-единственном месте — в обработчике ProcessReceipt.
Это самая ответственная функция во всей монетизации. Roblox вызывает её после оплаты и смотрит на ответ. Вернёшь PurchaseGranted — покупка считается выданной. Вернёшь что-то другое или ошибёшься — платформа позовёт функцию снова, возможно, уже на другом сервере. Значит, обработчик обязан быть устойчивым к повтору: дважды выданная пачка монет так же плоха, как невыданная.
local MarketplaceService = game:GetService("MarketplaceService")
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
-- Отдельное хранилище только под чеки: по нему видно,
-- что эта покупка уже обработана.
local receipts = DataStoreService:GetDataStore("Receipts_v1")
local PRODUCTS = {
[000000] = function(player) -- идентификатор товара из Creator Hub
local coins = player.leaderstats.Coins
coins.Value += 100
end,
}
local function handle(info)
local player = Players:GetPlayerByUserId(info.PlayerId)
if player == nil then
-- Игрок вышел — покупку не теряем: платформа
-- позовёт нас снова, когда он вернётся.
return Enum.ProductPurchaseDecision.NotProcessedYet
end
local grant = PRODUCTS[info.ProductId]
if grant == nil then
warn("Неизвестный товар: " .. tostring(info.ProductId))
return Enum.ProductPurchaseDecision.NotProcessedYet
end
local key = info.PurchaseId
local alreadyDone = false
-- Одна и та же покупка не должна сработать дважды,
-- поэтому отметка о ней ставится в том же обновлении.
local ok = pcall(function()
receipts:UpdateAsync(key, function(saved)
if saved then
alreadyDone = true
return saved
end
return os.time()
end)
end)
if not ok then
return Enum.ProductPurchaseDecision.NotProcessedYet
end
if not alreadyDone then
local granted = pcall(grant, player)
if not granted then
return Enum.ProductPurchaseDecision.NotProcessedYet
end
end
return Enum.ProductPurchaseDecision.PurchaseGranted
end
MarketplaceService.ProcessReceipt = handleОбработчик ProcessReceipt в игре только один
MarketplaceService.ProcessReceipt — это одно поле, а не событие. Назначишь его во втором скрипте — первый перестанет работать молча, и часть покупок исчезнет. Держи всю выдачу товаров в одном месте.
Сколько денег доходит до тебя#
Из цены, которую платит игрок, до создателя доходит не всё: часть удерживает площадка. Точную долю и текущие условия смотри в Creator Hub — цифры меняются, и полагаться на чей-то пост трёхлетней давности не стоит. Там же видно, сколько заработала каждая позиция.
Заработанные робуксы приходят на аккаунт создателя или в группу, если игра принадлежит группе. Как устроен вывод и какие для него условия — в гайде «Откуда берутся робуксы».
Честная монетизация: что можно и чего нельзя#
- Показывай, что именно покупается, до оплаты, а не после
- Не обещай в описании больше, чем даёт покупка: жалобы прилетают быстро
- Не ломай игру ради продаж — «без пропуска играть невозможно» отпугивает новичков сильнее, чем приносит денег
- Случайные наборы предметов регулируются правилами платформы: шансы нужно показывать честно, и правила эти лучше перечитать в Creator Hub перед запуском
- Не проси деньги за то, что игрок уже купил, и не забирай купленное
- Обмен робуксов «в личке» вне платформы — это скам и нарушение правил; внутриигровые покупки идут только через окно Roblox
Сначала игра, потом магазин
Игры, которые начинают с продажи пропусков и заканчивают геймплеем, не удерживают игроков. Работает обратный порядок: сделай так, чтобы в игру хотелось вернуться, и только потом добавляй платное — тем, кому и так нравится.
Дальше стоит посмотреть «Статистику своей игры»: там видно, покупают ли вообще, и «DataStore» — покупка должна пережить выход из игры. Про безопасность сделок между игроками — отдельный гайд.
Обсуждение
Пиши по делу и без личных данных. Ссылки на сторонние сайты, обмен и «бесплатные робуксы» мы скрываем — почему.
Комментарии пишут те, кто вошёл — так спокойнее всем.
ВойтиПока никто ничего не написал. Будь первым.