번들 패키지

*이 콘텐츠는 AI(베타)를 사용해 번역되었으며, 오류가 있을 수 있습니다. 이 페이지를 영어로 보려면 여기를 클릭하세요.

번들 기능 패키지는 플레이어에게 할인된 가격으로 아이템 컬렉션을 판매할 수 있는 즉시 사용 가능한 기능을 제공합니다. 플레이어가 사용자 정의 게임 내 통화 또는 Robux를 사용하여 번들을 구매할 수 있도록 허용할지, 사용할 번들 유형, 판매할 아이템 세트, 게임 플레이 중 플레이어에게 어떻게 프롬프트할지를 선택할 수 있습니다.

패키지의 사용자 정의 옵션을 사용하여 게임의 디자인 및 수익화 목표에 맞게 번들을 조정할 수 있습니다. 예를 들어:

  • 신규 플레이어에게 가치를 제공하고 초기 지출을 장려하는 할인된 스타터 팩을 제공하여 낮은 전환율 지표를 목표로 합니다.
  • 다양한 가격대의 아이템을 번들로 묶어 다양한 플레이어에게 어필하여 지출 깊이를 증가시킵니다.
  • 한정된 시간 동안 독점 아이템의 번들을 제공하여 라이브 운영(LiveOps) 이벤트를 수익화합니다.

패키지 가져오기

크리에이터 스토어는 프로젝트 내에서 사용할 수 있는 Roblox 및 Roblox 커뮤니티에서 만든 모든 자산을 찾는 데 사용할 수 있는 툴박스의 탭입니다. 여기에는 모델, 이미지, 메시, 오디오, 플러그인, 비디오 및 글꼴 자산이 포함됩니다. 크리에이터 스토어를 사용하여 기능 패키지를 포함하여 열린 게임에 하나 이상의 자산을 직접 추가할 수 있습니다!

모든 기능 패키지는 제대로 작동하기 위해 코어 기능 패키지가 필요합니다. 코어번들 기능 패키지 자산이 인벤토리에 있으면 플랫폼의 모든 프로젝트에서 재사용할 수 있습니다.

인벤토리에서 게임으로 패키지를 가져오려면:

  1. 다음 구성 요소 세트에서 인벤토리에 추가 링크를 클릭하여 Studio 내에서 코어번들 기능 패키지를 인벤토리에 추가합니다.

  2. Studio의 메뉴 또는 탭 도구 모음에서 툴박스를 엽니다.

  3. 툴박스 창에서 인벤토리 탭을 클릭합니다. 내 모델 정렬이 표시됩니다.

    Studio의 툴박스 창에서 인벤토리 탭이 강조 표시된 모습.
  4. 기능 패키지 코어 타일을 클릭한 다음 번들 기능 패키지 타일을 클릭합니다. 두 패키지 폴더가 탐색기 창에 표시됩니다.

  5. 패키지 폴더를 ReplicatedStorage로 드래그합니다.

  6. 패키지로 플레이어 구매를 추적할 수 있도록 데이터 저장소 호출을 허용합니다.

    1. Studio의 파일경험 설정 창을 엽니다.
    2. 보안 탭으로 이동한 다음 API 서비스에 대한 스튜디오 액세스 활성화를 활성화합니다.

통화 정의

게임에 자체 통화 시스템이 있는 경우, ReplicatedStorage.FeaturePackagesCore.Configs.Currencies에서 코어 기능 패키지에 등록할 수 있습니다. 이 파일에는 이미 주석 처리된 Gems 통화의 예가 있으므로 이를 자신의 것으로 교체하면 됩니다.

통화
Gems = {
displayName = "보석",
symbol = "💎",
icon = nil,
},

Currencies 스크립트는 코어 기능 패키지에 통화에 대한 메타데이터를 알려줍니다:

  • (필수) displayName - 통화의 이름입니다. 기호나 아이콘을 지정하지 않으면 이 이름이 구매 버튼에 사용됩니다(예: "100 보석").
  • (선택 사항) symbol - 통화의 아이콘으로 사용할 텍스트 문자가 있는 경우, 이는 구매 버튼에서 displayName 대신 사용됩니다(예: "💎100").
  • (선택 사항) icon - 통화에 대한 AssetId 이미지 아이콘이 있는 경우, 이는 구매 버튼에서 displayName 대신 사용됩니다(예: 가격 "🖼️100"의 왼쪽에 이미지가 배치됨).

통화가 설정되면 번들의 가격, 통화 및 아이콘을 수동으로 지정해야 하며, 이 정보는 번들과 연결된 개발자 제품에서 가져오지 않습니다.

번들
-- 개발 제품을 사용하려면 고유한 devProductId를 제공해야 하며, 이는 하나의 번들에서만 사용됩니다.
-- 우리는 개발자 제품에서 번들 가격과 아이콘을 가져올 것입니다.
pricing = {
priceType = CurrencyTypes.PriceType.Marketplace,
devProductId = 1795621566,
},
-- 그렇지 않으면, 개발 제품 대신 게임 내 통화를 사용하려면 다음을 사용할 수 있습니다:
-- 여기 가격은 게임 내 통화로, Robux가 아닙니다.
pricing = {
priceType = CurrencyTypes.PriceType.InExperience,
price = 79,
currencyId = "Gems",
icon = 18712203759,
},

또한 BundlesExample 스크립트를 참조하여 setInExperiencePurchaseHandler를 호출해야 합니다.

BundlesExample
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를 작성해야 하며, 이는 예제 initializePurchaseHandlers 내의 Currencies를 통해 호출됩니다(즉, 각 currencyIdBundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)를 통해 핸들러에 연결됩니다).

번들 정의

게임에서 제공할 수 있는 모든 번들은 ReplicatedStorage.Bundles.Configs.Bundles 내에서 정의할 수 있으며, 이 폴더의 Types 스크립트에서 내보낸 유형을 사용합니다.

devProductId를 사용하는 경우, 번들의 주요 devProductId를 게임의 것과 일치하도록 업데이트해야 합니다. 이는 MarketplaceService를 통해 번들을 구매하도록 프롬프트할 때 사용됩니다. 별도의 판매를 추적하기 쉽게 하기 위해 번들에 대해 새로운 개발자 제품을 사용하는 것이 강력히 권장됩니다.

여러 아이템이 포함된 번들을 원하고, 이러한 아이템이 이미 게임 내에서 개발자 제품으로 표현되어 있는 경우, 아이템 가격/assetId/name을 명시적으로 설정할 필요가 없으며, 제품 정보를 통해 가져옵니다:

README
{
itemType = ItemTypes.ItemType.DevProduct,
devProductId = <DEV_PRODUCT_ID>,
metadata = {
caption = {
text = "x1",
color = Color3.fromRGB(236, 201, 74),
} -- 캡션은 선택 사항입니다! 이 필드를 생략할 수도 있습니다.
}
},

그렇지 않으면, 이러한 아이템 세부정보를 수동으로 구성할 수 있습니다:

README
{
itemType = ItemTypes.ItemType.Robux,
priceInRobux = 49,
icon = <IMAGE_ASSET_ID>,
metadata = {
caption = {
text = "x1",
color = Color3.fromRGB(236, 201, 74),
} -- 캡션은 선택 사항입니다! 이 필드를 생략할 수도 있습니다.
}
},

예를 들어, 전체 번들은 다음과 같이 보일 것입니다:

README
local starterBundle: Types.RelativeTimeBundle = {
bundleType = Types.BundleType.RelativeTime,
-- 개발 제품을 사용하려면 고유한 devProductId를 제공해야 하며, 이는 하나의 번들에서만 사용됩니다.
-- 우리는 개발자 제품에서 번들 가격과 아이콘을 가져올 것입니다.
pricing = {
priceType = CurrencyTypes.PriceType.Marketplace,
devProductId = <DEV_PRODUCT_ID>,
},
-- 그렇지 않으면, 개발 제품 대신 게임 내 통화를 사용하려면 다음을 사용할 수 있습니다:
-- 여기 가격은 게임 내 통화로, Robux가 아닙니다.
-- pricing = {
-- priceType = CurrencyTypes.PriceType.InExperience,
-- price = 79,
-- currencyId = <CURRENCY_ID>,
-- icon = <IMAGE_ASSET_ID>,
-- },
includedItems = {
[1] = {
-- 아이템 자체는 개발자 제품을 통해 판매되지 않으므로, Robux에서 얼마나 가치가 있는지 표시하고 아이콘을 제공합니다.
-- priceInRobux는 Bundles가 번들 가격과 그 내용물의 합계의 상대 가치를 보여주는 데 도움이 됩니다.
itemType = ItemTypes.ItemType.Robux,
priceInRobux = 49,
icon = <IMAGE_ASSET_ID>,
-- 또는, 이 아이템에 개발자 제품이 있는 경우 위의 가격과 아이콘을 생략하고 devProductId만 설정합니다.
-- 가격과 아이콘은 개발자 제품에서 가져옵니다.
-- devProductId = <ITEM_DEV_PRODUCT_ID>
-- 필요에 따라 UI 전용으로 더 많은 선택적 메타데이터 필드가 있습니다.
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, -- 구매하거나 만료된 후에는 더 이상 유효하지 않으며, 게임이 프롬프트를 시도하더라도 유효하지 않습니다. Studio에서 테스트하는 동안에는 false로 설정할 수 있습니다.
durationInSeconds = 900, -- 15분
includesOfflineTime = false, -- 게임에서 경과한 시간만 계산합니다.
metadata = {
displayName = "스타터 번들",
description = "75% 절약하고 시작하세요!",
},
}

서버 논리 통합

ReplicatedStorage.Bundles.Server.Examples.BundlesExample을 살펴보면, 서버가 번들 기능 패키지 및 위의 ModuleScript의 메서드와 상호작용하는 방법을 보여줍니다. 아래의 코드 조각은 해당 스크립트에서 가져온 것입니다.

번들 기능 패키지를 게임에 드래그한 후 연결해야 할 네 가지가 있습니다:

  1. Bundles.setPurchaseHandler를 통해 구매 핸들러를 연결하여 구매가 처리될 때 아이템을 수여하기 위해 호출할 함수를 지정합니다.

    BundlesExample
    local function awardMarketplacePurchase(_player: Player, _bundleId: Types.BundleId, _receiptInfo: { [string]: any })
    -- 플레이어 데이터를 업데이트하고, 아이템을 제공하고, 등등.
    -- ... 그리고 receiptInfo.PurchaseId를 기록하여 사용자가 이미 이 번들을 가지고 있는지 확인합니다.
    task.wait(2)
    return Enum.ProductPurchaseDecision.PurchaseGranted
    end
    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
  2. MarketplaceService.ProcessReceipt에 대한 논리를 연결하지만, 게임에 이미 판매 중인 개발자 제품이 있는 경우 다른 곳에서 수행될 수 있습니다. 본질적으로, 개발자 제품 영수증이 처리될 때, 이제 Bundles.getBundleByDevProduct를 호출하여 제품이 번들에 속하는지 확인합니다. 그렇다면 스크립트는 Bundles.processReceipt를 호출합니다.

    BundlesExample
    -- 마켓플레이스에서 영수증을 처리하여 플레이어에게 요금을 부과해야 하는지 여부를 결정합니다.
    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] -- 제품에 대한 핸들러를 가져옵니다.
    local success, result = pcall(handler, receiptInfo, player) -- 핸들러를 호출하여 구매 논리가 성공적인지 확인합니다.
    if not success or not result then
    warn("영수증 처리 실패:", 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
    -- 이 구매는 번들에 속하므로 Bundles가 처리하도록 합니다.
    local purchaseDecision = Bundles.processReceiptAsync(player, bundleId, receiptInfo)
    return purchaseDecision == Enum.ProductPurchaseDecision.PurchaseGranted
    end
    -- 이 구매는 번들에 속하지 않습니다.
    -- ... 기존 논리가 있는 경우 여기에 처리합니다.
    return false
    end
  3. Players.PlayerAdded:Connect(Bundles.OnPlayerAdded)를 연결하여 번들 기능 패키지가 만료되지 않은 모든 활성 번들을 플레이어에게 다시 프롬프트하도록 합니다.

    README
    local function onPlayerAdded(player: Player)
    -- 플레이어가 가입할 때 Bundles에 알리므로 데이터를 다시 로드할 수 있습니다.
    Bundles.onPlayerAdded(player)
    -- 모든 신규 사용자에게 제공하고 싶은 스타터 번들이 있다면 여기에서 프롬프트할 수 있습니다.
    -- ... Bundles는 플레이어가 이미 구매했는지 또는 만료되었는지 처리합니다. 이는 반복할 수 없습니다.
    -- Bundles.promptIfValidAsync(player, "StarterBundle")
    -- 예시로 여기에서 호출하는 것일 뿐이며, 원하는 경우 언제든지 호출할 수 있습니다.
    onPromptBundleXYZEvent(player)
    end
  4. 번들을 프롬프트합니다. 이는 게임 플레이에 따라 다르지만, 예제에서는 onPlayerAdded에서 스타터 번들을 프롬프트합니다.

    • 번들 기능 패키지 논리는 각 플레이어가 이미 번들을 구매했거나 제안이 만료된 경우 반복 제안을 받지 않도록 보장합니다.

    • 플레이어에게 번들을 프롬프트하려면 Bundles.promptIfValidAsync(player, bundleId)를 호출합니다.

    README
    local function onPromptBundleXYZEvent(player: Player)
    -- 플레이어가 번들을 프롬프트받는 시점을 결정하는 게임 이벤트를 연결합니다.
    -- ... 이는 플레이어에게 번들을 프롬프트할 수 있는 자격 기준을 충족했을 때입니다.
    -- ... 예를 들어, 플레이어가 가입할 때 또는 플레이어가 레벨업할 때 번들을 프롬프트하고 싶다면
    task.spawn(Bundles.promptIfValidAsync, player, <Some_Bundle_Id>)
    -- ... 여러 번들을 생성하는 경우, 위의 함수 호출을 task.spawn()으로 감싸면 카운트다운 간의 불일치를 최소화할 수 있습니다.
    end

영수증 ID의 중복 기록에 대한 다음 모범 사례 지침을 고려하세요:

  • 번들 기능 패키지는 동일한 영수증을 두 번 처리하지 않도록 영수증 ID를 기록하지만, 구매 핸들러가 이미 완료된 후 구매 흐름이 실패할 경우를 대비하여 테이블 내에서 영수증 ID를 기록해야 합니다.

  • 번들 기능 패키지는 구매가 어떤 단계에서든 실패하면 영수증 ID를 기록하지 않으므로, 구매 핸들러의 일환으로 영수증을 처리하기 전에 테이블에 영수증 ID를 기록하고 있는지 확인해야 합니다.

  • 이 중복성은 모든 구매 논리가 적절하게 처리되었는지 확인하고 데이터 저장소와 번들 기능 패키지의 데이터 저장소가 최종 일관성에 도달하도록 도와줍니다. 데이터 저장소가 진실의 출처가 됩니다.

상수 구성

코어 기능 패키지의 상수는 두 곳에 존재합니다:

  • 공유 상수는 ReplicatedStorage.FeaturePackagesCore.Configs.SharedConstants에 있습니다.

  • 패키지별 상수, 이 경우 번들 기능 패키지는 ReplicatedStorage.Bundles.Configs.Constants에 있습니다.

게임의 디자인 요구 사항을 충족하기 위해 조정할 수 있는 주요 사항:

  • 사운드 자산 ID
  • 구매 효과 지속 시간 및 입자 색상
  • 헤드업 디스플레이의 접기 가능성

또한, 번역을 위한 문자열이 하나의 위치에 나누어져 있습니다: ReplicatedStorage.FeaturePackagesCore.Configs.TranslationStrings.

UI 구성 요소 사용자 정의

패키지 객체(예: 색상, 글꼴 및 투명도)를 수정하여 번들 프롬프트의 시각적 표현을 조정할 수 있습니다. 그러나 객체를 계층적으로 이동하면 코드가 이를 찾을 수 없으므로 코드에 대한 조정을 해야 합니다.

프롬프트는 두 개의 고수준 구성 요소로 구성됩니다:

  • PromptItem – 번들 내의 각 아이템에 대해 반복되는 개별 구성 요소(아이템 이미지, 캡션, 이름, 가격).
  • Prompt – 프롬프트 창 자체.

헤드업 디스플레이는 또한 두 개의 구성 요소로 구성됩니다:

  • HudItem – 헤드업 디스플레이의 각 메뉴 옵션을 나타내는 개별 구성 요소.
  • HudHudItems로 프로그래밍 방식으로 채워집니다.

헤드업 디스플레이에 대한 더 큰 제어를 원한다면, ReplicatedStorage.Bundles.Objects.BundlesGui 내의 기존 HUD UI를 사용하는 대신 자신의 디자인 요구 사항에 맞게 이동할 수 있습니다. 단, ReplicatedStorage.Bundles.Client.UIController 스크립트에서 클라이언트 스크립트 동작을 업데이트해야 합니다.

API 참조

유형

RelativeTime

RelativeTime 번들이 플레이어에게 제공되면, 시간 지속이 만료될 때까지 사용 가능합니다. 이 유형은 플레이어의 헤드업 디스플레이에 표시되며, 번들이 만료되거나 플레이어가 구매할 때까지 향후 세션에서 자동으로 프롬프트됩니다.

이 번들 유형의 일반적인 예는 모든 신규 플레이어에게 24시간 동안 표시되는 단일 사용 스타터 팩 제안입니다.

이름유형설명
includeOfflineTimebool(선택 사항) 설정하지 않으면 게임에서 소요된 시간만 남은 제안 지속 시간에 포함됩니다.
singleUsebool(선택 사항) 설정하지 않으면 구매 후 재활성화할 수 있습니다.

설정하면, 처음 구매하거나 만료된 후에는 다시 프롬프트할 수 없습니다. Bundles.promptIfValidAsync를 호출하더라도 마찬가지입니다.

FixedTime

FixedTime 번들이 플레이어에게 제공되면, 설정된 협정 세계시(UTC) 종료 시점까지 사용 가능합니다. 이 유형은 플레이어의 헤드업 디스플레이에 표시되며, 번들이 만료되거나 플레이어가 구매할 때까지 향후 세션에서 자동으로 프롬프트됩니다.

이 번들 유형의 일반적인 예는 특정 월에만 제공되는 휴일 제안입니다.

OneTime

OneTime 번들은 플레이어에게 제공되는 순간에만 사용 가능합니다. 플레이어의 헤드업 디스플레이에 표시되지 않으며, 플레이어가 프롬프트를 닫으면 서버에서 다시 프롬프트할 때까지 다시 열 수 없습니다.

이 번들 유형의 일반적인 예는 플레이어가 게임 내 통화를 소진했을 때 즉시 더 많은 통화를 구매하라는 제안입니다.

©2026 Roblox Corporation. Roblox 및 Roblox 로고, 'Powering Imagination'은 미국 및 기타 국가 내 당사의 등록 및 미등록 상표입니다.