Pakiet Bundles

*Ta zawartość została przetłumaczona przy użyciu narzędzi AI (w wersji beta) i może zawierać błędy. Aby wyświetlić tę stronę w języku angielskim, kliknij tutaj.

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:

  1. Dodaj pakiet funkcji Core i Bundles do swojego inwentarza w Studio, klikając link Dodaj do inwentarza w następującym zestawie komponentów.

  2. Z menu Okno w Studio lub z paska narzędzi zakładki Strona główna, otwórz Toolbox.

  3. W oknie Toolbox kliknij zakładkę Inwentarz. Wyświetli się sortowanie Moje modele.

    Okno Toolbox w Studio z wyróżnioną zakładką Inwentarz.
  4. Kliknij kafelek Pakiet funkcji Core, a następnie kafelek Pakiet funkcji Bundles. Oba foldery pakietów wyświetlą się w oknie Eksplorator.

  5. Przeciągnij foldery pakietów do ReplicatedStorage.

  6. Pozwól na wywołania przechowywania danych, aby śledzić zakupy graczy z pakietami.

    1. Otwórz okno PlikUstawienia doświadczenia w Studio.
    2. 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ą.

Waluty
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.

Pakiety
-- 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.

BundlesExample
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
end

Specjalnie 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:

README
{
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:

README
{
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:

README
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:

  1. Połącz handler zakupu przez Bundles.setPurchaseHandler, aby określić funkcje do wywołania w celu przyznania przedmiotów, gdy przetwarzany jest zakup.

    BundlesExample
    local 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 pakiet
    task.wait(2)
    return Enum.ProductPurchaseDecision.PurchaseGranted
    end
    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
    end
  2. Połą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 nie
    local function processReceipt(receiptInfo): Enum.ProductPurchaseDecision
    local userId, productId = receiptInfo.PlayerId, receiptInfo.ProductId
    local player = Players:GetPlayerByUserId(userId)
    if not player then
    return Enum.ProductPurchaseDecision.NotProcessedYet
    end
    local handler = receiptHandlers[productId] -- Pobierz handler dla produktu
    local success, result = pcall(handler, receiptInfo, player) -- Wywołaj handler, aby sprawdzić, czy logika zakupu jest udana
    if not success or not result then
    warn("Nie udało się przetworzyć paragonu:", receiptInfo, result)
    return Enum.ProductPurchaseDecision.NotProcessedYet
    end
    return Enum.ProductPurchaseDecision.PurchaseGranted
    end
    local 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.PurchaseGranted
    end
    -- Ten zakup nie należy do pakietu,
    -- ... Obsłuż całą swoją istniejącą logikę tutaj, jeśli masz jakąkolwiek
    return false
    end
  3. Połącz Players.PlayerAdded:Connect(Bundles.OnPlayerAdded), aby pakiet funkcji Bundles ponownie wywołał wszelkie aktywne pakiety, które jeszcze nie wygasły dla gracza.

    README
    local function onPlayerAdded(player: Player)
    -- Powiedz Bundles, kiedy gracz dołącza, aby mógł załadować ich dane
    Bundles.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 chcesz
    onPromptBundleXYZEvent(player)
    end
  4. Wywoł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).

    README
    local 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 awansuje
    task.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 odliczeniami
    end

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.

NazwaTypOpis
includeOfflineTimebool(Opcjonalne) Jeśli nie jest ustawione, tylko czas spędzony w grze będzie się liczył do pozostałego czasu oferty.
singleUsebool(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.

©2026 Roblox Corporation. Nazwa Roblox, logo Roblox oraz hasło „Powering Imagination” należą do naszych zarejestrowanych i niezarejestrowanych znaków towarowych na terenie Stanów Zjednoczonych oraz w innych krajach.