Le paquet de fonctionnalités bundles offre une fonctionnalité prête à l'emploi pour vendre des collections d'objets aux joueurs à rabais.Vous pouvez choisir d'autoriser les joueurs à acheter des forfaits à l'aide d'une devise personnalisée dans l'expérience ou de Robux, quel type de forfait vous souhaitez utiliser, quels articles vous souhaitez vendre et comment vous souhaitez demander aux joueurs pendant leur partie.
En utilisant les options de personnalisation du paquet, vous pouvez ajuster vos packages pour répondre aux objectifs de conception et de monétisation de vos expériences, tels que :
- Cibler un taux de conversion faible en offrant des packs de démarrage réduits qui fournissent de la valeur aux nouveaux joueurs et encouragent les dépenses précoces.
- Augmentation de la profondeur de dépenses en regroupant des articles à différents points de prix pour attirer une gamme de joueurs.
- Monétiser les opérations en direct (LiveOps) événements en offrant des packs à durée limitée d'articles exclusifs.

Obtenir le paquet
La boutique des créateurs est une tab de la boîte à outils que vous pouvez utiliser pour trouver toutes les ressources créées par Roblox et la communauté Roblox pour une utilisation dans vos projets, y compris le modèlisation, l'image, le maillage, l'audio, le plugin, la vidéo et les ressources de police .Vous pouvez utiliser la boutique des créateurs pour ajouter une ou plusieurs ressources directement dans une expérience ouverte, y compris des packages de fonctionnalités !
Chaque paquet de fonctionnalités nécessite que le paquet de fonctionnalité noyau fonctionne correctement.Une fois que les ressources du paquet de fonctionnalités noyau et bundles sont dans votre inventaire, vous pouvez les réutiliser dans n'importe quel projet sur la plateforme.
Pour obtenir les packages de votre inventaire dans votre expérience :
Ajoutez le paquet de fonctionnalités noyau et ensembles à votre inventaire dans Studio en cliquant sur le lien ajouter à l'inventaire dans le ensemble de composants suivant.
Dans la barre d'outils, sélectionnez l'onglet Affichage.
Cliquez sur boîte à outils . La fenêtre boîte à outils s'affiche.
Dans la fenêtre boîte à outils , cliquez sur l'onglet inventaire . Les affichages de tri mes modèles s'affichent.
Cliquez pavéla case Feature Package Core , puis sur la case Bundle Feature Package .Les deux dossiers de paquet s'affichent dans la fenêtre Explorateur .
Faites glisser les dossiers de paquet dans ReplicatedStorage .
Permettre aux appels de magasin de données de suivre les achats de joueurs avec les packages.
- Dans l'onglet Accueil de la barre d'outils, sélectionnez Paramètres de jeu .
- Accédez à l'onglet sécurité , puis activez activer l'accès au studio aux services d'API .
Définir les devises
Si votre expérience a son propre système de monnaie, vous pouvez les enregistrer avec le paquet de fonctionnalités noyau en les définissant dans ReplicatedStorage.FeaturePackagesCore.Configs.Currencies.Il y a un exemple commenté d'une devise de gemmes déjà dans ce fichier ; remplacez-le par le posséder.
Devises
Gems = {displayName = "Gems",symbol = "💎",icon = nil,},
Le script Currencies indique au package de fonctionnalité noyau certaines métadonnées sur votre monnaie:
- (requis) displayName - Le nom de votre monnaie. Si vous ne spécifiez pas de symbole ou d'icône, ce nom est utilisé dans les boutons d'achat (par exemple, « 100 gemmes »).
- (facultatif) symbol - Si vous avez un caractère de texte à utiliser comme icône pour votre monnaie, cela est utilisé au lieu du displayName dans les boutons d'achat (i.e. "💎100").
- (facultatif) icon - Si vous avez une icône d'image AssetId pour votre monnaie, cela est utilisé à la place du displayName dans les boutons d'achat (c'est-à-direl'image sera placée à gauche du prix "🖼️100")
Une fois que votre devise est configurée, vous devez spécifier manuellement le prix, la monnaieet l'icône du lotpour l'affichage en aperçu au lieu que cette information soit récupérée à partir du produit développeur associé au lot.
Ensembles
-- Si vous voulez utiliser un produit dev, vous devez fournir un ID de produit dev unique, utilisé par un seul lot.-- Nous récupérerons le prix du paquet et l'icône du produit du développeurpricing = {priceType = CurrencyTypes.PriceType.Marketplace,devProductId = 1795621566,},-- Sinon, si vous souhaitez utiliser une devise en expérience au lieu d'un produit dev, vous pouvez utiliser ce qui suit à la place :-- Le prix ici est dans la monnaieen expérience, pas en Robuxpricing = {priceType = CurrencyTypes.PriceType.InExperience,price = 79,currencyId = "Gems",icon = 18712203759,},
Vous devez également faire référence au script BundlesExample pour appeler setInExperiencePurchaseHandler.
Exemple de bundles
local function awardInExperiencePurchase(
_player: Player,
_bundleId: Types.BundleId,
_currencyId: CurrencyTypes.CurrencyId,
_price: number
)
-- Vérifiez si le joueur a suffisamment de devises pour acheter le lot
-- Mise à jour des données du joueur, donation d'objets, etc.
-- Déduire la devise du joueur
task.wait(2)
return true
end
local function initializePurchaseHandlers()
local bundles = Bundles.getBundles()
for bundleId, bundle in bundles do
-- Le paquet n'est pas associé à un produit de développeur s'il n'a pas de taperde prix de marché
if not bundle or bundle.pricing.priceType ~= "Marketplace" then
continue
end
Bundles.setPurchaseHandler(bundleId, awardMarketplacePurchase)
receiptHandlers[bundle.pricing.devProductId] = receiptHandler
end
-- Si vous avez des devises en expérience que vous utilisez pour les ensembles, définissez le traitement ici
for currencyId, _ in Currencies do
Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)
end
end
Spécifiquement, vous devez remplir awardInExperiencePurchase, qui est appelé par une boucle à travers Currencies à l'intérieur de l'exemple initializePurchaseHandlers (i.e.chaque currencyId est connecté au gestionnaire via Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase) ).
Définir des bundles
Tous les packages offerts dans votre expérience peuvent être définis dans ReplicatedStorage.Bundles.Configs.Bundles , avec des types exportés du script Types dans le même dossier.
Si vous utilisez un devProductId , vous devez mettre à jour le principal devProductId du lotpour correspondre à celui de votre expérience.C'est ce qui sera demandé à travers MarketplaceService pour acheter le paquet lui-même. Il est fortement recommandé d'utiliser un nouveau produit de développeur pour le paquet pour faciliter la traçabilité des ventes séparées. Si vous voulez un paquet avec plusieurs articles, et si ceux-ci sont déjà représentés par des produits de développeur dans votre expérience, vous n'avez pas besoin de définir explicitement le prix des articles/assetId/name, qui sera récupéré via les informations sur le produit :
LECTURE ME
{itemType = ItemTypes.ItemType.DevProduct,devProductId = <DEV_PRODUCT_ID>,metadata = {caption = {text = "x1",color = Color3.fromRGB(236, 201, 74),} -- La légende est facultative ! Vous pouvez également omettre ce champ}},
Sinon, vous pouvez configurer manuellement ces détails de l'élément :
LECTURE ME
{itemType = ItemTypes.ItemType.Robux,priceInRobux = 49,icon = <IMAGE_ASSET_ID>,metadata = {caption = {text = "x1",color = Color3.fromRGB(236, 201, 74),} -- La légende est facultative ! Vous pouvez également laisser omettre ce champ}},
Par exemple, votre ensemble complet ressemblera probablement à ceci :
LECTURE ME
local starterBundle: Types.RelativeTimeBundle = {bundleType = Types.BundleType.RelativeTime,-- Si vous voulez utiliser un produit dev, vous devez fournir un ID de produit dev unique, utilisé par un seul lot.-- Nous récupérerons le prix du paquet et l'icône du produit du développeurpricing = {priceType = CurrencyTypes.PriceType.Marketplace,devProductId = <DEV_PRODUCT_ID>,},-- Sinon, si vous souhaitez utiliser une devise en expérience au lieu d'un produit dev, vous pouvez utiliser ce qui suit à la place :-- Le prix ici est dans la monnaieen expérience, pas en Robux-- prix = {-- priceType = CurrencyTypes.PriceType.InExperience,-- prix = 79,-- currencyId = <CURRENCY_ID>,-- icône = <IMAGE_ASSET_ID>,-- },includedItems = {[1] = {-- L'article lui-même n'est pas vendu via un produit de développeur, indiquez donc à quel point il vaut en Robux et donnez une icône-- Le prix en Robux aide les ensembles à montrer la valeur relative du prix du paquet par rapport à la somme de son contenuitemType = ItemTypes.ItemType.Robux,priceInRobux = 49,icon = <IMAGE_ASSET_ID>,-- Alternativement, si cela a un produit dev laisser le prix et l'icône ci-dessus et simplement définir le devProductId-- Le prix et l'icône seront récupérés dans le produit du développeur-- id de produit dev = <ITEM_DEV_PRODUCT_ID>-- Il existe plus de champs de métadonnées facultatifs qui sont spécifiques à l'interface utilisateur si nécessairemetadata = {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, -- Une fois acheté ou expiré, plus valable même si votre expérience essaie d'inciter (onPlayerAdded). Vous pouvez rendre cela faux lors du test en studio.durationInSeconds = 900, -- 15 minincludesOfflineTime = false, -- Ne comptez que le temps écoulé dans l'expériencemetadata = {displayName = "STARTER BUNDLE",description = "Save 75% and get a head start!",},}
Intégrer la logique du serveur
Regardez ReplicatedStorage.Bundles.Server.Examples.BundlesExample , qui montre comment votre serveur interagira avec le paquet de fonctionnalités Bundles et les méthodes ci-dessus sur le ModuleScript .Les extraits ci-dessous proviennent de ce script.
Vous devez principalement brancher quatre choses une fois que vous avez fait glisser le paquet de fonctionnalité Bundles dans votre expérience :
Connectez les gestionnaires d'achat via Bundles.setPurchaseHandler pour spécifier les fonctions à appeler pour attribuer des articles lorsqu'un achat est traité.
Exemple de bundleslocal function awardMarketplacePurchase(_player: Player, _bundleId: Types.BundleId, _receiptInfo: { [string]: any })-- Mise à jour des données du joueur, donation d'objets, etc.-- ... ET enregistrer receiptInfo.PurchaseId afin que nous puissions vérifier si l'utilisateur a déjà ce lottask.wait(2)return Enum.ProductPurchaseDecision.PurchaseGrantedendlocal function awardInExperiencePurchase(_player: Player,_bundleId: Types.BundleId,_currencyId: CurrencyTypes.CurrencyId,_price: number)-- Vérifiez si le joueur a suffisamment de devises pour acheter le lot-- Mise à jour des données du joueur, donation d'objets, etc.-- Déduire la devise du joueurtask.wait(2)return trueendlocal function initializePurchaseHandlers()local bundles = Bundles.getBundles()for bundleId, bundle in bundles do-- Le paquet n'est pas associé à un produit de développeur s'il n'a pas de taperde prix de marchéif not bundle or bundle.pricing.priceType ~= "Marketplace" thencontinueendBundles.setPurchaseHandler(bundleId, awardMarketplacePurchase)receiptHandlers[bundle.pricing.devProductId] = receiptHandlerend-- Si vous avez des devises en expérience que vous utilisez pour les ensembles, définissez le traitement icifor currencyId, _ in Currencies doBundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)endendConnectez votre logique pour MarketplaceService.ProcessReceipt , mais cela pourrait être fait ailleurs si votre expérience a déjà des produits développeurs en offre.Essentiellement, lorsque la réception d'un produit de développeur est traitée, ils appelleront maintenant Bundles.getBundleByDevProduct pour vérifier si le produit appartient à un lot.Si c'est le cas, le script appelle ensuite Bundles.processReceipt .
Exemple de bundles-- Reçu de traitement du marché pour déterminer si le joueur doit être facturé ou nonlocal 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] -- Obtenez le gestionnaire du produitlocal success, result = pcall(handler, receiptInfo, player) -- Appeler le gestionnaire pour vérifier si la logique d'achat est réussieif 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-- Cet achat appartient à un lot, laissez les paquets le gérerlocal purchaseDecision = Bundles.processReceiptAsync(player, bundleId, receiptInfo)return purchaseDecision == Enum.ProductPurchaseDecision.PurchaseGrantedend-- Cet achat ne fait pas partie d'un lot,-- ... Gérer toute votre logique existante ici si vous en avez quelquereturn falseendConnectez Players.PlayerAdded:Connect(Bundles.OnPlayerAdded) afin que le paquet de fonctionnalités Bundles redémarre les packages actifs expirés pour un joueur.
LECTURE MElocal function onPlayerAdded(player: Player)-- Dites aux ensembles quand le joueur rejoint pour qu'il puisse recharger ses donnéesBundles.onPlayerAdded(player)-- Si vous aviez un package de démarrage que vous vouliez offrir à tous les nouveaux utilisateurs, vous pouvez demander cela ici-- ... les bundles s'occuperont si le joueur l'a déjà acheté ou s'il a expiré puisqu'il n'est pas répétable-- Bundles.promptIfValidAsync(joueur, « StarterBundle »)-- Appeler ceci ici juste pour l'exemple, vous pouvez appeler ceci quand et où vous le souhaitezonPromptBundleXYZEvent(player)endEnsembles de prompts. Bien que cela dépende du partie, l'exemple invite les joueurs avec un StarterBundle onPlayerAdded .
La logique du paquet de fonctionnalités Bundles garantit que chaque joueur ne reçoit pas une offre de répétition s'il a déjà acheté le lot, ou s'il laisse l'offre expirer déjà (selon la configuration du paquet).
Chaque fois que vous voulez inciter un paquet à un joueur, appelez Bundles.promptIfValidAsync(player, bundleId) .
LECTURE MElocal function onPromptBundleXYZEvent(player: Player)-- Connectez l'événement d'expérience que vous voulez utiliser pour déterminer quand un joueur est invité à obtenir le lot-- ... Ce sera chaque fois que vous aurez satisfait vos critères d'éligibilité pour inciter un joueur à prendre le lot-- ... Par exemple, si vous souhaitez demander un paquet lorsqu'un joueur rejoint, ou lorsqu'un joueur monte de niveautask.spawn(Bundles.promptIfValidAsync, player, <Some_Bundle_Id>)-- ... Si vous créez plusieurs bundles, l'utilisation de task.régénération, apparition() pour envelopper l'appel de fonction ci-dessus minimisera les désaccords entre les compteursend
Envisagez les directives de meilleure pratique suivantes sur les enregistrements redondants des ReceiptIds :
Bien que le paquet de fonctionnalité Bundles enregistre les ID de reçu pour éviter de traiter le même reçu deux fois, vous devriez également enregistrer les ID de reçu à l'intérieur de vos tables afin que si le flux d'achat échoue après que leur gestionnaire d'achat ait déjà terminé, vous sachiez lors de la réessai suivante de ne pas attribuer à nouveau des articles.
Le paquet de fonctionnalité Bundles n'enregistrera pas l'ID de reçu si l'achat échoue à n'importe quel étape, vous devez donc vous assurer que vous enregistrez l'ID de reçu dans vos tables avant de traiter le reçu comme partie de votre gestionnaire d'achat.
Cette redondance aide à garantir que toute la logique d'achat a été gérée de manière appropriée et que le stock de données de votre magasin de données et le stock de données du paquet de fonctionnalités Bundles atteignent une cohérence ultérieure, le magasin de données étant la source de vérité.
Configurer les constantes
Les constantes pour le paquet de fonctionnalités noyau vivent dans deux endroits :
Les constantes partagées vivent dans ReplicatedStorage.FeaturePackagesCore.Configs.SharedConstants .
Les constantes spécifiques au paquet, dans ce cas le paquet de fonctionnalité Bundles , vivent dans ReplicatedStorage.Bundles.Configs.Constants .
Les principales choses que vous pourriez vouloir ajuster pour répondre aux exigences de conception de votre expérience :
- ID de ressources sonores
- Durée de l'effet d'achat et couleurs des particules
- Affichage de l'effondrement des têtes
De plus, vous pouvez trouver des chaînes pour la traduction séparées en une seule position : ReplicatedStorage.FeaturePackagesCore.Configs.TranslationStrings .
Personnaliser les composants d'interface utilisateur
En modifiant les objets de paquet, tels que les couleurs, la police et la transparence, vous pouvez ajuster la présentation visuelle de vos invitations de paquet.Cependant, gardez à l'esprit que si vous déplacez l'un des objets autour hiérarchiquement, le code ne pourra pas les trouver et vous devrez faire des ajustements à votre code.
Une invite est composée de deux composants de haut niveau :
- PromptItem – La composante individuelle répétée pour chaque article dans un paquet (image de l'article, légende, nom, prix).
- Prompt – La fenêtre de confirmation elle-même.
L'affichage d'avertissement est également composé de deux composants :
- HudItem – Un composant individuel qui représente chaque option de menu dans l'affichage en aperçu.
- Hud – À être rempli avec programmatiquement avec HudItems .
Si vous souhaitez avoir un plus grand contrôle sur l'affichage en tête, au lieu d'utiliser simplement l'interface utilisateur HUD existante dans ReplicatedStorage.Bundles.Objects.BundlesGui , vous pouvez déplacer les choses pour répondre à vos propres exigences de conception.Assurez-vous simplement de mettre à jour le comportement du script client dans le script ReplicatedStorage.Bundles.Client.UIController.
Référence de l'API
Les types
Temps relativement
Une fois que le paquet RelativeTime est offert à un joueur, il reste disponible jusqu'à l'expiration de la durée.Ce type s'affiche sur l'affichage en tête du joueur et demande automatiquement des sessions futures jusqu'à l'expiration du paquet ou jusqu'à ce que le joueur l'achète.
Un exemple commun de ce type de paquet est une offre de paquet de démarrage à usage unique qui s'affiche à tous les nouveaux joueurs pendant 24 heures.Pour les meilleures pratiques de l'industrie sur la façon de mettre en œuvre des packs de démarrage, voir conception du pack de démarrage.
Nom | Type | Avertissement |
---|---|---|
includeOfflineTime | bool | (Facultatif) Si ce n'est pas configurer, se le temps passé dans l'expérience comptera pour la durée restante de l'offre. |
singleUse | bool | (Facultatif) Si ce n'est pas configurer, l'achat peut être réactivé après qu'il ait été acheté ou expiré.S'il est configurer, une fois acheté ou expiré la première fois, il ne sera plus jamais demandé, même si vous appelez Bundles.promptIfValidAsync avec le bundleId. |
Temps fixe
Une fois que le paquet FixedTime est offert à un joueur, il reste disponible jusqu'à la fin du temps universel coordonné (UTC).Ce type s'affiche sur l'affichage en tête du joueur et demande automatiquement des sessions futures jusqu'à l'expiration du paquet ou jusqu'à ce que le joueur l'achète.
Un exemple commun de ce type de paquet est une offre de vacances qui n'est disponible que pour un mois donné.
Une fois
Un paquet OneTime n'est disponible que dans le moment où il est offert à un joueur.Il ne s'affiche pas sur l'affichage en haut de la tête du joueur, et une fois qu'un joueur a fermé l'invite, il ne peut pas être réouvert jusqu'à ce qu'il soit à nouveau sollicité par le serveur.
Un exemple commun de ce type de paquet est une offre d'acheter plus de devises en expérience au moment où un joueur est à court.