バンドル 機能パッケージは、アイテムのコレクションをプレイヤーに割引で販売するための箱外機能を提供します。プレイヤーがカスタムインエクスペリエンス通貨または Robux を使用してバンドルを購入できるかどうか、使用したいバンドルタイプ、販売したいアイテムセット、ゲームプレイ中にプレイヤーに通知したい方法を選択できます。
パッケージのカスタマイズオプションを使用して、バンドルをカスタマイズして、エクスペリエンスのデザインと収益化目標に合わせることができます。たとえば:
- 費用の深さを増やして、さまざまな価格ポイントでアイテムをバンドルして、幅広いプレイヤーにアピールする
- ライブ操作 (LiveOps) を通じて、期間限定の独占アイテムのバンドルを提供して、ライブイベントをマネタイズします。

パッケージを取得
クリエイターストアは、 Roblox と Roblox コミュニティが作成したすべてのアセットを見つけるために使用できるツールボックスのタブ で、モデル、画像、メッシュ、オーディオ、プラグイン、ビデオ、フォントのアセットを含むプロジェクト内で使用されるすべてのアセットを見つけることができます。クリエイターストアを使用して、機能パッケージを含むオープンエクスペリエンスに 1つまたは複数のアセットを直接追加できます!
すべての機能パッケージには、適切に機能するには コア 機能パッケージが必要です。コア と バンドル 機能パッケージのアセットがインベントリ内持ち物リストあると、プラットフォーム上のどのプロジェクトでも再利用できます。
インベントリからエクスペリエンスにパッケージを取得するには:
スタジオ内のインベントリに コア と バンドル 機能パッケージを追加するには、次のコンポーネントの インベントリに追加 リンクをクリックしてください。
ツールバーで ビュー タブを選択します。
クリックして ツールボックス 。 ツールボックス ウィンドウが表示されます。
ツールボックス ウィンドウで、 インベントリ タブをクリックします。 マイモデル ソートが表示されます。
クリックする 機能パッケージコア タイル、次に バンドル機能パッケージ タイル。両方のパッケージフォルダが エクスプローラー ウィンドウに表示されます。
パッケージフォルダを ReplicatedStorage にドラッグします。
データストア呼び出しを許可して、パッケージでプレイヤー購入を追跡する
- ツールバーの ホーム タブで、 ゲーム設定 を選択します。
- ナビゲート to the セキュリティ tab, then enable API サービスへの Studio アクセスを有効にする 。
通貨を定義する
エクスペリエンスに独自の通貨システムがある場合、 コア 機能パッケージで定義して、ReplicatedStorage.FeaturePackagesCore.Configs.Currencies でそれらを登録できます。このファイルにはすでにコメント付きのジェム通貨の例があります;自所有のものに置き換えます。
通貨
Gems = {displayName = "Gems",symbol = "💎",icon = nil,},
Currencies スクリプトは、 コア 機能パッケージに通貨に関するメタデータを告知します:
- (必須) displayName - 通貨の名前。シンボルまたはアイコンを指定しない場合、この名前は購入ボタン (例: "100 ジェム") で使用されます。
- (オプション) symbol - 通貨のアイコンとして使用するテキストキャラクターがある場合、これは購入ボタン (例: "💎100") の displayName の代わりに使用されます。
- (オプション) icon - 通貨のための AssetId 画像アイコンがある場合、購入ボタンの displayName の代わりに使用されます (つまり画像は価格の左に配置されます "🖼️100")
通貨が設定されたら、バンドルの関連開発者製品から情報が取得されるのではなく、ヘッドアップディスプレイのためにバンドルの価格、通貨、アイコンを手動で指定する必要があります。
バンドル
-- 開発製品を使用したい場合は、1つのバンドルに使用される唯一の devProductId を提供する必要があります。-- 開発者製品からバンドル価格とアイコンを取得するpricing = {priceType = CurrencyTypes.PriceType.Marketplace,devProductId = 1795621566,},-- そうでない場合、開発製品の代わりに経験通貨を使用したい場合は、次のように使用できます:-- 価格は、経験中の通貨であり、Robuxではありませんpricing = {priceType = CurrencyTypes.PriceType.InExperience,price = 79,currencyId = "Gems",icon = 18712203759,},
また、BundlesExample スクリプトを参照して setInExperiencePurchaseHandler を呼び出す必要があります。
バンドルの例
local function awardInExperiencePurchase(
_player: Player,
_bundleId: Types.BundleId,
_currencyId: CurrencyTypes.CurrencyId,
_price: number
)
-- プレイヤーがバンドルを購入するのに十分な通貨を持っているかどうかをチェック
-- プレイヤーデータを更新し、アイテムなどを提供する
-- プレイヤーから通貨を控除する
task.wait(2)
return true
end
local function initializePurchaseHandlers()
local bundles = Bundles.getBundles()
for bundleId, bundle in bundles do
-- 市場価入力タイプがない場合、バンドルは開発者製品に関連していません
if not bundle or bundle.pricing.priceType ~= "Marketplace" then
continue
end
Bundles.setPurchaseHandler(bundleId, awardMarketplacePurchase)
receiptHandlers[bundle.pricing.devProductId] = receiptHandler
end
-- バンドルに使用している経験通貨がある場合は、ここでハンドラーを設定します
for currencyId, _ in Currencies do
Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)
end
end
特に、例の awardInExperiencePurchase 内でループを呼び出して Currencies によって記入する必要がある initializePurchaseHandlers (つまりそれぞれの通貨IDは、Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase) を介してハンドラーに接続されています。
バンドルを定義する
エクスペリエンスで提供できるすべてのバンドルは、ReplicatedStorage.Bundles.Configs.Bundles 内で定義でき、タイプは同じフォルダの Types スクリプトからエクスポートされます。
を使用している場合、バンドルのメイン をエクスペリエンスと一致させるために更新する必要があります。これは、バンドル自体を購入するように MarketplaceService を通じて求められるものです。 バンドルには、別々の販売を追跡しやすくするために、新しい開発者製品を使用することを強く推奨します。 複数のアイテムが含まれるバンドルを希望し、これらがすでにエクスペリエンス内の開発者製品で表現されている場合、製品情報を介して取得されるアイテム価格/アセットID/名前を明示的に設定する必要はありません:
読み込みメモ
{itemType = ItemTypes.ItemType.DevProduct,devProductId = <DEV_PRODUCT_ID>,metadata = {caption = {text = "x1",color = Color3.fromRGB(236, 201, 74),} -- キャプションはオプションです!このフィールドを省略することもできます}},
そうでない場合は、手動でそれらのアイテム詳細を構成できます:
読み込みメモ
{itemType = ItemTypes.ItemType.Robux,priceInRobux = 49,icon = <IMAGE_ASSET_ID>,metadata = {caption = {text = "x1",color = Color3.fromRGB(236, 201, 74),} -- キャプションはオプションです!このフィールドを省略することもできます}},
たとえば、バンドル全体はおそらくこのように見えます:
読み込みメモ
local starterBundle: Types.RelativeTimeBundle = {bundleType = Types.BundleType.RelativeTime,-- 開発製品を使用したい場合は、1つのバンドルに使用される唯一の devProductId を提供する必要があります。-- 開発者製品からバンドル価格とアイコンを取得するpricing = {priceType = CurrencyTypes.PriceType.Marketplace,devProductId = <DEV_PRODUCT_ID>,},-- そうでない場合、開発製品の代わりに経験通貨を使用したい場合は、次のように使用できます:-- 価格は、経験中の通貨であり、Robuxではありません-- 価格 = {-- priceType = 通貨タイプ.PriceType.InExperience、-- 料金 = 79、-- currencyId = <CURRENCY_ID>、-- アイコン = <IMAGE_ASSET_ID>、-- },includedItems = {[1] = {-- アイテム自体は開発者製品で販売されていないので、Robux での価値を示し、アイコンを付ける-- The priceInRobux は、バンドルの価格とバンドルの内容の合計の相対的な価値をバンドルの価格と比較するのに役立ちますitemType = ItemTypes.ItemType.Robux,priceInRobux = 49,icon = <IMAGE_ASSET_ID>,-- 代わりに、これにデベロッパー製品の価格とアイコンが上にあり、devProductId のみを設定する場合-- 価格とアイコンは、開発者製品から取得されます-- devProductId = <ITEM_DEV_PRODUCT_ID>-- 必要に応じて UI 特有のオプションメタデートフィールドが増えます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, -- 購入または期限切れ後、エクスペリエンスがプロンプトを表示しようとしても、もはや有効ではありません。スタジオでテスト中にこれを偽りにすることができます。durationInSeconds = 900, -- 15分includesOfflineTime = false, -- エクスペリエンス内で過ごした時間のみをカウントするmetadata = {displayName = "STARTER BUNDLE",description = "Save 75% and get a head start!",},}
サーバーロジックを統合する
にある、サーバーが バンドル 機能パッケージと上記のメソッドとどのように対話するかを示すを見てください。以下のスニペットは、そのスクリプトからです。
主に、 バンドル 機能パッケージをエクスペリエンスにドラッグした後、4つのものを接続する必要があります:
Bundles.setPurchaseHandler を介して購入ハンドラーを接続して、購入が処理されると報酬アイテムを呼び出す機能を指定します。
バンドルの例local function awardMarketplacePurchase(_player: Player, _bundleId: Types.BundleId, _receiptInfo: { [string]: any })-- プレイヤーデータを更新し、アイテムなどを提供する-- ... そしてレコード受信情報.PurchaseId を記録して、ユーザーがすでにこのバンドルを持っているかどうかをチェックできるようにしますtask.wait(2)return Enum.ProductPurchaseDecision.PurchaseGrantedendlocal function awardInExperiencePurchase(_player: Player,_bundleId: Types.BundleId,_currencyId: CurrencyTypes.CurrencyId,_price: number)-- プレイヤーがバンドルを購入するのに十分な通貨を持っているかどうかをチェック-- プレイヤーデータを更新し、アイテムなどを提供する-- プレイヤーから通貨を控除するtask.wait(2)return trueendlocal function initializePurchaseHandlers()local bundles = Bundles.getBundles()for bundleId, bundle in bundles do-- 市場価入力タイプがない場合、バンドルは開発者製品に関連していませんif not bundle or bundle.pricing.priceType ~= "Marketplace" thencontinueendBundles.setPurchaseHandler(bundleId, awardMarketplacePurchase)receiptHandlers[bundle.pricing.devProductId] = receiptHandlerend-- バンドルに使用している経験通貨がある場合は、ここでハンドラーを設定しますfor currencyId, _ in Currencies doBundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)endendロジックをMarketplaceService.ProcessReceiptに接続しますが、経験がすでに販売用の開発者製品を持っている場合は、別の場所で行う可能性があります。本質的に、開発者製品の領収書が処理されているとき、彼らは今 Bundles.getBundleByDevProduct を呼び出して、製品がバンドルに属しているかどうかをチェックします。そうなると、スクリプトは Bundles.processReceipt を呼び出します。
バンドルの例-- 市場から受け取ったレシートを処理して、プレイヤーに課金する必要があるかどうかを判断するlocal 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] -- 製品のハンドラを取得するlocal success, result = pcall(handler, receiptInfo, player) -- ハンドラーを呼び出して、購入ロジックが成功しているかどうかを確認するif 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-- この購入はバンドルに属しています、バンドルで処理させましょうlocal purchaseDecision = Bundles.processReceiptAsync(player, bundleId, receiptInfo)return purchaseDecision == Enum.ProductPurchaseDecision.PurchaseGrantedend-- この購入はバンドルに属していません、-- ... 既存のロジックをすべてここで処理します、もしあればreturn falseendConnect Players.PlayerAdded:Connect(Bundles.OnPlayerAdded) して、 バンドル 機能パッケージが、プレイヤーに期限切れしていないアクティブなバンドルを再度尋ねさせます。
読み込みメモlocal function onPlayerAdded(player: Player)-- プレイヤーが参加したときにバンドルに通知して、データを再読み込みできるようにするBundles.onPlayerAdded(player)-- すべての新規ユーザーに提供したいスターターバンドルがある場合は、ここでそれを促すことができます-- ...バンドルは、プレイヤーがすでに購入しているか、または期限切れであるため、処理します-- バンドル.promptIfValidAsync(playeプレイヤー, "スターターバンドル")-- これを例としてここで呼び出すと、いつでもどこでも呼び出すことができますonPromptBundleXYZEvent(player)endプロンプトバンドル。これはゲームプレイに依存しますが、例としてスターターバンドルでプレイヤーをプロンプトします onPlayerAdded 。
バンドル機能パッケージロジックは、すでにバンドルを購入したプレイヤーに再度オファーを提示しないようにし、またはバンドルの期限が切れているオファーをすでに期限切れにします(バンドルの構成に基づく)。
バンドルをプレイヤーに促すたびに、Bundles.promptIfValidAsync(player, bundleId) を呼び出します。
読み込みメモlocal function onPromptBundleXYZEvent(player: Player)-- 使用したい経験イベントを何でも接続して、プレイヤーがバンドルを求められるタイミングを決定する-- ... これは、プレイヤーにバンドルを提供するように求めるために、資格基準に出会ったときのいつでもあります-- ... たとえば、プレイヤーが参加するときや、プレイヤーがレベルアップするときにバンドルを表示したい場合task.spawn(Bundles.promptIfValidAsync, player, <Some_Bundle_Id>)-- ... 複数のバンドルを作成する場合、タスク.spawn() を使用して上記の機能呼び出しを包むと、カウントダウンの間の差異が最小化されますend
受領IDの重複記録に関する次のベストプラクティスガイドラインを考えてください:
バンドル機能パッケージは、同じレシープを二度処理しないようにレシープIDを記録していますが、購入ハンドラーが購入後すでに終了した場合、次のリトライでアイテムを授与しないことを知るために、テーブル内でレシープIDを記録する必要もあります。
バンドル 機能パッケージは、購入がどのステップでも失敗するとレシープIDを記録しないので、購入ハンドラーの一部としてレシープを処理する前にテーブルにレシープIDを記録していることを確認する必要があります。
この冗長性は、すべての購入ロジックが適切に処理され、データストアと バンドル 機能パッケージのデータストアが最終的に一致するようになることを保証し、データストアが真実の源となります。
定数を構成する
コア 機能パッケージの定数は 2つの場所でライブです:
共有されたコンスタントは ReplicatedStorage.FeaturePackagesCore.Configs.SharedConstants で生きています。
パッケージ固有のコンスタント、この場合、 バンドル 機能パッケージ、は ReplicatedStorage.Bundles.Configs.Constants でライブします。
エクスペリエンスのデザイン要件に対応するために調整したい主なもの:
- サウンドアセットID
- 購入効果の期間とパーティクルの色を購入する
- 頭上ディスプレイの崩壊可能性を注意する
さらに、1つの場所に分離された翻訳用のストリングを見つけることもできます:ReplicatedStorage.FeaturePackagesCore.Configs.TranslationStrings。
UI コンポーネントをカスタマイズする
色、フォント、透明性などのパッケージオブジェクトを変更することで、バンドルプロンプトのビジュアル表示を調整できます。ただし、オブジェクトのいずれかを階層的に移動すると、コードが見つけられない可能性があるため、コードを調整する必要があります。
プロンプトは 2つの高レベルコンポーネントで構成されています:
- PromptItem – バンドル内の各アイテムに対して繰り返される個々のコンポーネント (アイテム画像、キャプション、名前、価格)。
- Prompt – プロンプトウィンドウ自体。
ヘッドアップディスプレイは、2つのコンポーネントで構成されています:
- HudItem – 頭上表示で各メニューオプションを表現する個々のコンポーネント。
- Hud – プログラマティックに満たされて HudItems 。
ヘッドアップディスプレイをより制御したい場合は、ReplicatedStorage.Bundles.Objects.BundlesGui 内の既存の HUD UI だけを使用するのではなく、自分のデザイン要件に合わせて物事を移動できます。ただし、ReplicatedStorage.Bundles.Client.UIController スクリプトでクライアントスクリプトの動作を更新することを忘れないでください。
API リファレンス
タイプ
相対時間
RelativeTime バンドルがプレイヤーに提供されると、期間が切れるまで利用可能です。このタイプは、プレイヤーのヘッドアップディスプレイに表示され、バンドルの期限が切れるか、プレイヤーが購入するまで、将来のセッションに自動的に通知します。
このバンドルタイプの共通の例は、24時間すべての新規プレイヤーに表示される単一使用のスターターパックオファーです。スターターパックバンドルの実装方法に関する業界ベストプラクティスは、スターターパックデザイン を参照してください。
名前 | 種類 | 説明 |
---|---|---|
includeOfflineTime | bool | (オプション) 設設定するされていない場合、経験で過ごした時間だけが残りのオファー期間にカウントされます。 |
singleUse | bool | (オプション) 設定されていない場合、購入は購入または期限切れ後に再起動できます。設定されている場合は、最初に購入または期限切れしたとき、バンドルID で Bundles.promptIfValidAsync を呼んでも再度求められることはありません。 |
固定時間
FixedTime バンドルがプレイヤーに提供されると、設定された普遍的な時間 (UTC) の終了まで利用可能です。このタイプは、プレイヤーのヘッドアップディスプレイに表示され、バンドルの期限が切れるか、プレイヤーが購入するまで、将来のセッションに自動的に通知します。
このバンドルタイプの共通の例は、特定の月にのみ利用できるホリデーオファーです。
1回限り
A OneTime バンドルは、プレイヤーに提供された瞬間にのみ利用可能です。プレイヤーのヘッドアップディスプレイに表示されず、プレイヤーがプロンプトを閉じると、サーバーによって再度プロンプトが表示されるまで再開できません。
このバンドルタイプの一般的な例は、プレイヤーが品切れになった瞬間に、より多くの経験通貨を購入するオファーです。