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:
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.
No menu Janela do Studio ou na barra de ferramentas da aba Início, abra a Caixa de Ferramentas.
Na janela Caixa de Ferramentas, clique na aba Inventário. A ordenação Meus Modelos é exibida.

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.
Arraste as pastas de pacotes para ReplicatedStorage.
Permita chamadas de armazenamento de dados para rastrear as compras dos jogadores com os pacotes.
- Abra a janela Configurações da Experiência em Arquivo ⟩ Experiência.
- 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.
MoedasGems = {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 desenvolvedorpricing = {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 Robuxpricing = {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:
READMElocal 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 desenvolvedorpricing = {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údositemType = 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áriometadata = {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 minutosincludesOfflineTime = false, -- Apenas conta o tempo decorrido na experiênciametadata = {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:
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.
BundlesExamplelocal 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 pacotetask.wait(2)return Enum.ProductPurchaseDecision.PurchaseGrantedendlocal 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 jogadortask.wait(2)return trueendlocal 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 mercadoif not bundle or bundle.pricing.priceType ~= "Marketplace" thencontinueendBundles.setPurchaseHandler(bundleId, awardMarketplacePurchase)receiptHandlers[bundle.pricing.devProductId] = receiptHandlerend-- Se você tiver algumas moedas dentro da experiência que está usando para pacotes, defina o manipulador aquifor currencyId, _ in Currencies doBundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)endendConecte 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ãolocal function processReceipt(receiptInfo): Enum.ProductPurchaseDecisionlocal userId, productId = receiptInfo.PlayerId, receiptInfo.ProductIdlocal player = Players:GetPlayerByUserId(userId)if not player thenreturn Enum.ProductPurchaseDecision.NotProcessedYetendlocal handler = receiptHandlers[productId] -- Obter o manipulador para o produtolocal success, result = pcall(handler, receiptInfo, player) -- Chamar o manipulador para verificar se a lógica de compra é bem-sucedidaif not success or not result thenwarn("Falha ao processar recibo:", receiptInfo, result)return Enum.ProductPurchaseDecision.NotProcessedYetendreturn Enum.ProductPurchaseDecision.PurchaseGrantedendlocal 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 issolocal purchaseDecision = Bundles.processReceiptAsync(player, bundleId, receiptInfo)return purchaseDecision == Enum.ProductPurchaseDecision.PurchaseGrantedend-- Esta compra não pertence a um pacote,-- ... Lide com toda a sua lógica existente aqui, se você tiverreturn falseendConecte 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.
READMElocal function onPlayerAdded(player: Player)-- Informe aos Pacotes quando o jogador entrar para que eles possam recarregar seus dadosBundles.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 quiseronPromptBundleXYZEvent(player)endSolicite 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).
READMElocal 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íveltask.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 regressivasend
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.
| Nome | Tipo | Descrição |
|---|---|---|
| includeOfflineTime | bool | (Opcional) Se não definido, apenas o tempo gasto na experiência contará para a duração restante da oferta. |
| singleUse | bool | (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.