包裝 功能包提供出盒功能,以出售折扣的物品集給玩家。您可以選擇是否允許玩家使用自訂經驗貨幣或 Robux 購買包裝,使用哪種包裝類型,要出售哪些物品,以及如何在遊遊玩期間提示玩家。
使用包裝的自訂選項,您可以將包裝調整為符合體驗的設計和營收目標,例如:

取得包裹
創作者商店是您可以使用的工具箱標籤,包括模型、圖像、網格、音訊頻、外掛程式、視頻和字體資產,是 Roblox 和 Roblox 社群為您的項目所製作的所有資產。您可以使用創作者商店直接將一個或多個資產添加到開放的體驗中,包括功能包!
每個功能包都需要 核心 功能包正常運作。一旦 核心 和 包 功能包資產進入您的道具欄後,您可以在平台上的任何項目上重複使用它們。
要將庫存的包裹帶入您的體驗:
在工具欄中,選擇查看標籤。
點擊 工具箱。工具箱窗口顯示。
在 工具箱 窗口中,單擊 庫存 標籤。顯示 我的模型 排序。
點擊 功能包核心 磚塊,然後點擊 組合功能包 磚塊。兩個包夾都會顯示在 資料檢視器 視窗中。
將包裹文件夾拖入 ReplicatedStorage 。
允許數據儲存呼叫跟蹤玩家購買包裝的產品。
- 在工具欄的 首頁 標籤中,選擇 遊戲設定 。
- 導航到 安全 標籤,然後啟用 啟用Studio對API服務的訪問 。
定義貨幣
如果您的體驗有自己的貨幣系統,您可以使用 核心 功能包來定義它們,在 ReplicatedStorage.FeaturePackagesCore.Configs.Currencies 中定義它們。此文件中已包含一個已評論的寶石貨幣示例;請用自擁有的來替換它。
金幣
Gems = {displayName = "Gems",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 。
包裝示例
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 腳本中匯出到同一文件夾。
如果你使用 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-- 定價 = {-- 價格類型 = 貨幣類型.PriceType.InExperience,-- 價格 = 79,-- 貨幣ID = <CURRENCY_ID>,-- 圖示 = <IMAGE_ASSET_ID>,-- },includedItems = {[1] = {-- 物品本身不通過開發者產品出售,因此指示其在 Robux 中的價值以及提供圖示-- 價格InRobux幫助包裝顯示包裝價格與其內容總和的相對值itemType = ItemTypes.ItemType.Robux,priceInRobux = 49,icon = <IMAGE_ASSET_ID>,-- 或者,如果這有一個開發產品離開價格和上面的圖示,只需設置 devProductId-- 價格和圖示將從開發者產品中獲得-- devProductId = <ITEM_DEV_PRODUCT_ID>-- 如果需要,還有更多可選的元數據欄位,它們是用戶介面特定的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),也不再有效。您可以在工作室進行測試時將此設為假值。durationInSeconds = 900, -- 15 分鐘includesOfflineTime = false, -- 只計算經體驗中發生的時間metadata = {displayName = "STARTER BUNDLE",description = "Save 75% and get a head start!",},}
整合伺服器邏輯
看看 ReplicatedStorage.Bundles.Server.Examples.BundlesExample , 它顯示您的服務器如何與 包裝 功能包和上面的方法互動在 ModuleScript 上。下面的片段來自那個腳指令碼。
您主要需要將四個東西連接起來一次將 包裝功能包 拖入體驗時:
通過 Bundles.setPurchaseHandler 連接購買處理程序來指定在購買處理過程中呼叫獎勵項目的功能。
包裝示例local function awardMarketplacePurchase(_player: Player, _bundleId: Types.BundleId, _receiptInfo: { [string]: any })-- 更新玩家資料、提供物品等-- ... 並記錄收據信息.購買ID,以便我們檢查用戶是否已擁有此組合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 falseend連接 Players.PlayerAdded:Connect(Bundles.OnPlayerAdded) 以便 禮包 功能包重新提醒玩家尚未過期的已啟用的禮包。
閱讀說明local function onPlayerAdded(player: Player)-- 告訴包裹當玩家加入時,以便它重新載入他們的資料Bundles.onPlayerAdded(player)-- 如果您有一些新手包想向所有新用戶提供,您可以在此處提示-- ... 包將處理玩家已購買它或已過期,因為它不可重複-- 包裝.玩家示如果有效(player, "StarterBundle")-- 只是例子,你可以隨時隨地呼叫這個onPromptBundleXYZEvent(player)end提示包。雖然這取決於遊玩,但例如提示玩家使用新手包 onPlayerAdded 。
禮包功能包的邏輯確保每個玩家不會收到重複優惠,如果他們已經購買了組合,或者已經讓優惠過期(基於禮包配置)。
每當您想要向玩家提示包時,請呼叫 Bundles.promptIfValidAsync(player, bundleId) 。
閱讀說明local function onPromptBundleXYZEvent(player: Player)-- 連接您想使用的任何體驗事件來確定玩家何時收到組合-- ... 這將發生在你滿足資格要求時,即可向玩家提供組合-- ... 例如,如果你想在玩家加入時提示包裹,或當玩家升級時提示包裹task.spawn(Bundles.promptIfValidAsync, player, <Some_Bundle_Id>)-- ... 如果創建多個包,使用 task.spaw生成() 來包裝上述功能呼叫將最大限度地減少倒數之間的差異end
考慮以下關於 ReceiptIds 重複記錄的最佳實踐指南:
雖然 禮品包 功能包記錄了收據ID以避免兩次處理相同的收據,但你也應該記錄收據ID在你的表中,以便如果購買流程在購買處理器已經完成後失敗,你就知道在下一次重試時不要再授予物品。
如果在任何步驟中購買失敗, 包裝 功能包將不會記錄收據ID,因此您應該確保在處理收據作為您的購買處理器的一部分之前記錄收據ID。
這種重複有助於確保所有購買邏輯已適當處理,並確保您的數據存儲和 包裝 功能包的數據存儲最終達到一致,您的數據存儲是真實來源。
配置常量
核心 功能包的常量在兩個地方生活:
共用常量生活在 ReplicatedStorage.FeaturePackagesCore.Configs.SharedConstants 。
包裝特定常數,在這個例子中, 包裝 功能包包住在ReplicatedStorage.Bundles.Configs.Constants中。
您可能想調整以符合體驗設計需求的主要事項:
- 聲音資產ID
- 購買效果時間和粒子顏色
- 提醒顯示疊加可能性
此外,您可以在一個位置找到翻譯的字串:ReplicatedStorage.FeaturePackagesCore.Configs.TranslationStrings。
自訂介面元件
修改包裝對象,例如顏色、字體和透明度,可調整包裝提示的視覺呈現。但是,請記住,如果你移動任何對象在階層上,代碼將無法找到它們,你需要對你的代碼進行調整。
提示由兩個高級組成部分組成:
- PromptItem – 包裝內的每個項目內重複的個別組件(項目圖像、說明、名稱、價格)。
- Prompt – 提示窗口本身。
頭部顯示也由兩個組成部分組成:
- HudItem – 代表頭部顯示中每個選單選項的個別組件。
- Hud – 用程式填充 HudItems 。
如果您想對頭部顯示有更多控制,而不僅僅使用現有的 HUD 用戶介面內的 ReplicatedStorage.Bundles.Objects.BundlesGui,您可以將事物移動到滿足自己設計要求的地方。只要確保在 ReplicatedStorage.Bundles.Client.UIController 指令碼本中更新客戶端腳本行為。
API 參考
類型
相對時間
一旦 RelativeTime 包被提供給玩家,時間限制到期之前就會保持可用。這種類型會顯示在玩家的頭部上顯示,並在包裝到期或玩家購買它之前自動提醒未來會議。
這類包裝類型的常見例子是一個單次使用的新手包優惠,會在 24 小時內向所有新玩家顯示。有關如何實裝新手包組合的行業最佳實踐,請參閱新手包設計。
名稱 | 類型 | 說明 |
---|---|---|
includeOfflineTime | bool | (可選) 如果未設定,只有在體驗中花費的時間才會計入剩餘的優惠期。 |
singleUse | bool | (可選) 如果未設定,購買可在購買或過期後重新啟用。如果已設設定,一次購買或過期後,即使您呼叫Bundles.promptIfValidAsync與包裝ID一起,也不會再次提示。 |
固定時間
一旦 FixedTime 包被提供給玩家,它將在設定協調的普遍時間(UTC)結束之前保持可用。這種類型會顯示在玩家的頭部上顯示,並在包裝到期或玩家購買它之前自動提醒未來會議。
此類包裝類型的常見例子是只適用於特定月份的假期優惠。
一次性
包只在提供給玩家時才可用。它不會顯示在玩家的頭部顯示上,一旦玩家關閉提示,直到再次被服務器提示才能重新打開。
這類包裝類型的常見例子是玩家在經驗結束時購買更多經驗貨幣的提議。