إصلاح خيار Clear Player Data المفقود في Fortnite Creative: عمليات إعادة التعيين في Verse واستعادة حالة Backend المخصصة
باختصار
يتناول المقال كيفية التغلب على مشكلة اختفاء خيار "Clear Player Data" في Fortnite Creative من خلال تطبيق نظام لإصدارات الـ schema يدوياً في Verse. كما يوضح كيفية تهيئة إعدادات UEFN المحلية لتجاوز التخزين المؤقت وحل مشكلات تعطل اللعبة الناتجة عن عدم توافق البيانات. وأخيراً، يقدم الـ backend الخارجي مثل [horizOn](https://horizon.pm) كبديل متكامل يوفر للمطورين تحكماً كاملاً وحرية من قيود التخزين المحلية للمنصة.
خريطتك الأحدث في Fortnite Creative أو UEFN جاهزة الآن. لقد قمت بإعادة هيكلة (refactored) نظام الاقتصاد لديك، وأعدت تصميم نظام الفئات (class system)، وحسّنت حلقة اللعب (gameplay loop). ولكن عندما تذهب للنشر، تواجه كابوساً يعطل المحرك بالكامل: لقد اختفى خيار "Press Triangle to Clear Player Data" من شاشة تأكيد النشر. أنت الآن محاصر؛ حيث يدخل اللاعبون إلى خريطتك باستخدام persistence data قديمة، مما يؤدي إلى حدوث stack overflows فورية، وانهيار مسارات المهام (quest lines)، وحدوث state mismatches تتسبب في تعطل اللعبة.
لقد أرقت هذه المشكلة تحديداً منشئي المحتوى عبر منتديات مطوري Epic، حيث يؤدي الغياب المفاجئ لزر إعادة التعيين إلى عدم وجود أي آلية تعتمد على واجهة المستخدم (UI) لتطهير حالات الإنتاج القديمة. عندما يتغير schema الخاص ببيانات player persistence، يحاول الـ backend الخاص بـ Epic مطابقة البيانات التسلسلية (serialized data) القديمة مع هياكلك الجديدة. وإذا لم يكن الكود الخاص بك مصمماً للتعامل مع هذه الخصائص غير المتطابقة بسلاسة، فستتسبب في إتلاف حالة اللعبة (brick the game state) لـ 100% من اللاعبين العائدين.
في هذا البرنامج التعليمي، سنغوص عميقاً في الحلول البديلة التقنية لتجاوز هذا الخطأ (bug). وسنوضح لك كيفية بناء نظام قوي لإصدارات الـ schema (schema versioning) في Verse، وتهيئة إعدادات UEFN المحلية لمسح حالات الاختبار، واستكشاف كيف يمكن للـ custom external backends أن تحررك من قيود الـ persistence للمنصات المغلقة.
The Root Cause: Why Schemas Fail on Version Upgrades
في Fortnite Creative و Unreal Editor for Fortnite (UEFN)، يتم تخزين البيانات المستمرة (persistent data) في البنية التحتية لقاعدة بيانات Epic باستخدام هياكل weak_map. تربط الـ weak_map نسخة اللاعب (player instance) بفئة مخصصة (custom class) مميزة بمعرّف <persistable>. وعلى عكس قواعد البيانات التقليدية SQL أو قواعد بيانات المستندات (document databases)، لا تقوم طبقة التخزين في Epic بتشغيل pipeline ترحيل رسمي (migration pipeline) عند إصدار تحديث. بدلاً من ذلك، تحاول إلغاء تسلسل (deserialize) أي حمولة ثنائية (binary payload) موجودة في الحفظ السحابي للاعب وتحويلها إلى تعريف فئة Verse الحالي.
عندما تقوم بحذف متغير، أو إعادة تسمية خاصية، أو تغيير نوع (على سبيل المثال، تغيير حقل من int إلى float)، يواجه الـ deserializer عدم تطابق. واعتماداً على مدى خطورة التغيير، يؤدي تباين الـ schema هذا إلى أحد أنماط الفشل الثلاثة التالية:
- Silent Failures: يعين الـ runtime قيمًا افتراضية للخصائص المعدلة، مما يؤدي إلى مسح التقدم المحرز على أي حال ولكن دون حدوث تعطل (crashing).
- State Corruption: يقرأ التطبيق حالات ذاكرة غير صالحة أو يصادف هياكل بيانات غير متوقعة، مما يؤدي إلى حدوث أخطاء منطقية (logic errors) في مكان آخر.
- Severe Runtime Crashes: تفشل الخريطة في التحميل، أو تتوقف Verse VM عن التنفيذ لأنها لا تستطيع فك حزمة الـ persistent map.
عندما يختفي زر "Clear Player Data" الخاص بلوحة تحكم الناشر، تفقد القدرة على مسح البيانات والبدء من جديد بشكل شامل. وإذا أجرى المطور تغييرات جوهرية في تخطيط حفظ البيانات (save data layout)، فستتعطل اللعبة تماماً.
The Manual Fix: Schema Versioning inside Verse
نظراً لأنه لا يمكنك الاعتماد على بوابة نشر Epic لمسح البيانات، يجب عليك تنفيذ حل بديل (fallback) على مستوى البرمجيات. الاستراتيجية بسيطة ومباشرة: أدخل متتبع إصدار (version tracker) مباشرة في الفئات القابلة للاستمرار (persistable classes) الخاصة بك. من خلال مقارنة إصدار اللاعب المحفوظ بالإصدار النشط للتطبيق عند بدء الجلسة، يمكنك تحديد الحفظ القديم والكتابة فوقه بالقيم الافتراضية.
إليك تطبيق Verse جاهز للإنتاج يقوم بإعداد طبقة persistence خاضعة للتحكم في الإصدارات.
using { /Fortnite.com/Devices }
using { /Fortnite.com/Characters }
using { /Fortnite.com/Playspaces }
using { /Verse.org/Simulation }
# A simple persistable class representing our player save schema.
# Note: Classes tagged with <persistable> can only contain certain types like int, float, string, and maps.
player_save_data := class<persistable>:
# The SchemaVersion tracks which generation of player data this struct belongs to.
SchemaVersion : int = 0
GoldCount : int = 0
XP : int = 0
HasCompletedTutorial : int = 0 # Using int as a boolean flag representation for older Verse constraints
# The persistence manager device handles loading, validating, and migrating player data.
persistence_manager_device := class(creative_device):
# By updating this value in the Editor, we force a schema migration next time the game runs.
@editable
TargetSchemaVersion : int = 2
# Map to store player data in memory
var PlayerDataMap : weak_map(player, player_save_data) = map{}
# Initialize the persistence logic for a joining player
InitializePlayer(Player : player) : void =
# Check if the player already has persistent data
if (ExistingData := PlayerDataMap[Player]):
# If the stored version is older than our target version, trigger a manual reset
if (ExistingData.SchemaVersion < TargetSchemaVersion):
Print("Data version mismatch. Local: {ExistingData.SchemaVersion}, Target: {TargetSchemaVersion}. Resetting player data.")
ResetPlayerData(Player)
else:
Print("Loaded existing player data. Version: {ExistingData.SchemaVersion}")
else:
# If no data exists, initialize a new record with the current version
Print("No player data found. Initializing new record.")
ResetPlayerData(Player)
# Re-initializes a player's record with default values at the current version
ResetPlayerData(Player : player) : void =
NewData := player_save_data:
SchemaVersion := TargetSchemaVersion
GoldCount := 0
XP := 0
HasCompletedTutorial := 0
# Save the freshly initialized data block to the persistence map
if (set PlayerDataMap[Player] = NewData):
Print("Successfully saved fresh persistent data block.")
Deconstructing the Versioning Script
يعتمد هذا النمط على التحقق المبكر (early validation) داخل طريقة InitializePlayer. وتتسلسل المنطق خطوة بخطوة على النحو التالي:
- The Entry Check: عند انضمام لاعب، يستعلم النظام في الـ
PlayerDataMapعن نسخة موجودة منplayer_save_data. - The Version Assessment: في حالة وجود save state، يتحقق المحرك من خاصية
SchemaVersionالخاصة به. - The Migration Trigger: إذا كانت الـ
SchemaVersionأقل من الخاصية@editableالمسماةTargetSchemaVersionللجهاز، يستدعي السكربتResetPlayerData(Player). - State Overwrite: تقوم
ResetPlayerDataبإنشاء نسخة جديدة منplayer_save_dataمضبوطة على الـTargetSchemaVersionالجديد وتقوم بتحديث الخريطة.
من خلال زيادة الـ TargetSchemaVersion يدوياً في تكوين جهازك داخل UEFN، فإنك تفرض عملية مسح مستهدفة للبيانات لجميع اللاعبين عند بدء الجلسة التالية، مما يجعل زر واجهة مستخدم Epic المفقود غير ذي صلة.
Step-by-Step Testing Procedure
للتحقق بأمان من صحة سكربت الترحيل (migration script) هذا ضمن دورة اختبار اللعب الخاصة بك، اتبع هذه الخطوات المتتالية:
- اسحب جهازك المخصص
persistence_manager_deviceوضعه في منفذ عرض المستوى (viewport) في UEFN. - في لوحة تفاصيل UEFN (Details panel)، اضبط الـ
TargetSchemaVersionالمبدئي على 1. - ابدأ جلسة لعب، واجمع بعض النقاط أو الذهب، ثم أنهِ اللعبة.
- بالعودة إلى المحرر، قم بتحديث حقل
TargetSchemaVersionعلى الجهاز من 1 إلى 2. - ابدأ الجلسة مرة أخرى. راقب مخرجات سجل وحدة التحكم (console log): 'Data version mismatch. Local: 1, Target: 2. Resetting player data.'
- تحقق من إعادة تعيين الذهب والتقدم الخاص باللاعب بشكل نظيف دون التسبب في تعطل Verse VM.
Local Development Settings: Bypassing Save Files during Iteration
بينما يحل إصدار كود الـ runtime المشاكل على الخوادم الحية (live servers)، فإنه يضيف احتكاكاً غير مرغوب فيه أثناء اختبار اللعب المحلي. يعد تعديل رقم الإصدار يدوياً داخل UEFN في كل مرة تقوم فيها بتعديل ميزة قيد التطوير أمراً مملاً. لتسهيل عملية تصحيح الأخطاء (debugging)، يمكنك تجاوز الـ local persistence تماماً من خلال إعدادات المحرر لديك.
اتبع هذه الخطوات لتعطيل الـ local persistence:
- في UEFN، انتقل إلى Island Settings في لوحة Outliner.
- ابحث عن القسم الفرعي User Options - Game Rules.
- حدد موقع زر التبديل Enable Persistence وقم بتعطيله.
- إذا كنت تختبر حلقات Multiplayer محلياً، فافتح Editor Preferences، ثم توجه إلى قائمة In-Game Play، وحدد مربع الاختيار Clear Local Saved Data On Launch. يضمن تحديد هذا الخيار بدء كل محاكاة بذاكرة تخزين مؤقتة فارغة.
ومع ذلك، فإن عمليات التجاوز المحلية لا تحاكي سيناريوهات الإنتاج. في حين أن سرعات تكرار الاختبار يمكن أن تنخفض من 45 ثانية إلى أقل من 5 ثوانٍ مع تعطيل التخزين المؤقت للـ persistence، يجب عليك تشغيل دورة إعداد (staging cycle) كاملة واحدة على الأقل مع تمكين الـ persistence قبل النشر المباشر. وهنا يمكن أن تتفاقم عقبات الاتصال. تشتهر إعدادات اختبار اللعب المحلية بتعارضات التعريفات (driver conflicts)، مما يتسبب في إضاعة المطورين لوقت ثمين في تشخيص برامج تشغيل شبكة Unreal Engine أثناء فترات انتهاء مهلة بدء جلسة UEFN.
UEFN's Built-in Persistence Limitations
يأتي استخدام طبقة الـ persistence الأصلية لـ Verse مع قيود معمارية كبيرة. وحتى عندما تعمل خيارات مسح السحابة بشكل مثالي، فإن المطورين مقيدون بحدود صارمة للمنصة:
- Storage Budgets: يتم تحديد سقف كتل البيانات القابلة للاستمرار (Persistable data blocks) بـ 12 كيلوبايت لكل لاعب في الجلسة الواحدة. وتجاوز هذا الحد يمنع حفظ الحالة.
- Primitive Types Only: لا يمكنك عمل تسلسل (serialize) للفئات المخصصة (custom classes) التي لم يتم تمييزها بـ
<persistable>، ولا يمكنك تخزين مراجع لكائنات اللعبة (مثلcreative_deviceأو الممثلين الديناميكيين/dynamic actors). - No External Querying: يعتبر نظام Epic بمثابة صندوق أسود بالكامل. لا يمكنك الاستعلام عن بيانات اللاعب من لوحة تحكم خارجية، أو تدقيق حالات المستخدم بحثاً عن الثغرات، أو ترحيل قواعد البيانات ديناميكياً.
- Telemetry Bottlenecks: يخضع تصميم لوحات معلومات التحليلات المعقدة داخل UEFN لقيود شديدة بسبب حدود طول السلسلة النصية (string length limits). على سبيل المثال، يصارع المطورون باستمرار القيود المفروضة عند تجاوز حد الـ 32 حرفاً لاسم حدث جهاز تحليلات UEFN.
بالنسبة للألعاب المصغرة البسيطة، يمكن التعامل مع هذه الحدود. ولكن بالنسبة لألعاب RPG المستمرة المعقدة، أو ألعاب التصويب ذات العمليات الحية (live-ops shooters)، أو العناوين متعددة المنصات، فإن الاعتماد فقط على الـ persistence المملوكة والمغلقة يعيق مسار التوسع الخاص بك.
The Architect's Alternative: External Database States with horizOn
لبناء لعبة قابلة للتوسع ولا تقتصر على أخطاء واجهة المستخدم المحددة بالمنصة أو أحجام البيانات المملوكة لجهة معينة، يتجه المطورون إلى بنية الـ backend الخارجية. بدلاً من تخزين حالات اللعبة في خرائط الذاكرة المحلية لـ Verse، يمكن لمشروع UEFN الخاص بك الاتصال بقاعدة بيانات خارجية باستخدام وحدة http_client في Verse.
يمنحك هذا النهج تحكماً كاملاً في دورة حفظ اللاعب. عندما تحتاج إلى مسح بيانات player persistence، فلن تضطر إلى البحث عن زر مفقود على وحدة تحكم Epic أو دفع تحديث للكود. يمكنك ببساطة تشغيل سكربت إداري لقاعدة البيانات أو النقر فوق زر واحد في لوحة التحكم الخاصة بالـ backend لمسح جداول اللاعبين أو ترحيلها أو ترقيعها (patch).
يتطلب بناء هذا بنفسك إعداد load balancers، وعمل database sharding، وإدارة شهادات SSL — وهو عمل يستغرق بسهولة من 4 إلى 6 أسابيع. ولكن مع horizOn، تأتي خدمات الـ backend هذه مهيأة مسبقاً، مما يتيح لك إطلاق لعبتك بدلاً من الانشغال ببنيتك التحتية.
من خلال توجيه حالات اللاعب عبر horizOn، يصبح كود Verse الخاص بك مجرد عميل بسيط يقرأ ويكتب الحالة عبر نقاط نهاية REST نظيفة:
# Pseudocode showing HTTP-based state synchronization with [horizOn](https://horizon.pm)
sync_manager_device := class(creative_device):
# Send player progress to [horizOn](https://horizon.pm) endpoint
SavePlayerState(Player : player, SaveData : player_save_data) : void =
# In a real environment, you construct a JSON payload and dispatch it
# via the Verse http_client. This bypasses the 12KB local persistence limit.
RequestURL := "https://api.horizon.pm/v1/players/{GetPlayerID(Player)}/state"
Print("Dispatching persistent payload to [horizOn](https://horizon.pm) at: {RequestURL}")
# This keeps the server-side payload lightweight (under 240 bytes)
# while securing long-term storage off the Fortnite engine.
يفصل هذا الإعداد حلقة اللعب الخاصة بك تماماً عن أخطاء الـ backend المادية للمنصة. إذا تسببت رقعة ما (patch) في تعطل توافق الحفظ المحلي، فيمكنك بدء ترحيل schema مستهدف على قاعدة بيانات horizOn الخاصة بك في الوقت الفعلي، دون أي انقطاع لخادم اللعبة المباشر. وبذلك تتجنب خطر تعطل الخوادم الحية بسبب تعديلات هيكلية بسيطة.
Best Practices for Managing Game State Persistence
سواء كنت تلتزم بخرائط Verse الأصلية أو تستفيد من بنية خادم خارجية، فإن الحفاظ على دورة حياة persistence مستقرة أمر حيوي للألعاب متعددة اللاعبين (multiplayer games). التزم بهذه المبادئ لحماية حالات اللاعبين:
- Implement Implicit Versioning Early: قم دائماً بتضمين عدد صحيح لـ
SchemaVersionفي تخطيط بيانات الحفظ الأولية. وحتى لو لم تكن تخطط لتعديل الـ schema الخاص بك، فإن وجود مفتاح الإصدار من اليوم الأول يمنع حدوث تلف كارثي للبيانات في المستقبل. - Enforce State Validation Rules: عند قراءة الحالة المحملة، تحقق من صحة نطاقات كل سمة (attribute). إذا كان عدد الذهب المحفوظ للاعب سالباً أو يتجاوز الحد الهيكلي لخريطتك، فأعد تعيين هذه الخاصية المحددة لمنع استغلال الاقتصاد (economy exploits).
- Minimize Save Frequency: الكتابة إلى السحابة مكلفة. بدلاً من تحديث قاعدة البيانات عند كل عملية التقاط عملة معدنية، قم بتجميع عمليات الكتابة (batch your writes). قم بتشغيل عمليات الحفظ خلال المعالم الرئيسية، مثل اكتمال المباراة، أو ترقية مستوى اللاعب، أو عند مغادرة اللاعب للجلسة.
- Construct Clean Fallback Structs: لا تسمح أبداً للقراءة الفاشلة بمنع اللاعب من دخول اللعبة. إذا فشلت حمولة الـ persistence في إلغاء التسلسل (deserialize)، فارجع إلى بنية الحالة الافتراضية على الفور.
- Decouple Gameplay Code from Storage Logic: حافظ على عزل استعلامات التخزين الخاصة بك في جهاز مخصص لإدارة الـ persistence. يجب أن تستعلم أجهزة الأسلحة والتقدم وواجهة المستخدم (UI) من هذا المدير بدلاً من إجراء مكالمات مباشرة لخريطة البيانات.
Next Steps
يعد الاعتماد على واجهة مستخدم وحدة تحكم Epic لإدارة عمليات ترحيل الحالات الحرجة نمط تصميم محفوفاً بالمخاطر. عندما يفشل خيار "Clear Player Data"، فإن إصدار الـ runtime داخل Verse يعمل كخط دفاعك الأول. ومع ذلك، إذا كانت لعبتك تتطلب عمليات حفظ عالية التكرار، أو telemetry معقدة، أو تحكماً إدارياً كاملاً، فإن ترحيل قاعدة البيانات الخاصة بك إلى backend مخصص هو الحل الأمثل.
هل أنت مستعد لتوسيع نطاق الـ multiplayer backend الخاص بك؟ جرب horizOn مجاناً أو راجع وثائق API.