O pacote de recursos Pacotes oferece funcionalidade pronta para uso para vender coleções de itens para jogadores com desconto. Você pode escolher se permite que os jogadores comprem pacotes usando uma moeda personalizada do jogo ou Robux, qual tipo de pacote deseja usar, qual conjunto de itens deseja vender e como deseja solicitar aos 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 dos seus jogos, como:
- Alvo de uma baixa métrica de taxa de conversão oferecendo pacotes de início com desconto que proporcionam valor aos novos jogadores e incentivam gastos iniciais.
- Aumentar a profundidade de gastos agrupando itens em vários pontos de preço para atrair uma variedade de jogadores.
- Monetizar operações ao vivo (LiveOps) eventos oferecendo pacotes de itens exclusivos por tempo limitado.

Obter pacote
A Loja do Criador é uma aba da Caixa de Ferramentas que você pode usar para encontrar todos os ativos feitos pela Roblox e pela comunidade Roblox para uso em seus projetos, incluindo modelos, imagens, malhas, áudio, plugins, vídeos e fontes. Você pode usar a Loja do Criador para adicionar um ou mais ativos diretamente em um jogo aberto, incluindo pacotes de recursos!
Todo pacote de recursos requer o pacote de recursos Núcleo para funcionar corretamente. Uma vez que os ativos dos pacotes de recursos Núcleo e Pacotes estejam em seu inventário, você pode reutilizá-los em qualquer projeto na plataforma.
Para obter os pacotes do seu inventário para o seu jogo:
Adicione o pacote de recursos Núcleo e Pacotes ao seu inventário dentro do 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 da Caixa de Ferramentas, clique na aba Inventário. A classificação Meus Modelos é exibida.

Clique no bloco Pacote de Recursos Núcleo, depois no bloco Pacote de Recursos de Pacotes. Ambas as pastas de pacotes são exibidas na janela Explorer.
Arraste as pastas de pacotes para ReplicatedStorage.
Permita que as chamadas de armazenamento de dados rastreiem as compras dos jogadores com os pacotes.
- Abra a janela Arquivo ⟩ Configurações da Experiência do Studio.
- Navegue até a aba Segurança, depois ative Habilitar Acesso do Studio aos Serviços da API.
Definir moedas
Se o seu jogo tem 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.
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 é usado nos botões de compra (ou seja, "100 Gemas").
- (opcional) symbol - Se você tiver um caractere de texto para usar como ícone para sua moeda, este é usado em vez do displayName nos botões de compra (ou seja, "💎100").
- (opcional) icon - Se você tiver um ícone de imagem AssetId para sua moeda, este é usado em vez do displayName nos botões de compra (ou seja, 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 de cabeçalho em vez de essas informações serem buscadas do produto do desenvolvedor associado ao pacote.
-- Se você quiser usar um produto do desenvolvedor, deve fornecer um devProductId exclusivo, usado apenas por um pacote.
-- Nós buscaremos o preço e o ícone do pacote do produto do desenvolvedor
pricing = {
priceType = CurrencyTypes.PriceType.Marketplace,
devProductId = 1795621566,
},
-- Caso contrário, se você quiser usar moeda do jogo em vez de um produto do desenvolvedor, pode usar o seguinte:
-- O preço aqui está na moeda do jogo, 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.
local function awardInExperiencePurchase(
_player: Player,
_bundleId: Types.BundleId,
_currencyId: CurrencyTypes.CurrencyId,
_price: number
)
-- Verifique se o jogador tem moeda suficiente para comprar o pacote
-- Atualize os dados do jogador, dê itens, etc.
-- Deduzir 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 alguma moeda do jogo que está usando para pacotes, defina o manipulador aqui
for currencyId, _ in Currencies do
Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)
end
endEspecificamente, você precisa preencher awardInExperiencePurchase, que é chamado por um loop através de Currencies dentro do exemplo initializePurchaseHandlers (ou seja, cada currencyId está conectado ao manipulador através de Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)).
Definir pacotes
Todos os pacotes oferecidos em seu jogo 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 seu jogo. Isso é o que será solicitado através de 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ê quiser um pacote com vários itens, e se estes já estiverem representados por produtos do desenvolvedor em seu jogo, você não precisa definir explicitamente o preço/itemId/nome do item, que será buscado via informações do produto:
{
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:
{
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, seu pacote inteiro provavelmente parecerá assim:
local starterBundle: Types.RelativeTimeBundle = {
bundleType = Types.BundleType.RelativeTime,
-- Se você quiser usar um produto do desenvolvedor, deve fornecer um devProductId exclusivo, usado apenas por um pacote.
-- Nós buscaremos o preço e o ícone do pacote do produto do desenvolvedor
pricing = {
priceType = CurrencyTypes.PriceType.Marketplace,
devProductId = <DEV_PRODUCT_ID>,
},
-- Caso contrário, se você quiser usar moeda do jogo em vez de um produto do desenvolvedor, pode usar o seguinte:
-- O preço aqui está na moeda do jogo, 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 tiver um produto do desenvolvedor, omita o preço e o ícone acima e apenas defina o devProductId
-- O preço e o ícone serão buscados 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 que seu jogo tente solicitar (onPlayerAdded). Você pode tornar isso falso enquanto testa no studio.
durationInSeconds = 900, -- 15 minutos
includesOfflineTime = false, -- Contar apenas o tempo decorrido no jogo
metadata = {
displayName = "PACOTE INICIAL",
description = "Economize 75% e comece com vantagem!",
},
}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 ao arrastar o pacote de recursos Pacotes para o seu jogo:
Conecte manipuladores de compra através de Bundles.setPurchaseHandler para especificar as funções a serem chamadas para premiar itens quando uma compra está sendo processada.
BundlesExamplelocal function awardMarketplacePurchase(_player: Player, _bundleId: Types.BundleId, _receiptInfo: { [string]: any })-- Atualize os dados do jogador, dê itens, etc.-- ... E registre 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)-- Verifique se o jogador tem moeda suficiente para comprar o pacote-- Atualize os dados do jogador, dê itens, etc.-- Deduzir 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 alguma moeda do jogo 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 seu jogo já tiver produtos do desenvolvedor à venda. Essencialmente, quando um recibo de produto do desenvolvedor está sendo processado, eles agora chamarão Bundles.getBundleByDevProduct para verificar se o produto pertence a um pacote. Se sim, o script então chama 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-- Esta 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ê tiver algumareturn falseendConecte Players.PlayerAdded:Connect(Bundles.OnPlayerAdded) para que o pacote de recursos Pacotes re-solicite quaisquer pacotes ativos que ainda não tenham expirado para um jogador.
READMElocal function onPlayerAdded(player: Player)-- Informe aos Pacotes quando o jogador entrar para que possa 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 lidarão se o jogador já o comprou ou se expirou, pois não é repetível-- Bundles.promptIfValidAsync(player, "StarterBundle")-- Chamando isso aqui apenas como exemplo, você pode chamar isso sempre ou onde quiseronPromptBundleXYZEvent(player)endSolicite pacotes. Embora isso dependa da jogabilidade, o exemplo solicita aos jogadores 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 jogo que você 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:
Embora o pacote de recursos Pacotes registre ReceiptIds para evitar processar o mesmo recibo duas vezes, você também deve registrar 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 não premiar 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
As constantes para o pacote de recursos Núcleo vivem em dois locais:
Constantes compartilhadas vivem em ReplicatedStorage.FeaturePackagesCore.Configs.SharedConstants.
Constantes específicas do pacote, neste caso o pacote de recursos Pacotes, vivem em ReplicatedStorage.Bundles.Configs.Constants.
As principais coisas que você pode querer ajustar para atender aos requisitos de design do seu jogo:
- IDs de ativos de som
- Duração do efeito de compra e cores de partículas
- Colapsabilidade da exibição de cabeçalho
Além disso, você pode encontrar strings para tradução separadas em um local: ReplicatedStorage.FeaturePackagesCore.Configs.TranslationStrings.
Personalizar componentes da UI
Ao modificar os objetos do pacote, como cores, fonte e transparência, você pode ajustar a apresentação visual de suas solicitações de pacotes. No entanto, tenha em mente que se você mover qualquer um dos objetos ao redor hierarquicamente, 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 de cabeçalho também é composta por dois componentes:
- HudItem – Um componente individual que representa cada opção de menu na exibição de cabeçalho.
- Hud – Para ser preenchido programaticamente com HudItems.
Se você quiser ter maior controle sobre a exibição de cabeçalho, em vez de apenas usar a UI existente dentro de ReplicatedStorage.Bundles.Objects.BundlesGui, você pode mover as coisas para atender aos seus próprios requisitos de design. Apenas certifique-se de atualizar o comportamento do script do cliente no script ReplicatedStorage.Bundles.Client.UIController.
Referência da API
Tipos
RelativeTime
Uma vez que o pacote RelativeTime é oferecido a um jogador, ele permanece disponível até que a duração do tempo acabe. Este tipo é exibido na exibição de cabeçalho do jogador e solicita automaticamente em sessões futuras até que o pacote expire ou o jogador o compre.
Um exemplo comum desse tipo de pacote é uma oferta de pacote inicial de uso único que é exibida para todos os novos jogadores por 24 horas.
| Nome | Tipo | Descrição |
|---|---|---|
| includeOfflineTime | bool | (Opcional) Se não definido, apenas o tempo gasto no jogo 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 expirada pela primeira vez, não poderá ser solicitada novamente, mesmo que você chame Bundles.promptIfValidAsync com o bundleId. |
FixedTime
Uma vez que o pacote FixedTime é oferecido a um jogador, ele permanece disponível até o final do horário coordenado universal (UTC) definido. Este tipo é exibido na exibição de cabeçalho do jogador e solicita automaticamente em sessões futuras até que o pacote expire ou o jogador o compre.
Um exemplo comum desse tipo de pacote é uma oferta de feriado que está disponível apenas por um determinado mês.
OneTime
Um pacote OneTime está disponível apenas no momento em que é oferecido a um jogador. Ele não é exibido na exibição de cabeçalho do jogador e, uma vez que um jogador fecha a solicitação, não pode ser reaberto até que seja solicitado novamente pelo servidor.
Um exemplo comum desse tipo de pacote é uma oferta para comprar mais moeda do jogo no momento em que um jogador fica sem.