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 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:

  1. 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.

  2. Na barra de ferramentas, selecione a aba Ver.

  3. Clique em Caixa de Ferramentas . A janela Caixa de Ferramentas é exibida.

    Studio's View tab with the Toolbox tool highlighted.
  4. Na janela da Caixa de ferramentas , clique na aba Inventário . Os displays de Meus modelos são classificados.

    Studio's Toolbox window with the Inventory tab highlighted.
  5. 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 .

  6. Arraste as pastas do pacote para ReplicatedStorage .

  7. Permita que as chamadas de armazenamento de dados rastreiem as compras do jogador com os pacotes.

    1. Na aba Início da barra de ferramentas, selecione Configurações do Jogo .
    2. 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 desenvolvedor
pricing = {
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 Robux
pricing = {
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 desenvolvedor
pricing = {
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údo
itemType = 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á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 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 minutos
includesOfflineTime = false, -- Apenas contar o tempo decorrido na experiência
metadata = {
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:

  1. 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 pacotes

    local 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 pacote
    task.wait(2)
    return Enum.ProductPurchaseDecision.PurchaseGranted
    end
    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
  2. Conecte 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ã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] -- Obtenha o manipulador para o produto
    local success, result = pcall(handler, receiptInfo, player) -- Chame o manipulador para verificar se a lógica de compra é bem-sucedida
    if not success or not result then
    warn("Failed to process receipt:", 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
    -- Esta compra pertence a um pacote, deixe os Pacotes lidar com ele
    local purchaseDecision = Bundles.processReceiptAsync(player, bundleId, receiptInfo)
    return purchaseDecision == Enum.ProductPurchaseDecision.PurchaseGranted
    end
    -- Esta compra não pertence a um pacote,
    -- ... Gerencie toda a sua lógica existente aqui se você tiver alguma
    return false
    end
  3. Conecte Players.PlayerAdded:Connect(Bundles.OnPlayerAdded) para que o pacote de recursos Bundles reative quaisquer pacotes ativos que ainda não expiraram para um jogador.

    LEIAME

    local function onPlayerAdded(player: Player)
    -- Diga aos pacotes quando o jogador se junta para que possa recarregar seus dados
    Bundles.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 quiser
    onPromptBundleXYZEvent(player)
    end
  4. Pacotes 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) .

    LEIAME

    local 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ível
    task.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 contadores
    end

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 nomeTipoDescrição
includeOfflineTimebool (Opcional) Se não configurar, apenas o tempo gasto na experiência contará para a duração restante da oferta.
singleUsebool (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.