套裝功能包提供即開即用的功能,可以以折扣價格將物品集合出售給玩家。您可以選擇允許玩家使用自定義的體驗內貨幣或 Robux 購買套裝,選擇要使用的套裝類型,選擇要出售的物品集,以及您想在玩家遊玩過程中如何提示玩家。
通過使用此包的自定義選項,您可以根據您的體驗設計和貨幣化目標調整套裝,例如:
- 針對低 轉換率 指標,提供有價值的折扣入門包,從而吸引新玩家並鼓勵早期消費。
- 通過捆綁各個價格點的物品來提高 消費深度,以吸引一範圍的玩家。
- 通過提供限時專屬物品的套裝來貨幣化現場操作(LiveOps)事件。

獲取包
創作者商店是 工具箱 的一個選項卡,您可以使用它來查找所有由 Roblox 和 Roblox 社群製作的資產,這些資產可在您的項目中使用,包括模型、圖片、網格、音訊、插件、視頻和字體資產。您可以使用創作者商店直接將一個或多個資產添加到打開的體驗中,包括功能包!
每個功能包都需要 核心 功能包才能正常運行。只要 核心 和 套裝 功能包資產在您的庫存中,您就可以在平台的任何項目中重用它們。
要將包從您的庫存中導入您的體驗:
通過單擊以下組件集中的 添加到庫存 連結,將 核心 和 套裝 功能包添加到您在 Studio 中的庫存。
從 Studio 的 窗口 菜單或 主頁 標籤工具欄中,打開 工具箱。
在 工具箱 窗口中,單擊 庫存 標籤。顯示 我的模型 分類。

單擊 功能包核心 瓦片,然後單擊 包功能包 瓦片。兩個包資料夾會在 資源管理器 窗口中顯示。
將包資料夾拖入 ReplicatedStorage。
允許數據存儲調用來跟踪玩家使用包的購買情況。
- 打開 Studio 的 文件 ⟩ 體驗設置 窗口。
- 導航到 安全性 標籤,然後啟用 允許 Studio 訪問 API 服務。
定義貨幣
如果您的體驗有自己的貨幣系統,您可以通過在 ReplicatedStorage.FeaturePackagesCore.Configs.Currencies 中定義它們來將其註冊到 核心 功能包中。該文件中已經有一個註釋掉的 Gems 貨幣示例;將其替換為您自己的。
貨幣Gems = {displayName = "寶石",symbol = "💎",icon = nil,},
Currencies 腳本告訴 核心 功能包有關您的貨幣的一些元數據:
- (必需) displayName - 您的貨幣名稱。如果您不指定符號或圖標,則會在購買按鈕中使用此名稱(例如 "100 寶石")。
- (可選) symbol - 如果您有一個文本字符用作貨幣的圖標,則該符號將在購買按鈕中用於替代 displayName(例如 "💎100")。
- (可選) icon - 如果您有貨幣的 AssetId 圖像圖標,則會在購買按鈕中用於替代 displayName(例如圖片將放置在價格 "🖼️100" 的左側)
一旦您的貨幣設置完成,您還需要手動指定套裝的價格、貨幣和圖標,以替代從套裝的關聯開發者產品獲取的資訊。
套裝-- 如果您想使用開發產品,必須提供唯一的 devProductId,僅用於一個套裝。-- 我們將從開發者產品中獲取套裝價格和圖標pricing = {priceType = CurrencyTypes.PriceType.Marketplace,devProductId = 1795621566,},-- 否則,如果您想使用體驗內貨幣而不是開發產品,則可以使用以下內容:-- 此處的價格是以體驗內貨幣計算,而不是 Robuxpricing = {priceType = CurrencyTypes.PriceType.InExperience,price = 79,currencyId = "Gems",icon = 18712203759,},
您還需要引用 BundlesExample 腳本以調用 setInExperiencePurchaseHandler。
BundlesExample
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,該函數由在範例 initializePurchaseHandlers 中對 Currencies 的循環調用(即每個 currencyId 通過 Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase) 連接到處理程序)。
定義套裝
在 ReplicatedStorage.Bundles.Configs.Bundles 中可以定義您的體驗中可以提供的所有套裝,套裝類型從同一文件夾中的 Types 腳本中導出。
如果您使用 devProductId,則需要將套裝的主 devProductId 更新為與您的體驗中的一個匹配。這是在 MarketplaceService 中提示以購買套裝的對象。強烈建議為套裝使用一個新的開發產品,以便更容易追踪單獨的銷售。
如果您想要一個包含多個物品的套裝,並且這些物品已經在您的體驗中以開發產品的形式存在,則無需明確設置物品的價格/assetId/name,這些將會通過產品信息獲取:
README{itemType = ItemTypes.ItemType.DevProduct,devProductId = <DEV_PRODUCT_ID>,metadata = {caption = {text = "x1",color = Color3.fromRGB(236, 201, 74),} -- 標題是可選的!您也可以省略此字段}},
否則,您可以手動配置這些物品詳細信息:
README{itemType = ItemTypes.ItemType.Robux,priceInRobux = 49,icon = <IMAGE_ASSET_ID>,metadata = {caption = {text = "x1",color = Color3.fromRGB(236, 201, 74),} -- 標題是可選的!您也可以省略此字段}},
例如,您的整個套裝可能如下所示:
READMElocal starterBundle: Types.RelativeTimeBundle = {bundleType = Types.BundleType.RelativeTime,-- 如果您想使用開發產品,必須提供唯一的 devProductId,僅用於一個套裝。-- 我們將從開發者產品中獲取套裝價格和圖標pricing = {priceType = CurrencyTypes.PriceType.Marketplace,devProductId = <DEV_PRODUCT_ID>,},-- 否則,如果您想使用體驗內貨幣而不是開發產品,則可以使用以下內容:-- 此處的價格是以體驗內貨幣計算,而不是 Robux-- pricing = {-- priceType = CurrencyTypes.PriceType.InExperience,-- price = 79,-- currencyId = <CURRENCY_ID>,-- icon = <IMAGE_ASSET_ID>,-- },includedItems = {[1] = {-- 該物品本身不是通過開發產品出售的,因此請指明它在 Robux 中的價值並給予一個圖標-- 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, -- 購買後或過期後,將不再有效,即使您的體驗試圖提示(onPlayerAdded)。在 Studio 執行測試時,您可以將其設置為 false。durationInSeconds = 900, -- 15 分鐘includesOfflineTime = false, -- 僅計算在體驗中的時間metadata = {displayName = "入門套裝",description = "節省 75% 並獲得提前優勢!",},}
整合服務器邏輯
查看 ReplicatedStorage.Bundles.Server.Examples.BundlesExample,該文件顯示了您的服務器將如何與 套裝 功能包以及 ModuleScript 上述方法交互。下面的片段來自該腳本。
您主要需要在將 套裝 功能包拖入您的體驗後連接四件事:
通過 Bundles.setPurchaseHandler 連接購買處理程序,以指定在處理購買時調用的函數來授予物品。
BundlesExamplelocal function awardMarketplacePurchase(_player: Player, _bundleId: Types.BundleId, _receiptInfo: { [string]: any })-- 更新玩家數據,給予物品等-- ... 並記錄 receiptInfo.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。
BundlesExample-- 處理來自市場的收據以確定玩家是否需要被收費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("處理收據失敗:", 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-- 此購買屬於一個套裝,讓 Bundles 處理它local purchaseDecision = Bundles.processReceiptAsync(player, bundleId, receiptInfo)return purchaseDecision == Enum.ProductPurchaseDecision.PurchaseGrantedend-- 此購買不屬於任何套裝,-- ... 如果您有任何現有的邏輯,請在此處處理return falseend連接 Players.PlayerAdded:Connect(Bundles.OnPlayerAdded),這樣 套裝 功能包將重新提示任何尚未過期的活躍套裝給玩家。
READMElocal function onPlayerAdded(player: Player)-- 當玩家加入時告訴 Bundles,以便它可以重新加載他們的數據Bundles.onPlayerAdded(player)-- 如果您有一些想要提供給所有新用戶的入門套裝,您可以在這裡提示該套裝-- ... Bundles 將處理玩家是否已經購買或是否過期,因為它不可重複-- Bundles.promptIfValidAsync(player, "StarterBundle")-- 在這裡調用僅作為範例,您可以在任何時候或任何地方調用這個onPromptBundleXYZEvent(player)end提示套裝。雖然這取決於遊戲玩法,但示範中在 onPlayerAdded 時提示玩家使用入門套裝。
套裝 功能包邏輯確保每個玩家在已經購買該套裝或讓它過期(根據套裝配置)時不會再收到重複的提議。
每當您想提示套裝給玩家時,請調用 Bundles.promptIfValidAsync(player, bundleId)。
READMElocal function onPromptBundleXYZEvent(player: Player)-- 連接您希望使用的經驗事件,以確定何時提示玩家該套裝-- ... 這將是每當您滿足提示玩家套裝的資格標準時-- ... 例如,如果您想在玩家加入時提示套裝,或者在玩家升級時task.spawn(Bundles.promptIfValidAsync, player, <Some_Bundle_Id>)-- ... 如果創建多個套裝,使用 task.spawn() 包裝上述函數調用將最小化計時器之間的差異end
考慮以下有關冗餘記錄ReceiptIds的最佳實踐指導:
雖然 套裝 功能包會記錄 ReceiptIds 以避免重複處理相同收據,但您還應在您的表中記錄 ReceiptIds,以便如果購買流程在其購買處理程序已經完成後失敗,您知道在隨後的重試中不要再次獎勵物品。
如果購買在任何步驟中失敗,套裝 功能包將不會記錄 ReceiptId,因此您應確保在處理收據之前在您的表中記錄 ReceiptId。
此冗餘幫助確保所有購買邏輯已正確處理,並且您的數據存儲和 套裝 功能包的數據存儲最終達成一致,您的數據存儲是事實的來源。
配置常量
核心 功能包的常量位於兩個位置:
共享常量位於 ReplicatedStorage.FeaturePackagesCore.Configs.SharedConstants 中。
包特定常量,在此情況下是 套裝 功能包,位於 ReplicatedStorage.Bundles.Configs.Constants 中。
您可能想要調整的主要項目以滿足您的體驗設計需求:
- 音效資產 ID
- 購買效果持續時間和粒子顏色
- 界面顯示的可折疊性
此外,您可以在一個位置找到翻譯字符串:ReplicatedStorage.FeaturePackagesCore.Configs.TranslationStrings。
自定義 UI 組件
通過修改包對象,例如顏色、字體和透明度,您可以調整套裝提示的視覺展示。然而,請注意,如果您在層級上移動了任何對象,代碼將無法找到它們,您需要對代碼進行調整。
提示由兩個高級組件組成:
- PromptItem – 每個套裝內物品的個別組件(物品圖片、標題、名稱、價格)。
- Prompt – 提示窗口本身。
界面顯示也由兩個組件組成:
- HudItem – 表示介面顯示中每個菜單選項的個別組件。
- Hud – 由 HudItems 程序性填充。
如果您想要更好地控制介面顯示,而不僅僅是使用 ReplicatedStorage.Bundles.Objects.BundlesGui 中的現有 HUD UI,您可以將內容移動以滿足您的設計需求。請務必在 ReplicatedStorage.Bundles.Client.UIController 腳本中更新客戶端腳本的行為。
API 參考
類型
RelativeTime
一旦 RelativeTime 套裝提供給玩家,則在時間持續期到期之前保持可用。此類型會顯示在玩家的界面顯示中,並在未來的會話中自動提示,直到套裝過期或玩家購買它。
此套裝類型的一個常見示例是對所有新玩家提供的單次使用入門包優惠,持續 24 小時。
| 名稱 | 類型 | 描述 |
|---|---|---|
| includeOfflineTime | bool | (可選) 如果未設置,則僅在體驗中花費的時間將計入剩餘的優惠持續時間。 |
| singleUse | bool | (可選) 如果未設置,購買可以在購買或過期後重新啟用。 如果設置為 true,則在第一次購買或過期後,不會再提示,即使您調用 Bundles.promptIfValidAsync 並傳遞 bundleId。 |
FixedTime
一旦 FixedTime 套裝提供給玩家,它將保持可用,直到設置的協調世界時間(UTC)結束。此類型會顯示在玩家的界面顯示中,並在未來的會話中自動提示,直到套裝過期或玩家購買它。
此套裝類型的一個常見示例是僅在特定月份可用的假日優惠。
OneTime
OneTime 套裝僅在提供給玩家的那一刻可用。它不會顯示在玩家的界面顯示中,並且一旦玩家關閉提示,就無法再打開,直到服務器再次提示。
此套裝類型的一個常見示例是當玩家用完體驗內貨幣時,提供購買更多體驗內貨幣的優惠。