تقدم حزمة ميزة الحزم وظائف جاهزة لبيع مجموعات من العناصر للاعبين بسعر مخفض. يمكنك اختيار ما إذا كنت تريد السماح للاعبين بشراء الحزم باستخدام عملة داخل اللعبة مخصصة أو روبوكس، ونوع الحزمة التي تريد استخدامها، وما مجموعة العناصر التي تريد بيعها، وكيف تريد تحفيز اللاعبين خلال لعبهم.
باستخدام خيارات تخصيص الحزمة، يمكنك تخصيص حزمك لتلبية أهداف التصميم والت monetization لألعابك، مثل:
- استهداف معدل تحويل منخفض من خلال تقديم حزم بدء مخفضة توفر قيمة للاعبين الجدد وتشجع على الإنفاق المبكر.
- زيادة عمق الإنفاق من خلال تجميع العناصر بأسعار مختلفة لجذب مجموعة من اللاعبين.
- تحقيق الدخل من عمليات التشغيل المباشرة (LiveOps) الأحداث من خلال تقديم حزم محدودة الوقت من العناصر الحصرية.

الحصول على الحزمة
تعد متجر المبدعين علامة تبويب في صندوق الأدوات يمكنك استخدامها للعثور على جميع الأصول التي أنشأتها Roblox ومجتمع Roblox لاستخدامها في مشاريعك، بما في ذلك نموذج، صورة، شبكة، صوت، ملحق، فيديو، وأصول خط. يمكنك استخدام متجر المبدعين لإضافة أصل واحد أو أكثر مباشرة إلى لعبة مفتوحة، بما في ذلك حزم الميزات!
تتطلب كل حزمة ميزة حزمة النواة لتعمل بشكل صحيح. بمجرد أن تكون أصول حزمة النواة و الحزم ضمن مخزونك، يمكنك إعادة استخدامها في أي مشروع على المنصة.
للحصول على الحزم من مخزونك إلى لعبتك:
أضف حزمة النواة وحزمة الحزم إلى مخزونك داخل الاستوديو من خلال النقر على رابط إضافة إلى المخزون في مجموعة المكونات التالية.
من قائمة نافذة في الاستوديو أو شريط أدوات علامة التبويب الرئيسية، افتح صندوق الأدوات.
في نافذة صندوق الأدوات، انقر على علامة التبويب المخزون. يتم عرض فرز نماذجي.

انقر على بلاطة حزمة ميزة النواة، ثم بلاطة حزمة ميزة الحزمة. يتم عرض كلا مجلدي الحزمة في نافذة المستكشف.
اسحب مجلدي الحزمة إلى ReplicatedStorage.
اسمح لاستدعاءات متجر البيانات بتتبع مشتريات اللاعبين باستخدام الحزم.
- افتح نافذة ملف ⟩ إعدادات التجربة في الاستوديو.
- انتقل إلى علامة التبويب الأمان، ثم قم بتمكين تمكين وصول الاستوديو إلى خدمات API.
تعريف العملات
إذا كانت لعبتك تحتوي على نظام عملات خاص بها، يمكنك تسجيل تلك العملات مع حزمة النواة من خلال تعريفها في ReplicatedStorage.FeaturePackagesCore.Configs.Currencies. هناك مثال معلق لعملة الجواهر بالفعل في هذا الملف؛ استبدله بعملتك الخاصة.
Gems = {
displayName = "جواهر",
symbol = "💎",
icon = nil,
},تخبر سكريبت Currencies حزمة النواة ببعض البيانات الوصفية حول عملتك:
- (مطلوب) displayName - اسم عملتك. إذا لم تحدد رمزًا أو أيقونة، يتم استخدام هذا الاسم في أزرار الشراء (أي "100 جواهر").
- (اختياري) symbol - إذا كان لديك حرف نصي لاستخدامه كأيقونة لعملتك، يتم استخدامه بدلاً من displayName في أزرار الشراء (أي "💎100").
- (اختياري) icon - إذا كان لديك أيقونة صورة AssetId لعملتك، يتم استخدامها بدلاً من displayName في أزرار الشراء (أي سيتم وضع الصورة إلى يسار السعر "🖼️100")
بمجرد إعداد عملتك، تحتاج إلى تحديد سعر الحزمة يدويًا، العملة، والأيقونة لعرض المعلومات بدلاً من أن يتم جلب تلك المعلومات من المنتج المطور المرتبط بالحزمة.
-- إذا كنت تريد استخدام منتج مطور، يجب عليك توفير devProductId فريد، يُستخدم فقط من قبل حزمة واحدة.
-- سنقوم بجلب سعر الحزمة والأيقونة من المنتج المطور
pricing = {
priceType = CurrencyTypes.PriceType.Marketplace,
devProductId = 1795621566,
},
-- خلاف ذلك، إذا كنت تريد استخدام عملة داخل اللعبة بدلاً من منتج مطور، يمكنك استخدام ما يلي بدلاً من ذلك:
-- السعر هنا هو في العملة داخل اللعبة، وليس روبوكس
pricing = {
priceType = CurrencyTypes.PriceType.InExperience,
price = 79,
currencyId = "جواهر",
icon = 18712203759,
},تحتاج أيضًا إلى الإشارة إلى سكريبت BundlesExample لاستدعاء setInExperiencePurchaseHandler.
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، الذي يتم استدعاؤه بواسطة حلقة عبر Currencies داخل initializePurchaseHandlers (أي يتم ربط كل currencyId بالمعالج من خلال Bundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)).
تعريف الحزم
يمكن تعريف جميع الحزم القابلة للتقديم في لعبتك ضمن ReplicatedStorage.Bundles.Configs.Bundles، مع الأنواع المستخرجة من سكريبت Types في نفس المجلد.
إذا كنت تستخدم devProductId، تحتاج إلى تحديث devProductId الرئيسي للحزمة ليتطابق مع الموجود في لعبتك. هذا ما سيتم تحفيزه من خلال MarketplaceService لشراء الحزمة نفسها. يوصى بشدة باستخدام منتج مطور جديد للحزمة لتسهيل تتبع المبيعات المنفصلة.
إذا كنت تريد حزمة تحتوي على عناصر متعددة، وإذا كانت هذه العناصر ممثلة بالفعل بواسطة منتجات مطورة في لعبتك، فلا تحتاج إلى تعيين سعر العنصر/assetId/الاسم بشكل صريح، والذي سيتم جلبه عبر معلومات المنتج:
{
itemType = ItemTypes.ItemType.DevProduct,
devProductId = <DEV_PRODUCT_ID>,
metadata = {
caption = {
text = "x1",
color = Color3.fromRGB(236, 201, 74),
} -- التسمية اختيارية! يمكنك أيضًا حذف هذا الحقل
}
},خلاف ذلك، يمكنك تكوين تفاصيل العنصر يدويًا:
{
itemType = ItemTypes.ItemType.Robux,
priceInRobux = 49,
icon = <IMAGE_ASSET_ID>,
metadata = {
caption = {
text = "x1",
color = Color3.fromRGB(236, 201, 74),
} -- التسمية اختيارية! يمكنك أيضًا حذف هذا الحقل
}
},على سبيل المثال، من المحتمل أن تبدو حزمةك بالكامل كما يلي:
local starterBundle: Types.RelativeTimeBundle = {
bundleType = Types.BundleType.RelativeTime,
-- إذا كنت تريد استخدام منتج مطور، يجب عليك توفير devProductId فريد، يُستخدم فقط من قبل حزمة واحدة.
-- سنقوم بجلب سعر الحزمة والأيقونة من المنتج المطور
pricing = {
priceType = CurrencyTypes.PriceType.Marketplace,
devProductId = <DEV_PRODUCT_ID>,
},
-- خلاف ذلك، إذا كنت تريد استخدام عملة داخل اللعبة بدلاً من منتج مطور، يمكنك استخدام ما يلي بدلاً من ذلك:
-- السعر هنا هو في العملة داخل اللعبة، وليس روبوكس
-- pricing = {
-- priceType = CurrencyTypes.PriceType.InExperience,
-- price = 79,
-- currencyId = <CURRENCY_ID>,
-- icon = <IMAGE_ASSET_ID>,
-- },
includedItems = {
[1] = {
-- العنصر نفسه لا يتم بيعه عبر منتج مطور، لذا حدد كم هو قيمته بالروبوكس وقدم أيقونة
-- يساعد priceInRobux الحزم على إظهار القيمة النسبية لسعر الحزمة مقابل مجموع محتوياتها
itemType = ItemTypes.ItemType.Robux,
priceInRobux = 49,
icon = <IMAGE_ASSET_ID>,
-- بدلاً من ذلك، إذا كان هذا لديه منتج مطور، اترك السعر والأيقونة أعلاه وحدد فقط devProductId
-- سيتم جلب السعر والأيقونة من المنتج المطور
-- devProductId = <ITEM_DEV_PRODUCT_ID>
-- هناك المزيد من الحقول الوصفية الاختيارية التي تتعلق بواجهة المستخدم إذا لزم الأمر
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, -- بمجرد شرائها أو انتهاء صلاحيتها، لم تعد صالحة حتى لو حاولت لعبتك تحفيزها (onPlayerAdded). يمكنك جعل هذا false أثناء الاختبار في الاستوديو.
durationInSeconds = 900, -- 15 دقيقة
includesOfflineTime = false, -- احتساب الوقت المنقضي فقط في اللعبة
metadata = {
displayName = "حزمة البداية",
description = "وفر 75% واحصل على بداية مبكرة!",
},
}دمج منطق الخادم
ألقِ نظرة على ReplicatedStorage.Bundles.Server.Examples.BundlesExample، الذي يوضح كيف سيتفاعل خادمك مع حزمة ميزة الحزم والطرق المذكورة أعلاه في ModuleScript. المقاطع أدناه هي من ذلك السكريبت.
تحتاج بشكل أساسي إلى توصيل أربعة أشياء بمجرد سحب حزمة الحزم إلى لعبتك:
ربط معالجات الشراء من خلال Bundles.setPurchaseHandler لتحديد الوظائف التي سيتم استدعاؤها لمنح العناصر عند معالجة عملية الشراء.
BundlesExamplelocal function awardMarketplacePurchase(_player: Player, _bundleId: Types.BundleId, _receiptInfo: { [string]: any })-- تحديث بيانات اللاعب، منح العناصر، إلخ.-- ... وسجل receiptInfo.PurchaseId حتى نتمكن من التحقق مما إذا كان المستخدم لديه هذه الحزمة بالفعلtask.wait(2)return Enum.ProductPurchaseDecision.PurchaseGrantedendlocal function awardInExperiencePurchase(_player: Player,_bundleId: Types.BundleId,_currencyId: CurrencyTypes.CurrencyId,_price: number)-- تحقق مما إذا كان لدى اللاعب ما يكفي من العملة لشراء الحزمة-- تحديث بيانات اللاعب، منح العناصر، إلخ.-- خصم العملة من اللاعبtask.wait(2)return trueendlocal function initializePurchaseHandlers()local bundles = Bundles.getBundles()for bundleId, bundle in bundles do-- الحزمة ليست مرتبطة بمنتج مطور إذا لم يكن لديها نوع سعر سوقif not bundle or bundle.pricing.priceType ~= "Marketplace" thencontinueendBundles.setPurchaseHandler(bundleId, awardMarketplacePurchase)receiptHandlers[bundle.pricing.devProductId] = receiptHandlerend-- إذا كان لديك أي عملات داخل اللعبة تستخدمها للحزم، قم بتعيين المعالج هناfor currencyId, _ in Currencies doBundles.setInExperiencePurchaseHandler(currencyId, awardInExperiencePurchase)endendربط منطقك لـ MarketplaceService.ProcessReceipt، ولكن قد يتم ذلك في مكان آخر إذا كانت لعبتك تحتوي بالفعل على منتجات مطورة للبيع. بشكل أساسي، عندما يتم معالجة إيصال منتج مطور، سيتصلون الآن بـ Bundles.getBundleByDevProduct للتحقق مما إذا كان المنتج ينتمي إلى حزمة. إذا كان الأمر كذلك، يقوم السكريبت بعد ذلك باستدعاء Bundles.processReceipt.
BundlesExample-- معالجة الإيصال من السوق لتحديد ما إذا كان يجب فرض رسوم على اللاعب أم لاlocal 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] -- احصل على المعالج للمنتجlocal success, result = pcall(handler, receiptInfo, player) -- استدعاء المعالج للتحقق مما إذا كانت منطق الشراء ناجحةif not success or not result thenwarn("فشل في معالجة الإيصال:", 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-- تنتمي هذه الشراء إلى حزمة، دع الحزم تتعامل معهاlocal purchaseDecision = Bundles.processReceiptAsync(player, bundleId, receiptInfo)return purchaseDecision == Enum.ProductPurchaseDecision.PurchaseGrantedend-- لا تنتمي هذه الشراء إلى حزمة،-- ... تعامل مع كل منطقك الحالي هنا إذا كان لديك أيreturn falseendربط Players.PlayerAdded:Connect(Bundles.OnPlayerAdded) بحيث تعيد حزمة ميزة الحزم تحفيز أي حزم نشطة لم تنته صلاحيتها بعد للاعب.
READMElocal function onPlayerAdded(player: Player)-- أخبر الحزم عندما ينضم اللاعب حتى تتمكن من إعادة تحميل بياناتهمBundles.onPlayerAdded(player)-- إذا كان لديك بعض حزمة البداية التي تريد تقديمها لجميع المستخدمين الجدد، يمكنك تحفيز ذلك هنا-- ... ستتعامل الحزم مع ما إذا كان اللاعب قد اشترى بالفعل ذلك أو إذا انتهت صلاحيته منذ ذلك الحين لأنه ليس متكررًا-- Bundles.promptIfValidAsync(player, "StarterBundle")-- استدعاء هذا هنا فقط كمثال، يمكنك استدعاء هذا في أي وقت أو في أي مكان تريدهonPromptBundleXYZEvent(player)endتحفيز الحزم. بينما يعتمد هذا على طريقة اللعب، المثال يحفز اللاعبين بحزمة البداية onPlayerAdded.
تضمن منطق حزمة الحزم أن كل لاعب لا يحصل على عرض متكرر إذا كان قد اشترى الحزمة بالفعل، أو إذا سمح للعرض بالانتهاء (استنادًا إلى تكوين الحزمة).
كلما كنت تريد تحفيز حزمة للاعب، استدعِ Bundles.promptIfValidAsync(player, bundleId).
READMElocal function onPromptBundleXYZEvent(player: Player)-- ربط أي حدث لعبة تريد استخدامه لتحديد متى يتم تحفيز اللاعب بالحزمة-- ... سيكون هذا كلما استوفيت معايير الأهلية الخاصة بك لتحفيز اللاعب بالحزمة-- ... على سبيل المثال، إذا كنت تريد تحفيز حزمة عندما ينضم لاعب، أو عندما يرتقي لاعبtask.spawn(Bundles.promptIfValidAsync, player, <Some_Bundle_Id>)-- ... إذا كنت تنشئ حزمًا متعددة، فإن استخدام task.spawn() لتغليف استدعاء الوظيفة أعلاه سيقلل من التباينات بين العد التنازليend
اعتبر التوجيهات التالية لأفضل الممارسات حول التسجيلات الزائدة لـ ReceiptIds:
بينما تسجل حزمة الحزم ReceiptIds لتجنب معالجة نفس الإيصال مرتين، يجب عليك أيضًا تسجيل ReceiptIds داخل جداولك حتى إذا فشلت عملية الشراء بعد أن انتهت معالجتها، تعرف في المحاولة التالية ألا تمنح العناصر مرة أخرى.
لن تسجل حزمة الحزم ReceiptId إذا فشلت عملية الشراء في أي خطوة، لذا يجب عليك التأكد من أنك تسجل ReceiptId في جداولك قبل معالجة الإيصال كجزء من purchaseHandler الخاص بك.
تساعد هذه الزيادة في ضمان أن جميع منطق الشراء قد تم التعامل معه بشكل مناسب وأن متجر البيانات الخاص بك ومتجر البيانات الخاص بحزمة الحزم يصلان إلى اتساق نهائي، مع كون متجر البيانات الخاص بك هو مصدر الحقيقة.
تكوين الثوابت
تعيش الثوابت الخاصة بحزمة النواة في مكانين:
تعيش الثوابت المشتركة في ReplicatedStorage.FeaturePackagesCore.Configs.SharedConstants.
الثوابت الخاصة بالحزمة، في هذه الحالة حزمة الحزم، تعيش في ReplicatedStorage.Bundles.Configs.Constants.
الأشياء الرئيسية التي قد ترغب في تعديلها لتلبية متطلبات تصميم لعبتك:
- معرفات أصول الصوت
- مدة تأثير الشراء وألوان الجسيمات
- قابلية طي عرض المعلومات
بالإضافة إلى ذلك، يمكنك العثور على سلاسل للترجمة مفصولة إلى موقع واحد: ReplicatedStorage.FeaturePackagesCore.Configs.TranslationStrings.
تخصيص مكونات واجهة المستخدم
من خلال تعديل كائنات الحزمة، مثل الألوان، الخط، والشفافية، يمكنك ضبط العرض المرئي لتحفيزات الحزمة الخاصة بك. ومع ذلك، ضع في اعتبارك أنه إذا قمت بتحريك أي من الكائنات حولها هيكليًا، فلن يتمكن الكود من العثور عليها، وستحتاج إلى إجراء تعديلات على كودك.
يتكون التحفيز من مكونين عاليين:
- PromptItem – المكون الفردي المتكرر لكل عنصر داخل حزمة (صورة العنصر، التسمية، الاسم، السعر).
- Prompt – نافذة التحفيز نفسها.
يتكون عرض المعلومات أيضًا من مكونين:
- HudItem – مكون فردي يمثل كل خيار قائمة في عرض المعلومات.
- Hud – ليتم ملؤه برمجيًا بـ HudItems.
إذا كنت ترغب في الحصول على تحكم أكبر في عرض المعلومات، بدلاً من مجرد استخدام واجهة المستخدم الحالية داخل ReplicatedStorage.Bundles.Objects.BundlesGui، يمكنك تحريك الأشياء لتلبية متطلبات تصميمك الخاصة. فقط تأكد من تحديث سلوك السكريبت العميل في سكريبت ReplicatedStorage.Bundles.Client.UIController.
مرجع API
الأنواع
RelativeTime
بمجرد تقديم حزمة RelativeTime للاعب، تظل متاحة حتى تنفد مدة الوقت. يعرض هذا النوع في عرض المعلومات الخاص باللاعب، ويحفز تلقائيًا في الجلسات المستقبلية حتى تنتهي صلاحية الحزمة أو يشتريها اللاعب.
مثال شائع على هذا النوع من الحزم هو عرض حزمة بدء الاستخدام لمرة واحدة تظهر لجميع اللاعبين الجدد لمدة 24 ساعة.
| الاسم | النوع | الوصف |
|---|---|---|
| includeOfflineTime | bool | (اختياري) إذا لم يتم تعيينه، سيتم احتساب الوقت المنقضي في اللعبة فقط نحو مدة العرض المتبقية. |
| singleUse | bool | (اختياري) إذا لم يتم تعيينه، يمكن إعادة تنشيط الشراء بعد شرائه أو انتهاء صلاحيته. إذا تم تعيينه، بمجرد شرائه أو انتهاء صلاحيته للمرة الأولى، لن يتم تحفيزه مرة أخرى، حتى إذا استدعيت Bundles.promptIfValidAsync مع bundleId. |
FixedTime
بمجرد تقديم حزمة FixedTime للاعب، تظل متاحة حتى نهاية الوقت المنسق العالمي (UTC) المحدد. يعرض هذا النوع في عرض المعلومات الخاص باللاعب، ويحفز تلقائيًا في الجلسات المستقبلية حتى تنتهي صلاحية الحزمة أو يشتريها اللاعب.
مثال شائع على هذا النوع من الحزم هو عرض عطلة متاح فقط لشهر معين.
OneTime
حزمة OneTime متاحة فقط في اللحظة التي يتم تقديمها للاعب. لا تظهر في عرض المعلومات الخاص باللاعب، ومتى أغلق اللاعب التحفيز، لا يمكن إعادة فتحه حتى يتم تحفيزه بواسطة الخادم مرة أخرى.
مثال شائع على هذا النوع من الحزم هو عرض شراء المزيد من العملة داخل اللعبة في اللحظة التي ينفد فيها اللاعب.