Pakiet funkcji Bundles oferuje gotową funkcjonalność do sprzedaży kolekcji przedmiotów graczom z rabatem. Możesz wybrać, czy pozwolić graczom na zakup pakietów za pomocą niestandardowej waluty w grze lub Robux, jaki typ pakietu chcesz użyć, jaką zestaw przedmiotów chcesz sprzedać oraz jak chcesz zachęcać graczy podczas ich rozgrywki.
Korzystając z opcji dostosowywania pakietu, możesz dostosować swoje pakiety do celów projektowych i monetyzacyjnych swoich gier, takich jak:
- Celowanie w niski wskaźnik konwersji poprzez oferowanie rabatowych pakietów startowych, które zapewniają wartość nowym graczom i zachęcają do wczesnych wydatków.
- Zwiększanie głębokości wydatków poprzez łączenie przedmiotów w różnych przedziałach cenowych, aby przyciągnąć różnorodnych graczy.
- Monetyzacja operacji na żywo (LiveOps) wydarzeń poprzez oferowanie limitowanych czasowo pakietów ekskluzywnych przedmiotów.

Uzyskaj pakiet
Sklep Twórcy to zakładka w Toolbox, której możesz użyć do znalezienia wszystkich zasobów stworzonych przez Roblox i społeczność Roblox do wykorzystania w swoich projektach, w tym modele, obrazy, siatki, dźwięki, wtyczki, filmy i czcionki. Możesz użyć Sklepu Twórcy, aby dodać jeden lub więcej zasobów bezpośrednio do otwartej gry, w tym pakiety funkcji!
Każdy pakiet funkcji wymaga pakietu funkcji Core, aby działał poprawnie. Gdy zasoby pakietu Core i Bundles znajdą się w twoim inwentarzu, możesz je ponownie wykorzystać w dowolnym projekcie na platformie.
Aby przenieść pakiety z inwentarza do gry:
Dodaj pakiet funkcji Core i Bundles do swojego inwentarza w Studio, klikając link Dodaj do inwentarza w następującym zestawie komponentów.
Z menu Okno w Studio lub z paska narzędzi zakładki Strona główna, otwórz Toolbox.
W oknie Toolbox kliknij zakładkę Inwentarz. Wyświetli się sortowanie Moje modele.

Kliknij kafelek Pakiet funkcji Core, a następnie kafelek Pakiet funkcji Bundles. Oba foldery pakietów wyświetlą się w oknie Eksplorator.
Przeciągnij foldery pakietów do ReplicatedStorage.
Pozwól na wywołania przechowywania danych, aby śledzić zakupy graczy z pakietami.
- Otwórz okno Plik ⟩ Ustawienia doświadczenia w Studio.
- Przejdź do zakładki Bezpieczeństwo, a następnie włącz Włącz dostęp Studio do usług API.
Zdefiniuj waluty
Jeśli twoja gra ma własny system walutowy, możesz zarejestrować je w pakiecie funkcji Core, definiując je w ReplicatedStorage.FeaturePackagesCore.Configs.Currencies. W tym pliku znajduje się już zakomentowany przykład waluty Gem; zastąp go swoją własną.
Gems = {
displayName = "Gems",
symbol = "💎",
icon = nil,
},Skrypt Currencies informuje pakiet funkcji Core o pewnych metadanych dotyczących twojej waluty:
- (wymagane) displayName - Nazwa twojej waluty. Jeśli nie określisz symbolu ani ikony, ta nazwa jest używana w przyciskach zakupu (tj. "100 Gems").
- (opcjonalne) symbol - Jeśli masz znak tekstowy do użycia jako ikonę dla swojej waluty, jest on używany zamiast displayName w przyciskach zakupu (tj. "💎100").
- (opcjonalne) icon - Jeśli masz ikonę obrazu AssetId dla swojej waluty, jest ona używana zamiast displayName w przyciskach zakupu (tj. obraz zostanie umieszczony po lewej stronie ceny "🖼️100").
Gdy twoja waluta jest skonfigurowana, musisz ręcznie określić cenę pakietu, walutę i ikonę dla wyświetlacza zamiast tego, aby te informacje były pobierane z powiązanego produktu dewelopera pakietu.
-- Jeśli chcesz użyć produktu dewelopera, musisz podać unikalny devProductId, używany tylko przez jeden pakiet.
-- Pobierzemy cenę pakietu i ikonę z produktu dewelopera
pricing = {
priceType = CurrencyTypes.PriceType.Marketplace,
devProductId = 1795621566,
},
-- W przeciwnym razie, jeśli chcesz użyć waluty w grze zamiast produktu dewelopera, możesz użyć następującego:
-- Cena tutaj jest w walucie w grze, a nie w Robux
pricing = {
priceType = CurrencyTypes.PriceType.InExperience,
price = 79,
currencyId = "Gems",
icon = 18712203759,
},Musisz również odwołać się do skryptu BundlesExample, aby wywołać setInExperiencePurchaseHandler.
local function awardInExperiencePurchase(
_player: Player,
_bundleId: Types.BundleId,
_currencyId: CurrencyTypes.CurrencyId,
_price: number
)
-- Sprawdź, czy gracz ma wystarczającą ilość waluty, aby zakupić pakiet
-- Zaktualizuj dane gracza, daj przedmioty itp.
-- Odejmij walutę od gracza
task.wait(2)
return true
end
local function initializePurchaseHandlers()
local bundles = Bundles.getBundles()
for bundleId, bundle in bundles do
-- Pakiet nie jest powiązany z produktem dewelopera, jeśli nie ma typu ceny rynkowej
if not bundle or bundle.pricing.priceType ~= "Marketplace" then
continue
end
Bundles.setPurchaseHandler(bundleId, awardMarketplacePurchase)
receiptHandlers[bundle.pricing.devProductId] = receiptHandler
end
-- Jeśli masz jakiekolwiek waluty w grze, które używasz do pakietów, ustaw tutaj handler
for currencyId, _ in Currencies do
Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)
end
endSpecjalnie musisz wypełnić awardInExperiencePurchase, który jest wywoływany przez pętlę przez Currencies wewnątrz przykładowego initializePurchaseHandlers (tj. każdy currencyId jest połączony z handlerem przez Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)).
Zdefiniuj pakiety
Wszystkie pakiety oferowane w twojej grze mogą być zdefiniowane w ReplicatedStorage.Bundles.Configs.Bundles, z typami eksportowanymi z skryptu Types w tym samym folderze.
Jeśli używasz devProductId, musisz zaktualizować główny devProductId pakietu, aby pasował do tego w twojej grze. To będzie wywoływane przez MarketplaceService do zakupu samego pakietu. Zaleca się użycie nowego produktu dewelopera dla pakietu, aby ułatwić śledzenie oddzielnych sprzedaży.
Jeśli chcesz pakiet z wieloma przedmiotami, a jeśli są one już reprezentowane przez produkty dewelopera w twojej grze, nie musisz jawnie ustawiać ceny przedmiotu/assetId/nazwy, które będą pobierane za pomocą informacji o produkcie:
{
itemType = ItemTypes.ItemType.DevProduct,
devProductId = <DEV_PRODUCT_ID>,
metadata = {
caption = {
text = "x1",
color = Color3.fromRGB(236, 201, 74),
} -- Podpis jest opcjonalny! Możesz również pominąć to pole
}
},W przeciwnym razie możesz ręcznie skonfigurować te szczegóły przedmiotu:
{
itemType = ItemTypes.ItemType.Robux,
priceInRobux = 49,
icon = <IMAGE_ASSET_ID>,
metadata = {
caption = {
text = "x1",
color = Color3.fromRGB(236, 201, 74),
} -- Podpis jest opcjonalny! Możesz również pominąć to pole
}
},Na przykład, twój cały pakiet prawdopodobnie będzie wyglądał tak:
local starterBundle: Types.RelativeTimeBundle = {
bundleType = Types.BundleType.RelativeTime,
-- Jeśli chcesz użyć produktu dewelopera, musisz podać unikalny devProductId, używany tylko przez jeden pakiet.
-- Pobierzemy cenę pakietu i ikonę z produktu dewelopera
pricing = {
priceType = CurrencyTypes.PriceType.Marketplace,
devProductId = <DEV_PRODUCT_ID>,
},
-- W przeciwnym razie, jeśli chcesz użyć waluty w grze zamiast produktu dewelopera, możesz użyć następującego:
-- Cena tutaj jest w walucie w grze, a nie w Robux
-- pricing = {
-- priceType = CurrencyTypes.PriceType.InExperience,
-- price = 79,
-- currencyId = <CURRENCY_ID>,
-- icon = <IMAGE_ASSET_ID>,
-- },
includedItems = {
[1] = {
-- Przedmiot sam w sobie nie jest sprzedawany za pośrednictwem produktu dewelopera, więc wskaź, ile jest wart w Robux i podaj ikonę
-- Cena w Robux pomaga Bundles pokazać względną wartość ceny pakietu w porównaniu do sumy jego zawartości
itemType = ItemTypes.ItemType.Robux,
priceInRobux = 49,
icon = <IMAGE_ASSET_ID>,
-- Alternatywnie, jeśli to ma produkt dewelopera, pomiń cenę i ikonę powyżej i po prostu ustaw devProductId
-- Cena i ikona będą pobierane z produktu dewelopera
-- devProductId = <ITEM_DEV_PRODUCT_ID>
-- Istnieje więcej opcjonalnych pól metadanych, które są specyficzne dla UI, jeśli są potrzebne
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, -- Po zakupie lub wygaśnięciu, nie jest już ważny, nawet jeśli twoja gra próbuje go wywołać (onPlayerAdded). Możesz ustawić to na false podczas testowania w studio.
durationInSeconds = 900, -- 15 minut
includesOfflineTime = false, -- Licz tylko czas spędzony w grze
metadata = {
displayName = "PAKIET STARTOWY",
description = "Zaoszczędź 75% i zyskaj przewagę!",
},
}Zintegruj logikę serwera
Zobacz ReplicatedStorage.Bundles.Server.Examples.BundlesExample, który pokazuje, jak twój serwer będzie współdziałał z pakietem funkcji Bundles i powyższymi metodami w ModuleScript. Poniższe fragmenty pochodzą z tego skryptu.
Musisz głównie podłączyć cztery rzeczy po przeciągnięciu pakietu funkcji Bundles do swojej gry:
Połącz handler zakupu przez Bundles.setPurchaseHandler, aby określić funkcje do wywołania w celu przyznania przedmiotów, gdy przetwarzany jest zakup.
BundlesExamplelocal function awardMarketplacePurchase(_player: Player, _bundleId: Types.BundleId, _receiptInfo: { [string]: any })-- Zaktualizuj dane gracza, daj przedmioty itp.-- ... I zarejestruj receiptInfo.PurchaseId, abyśmy mogli sprawdzić, czy użytkownik już ma ten pakiettask.wait(2)return Enum.ProductPurchaseDecision.PurchaseGrantedendlocal function awardInExperiencePurchase(_player: Player,_bundleId: Types.BundleId,_currencyId: CurrencyTypes.CurrencyId,_price: number)-- Sprawdź, czy gracz ma wystarczającą ilość waluty, aby zakupić pakiet-- Zaktualizuj dane gracza, daj przedmioty itp.-- Odejmij walutę od graczatask.wait(2)return trueendlocal function initializePurchaseHandlers()local bundles = Bundles.getBundles()for bundleId, bundle in bundles do-- Pakiet nie jest powiązany z produktem dewelopera, jeśli nie ma typu ceny rynkowejif not bundle or bundle.pricing.priceType ~= "Marketplace" thencontinueendBundles.setPurchaseHandler(bundleId, awardMarketplacePurchase)receiptHandlers[bundle.pricing.devProductId] = receiptHandlerend-- Jeśli masz jakiekolwiek waluty w grze, które używasz do pakietów, ustaw tutaj handlerfor currencyId, _ in Currencies doBundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)endendPołącz swoją logikę dla MarketplaceService.ProcessReceipt, ale może to być zrobione gdzie indziej, jeśli twoja gra już ma produkty dewelopera na sprzedaż. Zasadniczo, gdy przetwarzany jest paragon produktu dewelopera, teraz wywołają Bundles.getBundleByDevProduct, aby sprawdzić, czy produkt należy do pakietu. Jeśli tak, skrypt wywołuje Bundles.processReceipt.
BundlesExample-- Przetwarzaj paragon z rynku, aby określić, czy gracz musi być obciążony, czy nielocal 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] -- Pobierz handler dla produktulocal success, result = pcall(handler, receiptInfo, player) -- Wywołaj handler, aby sprawdzić, czy logika zakupu jest udanaif not success or not result thenwarn("Nie udało się przetworzyć paragonu:", 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-- Ten zakup należy do pakietu, pozwól Bundles się tym zająćlocal purchaseDecision = Bundles.processReceiptAsync(player, bundleId, receiptInfo)return purchaseDecision == Enum.ProductPurchaseDecision.PurchaseGrantedend-- Ten zakup nie należy do pakietu,-- ... Obsłuż całą swoją istniejącą logikę tutaj, jeśli masz jakąkolwiekreturn falseendPołącz Players.PlayerAdded:Connect(Bundles.OnPlayerAdded), aby pakiet funkcji Bundles ponownie wywołał wszelkie aktywne pakiety, które jeszcze nie wygasły dla gracza.
READMElocal function onPlayerAdded(player: Player)-- Powiedz Bundles, kiedy gracz dołącza, aby mógł załadować ich daneBundles.onPlayerAdded(player)-- Jeśli miałeś jakiś pakiet startowy, który chciałeś zaoferować wszystkim nowym użytkownikom, mógłbyś go tutaj wywołać-- ... Bundles zajmie się tym, jeśli gracz już go kupił lub jeśli wygasł, ponieważ nie jest powtarzalny-- Bundles.promptIfValidAsync(player, "StarterBundle")-- Wywołanie tego tutaj tylko dla przykładu, możesz to wywołać kiedykolwiek lub gdziekolwiek chceszonPromptBundleXYZEvent(player)endWywołaj pakiety. Chociaż zależy to od rozgrywki, przykład wywołuje graczy z pakietem StarterBundle onPlayerAdded.
Logika pakietu Bundles zapewnia, że każdy gracz nie otrzyma powtórnej oferty, jeśli już kupił pakiet lub jeśli oferta już wygasła (na podstawie konfiguracji pakietu).
Kiedy chcesz wywołać pakiet dla gracza, wywołaj Bundles.promptIfValidAsync(player, bundleId).
READMElocal function onPromptBundleXYZEvent(player: Player)-- Połącz dowolne zdarzenie gry, które chcesz użyć do określenia, kiedy gracz otrzymuje wywołanie pakietu-- ... To będzie zawsze, gdy spełnisz swoje kryteria kwalifikacji, aby wywołać gracza pakiet-- ... Na przykład, jeśli chcesz wywołać pakiet, gdy gracz dołącza, lub gdy gracz awansujetask.spawn(Bundles.promptIfValidAsync, player, <Some_Bundle_Id>)-- ... Jeśli tworzysz wiele pakietów, użycie task.spawn() do owinięcia powyższego wywołania funkcji zminimalizuje rozbieżności między odliczeniamiend
Rozważ następujące najlepsze praktyki dotyczące redundantnych zapisów ReceiptIds:
Chociaż pakiet funkcji Bundles rejestruje ReceiptIds, aby uniknąć przetwarzania tego samego paragonu dwa razy, powinieneś również rejestrować ReceiptIds w swoich tabelach, aby w przypadku, gdy przepływ zakupu nie powiedzie się po zakończeniu handlera zakupu, wiedzieć przy kolejnej próbie, aby nie przyznawać przedmiotów ponownie.
Pakiet funkcji Bundles nie zarejestruje ReceiptId, jeśli zakup nie powiedzie się na którymkolwiek etapie, więc upewnij się, że rejestrujesz ReceiptId w swoich tabelach przed przetworzeniem paragonu jako część swojego purchaseHandlera.
Ta redundancja pomaga zapewnić, że cała logika zakupu została odpowiednio obsłużona i że twoja baza danych oraz baza danych pakietu funkcji Bundles osiągną ostateczną spójność, przy czym twoja baza danych jest źródłem prawdy.
Skonfiguruj stałe
Stałe dla pakietu funkcji Core znajdują się w dwóch miejscach:
Wspólne stałe znajdują się w ReplicatedStorage.FeaturePackagesCore.Configs.SharedConstants.
Stałe specyficzne dla pakietu, w tym przypadku pakietu funkcji Bundles, znajdują się w ReplicatedStorage.Bundles.Configs.Constants.
Główne rzeczy, które możesz chcieć dostosować, aby spełnić wymagania projektowe swojej gry:
- Identyfikatory zasobów dźwiękowych
- Czas trwania efektu zakupu i kolory cząsteczek
- Zwinność wyświetlacza
Dodatkowo możesz znaleźć ciągi do tłumaczenia rozbite w jedno miejsce: ReplicatedStorage.FeaturePackagesCore.Configs.TranslationStrings.
Dostosuj komponenty UI
Poprzez modyfikację obiektów pakietu, takich jak kolory, czcionka i przezroczystość, możesz dostosować wizualną prezentację swoich wywołań pakietów. Pamiętaj jednak, że jeśli przeniesiesz którykolwiek z obiektów w hierarchii, kod nie będzie w stanie ich znaleźć, a ty będziesz musiał dostosować swój kod.
Wywołanie składa się z dwóch komponentów na wysokim poziomie:
- PromptItem – Indywidualny komponent powtarzany dla każdego przedmiotu w pakiecie (obraz przedmiotu, podpis, nazwa, cena).
- Prompt – Samo okno wywołania.
Wyświetlacz również składa się z dwóch komponentów:
- HudItem – Indywidualny komponent, który reprezentuje każdą opcję menu w wyświetlaczu.
- Hud – Aby być wypełnionym programowo HudItems.
Jeśli chcesz mieć większą kontrolę nad wyświetlaczem, zamiast po prostu używać istniejącego UI HUD w ReplicatedStorage.Bundles.Objects.BundlesGui, możesz przenieść rzeczy, aby spełnić własne wymagania projektowe. Upewnij się tylko, że aktualizujesz zachowanie skryptu klienta w skrypcie ReplicatedStorage.Bundles.Client.UIController.
Referencje API
Typy
RelativeTime
Gdy pakiet RelativeTime jest oferowany graczowi, pozostaje dostępny, aż czas trwania wygaśnie. Ten typ wyświetla się na wyświetlaczu gracza i automatycznie wywołuje się w przyszłych sesjach, aż pakiet wygaśnie lub gracz go zakupi.
Typowym przykładem tego typu pakietu jest oferta jednorazowego pakietu startowego, która wyświetla się wszystkim nowym graczom przez 24 godziny.
| Nazwa | Typ | Opis |
|---|---|---|
| includeOfflineTime | bool | (Opcjonalne) Jeśli nie jest ustawione, tylko czas spędzony w grze będzie się liczył do pozostałego czasu oferty. |
| singleUse | bool | (Opcjonalne) Jeśli nie jest ustawione, zakup może być reaktywowany po jego zakupie lub wygaśnięciu. Jeśli jest ustawione, po zakupie lub wygaśnięciu po raz pierwszy, nie będzie można go już wywołać, nawet jeśli wywołasz Bundles.promptIfValidAsync z bundleId. |
FixedTime
Gdy pakiet FixedTime jest oferowany graczowi, pozostaje dostępny do końca ustalonego czasu uniwersalnego (UTC). Ten typ wyświetla się na wyświetlaczu gracza i automatycznie wywołuje się w przyszłych sesjach, aż pakiet wygaśnie lub gracz go zakupi.
Typowym przykładem tego typu pakietu jest oferta świąteczna, która jest dostępna tylko przez dany miesiąc.
OneTime
Pakiet OneTime jest dostępny tylko w momencie, gdy jest oferowany graczowi. Nie wyświetla się na wyświetlaczu gracza, a po zamknięciu wywołania nie można go ponownie otworzyć, dopóki nie zostanie ponownie wywołany przez serwer.
Typowym przykładem tego typu pakietu jest oferta zakupu większej ilości waluty w grze w momencie, gdy gracz się wyczerpuje.