Paket fitur Bundel menawarkan fungsionalitas siap pakai untuk menjual koleksi item kepada pemain dengan diskon. Anda dapat memilih apakah akan mengizinkan pemain membeli bundel menggunakan mata uang khusus dalam pengalaman atau Robux, jenis bundel mana yang ingin Anda gunakan, set item mana yang ingin Anda jual, dan bagaimana Anda ingin meminta pemain selama permainan mereka.
Menggunakan opsi kustomisasi paket, Anda dapat menyesuaikan bundel Anda untuk memenuhi tujuan desain dan monetisasi pengalaman Anda, seperti:
- Menargetkan metrik rasio konversi yang rendah dengan menawarkan paket pemula yang diskon yang memberikan nilai kepada pemain baru dan mendorong pengeluaran awal.
- Meningkatkan kedalaman pengeluaran dengan menggabungkan item pada berbagai level harga untuk menarik berbagai pemain.
- Memonetisasi operasi langsung (LiveOps) acara dengan menawarkan bundel item eksklusif dalam waktu terbatas.

Mendapatkan paket
Toko Kreator adalah tab dari Toolbox yang dapat Anda gunakan untuk menemukan semua aset yang dibuat oleh Roblox dan komunitas Roblox untuk digunakan dalam proyek Anda, termasuk model, gambar, mesh, audio, plugin, video, dan aset font. Anda dapat menggunakan Toko Kreator untuk menambahkan satu atau lebih aset langsung ke dalam pengalaman yang terbuka, termasuk paket fitur!
Setiap paket fitur memerlukan paket fitur Core agar berfungsi dengan baik. Setelah aset paket fitur Core dan Bundel ada dalam inventaris Anda, Anda dapat menggunakannya lagi dalam proyek apa pun di platform.
Untuk mendapatkan paket dari inventaris Anda ke pengalaman Anda:
Tambahkan paket fitur Core dan Bundel ke inventaris Anda di dalam Studio dengan mengklik tautan Tambahkan ke Inventaris dalam serangkaian komponen berikut.
Dari menu Jendela atau bilah alat tab Beranda Studio, buka Toolbox.
Di jendela Toolbox, klik tab Inventaris. Penyortiran Model Saya ditampilkan.

Klik ubin Paket Fitur Core, lalu ubin Paket Fitur Bundel. Kedua folder paket ditampilkan di jendela Explorer.
Tarik folder paket ke ReplicatedStorage.
Izinkan panggilan penyimpanan data untuk melacak pembelian pemain dengan paket tersebut.
- Buka jendela File ⟩ Pengaturan Pengalaman di Studio.
- Arahkan ke tab Keamanan, lalu aktifkan Aktifkan Akses Studio ke Layanan API.
Definisikan mata uang
Jika pengalaman Anda memiliki sistem mata uang sendiri, Anda dapat mendaftarkannya dengan paket fitur Core dengan mendefinisikannya di ReplicatedStorage.FeaturePackagesCore.Configs.Currencies. Ada contoh mata uang Gems yang dikomentari dalam berkas ini; ganti itu dengan milik Anda.
Mata UangGems = {displayName = "Gems",symbol = "💎",icon = nil,},
Skrip Currencies memberi tahu paket fitur Core beberapa metadata tentang mata uang Anda:
- (diperlukan) displayName - Nama mata uang Anda. Jika Anda tidak menentukan simbol atau ikon, nama ini digunakan dalam tombol pembelian (yaitu "100 Gems").
- (opsional) symbol - Jika Anda memiliki karakter teks yang digunakan sebagai ikon untuk mata uang Anda, ini digunakan sebagai pengganti displayName dalam tombol pembelian (yaitu "💎100").
- (opsional) icon - Jika Anda memiliki ikon gambar AssetId untuk mata uang Anda, ini digunakan sebagai penggantinya displayName dalam tombol pembelian (yaitu gambar akan diletakkan di sebelah kiri harga "🖼️100")
Setelah mata uang Anda disiapkan, Anda perlu secara manual menentukan harga, mata uang, dan ikon bundel untuk tampilan heads up alih-alih informasi tersebut diambil dari produk pengembang yang terkait dengan bundel.
Bundel-- Jika Anda ingin menggunakan produk pengembang, Anda harus menyediakan devProductId yang unik, hanya digunakan oleh satu bundel.-- Kami akan mengambil harga bundel dan ikon dari produk pengembangpricing = {priceType = CurrencyTypes.PriceType.Marketplace,devProductId = 1795621566,},-- Sebaliknya, jika Anda ingin menggunakan mata uang dalam pengalaman alih-alih produk pengembang, Anda dapat menggunakan yang berikut:-- Harga di sini adalah dalam mata uang dalam pengalaman, bukan Robuxpricing = {priceType = CurrencyTypes.PriceType.InExperience,price = 79,currencyId = "Gems",icon = 18712203759,},
Anda juga perlu merujuk skrip BundlesExample untuk memanggil setInExperiencePurchaseHandler.
BundlesExample
local function awardInExperiencePurchase(
_player: Player,
_bundleId: Types.BundleId,
_currencyId: CurrencyTypes.CurrencyId,
_price: number
)
-- Periksa apakah pemain memiliki cukup mata uang untuk membeli bundel
-- Perbarui data pemain, berikan item, dll.
-- Kurangi mata uang dari pemain
task.wait(2)
return true
end
local function initializePurchaseHandlers()
local bundles = Bundles.getBundles()
for bundleId, bundle in bundles do
-- Bundel tidak terkait dengan produk pengembang jika tidak memiliki jenis harga marketplace
if not bundle or bundle.pricing.priceType ~= "Marketplace" then
continue
end
Bundles.setPurchaseHandler(bundleId, awardMarketplacePurchase)
receiptHandlers[bundle.pricing.devProductId] = receiptHandler
end
-- Jika Anda memiliki mata uang dalam pengalaman yang Anda gunakan untuk bundel, setel penangan di sini
for currencyId, _ in Currencies do
Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)
end
end
Secara khusus, Anda perlu mengisi awardInExperiencePurchase, yang dipanggil oleh sebuah loop melalui Currencies di dalam contoh initializePurchaseHandlers (yaitu setiap currencyId terhubung ke handler melalui Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)).
Definisikan bundel
Semua bundel yang dapat ditawarkan dalam pengalaman Anda dapat didefinisikan di dalam ReplicatedStorage.Bundles.Configs.Bundles, dengan tipe yang diekspor dari skrip Types di folder yang sama.
Jika Anda menggunakan devProductId, Anda perlu memperbarui devProductId utama dari bundel agar sesuai dengan yang ada di pengalaman Anda. Inilah yang akan diminta melalui MarketplaceService untuk membeli bundel itu sendiri. Sangat disarankan untuk menggunakan produk pengembang baru untuk bundel agar lebih mudah melacak penjualan yang terpisah.
Jika Anda menginginkan bundel dengan beberapa item, dan jika item ini sudah diperoleh dengan produk pengembang dalam pengalaman Anda, Anda tidak perlu secara eksplisit mengatur harga item/assetId/nama, yang akan diambil melalui informasi produk:
README{itemType = ItemTypes.ItemType.DevProduct,devProductId = <DEV_PRODUCT_ID>,metadata = {caption = {text = "x1",color = Color3.fromRGB(236, 201, 74),} -- Keterangan bersifat opsional! Anda juga dapat menghilangkan bidang ini}},
Sebaliknya, Anda dapat mengonfigurasi detail item tersebut secara manual:
README{itemType = ItemTypes.ItemType.Robux,priceInRobux = 49,icon = <IMAGE_ASSET_ID>,metadata = {caption = {text = "x1",color = Color3.fromRGB(236, 201, 74),} -- Keterangan bersifat opsional! Anda juga dapat menghilangkan bidang ini}},
Sebagai contoh, keseluruhan bundel Anda kemungkinan akan terlihat seperti ini:
READMElocal starterBundle: Types.RelativeTimeBundle = {bundleType = Types.BundleType.RelativeTime,-- Jika Anda ingin menggunakan produk pengembang, Anda harus menyediakan devProductId yang unik, hanya digunakan oleh satu bundel.-- Kami akan mengambil harga bundel dan ikon dari produk pengembangpricing = {priceType = CurrencyTypes.PriceType.Marketplace,devProductId = <DEV_PRODUCT_ID>,},-- Sebaliknya, jika Anda ingin menggunakan mata uang dalam pengalaman alih-alih produk pengembang, Anda dapat menggunakan yang berikut:-- Harga di sini adalah dalam mata uang dalam pengalaman, bukan Robux-- pricing = {-- priceType = CurrencyTypes.PriceType.InExperience,-- price = 79,-- currencyId = <CURRENCY_ID>,-- icon = <IMAGE_ASSET_ID>,-- },includedItems = {[1] = {-- Item itu sendiri tidak dijual melalui produk pengembang, jadi indikasikan berapa nilai yang dimiliki dalam Robux dan berikan ikon-- hargaInRobux membantu Bundles menunjukkan nilai relatif dari harga bundel vs. total kontennyaitemType = ItemTypes.ItemType.Robux,priceInRobux = 49,icon = <IMAGE_ASSET_ID>,-- Sebagai alternatif, jika ini memiliki produk pengembang, hilangkan harga dan ikon di atas dan hanya atur devProductId-- Harga dan ikon akan diambil dari produk pengembang-- devProductId = <ITEM_DEV_PRODUCT_ID>-- Ada lebih banyak bidang metadata opsional yang bersifat UI jika diperlukanmetadata = {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, -- Setelah dibeli atau kedaluwarsa, tidak lagi valid bahkan jika pengalaman Anda mencoba untuk meminta (onPlayerAdded). Anda dapat membuat ini false saat menguji di studio.durationInSeconds = 900, -- 15 menitincludesOfflineTime = false, -- Hanya menghitung waktu yang berlalu dalam pengalamanmetadata = {displayName = "BUNDLE PEMULA",description = "Hemat 75% dan dapatkan keuntungan awal!",},}
Integrasikan logika server
Perhatikan ReplicatedStorage.Bundles.Server.Examples.BundlesExample, yang menunjukkan bagaimana server Anda akan berinteraksi dengan paket fitur Bundel dan metode di atas pada ModuleScript. Cuplikan di bawah ini berasal dari skrip tersebut.
Anda perlu menghubungkan empat hal setelah menarik paket fitur Bundel ke pengalaman Anda:
Hubungkan penangkap pembelian melalui Bundles.setPurchaseHandler untuk menetapkan fungsi yang dipanggil untuk memberikan item saat pembelian sedang diproses.
BundlesExamplelocal function awardMarketplacePurchase(_player: Player, _bundleId: Types.BundleId, _receiptInfo: { [string]: any })-- Perbarui data pemain, berikan item, dll.-- ... DAN catat receiptInfo.PurchaseId sehingga kita bisa memeriksa jika pengguna sudah memiliki bundel initask.wait(2)return Enum.ProductPurchaseDecision.PurchaseGrantedendlocal function awardInExperiencePurchase(_player: Player,_bundleId: Types.BundleId,_currencyId: CurrencyTypes.CurrencyId,_price: number)-- Periksa apakah pemain memiliki cukup mata uang untuk membeli bundel-- Perbarui data pemain, berikan item, dll.-- Kurangi mata uang dari pemaintask.wait(2)return trueendlocal function initializePurchaseHandlers()local bundles = Bundles.getBundles()for bundleId, bundle in bundles do-- Bundel tidak terkait dengan produk pengembang jika tidak memiliki jenis harga marketplaceif not bundle or bundle.pricing.priceType ~= "Marketplace" thencontinueendBundles.setPurchaseHandler(bundleId, awardMarketplacePurchase)receiptHandlers[bundle.pricing.devProductId] = receiptHandlerend-- Jika Anda memiliki mata uang dalam pengalaman yang Anda gunakan untuk bundel, setel penangan di sinifor currencyId, _ in Currencies doBundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)endendHubungkan logika Anda untuk MarketplaceService.ProcessReceipt, tetapi ini mungkin dilakukan di tempat lain jika pengalaman Anda sudah memiliki produk pengembang untuk dijual. Pada dasarnya, ketika tanda terima produk pengembang sedang diproses, mereka sekarang akan memanggil Bundles.getBundleByDevProduct untuk memeriksa apakah produk tersebut termasuk dalam bundel. Jika ya, maka skrip akan memanggil Bundles.processReceipt.
BundlesExample-- Proses tanda terima dari marketplace untuk menentukan apakah pemain perlu dikenakan biaya atau tidaklocal 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] -- Dapatkan penangkap untuk produklocal success, result = pcall(handler, receiptInfo, player) -- Panggil penangkap untuk memeriksa apakah logika pembelian berhasilif not success or not result thenwarn("Gagal memproses tanda terima:", 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-- Pembelian ini milik sebuah bundel, biarkan Bundles mengurusnyalocal purchaseDecision = Bundles.processReceiptAsync(player, bundleId, receiptInfo)return purchaseDecision == Enum.ProductPurchaseDecision.PurchaseGrantedend-- Pembelian ini tidak milik bundel,-- ... Tangani semua logika yang ada di sini jika Anda memilikireturn falseendHubungkan Players.PlayerAdded:Connect(Bundles.OnPlayerAdded) sehingga paket fitur Bundel meminta kembali semua bundel aktif yang belum kedaluwarsa untuk pemain.
READMElocal function onPlayerAdded(player: Player)-- Beritahu Bundles saat pemain bergabung sehingga dapat memuat ulang data merekaBundles.onPlayerAdded(player)-- Jika Anda memiliki bundel pemula yang ingin Anda tawarkan kepada semua pengguna baru, Anda bisa meminta itu di sini-- ... Bundles akan menangani jika pemain sudah membelinya atau jika sudah kedaluwarsa karena itu tidak dapat diulang-- Bundles.promptIfValidAsync(player, "StarterBundle")-- Memanggil ini di sini hanya sebagai contoh, Anda dapat memanggil ini kapan saja atau di mana saja yang Anda inginkanonPromptBundleXYZEvent(player)endMintalah bundel. Sementara ini tergantung pada gameplay, contoh ini meminta pemain dengan StarterBundle onPlayerAdded.
Logika paket fitur Bundel memastikan setiap pemain tidak mendapatkan tawaran ulang jika mereka sudah membeli bundel, atau jika mereka membiarkan tawaran tersebut kedaluwarsa (berdasarkan konfigurasi bundel).
Setiap kali Anda ingin meminta bundel kepada pemain, panggil Bundles.promptIfValidAsync(player, bundleId).
READMElocal function onPromptBundleXYZEvent(player: Player)-- Hubungkan event pengalaman apa pun yang ingin Anda gunakan untuk menentukan kapan pemain diminta bundel-- ... Ini akan terjadi kapan pun Anda memenuhi kriteria kelayakan Anda untuk meminta pemain bundel-- ... Misalnya, jika Anda ingin meminta bundel ketika pemain bergabung, atau ketika pemain naik leveltask.spawn(Bundles.promptIfValidAsync, player, <Some_Bundle_Id>)-- ... Jika membuat beberapa bundel, menggunakan task.spawn() untuk membungkus pemanggilan fungsi di atas akan meminimalkan perbedaan antara hitungan mundurend
Pertimbangkan panduan praktik terbaik berikut tentang pencatatan redundan ReceiptIds:
Sementara paket fitur Bundel mencatat ReceiptIds untuk menghindari memproses tanda terima yang sama dua kali, Anda juga harus mencatat ReceiptIds di dalam tabel Anda sehingga jika alur pembelian gagal setelah penangan pembelian Anda selesai, Anda tahu pada percobaan berikutnya untuk tidak memberikan item lagi.
Paket fitur Bundel tidak akan mencatat ReceiptId jika pembelian gagal pada langkah mana pun, jadi Anda harus memastikan bahwa Anda mencatat ReceiptId di dalam tabel Anda sebelum memproses tanda terima sebagai bagian dari purchaseHandler Anda.
Redundansi ini membantu memastikan bahwa semua logika pembelian telah ditangani dengan tepat dan bahwa penyimpanan data Anda dan penyimpanan data paket fitur Bundel mencapai konsistensi akhir, dengan penyimpanan data Anda menjadi sumber kebenaran.
Konfigurasikan konstanta
Konstanta untuk paket fitur Core ada di dua tempat:
Konstanta bersama ada di ReplicatedStorage.FeaturePackagesCore.Configs.SharedConstants.
Konstanta khusus paket, dalam hal ini paket fitur Bundel, ada di ReplicatedStorage.Bundles.Configs.Constants.
Hal utama yang mungkin ingin Anda sesuaikan untuk memenuhi persyaratan desain pengalaman Anda:
- ID aset suara
- Durasi efek pembelian dan warna partikel
- Kemampuan tampilan heads up untuk menyusut
Selain itu, Anda dapat menemukan string untuk terjemahan yang dibagi ke dalam satu lokasi: ReplicatedStorage.FeaturePackagesCore.Configs.TranslationStrings.
Kustomisasi komponen UI
Dengan memodifikasi objek paket, seperti warna, font, dan transparansi, Anda dapat menyesuaikan presentasi visual permintaan bundel Anda. Namun, ingatlah bahwa jika Anda memindahkan objek mana pun secara hierarkis, kode tidak akan dapat menemukannya, dan Anda perlu melakukan penyesuaian pada kode Anda.
Sebuah permintaan terdiri dari dua komponen tingkat tinggi:
- PromptItem – Komponen individu yang diulang untuk setiap item dalam bundel (gambar item, keterangan, nama, harga).
- Prompt – Jendela permintaan itu sendiri.
Tampilan heads up juga terdiri dari dua komponen:
- HudItem – Komponen individu yang mewakili setiap opsi menu dalam tampilan heads up.
- Hud – Harus diisi secara programatis dengan HudItems.
Jika Anda ingin memiliki kontrol lebih besar atas tampilan heads up, alih-alih hanya menggunakan UI HUD yang ada dalam ReplicatedStorage.Bundles.Objects.BundlesGui, Anda dapat memindahkan hal-hal untuk memenuhi persyaratan desain Anda. Pastikan untuk memperbarui perilaku skrip klien dalam skrip ReplicatedStorage.Bundles.Client.UIController.
Referensi API
Tipe
RelativeTime
Setelah bundel RelativeTime ditawarkan kepada pemain, bundel tersebut tetap tersedia hingga durasi waktu yang ditentukan habis. Tipe ini ditampilkan di tampilan heads up pemain, dan secara otomatis meminta pada sesi mendatang hingga bundel tersebut kedaluwarsa atau pemain membelinya.
Contoh umum dari tipe bundel ini adalah tawaran paket pemula satu kali yang ditampilkan kepada semua pemain baru selama 24 jam.
| Nama | Tipe | Deskripsi |
|---|---|---|
| includeOfflineTime | bool | (Opsional) Jika tidak ditetapkan, hanya waktu yang dihabiskan dalam pengalaman yang akan dihitung untuk durasi tawaran yang tersisa. |
| singleUse | bool | (Opsional) Jika tidak ditetapkan, pembelian dapat diaktifkan kembali setelah dibeli atau kedaluwarsa. Jika ditetapkan, setelah dibeli atau kedaluwarsa pertama kali, tidak akan dapat diminta lagi, bahkan jika Anda memanggil Bundles.promptIfValidAsync dengan bundleId. |
FixedTime
Setelah bundel FixedTime ditawarkan kepada pemain, bundel tersebut tetap tersedia hingga akhir waktu koordinator universal (UTC) yang ditetapkan. Tipe ini ditampilkan di tampilan heads up pemain, dan secara otomatis meminta pada sesi mendatang hingga bundel tersebut kedaluwarsa atau pemain membelinya.
Contoh umum dari tipe bundel ini adalah tawaran liburan yang hanya tersedia untuk bulan tertentu.
OneTime
Bundel OneTime hanya tersedia pada saat ditawarkan kepada pemain. Bundel ini tidak ditampilkan di tampilan heads up pemain, dan setelah pemain menutup permintaan, tidak dapat dibuka kembali hingga diminta oleh server lagi.
Contoh umum dari tipe bundel ini adalah tawaran untuk membeli lebih banyak mata uang dalam pengalaman saat pemain kehabisan.