번들 패키지

*이 콘텐츠는 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 = "Gems",
symbol = "💎",
icon = nil,
},

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

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

통화 설정이 완료되면, 번들의 가격, 통화 및 아이콘을 수동으로 지정해야 하며, 번들의 관련 개발 제품에서 해당 정보가 가져와지지 않도록 해야 합니다.

번들

-- dev 제품을 사용하려면, 하나의 번들에서만 사용될 고유한 devProductId를 제공해야 합니다.
-- 우리는 개발 제품에서 번들 가격과 아이콘을 가져올 것입니다
pricing = {
priceType = CurrencyTypes.PriceType.Marketplace,
devProductId = 1795621566,
},
-- 그렇지 않다면, dev 제품 대신 경험 내 통화를 사용하려면 다음을 사용하십시오:
-- 여기의 가격은 경험 내 통화로, 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를 구현해야 하며, 이는 예제 initializePurchaseHandlersCurrencies를 통과하는 루프에 의해 호출됩니다 (즉, 각 currencyId는 Bundles.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,
-- dev 제품을 사용하려면, 하나의 번들에만 사용될 고유한 devProductId를 제공해야 합니다.
-- 우리는 개발 제품에서 번들 가격과 아이콘을 가져올 것입니다
pricing = {
priceType = CurrencyTypes.PriceType.Marketplace,
devProductId = <DEV_PRODUCT_ID>,
},
-- 그렇지 않으면, dev 제품 대신 경험 내 통화를 사용하려면 다음을 사용하십시오:
-- 여기의 가격은 경험 내 통화로, Robux가 아닙니다
-- pricing = {
-- priceType = CurrencyTypes.PriceType.InExperience,
-- price = 79,
-- currencyId = <CURRENCY_ID>,
-- icon = <IMAGE_ASSET_ID>,
-- },
includedItems = {
[1] = {
-- 이 아이템은 개발 제품을 통해 판매되지 않으므로 Robux로 얼마의 가치가 있는지 표시하고 아이콘을 제공합니다
-- priceInRobux는 번들 가격과 그 내용물의 합계 간의 상대적 가치를 표시하는 데 도움이 됩니다
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, -- 구매되거나 만료되면 더 이상 유효하지 않으며 경험이 프롬프트를 시도하더라도 그렇습니다 (스튜디오에서 테스트 중에는 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
    -- 이 구매는 번들에 속하므로, 번들이 처리하게 하십시오
    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.onPlayerAdded(player)
    -- 모든 신규 사용자에게 제공할 스타터 번들이 있었다면, 여기에서 프롬프트를 제공할 수 있습니다
    -- ... 번들은 플레이어가 이미 구매했는지 또는 만료되었는지 여부를 처리합니다.
    -- 번들Id를 사용하여 `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의 중복 기록에 대한 모범 사례 지침은 다음과 같습니다:

  • 번들 기능 패키지는 동일한 영수증을 두 번 처리하지 않기 위해 ReceiptIds를 기록하지만, 구매 핸들러가 끝난 후 구매 흐름이 실패한 경우, 다시 시도할 때 아이템을 다시 수여하지 않도록 하려면 ReceiptIds를 테이블에 기록해야 합니다.

  • 번들 기능 패키지는 구매가 어떤 단계에서든 실패하면 ReceiptId를 기록하지 않기 때문에, 구매Handler의 일환으로 영수증을 처리하기 전에 ReceiptId를 테이블에 기록하는 것이 중요합니다.

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

상수 구성

코어 기능 패키지의 상수는 두 곳에 있습니다:

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

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

경험의 디자인 요구 사항을 충족하기 위해 조정할 수 있는 주요 사항은 다음과 같습니다:

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

또한, 번역할 문자열은 한 곳에 모여 있습니다: ReplicatedStorage.FeaturePackagesCore.Configs.TranslationStrings.

UI 구성 요소 사용자 정의

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

프롬프트는 두 가지 고급 구성 요소로 구성됩니다:

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

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

  • HudItem – 헤드업 디스플레이의 각 메뉴 옵션을 나타내는 개별 구성 요소.
  • Hud – 프로그램적으로 HudItems로 채워질 것입니다.

헤드업 디스플레이에 대한 더 높은 제어를 원한다면, 단순히 ReplicatedStorage.Bundles.Objects.BundlesGui 내의 기존 HUD UI를 사용하는 대신, 자신의 디자인 요구 사항에 맞게 요소를 이동할 수 있습니다. 클라이언트 스크립트의 동작을 ReplicatedStorage.Bundles.Client.UIController 스크립트에서 업데이트하는 것을 잊지 마십시오.

API 참조

유형

RelativeTime

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

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

이름유형설명
includeOfflineTimebool(선택 사항) 설정되지 않으면, 경험에 머무는 시간만이 남은 제안 지속 시간에 포함됩니다.
singleUsebool(선택 사항) 설정되지 않으면, 구매 후 다시 구매할 수 있습니다.

설정된 경우, 처음 구매되거나 만료되면, 번들을 더 이상 프롬프트할 수 없으며 Bundles.promptIfValidAsync를 번들Id로 호출해도 마찬가지입니다.

FixedTime

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

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

OneTime

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

이 번들 유형의 일반적인 예는 플레이어가 소모될 때 즉시 경험 내 통화를 구매할 수 있는 제안입니다.

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