Paquete de Paquetes

*Este contenido se traduce usando la IA (Beta) y puede contener errores. Para ver esta página en inglés, haz clic en aquí.

El paquete de características de Paquetes ofrece funcionalidad lista para usar para vender colecciones de artículos a los jugadores con un descuento. Puedes elegir si permitir a los jugadores comprar paquetes utilizando una moneda personalizada en la experiencia o Robux, qué tipo de paquete deseas usar, qué conjunto de artículos deseas vender y cómo deseas solicitar a los jugadores durante su jugabilidad.

Utilizando las opciones de personalización del paquete, puedes adaptar tus paquetes para cumplir con los objetivos de diseño y monetización de tus experiencias, tales como:

  • Dirigir un bajo tasa de conversión al ofrecer paquetes de inicio con descuento que proporcionan valor a los nuevos jugadores y fomentan el gasto temprano.
  • Aumentar la profundidad de gasto al agrupar artículos a diferentes niveles de precios para atraer a una variedad de jugadores.
  • Monetizar operaciones en vivo (LiveOps) eventos al ofrecer paquetes de artículos exclusivos por tiempo limitado.

Obtener paquete

La Tienda de Creadores es una pestaña de la Caja de Herramientas que puedes utilizar para encontrar todos los activos que han sido creados por Roblox y la comunidad de Roblox para su uso dentro de tus proyectos, incluyendo modelos, imágenes, mallas, audio, complementos, videos y fuentes. Puedes usar la Tienda de Creadores para agregar uno o más activos directamente en una experiencia abierta, ¡incluidos los paquetes de características!

Cada paquete de características requiere el paquete de características Núcleo para funcionar correctamente. Una vez que los activos de los paquetes de características Núcleo y Paquetes estén dentro de tu inventario, puedes reutilizarlos en cualquier proyecto de la plataforma.

Para obtener los paquetes de tu inventario en tu experiencia:

  1. Desde el menú Ventana de Studio o la barra de herramientas de la pestaña Inicio, abre la Caja de Herramientas.

  2. En la ventana de la Caja de Herramientas, haz clic en la pestaña Inventario. Se muestra la clasificación de Mis Modelos.

    Ventana de la Caja de Herramientas de Studio con la pestaña de Inventario resaltada.
  3. Haz clic en el tile del Paquete de Características Núcleo, luego en el tile del Paquete de Características de Paquetes. Ambas carpetas de paquetes se muestran en la ventana del Explorador.

  4. Arrastra las carpetas de paquetes a ReplicatedStorage.

  5. Permite que las llamadas de almacenamiento de datos rastreen las compras de los jugadores con los paquetes.

    1. Abre la ventana de ArchivoConfiguración de Experiencia de Studio.
    2. Navega hasta la pestaña Seguridad, luego habilita Habilitar acceso de Studio a los servicios API.

Definir monedas

Si tu experiencia tiene su propio sistema de monedas, puedes registrarlo con el paquete de características Núcleo definiéndolos en ReplicatedStorage.FeaturePackagesCore.Configs.Currencies. Ya hay un ejemplo comentado de una moneda de Gemas en este archivo; reemplázalo con el tuyo.

Monedas

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

El script de Monedas le dice al paquete de características Núcleo algunos metadatos sobre tu moneda:

  • (requerido) displayName - El nombre de tu moneda. Si no especificas un símbolo o icono, este nombre se utiliza en los botones de compra (es decir, "100 Gemas").
  • (opcional) symbol - Si tienes un carácter de texto para usar como el icono de tu moneda, se usa en lugar del displayName en los botones de compra (es decir, "💎100").
  • (opcional) icon - Si tienes un icono de imagen AssetId para tu moneda, este se utiliza en lugar del displayName en los botones de compra (es decir, la imagen será colocada a la izquierda del precio "🖼️100")

Una vez que tu moneda esté configurada, necesitas especificar manualmente el precio del paquete, la moneda y el icono para la pantalla de visualización en lugar de que esa información se obtenga del producto del desarrollador asociado al paquete.

Paquetes

-- Si deseas usar un producto de desarrollador, debes proporcionar un devProductId único, utilizado solo por un paquete.
-- Obtendremos el precio y el icono del paquete del producto del desarrollador
pricing = {
priceType = CurrencyTypes.PriceType.Marketplace,
devProductId = 1795621566,
},
-- De lo contrario, si deseas utilizar moneda en la experiencia en lugar de un producto de desarrollador, puedes usar lo siguiente:
-- El precio aquí es en la moneda de la experiencia, no en Robux
pricing = {
priceType = CurrencyTypes.PriceType.InExperience,
price = 79,
currencyId = "Gems",
icon = 18712203759,
},

También necesitas referenciar el script BundlesExample para llamar a setInExperiencePurchaseHandler.

BundlesExample

local function awardInExperiencePurchase(
_player: Player,
_bundleId: Types.BundleId,
_currencyId: CurrencyTypes.CurrencyId,
_price: number
)
-- Verificar si el jugador tiene suficiente moneda para comprar el paquete
-- Actualizar los datos del jugador, dar artículos, etc.
-- Deduct la moneda del jugador
task.wait(2)
return true
end
local function initializePurchaseHandlers()
local bundles = Bundles.getBundles()
for bundleId, bundle in bundles do
-- El paquete no está asociado con un producto de desarrollador si no tiene tipo de precio de mercado
if not bundle or bundle.pricing.priceType ~= "Marketplace" then
continue
end
Bundles.setPurchaseHandler(bundleId, awardMarketplacePurchase)
receiptHandlers[bundle.pricing.devProductId] = receiptHandler
end
-- Si tienes alguna moneda en la experiencia que estás usando para paquetes, establece el controlador aquí
for currencyId, _ in Currencies do
Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)
end
end

Específicamente, necesitas completar awardInExperiencePurchase, que es llamado por un bucle a través de Currencies dentro del ejemplo initializePurchaseHandlers (es decir, cada currencyId está conectado al manejador a través de Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)).

Definir paquetes

Todos los paquetes que se pueden ofrecer en tu experiencia pueden definirse dentro de ReplicatedStorage.Bundles.Configs.Bundles, con tipos exportados del script Types en la misma carpeta.

Si estás usando un devProductId, necesitas actualizar el devProductId principal del paquete para que coincida con el de tu experiencia. Esto es lo que se solicitará a través de MarketplaceService para comprar el paquete en sí. Se recomienda encarecidamente utilizar un nuevo producto de desarrollador para el paquete para facilitar el seguimiento de las ventas separadas.

Si deseas un paquete con múltiples artículos, y si estos ya están representados por productos de desarrollador en tu experiencia, no necesitas establecer explícitamente el precio / assetId / nombre del artículo, que se obtendrá a través de la información del producto:

README

{
itemType = ItemTypes.ItemType.DevProduct,
devProductId = <DEV_PRODUCT_ID>,
metadata = {
caption = {
text = "x1",
color = Color3.fromRGB(236, 201, 74),
} -- ¡El título es opcional! También puedes omitir este campo
}
},

De lo contrario, puedes configurar manualmente esos detalles del artículo:

README

{
itemType = ItemTypes.ItemType.Robux,
priceInRobux = 49,
icon = <IMAGE_ASSET_ID>,
metadata = {
caption = {
text = "x1",
color = Color3.fromRGB(236, 201, 74),
} -- ¡El título es opcional! También puedes dejar omitir este campo
}
},

Por ejemplo, tu paquete completo probablemente se verá así:

README

local starterBundle: Types.RelativeTimeBundle = {
bundleType = Types.BundleType.RelativeTime,
-- Si deseas usar un producto de desarrollador, debes proporcionar un devProductId único, utilizado solo por un paquete.
-- Obtendremos el precio y el icono del paquete del producto del desarrollador
pricing = {
priceType = CurrencyTypes.PriceType.Marketplace,
devProductId = <DEV_PRODUCT_ID>,
},
-- De lo contrario, si deseas utilizar moneda en la experiencia en lugar de un producto de desarrollador, puedes usar lo siguiente:
-- El precio aquí es en la moneda de la experiencia, no en Robux
-- pricing = {
-- priceType = CurrencyTypes.PriceType.InExperience,
-- price = 79,
-- currencyId = <CURRENCY_ID>,
-- icon = <IMAGE_ASSET_ID>,
-- },
includedItems = {
[1] = {
-- El artículo en sí no se vende a través de un producto de desarrollador, así que indica cuánto vale en Robux y da un icono
-- El priceInRobux ayuda a Paquetes a mostrar el valor relativo del precio del paquete frente a la suma de su contenido
itemType = ItemTypes.ItemType.Robux,
priceInRobux = 49,
icon = <IMAGE_ASSET_ID>,
-- Alternativamente, si esto tiene un producto de desarrollador, omite el precio y el icono anteriores y solo establece el devProductId
-- El precio y el icono se obtendrán del producto del desarrollador
-- devProductId = <ITEM_DEV_PRODUCT_ID>
-- Hay más campos de metadatos opcionales que son específicos de la interfaz de usuario si es necesario
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, -- Una vez comprado o expirado, ya no es válido incluso si tu experiencia intenta solicitarlo (onPlayerAdded). Puedes hacer esto falso mientras pruebas en studio.
durationInSeconds = 900, -- 15 minutos
includesOfflineTime = false, -- Solo cuenta el tiempo transcurrido en la experiencia
metadata = {
displayName = "PAQUETE INICIAL",
description = "¡Ahorra un 75% y consigue una ventaja!",
},
}

Integrar lógica del servidor

Echa un vistazo a ReplicatedStorage.Bundles.Server.Examples.BundlesExample, que muestra cómo tu servidor interactuará con el paquete de características Paquetes y los métodos anteriores en el ModuleScript. Los fragmentos a continuación son de ese script.

Principalmente necesitas conectar cuatro cosas una vez que arrastres el paquete de características Paquetes a tu experiencia:

  1. Conectar manejadores de compra a través de Bundles.setPurchaseHandler para especificar las funciones que se llamarán para otorgar artículos cuando se procese una compra.

    BundlesExample

    local function awardMarketplacePurchase(_player: Player, _bundleId: Types.BundleId, _receiptInfo: { [string]: any })
    -- Actualizar datos del jugador, otorgar artículos, etc.
    -- ... Y registrar receiptInfo.PurchaseId para que podamos verificar si el usuario ya tiene este paquete
    task.wait(2)
    return Enum.ProductPurchaseDecision.PurchaseGranted
    end
    local function awardInExperiencePurchase(
    _player: Player,
    _bundleId: Types.BundleId,
    _currencyId: CurrencyTypes.CurrencyId,
    _price: number
    )
    -- Verificar si el jugador tiene suficiente moneda para comprar el paquete
    -- Actualizar datos del jugador, otorgar artículos, etc.
    -- Deduct la moneda del jugador
    task.wait(2)
    return true
    end
    local function initializePurchaseHandlers()
    local bundles = Bundles.getBundles()
    for bundleId, bundle in bundles do
    -- El paquete no está asociado con un producto de desarrollador si no tiene tipo de precio de mercado
    if not bundle or bundle.pricing.priceType ~= "Marketplace" then
    continue
    end
    Bundles.setPurchaseHandler(bundleId, awardMarketplacePurchase)
    receiptHandlers[bundle.pricing.devProductId] = receiptHandler
    end
    -- Si tienes alguna moneda en la experiencia que estás usando para paquetes, establece el manejador aquí
    for currencyId, _ in Currencies do
    Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)
    end
    end
  2. Conecta tu lógica para MarketplaceService.ProcessReceipt, pero esto podría realizarse en otro lugar si tu experiencia ya tiene productos de desarrollador a la venta. Esencialmente, cuando se esté procesando un recibo de producto de desarrollador, ahora llamarán a Bundles.getBundleByDevProduct para verificar si el producto pertenece a un paquete. Si lo hace, el script luego llama a Bundles.processReceipt.

    BundlesExample

    -- Procesar el recibo del mercado para determinar si se necesita cobrar al jugador o no
    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] -- Obtener el manejador para el producto
    local success, result = pcall(handler, receiptInfo, player) -- Llamar al manejador para verificar si la lógica de compra fue exitosa
    if not success or not result then
    warn("Error al procesar el 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
    -- Esta compra pertenece a un paquete, deja que Paquetes lo maneje
    local purchaseDecision = Bundles.processReceiptAsync(player, bundleId, receiptInfo)
    return purchaseDecision == Enum.ProductPurchaseDecision.PurchaseGranted
    end
    -- Esta compra no pertenece a un paquete,
    -- ... Maneja toda tu lógica existente aquí si tienes alguna
    return false
    end
  3. Conecta Players.PlayerAdded:Connect(Bundles.OnPlayerAdded) para que el paquete de características Paquetes vuelva a solicitar cualquier paquete activo que no haya expirada aún para un jugador.

    README

    local function onPlayerAdded(player: Player)
    -- Informar a Paquetes cuando el jugador se une para que pueda recargar sus datos
    Bundles.onPlayerAdded(player)
    -- Si tenías algún paquete inicial que querías ofrecer a todos los nuevos usuarios, podrías solicitar eso aquí
    -- ... Paquetes se encargará si el jugador ya lo ha comprado o si ha expirado ya que no es repetible
    -- Bundles.promptIfValidAsync(player, "StarterBundle")
    -- Llamar esto aquí solo como ejemplo, puedes llamar a esto cada vez o donde quieras
    onPromptBundleXYZEvent(player)
    end
  4. Solicita paquetes. Si bien esto depende de la jugabilidad, el ejemplo solicita a los jugadores un StarterBundle onPlayerAdded.

    • La lógica del paquete de características Paquetes asegura que cada jugador no reciba una oferta repetida si ya ha comprado el paquete, o si dejaron expirar la oferta (basado en la configuración del paquete).

    • Siempre que desees solicitar un paquete a un jugador, llama a Bundles.promptIfValidAsync(player, bundleId).

    README

    local function onPromptBundleXYZEvent(player: Player)
    -- Conectar cualquier evento de experiencia que desees usar para determinar cuándo se solicita el paquete al jugador
    -- ... Esto será cada vez que hayas cumplido con tus criterios de elegibilidad para solicitar un paquete al jugador
    -- ... Por ejemplo, si deseas solicitar un paquete cuando un jugador se une, o cuando un jugador sube de nivel
    task.spawn(Bundles.promptIfValidAsync, player, <Some_Bundle_Id>)
    -- ... Si estás creando varios paquetes, usar task.spawn() para envolver la llamada de función anterior minimizará discrepancias entre las cuentas regresivas
    end

Considera la siguiente guía de mejores prácticas sobre grabaciones redundantes de ReceiptIds:

  • Mientras que el paquete de características Paquetes registra ReceiptIds para evitar procesar el mismo recibo dos veces, también debes estar registrando ReceiptIds dentro de tus tablas para que si el flujo de compra falla después de que su manejador de compra ya haya terminado, sepas que en los intentos subsiguientes no otorgar artículos nuevamente.

  • El paquete de características Paquetes no registrará el ReceiptId si la compra falla en algún paso, así que debes asegurarte de que estás registrando el ReceiptId en tus tablas antes de procesar el recibo como parte de tu purchaseHandler.

  • Esta redundancia ayuda a garantizar que toda la lógica de compra se haya manejado adecuadamente y que tu almacén de datos y el almacén de datos del paquete de características Paquetes alcancen consistencia eventual, siendo tu almacén de datos la fuente de verdad.

Configurar constantes

Las constantes para el paquete de características Núcleo viven en dos ubicaciones:

  • Las constantes compartidas se encuentran en ReplicatedStorage.FeaturePackagesCore.Configs.SharedConstants.

  • Las constantes específicas del paquete, en este caso el paquete de características Paquetes, se encuentran en ReplicatedStorage.Bundles.Configs.Constants.

Las principales cosas que podrías querer ajustar para cumplir con los requisitos de diseño de tu experiencia:

  • IDs de activos de sonido
  • Duración del efecto de compra y colores de partículas
  • Colapsabilidad de la pantalla de visualización

Además, puedes encontrar cadenas para traducción desglosadas en un solo lugar: ReplicatedStorage.FeaturePackagesCore.Configs.TranslationStrings.

Personalizar componentes de UI

Al modificar los objetos del paquete, como colores, fuentes y transparencia, puedes ajustar la presentación visual de tus solicitudes de paquete. Sin embargo, ten en cuenta que si mueves alguno de los objetos jerárquicamente, el código no podrá encontrarlos, y necesitarás hacer ajustes a tu código.

Una solicitud se compone de dos componentes de alto nivel:

  • PromptItem – El componente individual repetido para cada artículo dentro de un paquete (imagen del artículo, título, nombre, precio).
  • Prompt – La ventana de solicitud en sí.

La pantalla de visualización también está compuesta por dos componentes:

  • HudItem – Un componente individual que representa cada opción de menú en la pantalla de visualización.
  • Hud – Para ser llenado programáticamente con HudItems.

Si deseas tener un mayor control sobre la pantalla de visualización, en lugar de usar simplemente la interfaz de HUD existente dentro de ReplicatedStorage.Bundles.Objects.BundlesGui, puedes mover las cosas para cumplir con tus propios requisitos de diseño. Asegúrate de actualizar el comportamiento del script del cliente en el script ReplicatedStorage.Bundles.Client.UIController.

Referencia de API

Tipos

RelativeTime

Una vez que el paquete RelativeTime se ofrece a un jugador, permanece disponible hasta que se agote la duración del tiempo. Este tipo se muestra en la pantalla de visualización del jugador y automáticamente solicita en futuras sesiones hasta que el paquete expire o el jugador lo compre.

Un ejemplo común de este tipo de paquete es una oferta de paquete de inicio de un solo uso que se muestra a todos los nuevos jugadores durante 24 horas.

NombreTipoDescripción
includeOfflineTimebool(Opcional) Si no se establece, solo el tiempo pasado en la experiencia contará para la duración de la oferta restante.
singleUsebool(Opcional) Si no se establece, la compra se puede reactivar después de ser comprada o expirada.

Si se establece, una vez comprada o expirada la primera vez, no se podrá solicitar nunca más, incluso si llamas a Bundles.promptIfValidAsync con el bundleId.

FixedTime

Una vez que el paquete FixedTime se ofrece a un jugador, permanece disponible hasta el final del tiempo universal coordinado (UTC) establecido. Este tipo se muestra en la pantalla de visualización del jugador y automáticamente solicita en futuras sesiones hasta que el paquete expire o el jugador lo compre.

Un ejemplo común de este tipo de paquete es una oferta navideña que solo está disponible por un mes determinado.

OneTime

Un paquete OneTime solo está disponible en el momento en que se ofrece a un jugador. No se muestra en la pantalla de visualización del jugador, y una vez que un jugador cierra la solicitud, no se puede volver a abrir hasta que se solicite nuevamente por el servidor.

Un ejemplo común de este tipo de paquete es una oferta para comprar más moneda en la experiencia en el momento en que un jugador se queda sin ella.

©2026 Roblox Corporation. Roblox, el logotipo de Roblox y "Powering Imagination" son algunas de nuestras marcas registradas y no registradas en los Estados Unidos y otros países.