Le paquet de fonctionnalités Bundles offre une fonctionnalité prête à l'emploi pour vendre des collections d'objets aux joueurs à prix réduit. Vous pouvez choisir de permettre aux joueurs d'acheter des bundles en utilisant une monnaie personnalisée dans le jeu ou des Robux, quel type de bundle vous souhaitez utiliser, quel ensemble d'objets vous souhaitez vendre et comment vous souhaitez inciter les joueurs pendant leur gameplay.
En utilisant les options de personnalisation du paquet, vous pouvez adapter vos bundles pour répondre aux objectifs de conception et de monétisation de vos jeux, tels que :
- Cibler un faible taux de conversion en proposant des packs de démarrage à prix réduit qui apportent de la valeur aux nouveaux joueurs et encouragent les dépenses précoces.
- Augmenter la profondeur de dépense en regroupant des objets à différents niveaux de prix pour séduire une gamme de joueurs.
- Monétiser les opérations en direct (LiveOps) événements en proposant des bundles d'objets exclusifs à durée limitée.

Obtenir le paquet
Le Creator Store est un onglet de la Toolbox que vous pouvez utiliser pour trouver tous les actifs créés par Roblox et la communauté Roblox pour une utilisation dans vos projets, y compris des modèles, des images, des maillages, des audio, des plugins, des vidéos et des polices. Vous pouvez utiliser le Creator Store pour ajouter un ou plusieurs actifs directement dans un jeu ouvert, y compris des paquets de fonctionnalités !
Chaque paquet de fonctionnalités nécessite le paquet de fonctionnalités Core pour fonctionner correctement. Une fois que les actifs des paquets de fonctionnalités Core et Bundles sont dans votre inventaire, vous pouvez les réutiliser dans n'importe quel projet sur la plateforme.
Pour obtenir les paquets de votre inventaire dans votre jeu :
Ajoutez le paquet de fonctionnalités Core et Bundles à votre inventaire dans Studio en cliquant sur le lien Ajouter à l'inventaire dans le jeu de composants suivant.
Dans le menu Fenêtre de Studio ou la barre d'outils de l'onglet Accueil, ouvrez la Toolbox.
Dans la fenêtre Toolbox, cliquez sur l'onglet Inventaire. Le tri Mes modèles s'affiche.

Cliquez sur le carreau Paquet de fonctionnalités Core, puis sur le carreau Paquet de fonctionnalités Bundles. Les deux dossiers de paquets s'affichent dans la fenêtre Explorer.
Faites glisser les dossiers de paquets dans ReplicatedStorage.
Autorisez les appels au magasin de données pour suivre les achats des joueurs avec les paquets.
- Ouvrez la fenêtre Fichier ⟩ Paramètres de l'expérience de Studio.
- Accédez à l'onglet Sécurité, puis activez Activer l'accès Studio aux services API.
Définir les devises
Si votre jeu a son propre système de devises, vous pouvez les enregistrer avec le paquet de fonctionnalités Core en les définissant dans ReplicatedStorage.FeaturePackagesCore.Configs.Currencies. Il y a un exemple commenté d'une devise de Gems déjà dans ce fichier ; remplacez-le par le vôtre.
Gems = {
displayName = "Gems",
symbol = "💎",
icon = nil,
},Le script Currencies indique au paquet de fonctionnalités Core certaines métadonnées sur votre devise :
- (obligatoire) displayName - Le nom de votre devise. Si vous ne spécifiez pas de symbole ou d'icône, ce nom est utilisé dans les boutons d'achat (c'est-à-dire "100 Gems").
- (optionnel) symbol - Si vous avez un caractère texte à utiliser comme icône pour votre devise, cela est utilisé à la place du displayName dans les boutons d'achat (c'est-à-dire "💎100").
- (optionnel) icon - Si vous avez un AssetId d'image icône pour votre devise, cela est utilisé à la place du displayName dans les boutons d'achat (c'est-à-dire que l'image sera placée à gauche du prix "🖼️100").
Une fois votre devise configurée, vous devez spécifier manuellement le prix du bundle, la devise et l'icône pour l'affichage au lieu que ces informations soient récupérées à partir du produit développeur associé au bundle.
-- Si vous souhaitez utiliser un produit développeur, vous devez fournir un devProductId unique, utilisé uniquement par un bundle.
-- Nous allons récupérer le prix et l'icône du bundle à partir du produit développeur
pricing = {
priceType = CurrencyTypes.PriceType.Marketplace,
devProductId = 1795621566,
},
-- Sinon, si vous souhaitez utiliser une devise dans le jeu au lieu d'un produit développeur, vous pouvez utiliser ce qui suit :
-- Le prix ici est en devise dans le jeu, pas en Robux
pricing = {
priceType = CurrencyTypes.PriceType.InExperience,
price = 79,
currencyId = "Gems",
icon = 18712203759,
},Vous devez également référencer le script BundlesExample pour appeler setInExperiencePurchaseHandler.
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 bundle
-- Mettez à jour les données du joueur, donnez des objets, etc.
-- Déduisez la devise du joueur
task.wait(2)
return true
end
local function initializePurchaseHandlers()
local bundles = Bundles.getBundles()
for bundleId, bundle in bundles do
-- Le bundle n'est pas associé à un produit développeur s'il n'a pas de type de 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 dans le jeu que vous utilisez pour les bundles, définissez le gestionnaire ici
for currencyId, _ in Currencies do
Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)
end
endSpécifiquement, vous devez remplir awardInExperiencePurchase, qui est appelé par une boucle à travers Currencies à l'intérieur de l'exemple initializePurchaseHandlers (c'est-à-dire que chaque currencyId est connecté au gestionnaire via Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)).
Définir les bundles
Tous les bundles offerts dans votre jeu peuvent être définis dans ReplicatedStorage.Bundles.Configs.Bundles, avec des types exportés depuis le script Types dans le même dossier.
Si vous utilisez un devProductId, vous devez mettre à jour le devProductId principal du bundle pour qu'il corresponde à celui de votre jeu. C'est ce qui sera demandé via MarketplaceService pour acheter le bundle lui-même. Il est fortement recommandé d'utiliser un nouveau produit développeur pour le bundle afin de faciliter le suivi des ventes séparées.
Si vous souhaitez un bundle avec plusieurs objets, et si ceux-ci sont déjà représentés par des produits développeurs dans votre jeu, vous n'avez pas besoin de définir explicitement le prix/l'assetId/le nom de l'objet, qui seront récupérés via les informations du produit :
{
itemType = ItemTypes.ItemType.DevProduct,
devProductId = <DEV_PRODUCT_ID>,
metadata = {
caption = {
text = "x1",
color = Color3.fromRGB(236, 201, 74),
} -- La légende est optionnelle ! Vous pouvez également omettre ce champ
}
},Sinon, vous pouvez configurer manuellement ces détails d'objet :
{
itemType = ItemTypes.ItemType.Robux,
priceInRobux = 49,
icon = <IMAGE_ASSET_ID>,
metadata = {
caption = {
text = "x1",
color = Color3.fromRGB(236, 201, 74),
} -- La légende est optionnelle ! Vous pouvez également omettre ce champ
}
},Par exemple, votre bundle entier ressemblera probablement à ceci :
local starterBundle: Types.RelativeTimeBundle = {
bundleType = Types.BundleType.RelativeTime,
-- Si vous souhaitez utiliser un produit développeur, vous devez fournir un devProductId unique, utilisé uniquement par un bundle.
-- Nous allons récupérer le prix et l'icône du bundle à partir du produit développeur
pricing = {
priceType = CurrencyTypes.PriceType.Marketplace,
devProductId = <DEV_PRODUCT_ID>,
},
-- Sinon, si vous souhaitez utiliser une devise dans le jeu au lieu d'un produit développeur, vous pouvez utiliser ce qui suit :
-- Le prix ici est en devise dans le jeu, pas en Robux
-- pricing = {
-- priceType = CurrencyTypes.PriceType.InExperience,
-- price = 79,
-- currencyId = <CURRENCY_ID>,
-- icon = <IMAGE_ASSET_ID>,
-- },
includedItems = {
[1] = {
-- L'objet lui-même n'est pas vendu via un produit développeur, donc indiquez combien il vaut en Robux et donnez une icône
-- Le priceInRobux aide Bundles à montrer la valeur relative du prix du bundle par rapport à la somme de son contenu
itemType = ItemTypes.ItemType.Robux,
priceInRobux = 49,
icon = <IMAGE_ASSET_ID>,
-- Alternativement, si cela a un produit développeur, laissez de côté le prix et l'icône ci-dessus et définissez simplement le devProductId
-- Le prix et l'icône seront récupérés à partir du produit développeur
-- devProductId = <ITEM_DEV_PRODUCT_ID>
-- Il existe d'autres champs de métadonnées optionnels qui sont spécifiques à l'UI si nécessaire
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, -- Une fois acheté ou expiré, il n'est plus valide même si votre jeu essaie de le proposer (onPlayerAdded). Vous pouvez mettre cela à false pendant les tests dans le studio.
durationInSeconds = 900, -- 15 minutes
includesOfflineTime = false, -- Ne comptez que le temps écoulé dans le jeu
metadata = {
displayName = "BUNDLE DE DÉMARRAGE",
description = "Économisez 75 % et obtenez un bon départ !",
},
}Intégrer la logique serveur
Jetez un œil à 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 connecter quatre choses une fois que vous avez glissé le paquet de fonctionnalités Bundles dans votre jeu :
Connectez les gestionnaires d'achat via Bundles.setPurchaseHandler pour spécifier les fonctions à appeler pour attribuer des objets lorsqu'un achat est en cours de traitement.
BundlesExamplelocal function awardMarketplacePurchase(_player: Player, _bundleId: Types.BundleId, _receiptInfo: { [string]: any })-- Mettez à jour les données du joueur, donnez des objets, etc.-- ... ET enregistrez receiptInfo.PurchaseId afin que nous puissions vérifier si l'utilisateur a déjà ce bundletask.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 bundle-- Mettez à jour les données du joueur, donnez des objets, etc.-- Déduisez la devise du joueurtask.wait(2)return trueendlocal function initializePurchaseHandlers()local bundles = Bundles.getBundles()for bundleId, bundle in bundles do-- Le bundle n'est pas associé à un produit développeur s'il n'a pas de type de 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 dans le jeu que vous utilisez pour les bundles, définissez le gestionnaire icifor currencyId, _ in Currencies doBundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)endendConnectez votre logique pour MarketplaceService.ProcessReceipt, mais cela peut être fait ailleurs si votre jeu a déjà des produits développeurs à vendre. Essentiellement, lorsqu'un reçu de produit développeur est en cours de traitement, ils appelleront maintenant Bundles.getBundleByDevProduct pour vérifier si le produit appartient à un bundle. Si c'est le cas, le script appelle ensuite Bundles.processReceipt.
BundlesExample-- Traitez le reçu 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 pour le produitlocal success, result = pcall(handler, receiptInfo, player) -- Appelez le gestionnaire pour vérifier si la logique d'achat est réussieif not success or not result thenwarn("Échec du traitement du reçu :", 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 bundle, laissez Bundles s'en occuperlocal purchaseDecision = Bundles.processReceiptAsync(player, bundleId, receiptInfo)return purchaseDecision == Enum.ProductPurchaseDecision.PurchaseGrantedend-- Cet achat n'appartient pas à un bundle,-- ... Gérez toute votre logique existante ici si vous en avezreturn falseendConnectez Players.PlayerAdded:Connect(Bundles.OnPlayerAdded) afin que le paquet de fonctionnalités Bundles redemande tous les bundles actifs qui n'ont pas encore expiré pour un joueur.
READMElocal function onPlayerAdded(player: Player)-- Informez Bundles lorsque le joueur rejoint afin qu'il puisse recharger ses donnéesBundles.onPlayerAdded(player)-- Si vous aviez un bundle de démarrage que vous souhaitiez offrir à tous les nouveaux utilisateurs, vous pourriez le proposer ici-- ... Bundles s'occupera de vérifier si le joueur a déjà acheté ou si c'est expiré puisque ce n'est pas répétable-- Bundles.promptIfValidAsync(player, "StarterBundle")-- Appeler cela ici juste à titre d'exemple, vous pouvez appeler cela quand vous le souhaitezonPromptBundleXYZEvent(player)endProposez des bundles. Bien que cela dépende du gameplay, l'exemple propose aux joueurs un StarterBundle onPlayerAdded.
La logique du paquet de fonctionnalités Bundles garantit que chaque joueur ne reçoit pas une offre répétée s'il a déjà acheté le bundle, ou s'il a laissé l'offre expirer (en fonction de la configuration du bundle).
Chaque fois que vous souhaitez proposer un bundle à un joueur, appelez Bundles.promptIfValidAsync(player, bundleId).
READMElocal function onPromptBundleXYZEvent(player: Player)-- Connectez n'importe quel événement de jeu que vous souhaitez utiliser pour déterminer quand un joueur reçoit le bundle-- ... Cela sera chaque fois que vous aurez rempli vos critères d'éligibilité pour proposer un bundle à un joueur-- ... Par exemple, si vous souhaitez proposer un bundle lorsqu'un joueur rejoint, ou lorsqu'un joueur monte de niveautask.spawn(Bundles.promptIfValidAsync, player, <Some_Bundle_Id>)-- ... Si vous créez plusieurs bundles, utilisez task.spawn() pour envelopper l'appel de fonction ci-dessus minimisera les écarts entre les compte à reboursend
Considérez les conseils de bonnes pratiques suivants sur les enregistrements redondants des ReceiptIds :
Bien que le paquet de fonctionnalités Bundles enregistre les ReceiptIds pour éviter de traiter le même reçu deux fois, vous devriez également enregistrer les ReceiptIds dans vos tables afin que si le flux d'achat échoue après que son gestionnaire d'achat a déjà terminé, vous sachiez lors de la tentative suivante de ne pas attribuer à nouveau des objets.
Le paquet de fonctionnalités Bundles n'enregistrera pas le ReceiptId si l'achat échoue à n'importe quelle étape, donc vous devez vous assurer que vous enregistrez le ReceiptId dans vos tables avant de traiter le reçu dans le cadre de votre purchaseHandler.
Cette redondance aide à garantir que toute la logique d'achat a été correctement gérée et que votre magasin de données et le magasin de données du paquet de fonctionnalités Bundles atteignent une cohérence éventuelle, votre magasin de données étant la source de vérité.
Configurer les constantes
Les constantes pour le paquet de fonctionnalités Core se trouvent à deux endroits :
Les constantes partagées se trouvent dans ReplicatedStorage.FeaturePackagesCore.Configs.SharedConstants.
Les constantes spécifiques au paquet, dans ce cas le paquet de fonctionnalités Bundles, se trouvent dans ReplicatedStorage.Bundles.Configs.Constants.
Les principales choses que vous pourriez vouloir ajuster pour répondre aux exigences de conception de votre jeu :
- Identifiants d'actifs sonores
- Durée de l'effet d'achat et couleurs des particules
- Capacité de réduction de l'affichage
De plus, vous pouvez trouver des chaînes pour la traduction regroupées en un seul endroit : ReplicatedStorage.FeaturePackagesCore.Configs.TranslationStrings.
Personnaliser les composants UI
En modifiant les objets du paquet, tels que les couleurs, la police et la transparence, vous pouvez ajuster la présentation visuelle de vos invites de bundle. Cependant, gardez à l'esprit que si vous déplacez l'un des objets dans la hiérarchie, le code ne pourra pas les trouver, et vous devrez apporter des ajustements à votre code.
Une invite est composée de deux composants de haut niveau :
- PromptItem – Le composant individuel répété pour chaque objet dans un bundle (image de l'objet, légende, nom, prix).
- Prompt – La fenêtre d'invite elle-même.
L'affichage est également composé de deux composants :
- HudItem – Un composant individuel qui représente chaque option de menu dans l'affichage.
- Hud – À remplir de manière programmatique avec HudItems.
Si vous souhaitez avoir un meilleur contrôle sur l'affichage, au lieu d'utiliser simplement l'UI HUD existante dans ReplicatedStorage.Bundles.Objects.BundlesGui, vous pouvez déplacer les éléments 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 API
Types
RelativeTime
Une fois que le bundle RelativeTime est proposé à un joueur, il reste disponible jusqu'à ce que la durée définie expire. Ce type s'affiche sur l'affichage du joueur et invite automatiquement lors des sessions futures jusqu'à ce que le bundle expire ou que le joueur l'achète.
Un exemple courant de ce type de bundle est une offre de pack de démarrage à usage unique qui s'affiche à tous les nouveaux joueurs pendant 24 heures.
| Nom | Type | Description |
|---|---|---|
| includeOfflineTime | bool | (Optionnel) Si non défini, seul le temps passé dans le jeu comptera pour la durée restante de l'offre. |
| singleUse | bool | (Optionnel) Si non défini, l'achat peut être réactivé après son achat ou son expiration. Si défini, une fois acheté ou expiré la première fois, il ne pourra plus jamais être proposé, même si vous appelez Bundles.promptIfValidAsync avec le bundleId. |
FixedTime
Une fois que le bundle FixedTime est proposé à un joueur, il reste disponible jusqu'à la fin de l'heure universelle coordonnée (UTC) définie. Ce type s'affiche sur l'affichage du joueur et invite automatiquement lors des sessions futures jusqu'à ce que le bundle expire ou que le joueur l'achète.
Un exemple courant de ce type de bundle est une offre de vacances qui n'est disponible que pendant un mois donné.
OneTime
Un bundle OneTime n'est disponible que au moment où il est proposé à un joueur. Il ne s'affiche pas sur l'affichage du joueur, et une fois qu'un joueur ferme l'invite, elle ne peut pas être rouverte jusqu'à ce qu'elle soit proposée à nouveau par le serveur.
Un exemple courant de ce type de bundle est une offre pour acheter plus de devises dans le jeu au moment où un joueur en manque.