جارٍ إضافة الألعاب المحفوظة إلى لعبتك

يعرض لك هذا الدليل كيفية حفظ بيانات تقدم اللاعب في اللعبة وتحميلها باستخدام خدمة الألعاب المحفوظة في تطبيق C++. يمكنك استخدام هذه الخدمة لتحميل مستوى تقدم لعبة اللاعب وحفظه تلقائيًا في أي وقت أثناء اللعب. ويمكن لهذه الخدمة أيضًا تمكين اللاعبين من تشغيل واجهة المستخدم لتحديث لعبة حفظ حالية أو استعادتها، أو إنشاء لعبة جديدة.

قبل البدء

إذا لم تكن قد فعلت ذلك من قبل، قد يكون من المفيد مراجعة مفاهيم الألعاب المحفوظة.

قبل البدء في الترميز باستخدام واجهة برمجة تطبيقات الألعاب المحفوظة:

تنسيقات البيانات والتوافق عبر الأنظمة الأساسية

يجب أن تكون بيانات الألعاب المحفوظة التي تحفظها على خوادم Google بتنسيق std::vector<uint8_t>. تهتم خدمة "الألعاب المحفوظة" بترميز بياناتك للتوافق عبر الأنظمة الأساسية، ويمكن لتطبيقات Android قراءة البيانات نفسها كمصفوفة بايت بدون أي مشكلات توافق عبر الأنظمة الأساسية.

تجنب استخدام تنسيقات متعلقة بالأنظمة الأساسية عند اختيار تنسيق بيانات لبيانات الألعاب المحفوظة. ونوصيك بشدة باستخدام تنسيق بيانات، مثل ملفات XML أو JSON، يحظى بدعم قوي للمكتبات على أنظمة أساسية متعددة.

تمكين خدمة الألعاب المحفوظة

قبل أن تتمكن من استخدام خدمة الألعاب المحفوظة، يجب أولاً تمكين الدخول إليها. لإجراء ذلك، يمكنك الاتصال بـ EnableSnapshots() عند إنشاء الخدمة باستخدام gpg::GameServices::Builder. سيؤدي هذا إلى تمكين نطاقات المصادقة الإضافية التي تتطلبها الألعاب المحفوظة في حدث المصادقة التالي.

عرض الألعاب المحفوظة

في لعبتك، يمكنك توفير خيار يستطيع اللاعبون تشغيله لحفظ المباريات المحفوظة أو استعادتها. عندما يحدد اللاعبون هذا الخيار، يجب أن تعرض لعبتك شاشة تعرض خانات الحفظ الحالية وأن تسمح للاعبين إما بالحفظ في إحدى هذه الشرائح أو التحميل منها، أو إنشاء لعبة محفوظة جديدة. استخدم الطريقة التالية لإجراء ذلك:

  SnapshotManager::ShowSelectUIOperation(...)

تسمح واجهة مستخدم اختيار الألعاب المحفوظة للاعبين بإنشاء لعبة محفوظة جديدة وعرض تفاصيل حول الألعاب المحفوظة الحالية وتحميل ألعاب محفوظة مسبقًا.

  SnapshotManager::SnapshotSelectUIResponse response;
  if (IsSuccess(response.status)) {
  if (response.data.Valid()) {
    LogI("Description: %s", response.data.Description().c_str());
    LogI("FileName %s", response.data.FileName().c_str());
    //Opening the snapshot data
    …
  } else {
    LogI("Creating new snapshot");
    …
  }
} else {
  LogI("ShowSelectUIOperation returns an error %d", response.status);
}

يوضح المثال التالي كيفية عرض واجهة مستخدم الألعاب المحفوظة الافتراضية والتعامل مع تحديد واجهة مستخدم المشغل:

  service_->Snapshots().ShowSelectUIOperation(
  ALLOW_CREATE_SNAPSHOT,
  ALLOW_DELETE_SNAPSHOT,
  MAX_SNAPSHOTS,
  SNAPSHOT_UI_TITLE,
  [this](gpg::SnapshotManager::SnapshotSelectUIResponse const & response) {
  …
      }

في المثال أعلاه، إذا كانت ALLOW_CREATE_SNAPSHOT هي true وكانت MAX_SNAPSHOTS أكبر من العدد الفعلي للّقطات التي أنشأها المستخدم حاليًا، تزوِّد واجهة مستخدم Snapshot التلقائية اللاعبين بزر لإنشاء لعبة حفظ جديدة، بدلاً من اختيار لعبة حالية. (عند عرض الزر، يظهر في الجزء السفلي من واجهة المستخدم.) عندما ينقر أحد اللاعبين على هذا الزر، تكون استجابة SnapshotSelectUIResponse صالحة ولكن بدون بيانات.

فتح الألعاب المحفوظة وقراءتها

للوصول إلى لعبة محفوظة وقراءة محتوياتها أو تعديلها، افتح أولاً الكائن SnapshotMetadata الذي يمثّل اللعبة المحفوظة. بعد ذلك، يمكنك استدعاء طريقة SnapshotManager::Read*().

يوضح المثال التالي كيفية فتح لعبة محفوظة:

  LogI("Opening file");
  service_->Snapshots()
  .Open(current_snapshot_.FileName(),
               gpg::SnapshotConflictPolicy::BASE_WINS,
        [this](gpg::SnapshotManager::OpenResponse const & response) {
           LogI("Reading file");
           gpg::SnapshotManager::ReadResponse responseRead =
           service_->Snapshots().ReadBlocking(response.data);
          …
        }

اكتشاف تعارض البيانات وإصلاحه

عند فتح الكائن SnapshotMetadata، تكتشف خدمة "الألعاب المحفوظة" ما إذا كانت هناك لعبة محفوظة متعارضة. قد تحدث تعارضات في البيانات عندما تكون اللعبة المحفوظة المخزَّنة على الجهاز المحلي للاعب غير متزامنة مع الإصدار البعيد المُخزَّن في خوادم Google.

تخبر سياسة التعارض التي تحددها عند فتح لعبة محفوظة خدمة الألعاب المحفوظة بكيفية حل تعارض البيانات تلقائيًا. يمكن أن تكون السياسة واحدة من الحالات التالية:

سياسة التعارض الوصف
SnapshotConflictPolicy::MANUAL للإشارة إلى أنه يجب ألا تتخذ خدمة "الألعاب المحفوظة" أي إجراء لحل المشكلة. وبدلاً من ذلك، ستجري لعبتك دمجًا مخصّصًا.
SnapshotConflictPolicy::LONGEST_PLAYTIME يشير إلى أن خدمة "الألعاب المحفوظة" يجب أن تختار اللعبة المحفوظة التي تتمتع بأعلى قيمة لوقت التشغيل.
SnapshotConflictPolicy::BASE_WINS يشير إلى أن خدمة الألعاب المحفوظة يجب أن تختار اللعبة الأساسية المحفوظة.
SnapshotConflictPolicy::REMOTE_WINS يشير إلى أن خدمة الألعاب المحفوظة يجب أن تختار اللعبة المحفوظة عن بُعد. النسخة البعيدة هي نسخة من اللعبة المحفوظة والتي تم اكتشافها على أحد أجهزة المشغّل وتتضمّن طابعًا زمنيًا أحدث من الإصدار الأساسي.

إذا حددت سياسة تضارب بخلاف GPGSnapshotConflictPolicyManual، فستعمل خدمة الألعاب المحفوظة على دمج اللعبة المحفوظة وعرض الإصدار المحدّث من خلال القيمة SnapshotManager::OpenResponse الناتجة. يمكن للعبتك فتح اللعبة المحفوظة، والكتابة إليها، ثم استدعاء طريقة SnapshotManager::Commit(...) لتنفيذ اللعبة المحفوظة على خوادم Google.

إجراء دمج مخصص

إذا حددت SnapshotConflictPolicy::MANUAL على أنها سياسة التعارض، يجب أن تحل اللعبة أي تعارض في البيانات يتم اكتشافه قبل إجراء المزيد من عمليات القراءة أو الكتابة في اللعبة المحفوظة.

في هذه الحالة، عندما يتم اكتشاف تعارض بيانات، تعرض الخدمة المعلمات التالية من خلال SnapshotManager::OpenResponse:

  • conflict_id لتحديد هذا التعارض بشكل فريد (ستستخدم هذه القيمة عند تنفيذ الإصدار النهائي من اللعبة المحفوظة)،
  • النسخة الأساسية المتعارضة من اللعبة المحفوظة
  • النسخة البعيدة المتضاربة من اللعبة المحفوظة.

يجب أن تحدد اللعبة البيانات التي يجب حفظها، ثم استدعاء طريقة SnapshotManager::ResolveConflictBlocking() لتنفيذ/حل الإصدار النهائي بخوادم Google.

    //Resolve conflict
    gpg::SnapshotManager::OpenResponse resolveResponse =
        manager.ResolveConflictBlocking(openResponse.conflict_base, metadata_change,
                                  openResponse.conflict_id);

كتابة الألعاب المحفوظة

لكتابة لعبة محفوظة، افتح أولاً الكائن SnapshotMetadata الذي يمثّل هذه اللعبة المحفوظة، وحلّ أي تعارض في البيانات التي تم رصدها، ثم استدعاء طريقة SnapshotManager::Commit() لتنفيذ تغييرات اللعبة المحفوظة.

يوضح المثال التالي كيفية إنشاء تغيير وتنفيذ لعبة محفوظة.

  1. أولاً، افتح اللقطة التي نريد تعديلها، وتأكد من حل جميع التعارضات عن طريق اختيار القاعدة.

    service_->Snapshots().Open(
          file_name,
          gpg::SnapshotConflictPolicy::BASE_WINS,
          [this](gpg::SnapshotManager::OpenResponse const &response) {
            if (IsSuccess(response.status)) {
              // metadata : gpg::SnapshotMetadata
              metadata = response.data;
            } else {
              // Handle snapshot open error here
            }
          });
    
  2. بعد ذلك، أنشئ تغييرًا محفوظًا للعبة يتضمن بيانات الصورة المستخدمة لصورة الغلاف:

    gpg::SnapshotMetadataChange::Builder builder;
    gpg::SnapshotMetadataChange metadata_change =
        builder.SetDescription("CollectAllTheStar savedata")
                 .SetCoverImageFromPngData(pngData).Create();
    
  3. وأخيرًا، التزم بإجراءات اللعبة المحفوظة.

    gpg::SnapshotManager::CommitResponse commitResponse =
        service_->Snapshots().CommitBlocking(metadata, metadata_change, SetupSnapshotData());
    

    تحتوي معلمة البيانات على جميع بيانات اللعبة التي تخزنها. يتضمن التغيير أيضًا بيانات وصفية إضافية محفوظة للعبة، مثل وقت اللعب ووصف للعبة التي تم حفظها.

إذا اكتملت عملية التنفيذ بنجاح، يمكن للاعبين مشاهدة اللعبة المحفوظة في واجهة مستخدم اختيار الألعاب المحفوظة.