套件功能包提供現成的功能,讓玩家以折扣價格購買物品集合。您可以選擇允許玩家使用自定義的遊戲內貨幣或 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,
},
-- 否則,如果您想使用遊戲內貨幣而不是開發產品,則可以使用以下內容:
-- 此處的價格為遊戲內貨幣,而不是 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,該函數由 initializePurchaseHandlers 中的 Currencies 循環調用(即每個 currencyId 通過 Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase) 連接到處理程序)。
定義套件
您遊戲中可提供的所有套件可以在 ReplicatedStorage.Bundles.Configs.Bundles 中定義,類型從同一文件夾中的 Types 腳本導出。
如果您使用 devProductId,則需要將套件的主要 devProductId 更新為與您遊戲中的相符。這將通過 MarketplaceService 提示以購買該套件。強烈建議為該套件使用新的開發者產品,以便更輕鬆地跟踪單獨的銷售。
如果您想要一個包含多個物品的套件,並且這些物品已經在您的遊戲中由開發者產品表示,則不需要明確設置物品價格/assetId/名稱,這些將通過產品信息獲取:
{
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,
-- 如果您想使用開發產品,則必須提供唯一的 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, -- 一旦購買或過期,即使您的遊戲嘗試提示也不再有效。您可以在 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 時使用 StarterBundle。
套件 功能包邏輯確保每個玩家不會重複獲得已經購買的套件的報價,或者如果他們讓報價過期(根據套件配置)。
每當您想要向玩家提示一個套件時,請調用 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 | (可選) 如果未設置,則購買可以在購買或過期後重新激活。 如果設置,則一旦第一次購買或過期,將不再提示,即使您調用 Bundles.promptIfValidAsync 並使用 bundleId。 |
FixedTime
一旦將 FixedTime 套件提供給玩家,它將保持可用,直到設置的協調世界時間 (UTC) 結束。此類型在玩家的頭部顯示中顯示,並在未來的會話中自動提示,直到套件過期或玩家購買它。
此套件類型的一個常見示例是僅在特定月份可用的假日報價。
OneTime
OneTime 套件僅在提供給玩家的時候可用。它不會顯示在玩家的頭部顯示中,一旦玩家關閉提示,則無法重新打開,直到伺服器再次提示它。
此套件類型的一個常見示例是當玩家用完時購買更多遊戲內貨幣的報價。