Pacote de Pacotes

*Este conteúdo é traduzido por IA (Beta) e pode conter erros. Para ver a página em inglês, clique aqui.

O pacote de recursos Pacotes oferece funcionalidade pronta para uso para vender coleções de itens para jogadores com desconto. Você pode escolher se deseja permitir que os jogadores comprem pacotes usando uma moeda personalizada dentro da experiência ou Robux, qual tipo de pacote deseja usar, que conjunto de itens deseja vender e como deseja avisar os jogadores durante o jogo.

Usando as opções de personalização do pacote, você pode adaptar seus pacotes para atender aos objetivos de design e monetização de suas experiências, como:

  • Alcançar uma baixa métrica de taxa de conversão oferecendo pacotes de iniciantes com desconto que fornecem valor aos novos jogadores e incentivam gastos iniciais.
  • Aumentar a profundidade de gasto agrupando itens a vários pontos de preço para atrair uma variedade de jogadores.
  • Monetizar operações ao vivo (LiveOps) eventos oferecendo pacotes limitados de itens exclusivos.

Obter pacote

A Loja do Criador é uma aba da Caixa de Ferramentas que você pode usar para encontrar todos os ativos criados pela Roblox e pela comunidade Roblox para uso em seus projetos, incluindo modelos, imagens, malhas, áudio, plug-ins, vídeos e fontes. Você pode usar a Loja do Criador para adicionar um ou mais ativos diretamente em uma experiência aberta, incluindo pacotes de recursos!

Todo pacote de recursos requer o pacote de recursos Núcleo para funcionar corretamente. Uma vez que os ativos do pacote de recursos Núcleo e Pacotes estejam em seu inventário, você pode reutilizá-los em qualquer projeto na plataforma.

Para obter os pacotes de seu inventário para sua experiência:

  1. Adicione o pacote de recursos Núcleo e Pacotes ao seu inventário no Studio clicando no link Adicionar ao Inventário no seguinte conjunto de componentes.

  2. No menu Janela do Studio ou na barra de ferramentas da aba Início, abra a Caixa de Ferramentas.

  3. Na janela Caixa de Ferramentas, clique na aba Inventário. A ordenação Meus Modelos é exibida.

    Janela da Caixa de Ferramentas do Studio com a aba Inventário destacada.
  4. Clique no bloco do pacote Pacote de Recursos Núcleo, em seguida, no bloco do pacote Pacote de Recursos de Pacotes. Ambas as pastas de pacotes são exibidas na janela Explorer.

  5. Arraste as pastas de pacotes para ReplicatedStorage.

  6. Permita chamadas de armazenamento de dados para rastrear as compras dos jogadores com os pacotes.

    1. Abra a janela Configurações da Experiência em ArquivoExperiência.
    2. Navegue até a aba Segurança e habilite Habilitar Acesso do Studio aos Serviços de API.

Definir moedas

Se sua experiência tiver seu próprio sistema de moeda, você pode registrá-las com o pacote de recursos Núcleo definindo-as em ReplicatedStorage.FeaturePackagesCore.Configs.Currencies. Há um exemplo comentado de uma moeda Gems já neste arquivo; substitua-o pela sua própria.

Moedas

Gems = {
displayName = "Gemas",
symbol = "💎",
icon = nil,
},

O script Moedas informa ao pacote de recursos Núcleo alguns metadados sobre sua moeda:

  • (obrigatório) displayName - O nome da sua moeda. Se você não especificar um símbolo ou ícone, este nome será usado nos botões de compra (i.e. "100 Gemas").
  • (opcional) symbol - Se você tiver um caractere de texto para usar como ícone para sua moeda, este será usado em vez do displayName nos botões de compra (i.e. "💎100").
  • (opcional) icon - Se você tiver um ícone de imagem com AssetId para sua moeda, este será usado em vez do displayName nos botões de compra (i.e. a imagem será colocada à esquerda do preço "🖼️100")

Uma vez que sua moeda esteja configurada, você precisa especificar manualmente o preço do pacote, a moeda e o ícone para a exibição em tempo real em vez que essa informação seja obtida do produto do desenvolvedor associado ao pacote.

Pacotes

-- Se você quiser usar um produto dev, deve fornecer um devProductId exclusivo, usado apenas por um pacote.
-- Nós buscaremos o preço e ícone do pacote a partir do produto do desenvolvedor
pricing = {
priceType = CurrencyTypes.PriceType.Marketplace,
devProductId = 1795621566,
},
-- Caso contrário, se você quiser usar a moeda dentro da experiência em vez de um produto dev, pode usar o seguinte:
-- O preço aqui está na moeda dentro da experiência, não em Robux
pricing = {
priceType = CurrencyTypes.PriceType.InExperience,
price = 79,
currencyId = "Gems",
icon = 18712203759,
},

Você também precisa referenciar o script BundlesExample para chamar setInExperiencePurchaseHandler.

BundlesExample

local function awardInExperiencePurchase(
_player: Player,
_bundleId: Types.BundleId,
_currencyId: CurrencyTypes.CurrencyId,
_price: number
)
-- Verifica se o jogador tem moeda suficiente para comprar o pacote
-- Atualiza os dados do jogador, fornece itens, etc.
-- Deduz a moeda do jogador
task.wait(2)
return true
end
local function initializePurchaseHandlers()
local bundles = Bundles.getBundles()
for bundleId, bundle in bundles do
-- O pacote não está associado a um produto do desenvolvedor se não tiver tipo de preço de mercado
if not bundle or bundle.pricing.priceType ~= "Marketplace" then
continue
end
Bundles.setPurchaseHandler(bundleId, awardMarketplacePurchase)
receiptHandlers[bundle.pricing.devProductId] = receiptHandler
end
-- Se você tiver quaisquer moedas dentro da experiência que estiver usando para pacotes, defina o manipulador aqui
for currencyId, _ in Currencies do
Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)
end
end

Especificamente, você precisa preencher awardInExperiencePurchase, que é chamado por um loop através de Currencies dentro do exemplo initializePurchaseHandlers (ou seja, cada currencyId é conectado ao manipulador através de Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)).

Definir pacotes

Todos os pacotes oferecidos em sua experiência podem ser definidos dentro de ReplicatedStorage.Bundles.Configs.Bundles, com tipos exportados do script Types na mesma pasta.

Se você estiver usando um devProductId, precisará atualizar o devProductId principal do pacote para corresponder ao que está em sua experiência. Isso é o que será solicitado através do MarketplaceService para comprar o pacote em si. É altamente recomendável usar um novo produto do desenvolvedor para o pacote para facilitar o rastreamento de vendas separadas.

Se você deseja um pacote com vários itens, e se estes já forem representados por produtos do desenvolvedor em sua experiência, você não precisa definir explicitamente o preço/item/assetId/nome, que será obtido através das informações do produto:

README

{
itemType = ItemTypes.ItemType.DevProduct,
devProductId = <DEV_PRODUCT_ID>,
metadata = {
caption = {
text = "x1",
color = Color3.fromRGB(236, 201, 74),
} -- A legenda é opcional! Você também pode omitir este campo
}
},

Caso contrário, você pode configurar manualmente esses detalhes do item:

README

{
itemType = ItemTypes.ItemType.Robux,
priceInRobux = 49,
icon = <IMAGE_ASSET_ID>,
metadata = {
caption = {
text = "x1",
color = Color3.fromRGB(236, 201, 74),
} -- A legenda é opcional! Você também pode omitir este campo
}
},

Por exemplo, todo o seu pacote provavelmente parecerá assim:

README

local starterBundle: Types.RelativeTimeBundle = {
bundleType = Types.BundleType.RelativeTime,
-- Se você quiser usar um produto dev, deve fornecer um devProductId exclusivo, usado apenas por um pacote.
-- Nós buscaremos o preço e ícone do pacote a partir do produto do desenvolvedor
pricing = {
priceType = CurrencyTypes.PriceType.Marketplace,
devProductId = <DEV_PRODUCT_ID>,
},
-- Caso contrário, se você quiser usar a moeda dentro da experiência em vez de um produto dev, pode usar o seguinte:
-- O preço aqui está na moeda dentro da experiência, não em Robux
-- pricing = {
-- priceType = CurrencyTypes.PriceType.InExperience,
-- price = 79,
-- currencyId = <CURRENCY_ID>,
-- icon = <IMAGE_ASSET_ID>,
-- },
includedItems = {
[1] = {
-- O item em si não é vendido via um produto do desenvolvedor, então indique quanto vale em Robux e dê um ícone
-- O priceInRobux ajuda os Pacotes a mostrar o valor relativo do preço do pacote em comparação com a soma de seus conteúdos
itemType = ItemTypes.ItemType.Robux,
priceInRobux = 49,
icon = <IMAGE_ASSET_ID>,
-- Alternativamente, se isso possui um produto do desenvolvedor, deixe de fora o preço e o ícone acima e apenas defina o devProductId
-- O preço e o ícone serão obtidos do produto do desenvolvedor
-- devProductId = <ITEM_DEV_PRODUCT_ID>
-- Existem mais campos de metadados opcionais que são específicos da UI, se necessário
metadata = {
caption = {
text = "x1",
color = Color3.fromRGB(236, 201, 74),
},
},
},
[2] = {
itemType = ItemTypes.ItemType.Robux,
priceInRobux = 99,
icon = <IMAGE_ASSET_ID>,
metadata = {
caption = {
text = "x1",
color = Color3.fromRGB(236, 201, 74),
},
},
},
[3] = {
itemType = ItemTypes.ItemType.Robux,
priceInRobux = 149,
icon = <IMAGE_ASSET_ID>,
metadata = {
caption = {
text = "x1",
color = Color3.fromRGB(236, 201, 74),
},
},
},
},
singleUse = true, -- Uma vez comprado ou expirado, não é mais válido, mesmo se sua experiência tentar solicitar (onPlayerAdded). Você pode definir isso como falso enquanto testa no studio.
durationInSeconds = 900, -- 15 minutos
includesOfflineTime = false, -- Apenas conta o tempo decorrido na experiência
metadata = {
displayName = "PACOTE INICIANTE",
description = "Economize 75% e tenha uma vantagem inicial!",
},
}

Integrar lógica do servidor

Dê uma olhada em ReplicatedStorage.Bundles.Server.Examples.BundlesExample, que mostra como seu servidor interagirá com o pacote de recursos Pacotes e os métodos acima no ModuleScript. Os trechos abaixo são desse script.

Você precisará conectar quatro coisas principais após arrastar o pacote de recursos Pacotes para sua experiência:

  1. Conectar manipuladores de compra através de Bundles.setPurchaseHandler para especificar as funções a serem chamadas para conceder itens quando uma compra está sendo processada.

    BundlesExample

    local function awardMarketplacePurchase(_player: Player, _bundleId: Types.BundleId, _receiptInfo: { [string]: any })
    -- Atualiza os dados do jogador, fornece itens, etc.
    -- ... E registra receiptInfo.PurchaseId para que possamos verificar se o usuário já possui este pacote
    task.wait(2)
    return Enum.ProductPurchaseDecision.PurchaseGranted
    end
    local function awardInExperiencePurchase(
    _player: Player,
    _bundleId: Types.BundleId,
    _currencyId: CurrencyTypes.CurrencyId,
    _price: number
    )
    -- Verifica se o jogador tem moeda suficiente para comprar o pacote
    -- Atualiza os dados do jogador, fornece itens, etc.
    -- Deduz a moeda do jogador
    task.wait(2)
    return true
    end
    local function initializePurchaseHandlers()
    local bundles = Bundles.getBundles()
    for bundleId, bundle in bundles do
    -- O pacote não está associado a um produto do desenvolvedor se não tiver tipo de preço de mercado
    if not bundle or bundle.pricing.priceType ~= "Marketplace" then
    continue
    end
    Bundles.setPurchaseHandler(bundleId, awardMarketplacePurchase)
    receiptHandlers[bundle.pricing.devProductId] = receiptHandler
    end
    -- Se você tiver algumas moedas dentro da experiência que está usando para pacotes, defina o manipulador aqui
    for currencyId, _ in Currencies do
    Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)
    end
    end
  2. Conecte sua lógica para MarketplaceService.ProcessReceipt, mas isso pode ser feito em outro lugar se sua experiência já tiver produtos do desenvolvedor à venda. Essencialmente, quando um recibo de produto do desenvolvedor estiver sendo processado, eles agora chamarão Bundles.getBundleByDevProduct para verificar se o produto pertence a um pacote. Se sim, o script chamará Bundles.processReceipt.

    BundlesExample

    -- Processar recibo do mercado para determinar se o jogador precisa ser cobrado ou não
    local function processReceipt(receiptInfo): Enum.ProductPurchaseDecision
    local userId, productId = receiptInfo.PlayerId, receiptInfo.ProductId
    local player = Players:GetPlayerByUserId(userId)
    if not player then
    return Enum.ProductPurchaseDecision.NotProcessedYet
    end
    local handler = receiptHandlers[productId] -- Obter o manipulador para o produto
    local success, result = pcall(handler, receiptInfo, player) -- Chamar o manipulador para verificar se a lógica de compra é bem-sucedida
    if not success or not result then
    warn("Falha ao processar recibo:", receiptInfo, result)
    return Enum.ProductPurchaseDecision.NotProcessedYet
    end
    return Enum.ProductPurchaseDecision.PurchaseGranted
    end
    local function receiptHandler(receiptInfo: { [string]: any }, player: Player)
    local bundleId, _bundle = Bundles.getBundleByProductId(receiptInfo.ProductId)
    if bundleId then
    -- Essa compra pertence a um pacote, deixe os Pacotes lidarem com isso
    local purchaseDecision = Bundles.processReceiptAsync(player, bundleId, receiptInfo)
    return purchaseDecision == Enum.ProductPurchaseDecision.PurchaseGranted
    end
    -- Esta compra não pertence a um pacote,
    -- ... Lide com toda a sua lógica existente aqui, se você tiver
    return false
    end
  3. Conecte Players.PlayerAdded:Connect(Bundles.OnPlayerAdded) para que o pacote de recursos Pacotes re-solicite quaisquer pacotes ativos que ainda não expiraram para um jogador.

    README

    local function onPlayerAdded(player: Player)
    -- Informe aos Pacotes quando o jogador entrar para que eles possam recarregar seus dados
    Bundles.onPlayerAdded(player)
    -- Se você tivesse algum pacote inicial que gostaria de oferecer a todos os novos usuários, poderia solicitar isso aqui
    -- ... Os Pacotes lidaram se o jogador já o comprou ou se expirou, já que não pode ser repetido
    -- Bundles.promptIfValidAsync(player, "StarterBundle")
    -- Chamando isso aqui apenas como exemplo, você pode chamar isso sempre que ou onde quiser
    onPromptBundleXYZEvent(player)
    end
  4. Solicite pacotes. Embora isso dependa da jogabilidade, o exemplo solicita aos jogadores que recebam um StarterBundle onPlayerAdded.

    • A lógica do pacote de recursos Pacotes garante que cada jogador não receba uma oferta repetida se já comprou o pacote, ou se deixou a oferta expirar (com base na configuração do pacote).

    • Sempre que você quiser solicitar um pacote a um jogador, chame Bundles.promptIfValidAsync(player, bundleId).

    README

    local function onPromptBundleXYZEvent(player: Player)
    -- Conecte qualquer evento de experiência que deseja usar para determinar quando um jogador recebe a solicitação do pacote
    -- ... Isso será sempre que você atender aos seus critérios de elegibilidade para solicitar um pacote ao jogador
    -- ... Por exemplo, se você quiser solicitar um pacote quando um jogador entrar, ou quando um jogador subir de nível
    task.spawn(Bundles.promptIfValidAsync, player, <Some_Bundle_Id>)
    -- ... Se criar vários pacotes, usar task.spawn() para envolver a chamada da função acima minimizará discrepâncias entre contagens regressivas
    end

Considere as seguintes orientações de melhores práticas sobre gravações redundantes de ReceiptIds:

  • Enquanto o pacote de recursos Pacotes grava ReceiptIds para evitar processar o mesmo recibo duas vezes, você também deve estar registrando ReceiptIds dentro de suas tabelas para que, se o fluxo de compra falhar após o manipulador de compra já ter terminado, você saiba em uma nova tentativa posterior que não deve conceder os itens novamente.

  • O pacote de recursos Pacotes não registrará o ReceiptId se a compra falhar em qualquer etapa, portanto, você deve garantir que está registrando o ReceiptId em suas tabelas antes de processar o recibo como parte do seu purchaseHandler.

  • Essa redundância ajuda a garantir que toda a lógica de compra tenha sido tratada adequadamente e que seu armazenamento de dados e o armazenamento de dados do pacote de recursos Pacotes alcancem consistência eventual, com seu armazenamento de dados sendo a fonte da verdade.

Configurar constantes

Constantes para o pacote de recursos Núcleo residem em dois locais:

  • Constantes compartilhadas residem em ReplicatedStorage.FeaturePackagesCore.Configs.SharedConstants.

  • Constantes específicas do pacote, neste caso o pacote de recursos Pacotes, residem em ReplicatedStorage.Bundles.Configs.Constants.

As principais coisas que você pode querer ajustar para atender aos requisitos de design de sua experiência:

  • IDs de ativos de som
  • Duração do efeito de compra e cores das partículas
  • Colapsabilidade da exibição em tempo real

Além disso, você pode encontrar strings para tradução organizadas em um único local: ReplicatedStorage.FeaturePackagesCore.Configs.TranslationStrings.

Personalizar componentes de UI

Modificando os objetos do pacote, como cores, fontes e transparência, você pode ajustar a apresentação visual de suas solicitações de pacotes. No entanto, lembre-se de que se você mover quaisquer objetos na hierarquia, o código não conseguirá encontrá-los, e você precisará fazer ajustes no seu código.

Uma solicitação é composta por dois componentes de alto nível:

  • PromptItem – O componente individual repetido para cada item dentro de um pacote (imagem do item, legenda, nome, preço).
  • Prompt – A janela de solicitação em si.

A exibição em tempo real também é composta por dois componentes:

  • HudItem – Um componente individual que representa cada opção de menu na exibição em tempo real.
  • Hud – Para ser preenchido programaticamente com HudItems.

Se você quiser ter maior controle sobre a exibição em tempo real, em vez de apenas usar a UI HUD existente dentro de ReplicatedStorage.Bundles.Objects.BundlesGui, você pode mover as coisas para atender aos seus próprios requisitos de design. Basta garantir que você atualize o comportamento do script do cliente no script ReplicatedStorage.Bundles.Client.UIController.

Referência da API

Tipos

TempoRelativo

Uma vez que o pacote TempoRelativo é oferecido a um jogador, ele permanecerá disponível até que a duração de tempo se esgote. Este tipo é exibido na exibição em tempo real do jogador e automaticamente solicita em futuras sessões até que o pacote expire ou o jogador o compre.

Um exemplo comum deste tipo de pacote é uma oferta de pacote inicial de uso único que é exibida a todos os novos jogadores por 24 horas.

NomeTipoDescrição
includeOfflineTimebool(Opcional) Se não definido, apenas o tempo gasto na experiência contará para a duração restante da oferta.
singleUsebool(Opcional) Se não definido, a compra pode ser reativada após ser comprada ou expirada.

Se definido, uma vez comprada ou expirado pela primeira vez, não poderá ser solicitado novamente, mesmo que você chame Bundles.promptIfValidAsync com o bundleId.

TempoFixo

Uma vez que o pacote TempoFixo é oferecido a um jogador, ele permanecerá disponível até o final do horário universal coordenado (UTC) definido. Este tipo é exibido na exibição em tempo real do jogador e automaticamente solicita em futuras sessões até que o pacote expire ou o jogador o compre.

Um exemplo comum deste tipo de pacote é uma oferta de feriado que está disponível apenas por um determinado mês.

UmaVez

Um pacote UmaVez está disponível apenas no momento em que é oferecido a um jogador. Ele não é exibido na exibição em tempo real do jogador, e uma vez que um jogador fecha a solicitação, não pode ser reaberta até que seja solicitada novamente pelo servidor.

Um exemplo comum deste tipo de pacote é uma oferta para comprar mais moeda dentro da experiência no momento em que um jogador fica sem.

©2026 Roblox Corporation, Roblox, o logotipo Roblox e Powering Imagination estão entre nossas marcas registradas e não registradas nos EUA e em outros países.