Gói tính năng Bundles cung cấp chức năng có sẵn để bán các bộ sưu tập đồ vật cho người chơi với mức giá giảm. Bạn có thể chọn liệu có cho phép người chơi mua các bundle bằng một loại tiền tệ tùy chỉnh trong trải nghiệm hay Robux, loại bundle mà bạn muốn sử dụng, bộ đồ vật mà bạn muốn bán, và cách mà bạn muốn nhắc nhở người chơi trong quá trình chơi của họ.
Với các tùy chọn tùy chỉnh của gói này, bạn có thể tạo các bundle của mình để đáp ứng các mục tiêu thiết kế và kiếm tiền của trải nghiệm, chẳng hạn như:
- Nhắm vào một tỷ lệ chuyển đổi thấp bằng cách cung cấp các gói khởi động giá rẻ mang lại giá trị cho người chơi mới và khuyến khích chi tiêu sớm.
- Tăng cường chi tiêu sâu bằng cách đóng gói các đồ vật ở nhiều mức giá khác nhau để thu hút nhiều loại người chơi.
- Kiếm tiền từ các hoạt động trực tiếp (LiveOps) sự kiện bằng cách cung cấp các bundle có thời gian giới hạn với các đồ vật độc quyền.

Nhận gói
Creator Store là một tab của Toolbox mà bạn có thể sử dụng để tìm tất cả các tài sản được tạo ra bởi Roblox và cộng đồng Roblox để sử dụng trong dự án của bạn, bao gồm các mô hình, hình ảnh, lưới, âm thanh, plugin, video, và tài sản font. Bạn có thể sử dụng Creator Store để thêm một hoặc nhiều tài sản trực tiếp vào một trải nghiệm mở, bao gồm cả các gói tính năng!
Mỗi gói tính năng đều yêu cầu gói tính năng Core để hoạt động chính xác. Khi tài sản của gói tính năng Core và Bundles nằm trong kho của bạn, bạn có thể tái sử dụng chúng trong bất kỳ dự án nào trên nền tảng.
Để đưa các gói từ kho của bạn vào trải nghiệm:
Thêm gói tính năng Core và Bundles vào kho của bạn trong Studio bằng cách nhấp vào liên kết Add to Inventory trong bộ thành phần sau.
Từ menu Window hoặc thanh công cụ tab Home, mở Toolbox.
Trong cửa sổ Toolbox, nhấp vào tab Inventory. Sắp xếp My Models sẽ hiển thị.

Nhấp vào ô gói tính năng Core, sau đó là ô gói tính năng Bundle. Cả hai thư mục gói sẽ hiển thị trong cửa sổ Explorer.
Kéo các thư mục gói vào ReplicatedStorage.
Cho phép các cuộc gọi lưu trữ dữ liệu theo dõi các giao dịch mua của người chơi với các gói.
- Mở cửa sổ File ⟩ Experience Settings trong Studio.
- Chuyển đến tab Security, sau đó bật Enable Studio Access to API Services.
Xác định các loại tiền tệ
Nếu trải nghiệm của bạn có hệ thống tiền tệ riêng, bạn có thể đăng ký chúng với gói tính năng Core bằng cách xác định chúng trong ReplicatedStorage.FeaturePackagesCore.Configs.Currencies. Trong tệp này đã có một ví dụ đã được chú thích về một loại tiền tệ Gems; hãy thay thế nó bằng loại của riêng bạn.
CurrenciesGems = {displayName = "Gems",symbol = "💎",icon = nil,},
Kịch bản Currencies nói với gói tính năng Core một số siêu dữ liệu về loại tiền tệ của bạn:
- (bắt buộc) displayName - Tên của loại tiền tệ của bạn. Nếu bạn không chỉ định biểu tượng hoặc biểu tượng hình ảnh, tên này sẽ được sử dụng trong các nút mua (ví dụ: "100 Gems").
- (tùy chọn) symbol - Nếu bạn có một ký tự văn bản để sử dụng làm biểu tượng cho tiền tệ của bạn, ký tự này sẽ được sử dụng thay cho displayName trong các nút mua (ví dụ: "💎100").
- (tùy chọn) icon - Nếu bạn có một biểu tượng hình ảnh AssetId cho tiền tệ của bạn, biểu tượng này sẽ được sử dụng thay cho displayName trong các nút mua (ví dụ: hình ảnh sẽ được đặt bên trái của giá "🖼️100")
Khi tiền tệ của bạn được thiết lập, bạn cần chỉ định thủ công giá của bundle, tiền tệ, và biểu tượng cho hiển thị đầu để thông tin đó không được lấy từ sản phẩm nhà phát triển liên kết với bundle.
Bundles-- Nếu bạn muốn sử dụng một sản phẩm phát triển, bạn phải cung cấp một devProductId duy nhất, chỉ được sử dụng bởi một bundle.-- Chúng tôi sẽ lấy giá của bundle và biểu tượng từ sản phẩm phát triểnpricing = {priceType = CurrencyTypes.PriceType.Marketplace,devProductId = 1795621566,},-- Ngược lại, nếu bạn muốn sử dụng tiền tệ trong trải nghiệm thay vì sản phẩm phát triển, bạn có thể sử dụng cái sau:-- Giá ở đây là trong tiền tệ trong trải nghiệm, không phải Robuxpricing = {priceType = CurrencyTypes.PriceType.InExperience,price = 79,currencyId = "Gems",icon = 18712203759,},
Bạn cũng cần tham chiếu đến kịch bản BundlesExample để gọi setInExperiencePurchaseHandler.
BundlesExample
local function awardInExperiencePurchase(
_player: Player,
_bundleId: Types.BundleId,
_currencyId: CurrencyTypes.CurrencyId,
_price: number
)
-- Kiểm tra xem người chơi có đủ tiền tệ để mua bundle không
-- Cập nhật dữ liệu của người chơi, cung cấp các đồ vật, v.v.
-- Khấu trừ tiền tệ từ người chơi
task.wait(2)
return true
end
local function initializePurchaseHandlers()
local bundles = Bundles.getBundles()
for bundleId, bundle in bundles do
-- Bundle không liên kết với một sản phẩm phát triển nếu nó không có loại giá thị trường
if not bundle or bundle.pricing.priceType ~= "Marketplace" then
continue
end
Bundles.setPurchaseHandler(bundleId, awardMarketplacePurchase)
receiptHandlers[bundle.pricing.devProductId] = receiptHandler
end
-- Nếu bạn có bất kỳ loại tiền tệ nào trong trải nghiệm mà bạn đang sử dụng cho các bundle, hãy đặt bộ xử lý ở đây
for currencyId, _ in Currencies do
Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)
end
end
Cụ thể, bạn cần điền vào awardInExperiencePurchase, được gọi bởi một vòng lặp qua Currencies bên trong ví dụ initializePurchaseHandlers (ví dụ: mỗi currencyId được kết nối với bộ xử lý thông qua Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)).
Định nghĩa các bundle
Tất cả các bundle có thể cung cấp trong trải nghiệm của bạn có thể được xác định trong ReplicatedStorage.Bundles.Configs.Bundles, với các loại được xuất khẩu từ kịch bản Types trong cùng thư mục.
Nếu bạn đang sử dụng một devProductId, bạn cần cập nhật devProductId chính của bundle để nó khớp với cái trong trải nghiệm của bạn. Đây sẽ là cái được nhắc nhở qua MarketplaceService để mua bundle. Khiến nghị mạnh mẽ là sử dụng một sản phẩm phát triển mới cho bundle để dễ dàng theo dõi doanh thu riêng.
Nếu bạn muốn một bundle với nhiều đồ vật, và nếu những đồ vật này đã được đại diện bởi các sản phẩm phát triển trong trải nghiệm của bạn, bạn không cần thiết lập giá/assetId/tên của đồ vật một cách rõ ràng, mà sẽ được lấy qua thông tin sản phẩm:
README{itemType = ItemTypes.ItemType.DevProduct,devProductId = <DEV_PRODUCT_ID>,metadata = {caption = {text = "x1",color = Color3.fromRGB(236, 201, 74),} -- Chú thích là tùy chọn! Bạn cũng có thể bỏ qua trường này}},
Ngược lại, bạn có thể cấu hình chi tiết về các đồ vật đó theo cách thủ công:
README{itemType = ItemTypes.ItemType.Robux,priceInRobux = 49,icon = <IMAGE_ASSET_ID>,metadata = {caption = {text = "x1",color = Color3.fromRGB(236, 201, 74),} -- Chú thích là tùy chọn! Bạn cũng có thể bỏ qua trường này}},
Ví dụ, toàn bộ bundle của bạn có thể sẽ trông như thế này:
READMElocal starterBundle: Types.RelativeTimeBundle = {bundleType = Types.BundleType.RelativeTime,-- Nếu bạn muốn sử dụng một sản phẩm phát triển, bạn phải cung cấp một devProductId duy nhất, chỉ được sử dụng bởi một bundle.-- Chúng tôi sẽ lấy giá của bundle và biểu tượng từ sản phẩm phát triểnpricing = {priceType = CurrencyTypes.PriceType.Marketplace,devProductId = <DEV_PRODUCT_ID>,},-- Ngược lại, nếu bạn muốn sử dụng tiền tệ trong trải nghiệm thay vì một sản phẩm phát triển, bạn có thể sử dụng cái sau:-- Giá ở đây là trong tiền tệ trong trải nghiệm, không phải Robux-- pricing = {-- priceType = CurrencyTypes.PriceType.InExperience,-- price = 79,-- currencyId = <CURRENCY_ID>,-- icon = <IMAGE_ASSET_ID>,-- },includedItems = {[1] = {-- Đồ vật này không được bán qua một sản phẩm phát triển, vì vậy hãy cho biết giá trị của nó trong Robux và cung cấp một biểu tượng-- Giá `priceInRobux` giúp Bundles hiển thị giá trị tương đối của giá bundle so với tổng giá trị của nội dung của nóitemType = ItemTypes.ItemType.Robux,priceInRobux = 49,icon = <IMAGE_ASSET_ID>,-- Ngoài ra, nếu đây có một sản phẩm phát triển, hãy bỏ qua giá và biểu tượng ở trên và chỉ cần đặt `devProductId`-- Giá và biểu tượng sẽ được lấy từ sản phẩm phát triển-- devProductId = <ITEM_DEV_PRODUCT_ID>-- Có nhiều trường siêu dữ liệu tùy chọn hơn mà có thể cụ thể cho UI nếu cầnmetadata = {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, -- Một lần mua hoặc hết hạn, sẽ không có giá trị dù trải nghiệm của bạn cố gắng nhắc nhở (onPlayerAdded). Bạn có thể thiết lập điều này là false trong khi thử nghiệm trong studio.durationInSeconds = 900, -- 15 phútincludesOfflineTime = false, -- Chỉ tính thời gian trôi qua trong trải nghiệmmetadata = {displayName = "BUNDLE KHỞI ĐẦU",description = "Tiết kiệm 75% và bắt đầu thuận lợi!",},}
Tích hợp logic máy chủ
Hãy xem ReplicatedStorage.Bundles.Server.Examples.BundlesExample, cho thấy cách mà máy chủ của bạn tương tác với gói tính năng Bundles và các phương pháp trên ModuleScript. Các đoạn mã dưới đây được lấy từ kịch bản đó.
Bạn chủ yếu cần kết nối bốn thứ sau khi kéo gói tính năng Bundles vào trải nghiệm của bạn:
Kết nối các bộ xử lý mua thông qua Bundles.setPurchaseHandler để chỉ định các hàm sẽ được gọi để trao tặng đồ vật khi một giao dịch mua đang được xử lý.
BundlesExamplelocal function awardMarketplacePurchase(_player: Player, _bundleId: Types.BundleId, _receiptInfo: { [string]: any })-- Cập nhật dữ liệu của người chơi, cung cấp các đồ vật, v.v.-- ... VÀ ghi lại `receiptInfo.PurchaseId` để chúng ta có thể kiểm tra nếu người dùng đã có bundle nàytask.wait(2)return Enum.ProductPurchaseDecision.PurchaseGrantedendlocal function awardInExperiencePurchase(_player: Player,_bundleId: Types.BundleId,_currencyId: CurrencyTypes.CurrencyId,_price: number)-- Kiểm tra xem người chơi có đủ tiền tệ để mua bundle không-- Cập nhật dữ liệu của người chơi, cung cấp các đồ vật, v.v.-- Khấu trừ tiền tệ từ người chơitask.wait(2)return trueendlocal function initializePurchaseHandlers()local bundles = Bundles.getBundles()for bundleId, bundle in bundles do-- Bundle không liên kết với một sản phẩm phát triển nếu nó không có loại giá thị trườngif not bundle or bundle.pricing.priceType ~= "Marketplace" thencontinueendBundles.setPurchaseHandler(bundleId, awardMarketplacePurchase)receiptHandlers[bundle.pricing.devProductId] = receiptHandlerend-- Nếu bạn có bất kỳ loại tiền tệ nào trong trải nghiệm mà bạn đang sử dụng cho các bundle, hãy đặt bộ xử lý ở đâyfor currencyId, _ in Currencies doBundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)endendKết nối logic của bạn cho MarketplaceService.ProcessReceipt, nhưng điều này có thể được thực hiện ở nơi khác nếu trải nghiệm của bạn đã có sản phẩm phát triển để bán. thực chất, khi một biên nhận sản phẩm phát triển đang được xử lý, hệ thống sẽ gọi Bundles.getBundleByDevProduct để kiểm tra xem sản phẩm có thuộc về một bundle không. Nếu có, kịch bản sẽ gọi Bundles.processReceipt.
BundlesExample-- Xử lý biên nhận từ thị trường để xác định xem người chơi có cần được tính phí hay khônglocal 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] -- Lấy bộ xử lý cho sản phẩmlocal success, result = pcall(handler, receiptInfo, player) -- Gọi bộ xử lý để kiểm tra xem logic mua hàng có thành công khôngif not success or not result thenwarn("Không thể xử lý biên nhận:", 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-- Giao dịch mua này thuộc về một bundle, để Bundles xử lý nólocal purchaseDecision = Bundles.processReceiptAsync(player, bundleId, receiptInfo)return purchaseDecision == Enum.ProductPurchaseDecision.PurchaseGrantedend-- Giao dịch mua này không thuộc về một bundle,-- ... Xử lý tất cả logic hiện có của bạn ở đây nếu bạn córeturn falseendKết nối Players.PlayerAdded:Connect(Bundles.OnPlayerAdded) để gói tính năng Bundles nhắc lại bất kỳ bundle nào đang hoạt động mà chưa hết hạn cho một người chơi.
READMElocal function onPlayerAdded(player: Player)-- Thông báo cho Bundles khi người chơi tham gia để nó có thể tải lại dữ liệu của họBundles.onPlayerAdded(player)-- Nếu bạn có một bundle khởi đầu nào đó mà bạn muốn cung cấp cho tất cả người dùng mới, bạn có thể nhắc nhở điều đó ở đây-- ... Bundles sẽ tự động xử lý nếu người chơi đã mua nó hoặc nếu nó đã hết hạn vì nó không thể được mua lại-- Bundles.promptIfValidAsync(player, "StarterBundle")-- Gọi điều này ở đây chỉ cho ví dụ, bạn có thể gọi bất cứ khi nào hoặc ở đâu bạn muốnonPromptBundleXYZEvent(player)endNhắc nhở các bundle. Trong khi điều này phụ thuộc vào lối chơi, ví dụ nhắc nhở người chơi với một StarterBundle onPlayerAdded.
Logic gói tính năng Bundles đảm bảo rằng mỗi người chơi không nhận được một ưu đãi lặp lại nếu họ đã mua bundle, hoặc nếu họ để ưu đãi hết hạn (dựa trên cấu hình bundle).
Bất cứ khi nào bạn muốn nhắc nhở một bundle cho một người chơi, gọi Bundles.promptIfValidAsync(player, bundleId).
READMElocal function onPromptBundleXYZEvent(player: Player)-- Kết nối bất kỳ sự kiện trải nghiệm nào mà bạn muốn sử dụng để xác định khi nào một người chơi được nhắc nhở về bundle-- ... Điều này sẽ xảy ra bất cứ khi nào bạn đã đáp ứng các tiêu chí đủ điều kiện để nhắc một người chơi về bundle-- ... Ví dụ, nếu bạn muốn nhắc một bundle khi một người chơi tham gia, hoặc khi một người chơi lên cấptask.spawn(Bundles.promptIfValidAsync, player, <Some_Bundle_Id>)-- ... Nếu tạo nhiều bundle, việc sử dụng task.spawn() để gói gọi hàm trên sẽ giảm thiểu sự khác biệt giữa các đồng hồ đếm ngượcend
Hãy xem xét các hướng dẫn thực hành tốt nhất sau đây về việc ghi lại thừa của ReceiptIds:
Trong khi gói tính năng Bundles ghi lại ReceiptIds để tránh xử lý cùng một biên nhận hai lần, bạn cũng nên ghi lại ReceiptIds bên trong các bảng của bạn để nếu quá trình mua hàng không thành công sau khi bộ xử lý giao dịch mua của họ đã hoàn thành, bạn biết khi thử lại không nên trao tặng đồ vật một lần nữa.
Gói tính năng Bundles sẽ không ghi lại ReceiptId nếu giao dịch mua thất bại ở bất kỳ bước nào, vì vậy bạn nên đảm bảo rằng bạn đang ghi nhận ReceiptId trong các bảng của bạn trước khi xử lý biên nhận như một phần của purchaseHandler của bạn.
Sự dư thừa này giúp đảm bảo rằng toàn bộ logic mua hàng đã được xử lý chính xác và rằng kho dữ liệu của bạn và kho dữ liệu của gói tính năng Bundles đạt sự nhất quán cuối cùng, với kho dữ liệu của bạn là nguồn sự thật.
Cấu hình các hằng số
Các hằng số cho gói tính năng Core sống trong hai vị trí:
Các hằng số chia sẻ sống trong ReplicatedStorage.FeaturePackagesCore.Configs.SharedConstants.
Các hằng số cụ thể cho gói, trong trường hợp này là gói tính năng Bundles, sống trong ReplicatedStorage.Bundles.Configs.Constants.
Các điều chính bạn có thể muốn điều chỉnh để đáp ứng các yêu cầu thiết kế của trải nghiệm của bạn:
- ID tài sản âm thanh
- Thời gian hiệu ứng mua và màu sắc hạt
- Khả năng thu gọn của hiển thị đầu
Ngoài ra, bạn có thể tìm thấy các chuỗi để dịch được phân tách thành một vị trí: ReplicatedStorage.FeaturePackagesCore.Configs.TranslationStrings.
Tùy chỉnh các thành phần UI
Bằng cách sửa đổi các đối tượng gói, chẳng hạn như màu sắc, phông chữ và độ trong suốt, bạn có thể điều chỉnh cách trình bày trực quan của các nhắc nhở bundle của bạn. Tuy nhiên, hãy lưu ý rằng nếu bạn di chuyển bất kỳ đối tượng nào xung quanh về mặt phân cấp, mã sẽ không thể tìm thấy chúng, và bạn sẽ cần thực hiện các điều chỉnh trong mã của mình.
Một nhắc nhở được tạo thành từ hai thành phần cấp cao:
- PromptItem – Thành phần cá nhân được lặp lại cho mỗi đồ vật trong một bundle (hình ảnh đồ vật, chú thích, tên, giá).
- Prompt – Cửa sổ nhắc nhở.
Hiển thị đầu cũng được tạo thành từ hai thành phần:
- HudItem – Một thành phần cá nhân đại diện cho mỗi tùy chọn menu trong hiển thị đầu.
- Hud – Để điền một cách lập trình với HudItems.
Nếu bạn muốn có quyền kiểm soát lớn hơn về hiển thị đầu, thay vì chỉ sử dụng giao diện HUD hiện có trong ReplicatedStorage.Bundles.Objects.BundlesGui, bạn có thể di chuyển mọi thứ xung quanh để đáp ứng các yêu cầu thiết kế của riêng bạn. Chỉ cần đảm bảo cập nhật hành vi kịch bản khách hàng trong kịch bản ReplicatedStorage.Bundles.Client.UIController.
Tham khảo API
Loại
RelativeTime
Khi bundle RelativeTime được cung cấp cho một người chơi, nó sẽ vẫn có sẵn cho đến khi thời gian hết hạn. Loại này hiển thị trên hiển thị đầu của người chơi, và tự động nhắc nhở trong các phiên sau cho đến khi bundle hết hạn hoặc người chơi mua nó.
Một ví dụ phổ biến của loại bundle này là một ưu đãi gói khởi động sử dụng một lần được hiển thị cho tất cả người chơi mới trong 24 giờ.
| Tên | Loại | Mô tả |
|---|---|---|
| includeOfflineTime | bool | (Tùy chọn) Nếu không được đặt, chỉ thời gian mà người chơi ở trong trải nghiệm sẽ được tính vào thời gian còn lại của ưu đãi. |
| singleUse | bool | (Tùy chọn) Nếu không được đặt, giao dịch mua có thể được kích hoạt lại sau khi đã mua hoặc hết hạn. Nếu được đặt, một khi đã mua hoặc hết hạn lần đầu, nó sẽ không bao giờ có thể được nhắc nhở lại, ngay cả khi bạn gọi Bundles.promptIfValidAsync với bundleId. |
FixedTime
Khi bundle FixedTime được cung cấp cho một người chơi, nó sẽ vẫn có sẵn cho đến cuối thời gian hợp nhất đã được đặt (UTC). Loại này hiển thị trên hiển thị đầu của người chơi, và tự động nhắc nhở trong các phiên sau cho đến khi bundle hết hạn hoặc người chơi mua nó.
Một ví dụ phổ biến của loại bundle này là một ưu đãi lễ hội chỉ có sẵn trong một tháng nhất định.
OneTime
Một bundle OneTime chỉ có sẵn vào thời điểm mà nó được cung cấp cho một người chơi. Nó không hiển thị trên hiển thị đầu của người chơi, và một khi người chơi đóng nhắc nhở, nó không thể được mở lại cho đến khi được nhắc nhở bởi máy chủ một lần nữa.
Một ví dụ phổ biến của loại bundle này là một ưu đãi để mua thêm tiền tệ trong trải nghiệm ngay khi một người chơi hết.