O pacote de recursos Bundles oferece funcionalidade fora da caixa para vender coleções de itens aos jogadores com desconto.Você pode escolher se permitir que os jogadores comprem pacotes usando uma moeda personalizada na experiência ou Robux, que tipo de pacote você deseja usar, quais itens você deseja vender e como deseja solicitar aos jogadores durante o jogabilidade.
Usando as opções de personalização do pacote, você pode ajustar seus pacotes para atender aos objetivos de design e monetização de suas experiências, como:
- Mirar uma taxa de conversão baixa de 2% métrica oferecendo pacotes iniciais descontados que fornecem valor a novos jogadores e incentivam gastos antecipados.
- Aumentar profundidade de gasto aumentando ao agrupar itens em vários pontos de preço para apelar a uma gama de jogadores.
- Monetizar operações ao vivo (LiveOps) eventos oferecendo pacotes de tempo limitado de itens exclusivos.

Obtenha o pacote
A Loja do Criador é uma aba da Caixa de Ferramentas que você pode usar para encontrar todos os recursos que são feitos por Roblox e a comunidade Roblox para uso dentro de seus projetos, incluindo modelo, imagem, malha, áudio, plugin, vídeo e fontes.Você pode usar a Loja do Criador para adicionar um ou mais recursos diretamente em uma experiência aberta, incluindo pacotes de recursos!
Cada pacote de recursos requer que o pacote de recursos Núcleo funcione corretamente.Uma vez que os recursos do pacote de recursos Núcleo e Pacotes estejam dentro do seu inventário, você pode reutilizá-los em qualquer projeto na plataforma.
Para obter os pacotes do seu inventário para a sua experiência:
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.
Na barra de ferramentas, selecione a aba Ver.
Clique em Caixa de Ferramentas . A janela Caixa de Ferramentas é exibida.
Na janela da Caixa de ferramentas , clique na aba Inventário . Os displays de Meus modelos são classificados.
Clique no mosaico tijolode Recursos Principal, então no mosaico tijolode Recursos de Pacote.Ambas as pastas de pacote são exibidas na janela Explorer .
Arraste as pastas do pacote para ReplicatedStorage .
Permita que as chamadas de armazenamento de dados rastreiem as compras do jogador com os pacotes.
- Na aba Início da barra de ferramentas, selecione Configurações do Jogo .
- Navegue até a aba Segurança , então ative Habilitar Acesso ao Studio aos Serviços de API .
Defina moedas
Se a sua experiência tiver seu próprio sistema de moeda, você pode registrá-las com o pacote de recursos Núcleo, definindo-as em >.Há um exemplo comentado de uma moeda de Gemas já neste arquivo; substitua-o pelo seu possuir.
Moedas
Gems = {displayName = "Gems",symbol = "💎",icon = nil,},
O script Currencies diz ao pacote de recursos Núcleo algumas metadados sobre sua moeda:
- (obrigatório) displayName - O nome da sua moeda. Se você não especificar um símbolo ou ícone, esse nome é usado em botões de compra (ou seja, "100 Gemas").
- (opcional) symbol - Se você tiver um personagem de texto para usar como ícone para sua moeda, isso é usado em vez do displayName em botões de compra (ou seja, "💎100").
- (opcional) icon - Se você tiver um ícone de imagem AssetId para sua moeda, isso é usado em vez do displayName em botões de compra (ou seja,a imagem será colocada à esquerda do preço "🖼️100")
Uma vez que sua moeda é configurada, você precisa especificar manualmente o preço, a moeda e o ícone do pacote para a exibição de alerta, em vez de essas informações serem recuperadas do produto de desenvolvedor associado ao pacote.
Pacotes
-- Se você quiser usar um produto de desenvolvedor, deve fornecer um ID de produto exclusivo, usado apenas por um pacote.-- Vamos obter o preço do pacote e o ícone do produto do desenvolvedorpricing = {priceType = CurrencyTypes.PriceType.Marketplace,devProductId = 1795621566,},-- Caso contrário, se você quiser usar moeda na experiência em vez de um produto dev, você pode usar o seguinte em vez disso:-- O preço aqui está na moeda na experiência, não no Robuxpricing = {priceType = CurrencyTypes.PriceType.InExperience,price = 79,currencyId = "Gems",icon = 18712203759,},
Você também precisa referenciar o BundlesExample script para chamar setInExperiencePurchaseHandler .
Exemplo de pacotes
local function awardInExperiencePurchase(
_player: Player,
_bundleId: Types.BundleId,
_currencyId: CurrencyTypes.CurrencyId,
_price: number
)
-- Verifique se o jogador tem moeda suficiente para comprar o pacote
-- Atualizar dados do jogador, dar itens, etc
-- Deduza 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 de desenvolvedor se não tiver digitarde 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 na 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 está conectado ao manipulador através de Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase) ).
Defina pacotes
Todos os pacotes oferecidos na 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 , você precisa atualizar o principal devProductId do pacote para combinar com o da sua experiência.Isto é o que será solicitado através de MarketplaceService para comprar o próprio pacote. É altamente recomendado usar um novo produto de desenvolvedor para o pacote para tornar mais fácil rastrear vendas separadas. Se você quiser um pacote com vários itens e se eles já estiverem representados por produtos de desenvolvedor em sua experiência, você não precisa definir explicitamente o preço do item/assetId/nome, que será recuperado via informações do produto:
LEIAME
{itemType = ItemTypes.ItemType.DevProduct,devProductId = <DEV_PRODUCT_ID>,metadata = {caption = {text = "x1",color = Color3.fromRGB(236, 201, 74),} -- Legenda é opcional! Você também pode omitir este campo}},
Caso contrário, você pode configurar manualmente esses detalhes do item:
LEIAME
{itemType = ItemTypes.ItemType.Robux,priceInRobux = 49,icon = <IMAGE_ASSET_ID>,metadata = {caption = {text = "x1",color = Color3.fromRGB(236, 201, 74),} -- Legenda é opcional! Você também pode deixar omitir este campo}},
Por exemplo, todo o seu pacote provavelmente vai parecer isso:
LEIAME
local starterBundle: Types.RelativeTimeBundle = {bundleType = Types.BundleType.RelativeTime,-- Se você quiser usar um produto de desenvolvedor, deve fornecer um ID de produto exclusivo, usado apenas por um pacote.-- Vamos obter o preço do pacote e o ícone do produto do desenvolvedorpricing = {priceType = CurrencyTypes.PriceType.Marketplace,devProductId = <DEV_PRODUCT_ID>,},-- Caso contrário, se você quiser usar moeda na experiência em vez de um produto dev, você pode usar o seguinte em vez disso:-- O preço aqui está na moeda na experiência, não no Robux-- preços = {-- 价格类型 = CurrencyTypes.PriceType.InExperiência,-- preço = 79,-- carteiraId = <CURRENCY_ID>,-- ícone = <IMAGE_ASSET_ID>,-- },includedItems = {[1] = {-- O próprio item não é vendido através de um produto de desenvolvedor, então indique o quanto vale em Robux e dê um ícone-- O preçoInRobux ajuda os pacotes a mostrar o valor relativo do preço do pacote vs. a soma de seu conteúdoitemType = ItemTypes.ItemType.Robux,priceInRobux = 49,icon = <IMAGE_ASSET_ID>,-- Alternativamente, se isso tem um produto de desenvolvedor deixe de preço e ícone acima e apenas defina o devProductId-- O preço e o ícone serão recuperados do produto do desenvolvedor-- produtoDevId = <ITEM_DEV_PRODUCT_ID>-- Há mais campos de metadados opcionais que são específicos de 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 comprada ou expirada, não é mais válida mesmo que sua experiência tente solicitar (onPlayerAdded). Você pode tornar isso falso durante o teste no estúdio.durationInSeconds = 900, -- 15 minutosincludesOfflineTime = false, -- Apenas contar o tempo decorrido na experiênciametadata = {displayName = "STARTER BUNDLE",description = "Save 75% and get a head start!",},}
Integar lógica do servidor
Dê uma olhada em ReplicatedStorage.Bundles.Server.Examples.BundlesExample, que mostra como o seu servidor interagirá com o pacote de recursos Bundles e os métodos acima no ModuleScript.Os trechos abaixo são desse script.
Você precisa principalmente conectar quatro coisas uma vez que arrasta o pacote de recursos Bundles para sua experiência:
Conecte os manipuladores de compra através de Bundles.setPurchaseHandler para especificar as funções a serem chamadas para obter itens de prêmio quando uma compra está sendo processada.
Exemplo de pacoteslocal function awardMarketplacePurchase(_player: Player, _bundleId: Types.BundleId, _receiptInfo: { [string]: any })-- Atualizar dados do jogador, dar itens, etc-- ... E registro receiptInfo.PurchaseId para que possamos verificar se o usuário já tem esse 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-- Atualizar dados do jogador, dar itens, etc-- Deduza 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 de desenvolvedor se não tiver digitarde 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 na experiência que estiver 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 de desenvolvedor à promoção/venda.Basicamente, quando um recibo de produto de desenvolvedor está sendo processado, eles agora chamarão Bundles.getBundleByDevProduct para verificar se o produto pertence a um pacote.Se o fizer, o script então chama Bundles.processReceipt .
Exemplo de pacotes-- Receber processo 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] -- Obtenha o manipulador para o produtolocal success, result = pcall(handler, receiptInfo, player) -- Chame o manipulador para verificar se a lógica de compra é bem-sucedidaif not success or not result thenwarn("Failed to process receipt:", 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 lidar com elelocal purchaseDecision = Bundles.processReceiptAsync(player, bundleId, receiptInfo)return purchaseDecision == Enum.ProductPurchaseDecision.PurchaseGrantedend-- Esta compra não pertence a um pacote,-- ... Gerencie toda a sua lógica existente aqui se você tiver algumareturn falseendConecte Players.PlayerAdded:Connect(Bundles.OnPlayerAdded) para que o pacote de recursos Bundles reative quaisquer pacotes ativos que ainda não expiraram para um jogador.
LEIAMElocal function onPlayerAdded(player: Player)-- Diga aos pacotes quando o jogador se junta para que possa recarregar seus dadosBundles.onPlayerAdded(player)-- Se você tivesse algum pacote inicial que queria oferecer a todos os novos usuários, você poderia solicitar isso aqui-- ... Pacotes lidarão se o jogador já o comprou ou se expirou desde que não é repetível-- Bundles.promptIfValidAsync(jogador, "Pacote Inicial")-- Chamando isso aqui apenas como exemplo, você pode chamar isso quando quiser ou onde quiseronPromptBundleXYZEvent(player)endPacotes de solicitação. Embora isso dependa do jogabilidade, o exemplo solicita jogadores com um StarterBundle onPlayerAdded .
A lógica do pacote de recursos Bundles garante que cada jogador não receba uma oferta repetida se já tiver comprado o pacote, ou se deixar 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) .
LEIAMElocal function onPromptBundleXYZEvent(player: Player)-- Conecte qualquer evento de experiência que você queira usar para determinar quando um jogador é solicitado o pacote-- ... Isso será sempre que você tenha atendido aos seus critérios de elegibilidade para solicitar a um jogador o pacote-- ... Por exemplo, se você quiser solicitar um pacote quando um jogador se juntar ou quando um jogador subir de níveltask.spawn(Bundles.promptIfValidAsync, player, <Some_Bundle_Id>)-- ... Se criar vários pacotes, usar task.Gerar() para embalar a chamada de função acima minimizará as discrepâncias entre os contadoresend
Considere as seguintes diretrizes de melhor prática sobre gravações redundantes de ReceiptIds:
Enquanto o pacote de recursos Bundles 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 processamento do manipulador de compras já ter terminado, você saiba na próxima tentativa não conceder itens novamente.
O pacote de recursos Bundles não registrará o ReceiptId se a compra falhar em qualquer etapa, então você deve garantir que está registrando o ReceiptId em suas tabelas antes de processar o recibo como parte de seu processador de compras.
Essa redundância ajuda a garantir que toda a lógica de compra tenha sido devidamente tratada e que o armazenamento de dados da sua loja de dados e o pacote de recursos Bundles alcancem consistência eventual, com o armazenamento de dados da sua loja de dados sendo a fonte da verdade.
Configurar constantes
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 Bundles , vive em ReplicatedStorage.Bundles.Configs.Constants.
As principais coisas que você pode querer ajustar para atender aos requisitos de design da sua experiência:
- IDs de recurso de som
- Duração do efeito de compra e cores de partículas
- Aviso de colapso de exibição de cabeças
Além disso, você pode encontrar strings para tradução divididas em um local: ReplicatedStorage.FeaturePackagesCore.Configs.TranslationStrings .
Personalizar componentes de UI
Ao modificar os objetos do pacote, como cores, fontes e transparência, você pode ajustar a apresentação visual de seus prompts de pacote.No entanto, tenha em mente que se você mover qualquer um dos objetos ao redor hierarquicamente, o código não será capaz de encontrá-los e você precisará fazer ajustes em seu código.
Um prompt é composto 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 prompts em si.
O painel de aviso também é composto por dois componentes:
- HudItem – Um componente individual que representa cada opção de menu na exibição de cabeçalho.
- Hud – Para ser preenchido com programaticamente com HudItems .
Se você quiser ter maior controle sobre a exibição de cabeças, em vez de usar apenas a interface de usuário HUD 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
Tempo Relativo
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 no display de cabeça para cima 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 de iniciante de uso único que é exibida a todos os novos jogadores por 24 horas.Para melhores práticas da indústria sobre como implementar pacotes de pacotes iniciais, veja Design do Pacote Inicial.
Qual o nome | Tipo | Descrição |
---|---|---|
includeOfflineTime | bool | (Opcional) Se não configurar, apenas o tempo gasto na experiência contará para a duração restante da oferta. |
singleUse | bool | (Opcional) Se não for configurar, a compra pode ser reativada após ser comprada ou expirada.Se configurar, uma vez comprada ou expirada pela primeira vez, não será solicitada novamente, mesmo que você chame Bundles.promptIfValidAsync com o bundleId. |
Tempo Fixo
Uma vez que o pacote FixedTime for oferecido a um jogador, ele permanece disponível até o fim do tempo universal coordenado (UTC).Este tipo é exibido no display de cabeça para cima 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 férias que está disponível apenas para um mês específico.
Uma vez
Um pacote OneTime é disponível apenas no momento em que é oferecido a um jogador.Ele não é exibido no painel de cabeças do jogador e, uma vez que um jogador fecha o prompt, ele não pode ser reaberto até ser solicitado pelo servidor novamente.
Um exemplo comum desse tipo de pacote é uma oferta para comprar mais moedas na experiência no momento em que um jogador fica sem.