Заработок

Игровые пропуска и покупки: как игра начинает приносить робуксы

Чем Game Pass отличается от Developer Product, как обработать покупку без потери денег игрока и почему обработчик чека обязан выдерживать повтор.

Сложно 25 минут проверен 1 час назад

Что понадобится

Опубликованная игра и основы Luau. Гайд «Клиент и сервер в Roblox» стоит прочитать до этого.

Текст гайда «Игровые пропуска и покупки: как игра начинает приносить робуксы»

Содержание

В Roblox есть два способа взять с игрока робуксы внутри своей игры, и путать их нельзя. Один — покупка навсегда, второй — покупка, которую можно повторять. От выбора зависит и код, и то, как игра будет ощущаться.

Два вида покупок
Game PassDeveloper Product
Сколько раз покупаетсяОдин раз навсегдаСколько угодно
Типичный примерVIP-зона, второй прыжок, скинПачка монет, воскрешение, ускорение
Где создаётсяCreator Hub → опыт → PassesCreator Hub → опыт → Developer Products
Как проверить владениеUserOwnsGamePassAsyncВладения нет — есть факт оплаты
Что вызывает покупкуPromptGamePassPurchasePromptProductPurchase
Где обрабатываетсяПроверкой при входе и по событиюТолько в ProcessReceipt

Всё, что связано с деньгами, живёт на сервере

Клиент может только показать окно покупки. Проверять владение пропуском, выдавать предмет и записывать покупку обязан сервер. Иначе «покупка» превращается в одну строчку, которую игрок отправляет сам себе.

Game Pass: покупка навсегда#

Пропуск создаётся в Creator Hub: открываешь свой опыт, раздел Monetization → Passes, задаёшь название, картинку и цену. После создания у пропуска появляется числовой идентификатор — он и нужен коду.

Проверять владение надо в двух местах: когда игрок заходит и когда он покупает пропуск прямо во время игры. Второе забывают чаще всего — и человек, только что заплативший, не получает ничего до перезахода.

Пропуск на двойную скорость · Luau
-- 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 — покупка считается выданной. Вернёшь что-то другое или ошибёшься — платформа позовёт функцию снова, возможно, уже на другом сервере. Значит, обработчик обязан быть устойчивым к повтору: дважды выданная пачка монет так же плоха, как невыданная.

Обработчик чека, который не теряет и не задваивает · Luau
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» — покупка должна пережить выход из игры. Про безопасность сделок между игроками — отдельный гайд.

Обсуждение

Пиши по делу и без личных данных. Ссылки на сторонние сайты, обмен и «бесплатные робуксы» мы скрываем — почему.

Комментарии пишут те, кто вошёл — так спокойнее всем.

Войти

Пока никто ничего не написал. Будь первым.