套件功能包提供現成的功能,以折扣價格向玩家銷售物品集合。您可以選擇是否允許玩家使用自定義的體驗內貨幣或Robux來購買套件,您想使用的套件類型,您想出售的物品集,以及您希望在玩家遊玩過程中如何提示他們。
利用該包的自定義選項,您可以調整您的套件以滿足您體驗的設計和獲利目標,例如:

獲取套件
創作者商店是工具箱的一個選項卡,您可以用來查找Roblox和Roblox社區為供您項目使用而創建的所有資產,包括模型、圖像、網格、音訊、插件、視頻和字體資產。您可以使用創作者商店直接將一個或多個資產添加到打開的體驗中,包括功能包!
每個功能包需要核心功能包才能正常運作。一旦核心和套件功能包資產在您的庫中,您可以在平台上的任何項目中重用它們。
要將包從您的庫中帶入您的體驗:
通過單擊以下組件中的添加到庫鏈接,將核心和套件功能包添加到您在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更新為匹配您體驗中的該ID。這將通過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),} -- 標題是可選的!您也可以省略此字段}},
例如,您的整個套件可能看起來像這樣:
README
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幫助Bundles顯示套件價格與其內容的總和的相對價值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提示套件。雖然這取決於遊玩方式,但示例會在玩家加入時提示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參考
類型
相對時間
一旦相對時間套件提供給玩家,它將保持可用,直到時間持續時間結束。此類型顯示在玩家的提示顯示上,並且在未來的會話中自動提示,直到套件過期或玩家購買它。
此類型的常見例子是一個單次使用的起始包優惠,對於所有新玩家顯示24小時。關於如何實施起始包套件的行業最佳實踐,請參見起始包設計。
名稱 | 類型 | 描述 |
---|---|---|
includeOfflineTime | bool | **(可選)**如果未設置,則只計算在體驗中花費的時間,該時間將計入剩餘優惠的持續時間。 |
singleUse | bool | **(可選)**如果未設置,則可以在購買或過期後重新激活該購買。 如果設置,則在首次購買或過期後將不再提示,即使您調用Bundles.promptIfValidAsync也無法提示該套件。 |
固定時間
一旦固定時間套件提供給玩家,它將保持可用,直到設定的協調世界時間(UTC)結束。此類型顯示在玩家的提示顯示上,並且在未來的會話中自動提示,直到套件過期或玩家購買它。
此類型的常見例子是一個僅在給定月份內可用的假日優惠。
單次使用
單次使用套件僅在提供給玩家時可用。它不在玩家的提示顯示上顯示,一旦玩家關閉提示,就無法重新打開,直到服務器再次提示它。
此類型的常見例子是一個在玩家耗盡體驗內貨幣時提供的優惠。