Skip to content

SDK

يوفّر هذا الـ SDK قناة اتصال آمنة ثنائية الاتجاه بين اللعبة والعميل الأصلي (iOS وAndroid)، لتمكين مطوري الطرف الثالث من استدعاء القدرات الأصلية.

اسم واجهة الـ API العامة: window.GameTokSDK

القدرات المتوفرة:

الإجراءالوصف
GET_PROFILEجلب ملف المستخدم الحالي
GET_PROFILESجلب ملفات مستخدمين متعددين دفعةً واحدة (بمصفوفة uid)
PURCHASEبدء عملية شراء (مثل الشراء داخل التطبيق)
STORAGE_SETكتابة تخزين مفاتيح/قيم محلي (إعدادات، تقدّم، إلخ)
STORAGE_GETقراءة تخزين مفاتيح/قيم محلي (البيانات المحفوظة سابقًا)
ADD_SCOREإرسال نتيجة (لوائح المتصدرين، مشاركة، إلخ)
GET_PERMISSION_MICطلب إذن الميكروفون (أندرويد)
TOPUPشحن الرصيد عند نقص العملات داخل اللعبة؛ يفتح تدفق الشحن في العميل
ROUND_STARTإشعار بداية الجولة (مرة عند بدء كل جولة؛ مرتبط بسياسات المنصة للجولات/الطاقة)
ROUND_ENDإشعار نهاية الجولة (عند انتهاء الجولة؛ يجب أن يطابق round_id إشعار ROUND_START لنفس الجولة)
GET_COUPON_TARGET_SCOREجلب النتيجة المستهدفة لعرض نافذة القسيمة (يُعيد العميل قيمة النتيجة المحددة)
SHOW_COUPON_DIALOGإخطار المنصة لعرض نافذة القسيمة (يُعرضها العميل في طبقة حاوية اللعبة)
onAudioSuspendالاستماع لحدث تعليق الصوت (يُطلق عندما تطلب المنصة من اللعبة الكتم)
offAudioSuspendإزالة مستمع حدث تعليق الصوت
onAudioResumeالاستماع لحدث استئناف الصوت (يُطلق عندما تسمح المنصة للعبة باستعادة الصوت)
offAudioResumeإزالة مستمع حدث استئناف الصوت

أمثلة الدمج

يوضّح ما يلي كيفية تضمين الـ SDK في الصفحة واستدعاء كل قدرة.

1.1 تضمين السكربت

الطريقة الأولى: تحميل الحزمة عبر <script>

html
<script src="https://play.letskix.com/res/game/sdk-js/GameTokSDK.js"></script>
<script>
  console.log('GameTokSDK version loaded:', !!window.GameTokSDK);
</script>

التطوير المحلي (الصفحة غير مضمّنة في التطبيق، التصحيح في المتصفح): بعد تحميل الـ SDK استدعِ GameTokSDK.enableMock()؛ واجهات الـ Promise ستستخدم بيانات محاكاة مدمجة دون الحاجة للعميل الأصلي. لا تستدعِها في الإنتاج أو نسخ المتجر، وإلا سيرى المستخدمون بيانات وهمية.

html
<script src="https://play.letskix.com/res/game/sdk-js/GameTokSDK.js"></script>
<script>
  GameTokSDK.enableMock();
</script>

الاختبار على جهاز حقيقي (WebView داخل التطبيق): عند الحاجة للسجلات، بعد تحميل الـ SDK استدعِ GameTokSDK.enableDebug() (تُعيد Promise؛ تُحمّل vConsole في الصفحة وتُسجّل تبادلات الجسر مع الأصل). لا تستخدمها مع enableMock في آن واحد.

html
<script src="https://play.letskix.com/res/game/sdk-js/GameTokSDK.js"></script>
<script>
  GameTokSDK.enableDebug();
</script>

1.2 أمثلة الاستدعاء

تستخدم الأمثلة أسلوب then / catch مع الـ Promise. عند النجاح يُعاد كائن استجابة موحّد؛ عند الفشل يُرفض الـ Promise.

1.2.1 جلب الملف الشخصي

javascript
// مثال استدعاء
GameTokSDK.getProfile()
  .then(({ action, error, data }) => {
    console.log('تم جلب الملف الشخصي:', data);
  })
  .catch((e) => {
    console.error('فشل جلب الملف الشخصي:', e);
  });

مثال الاستجابة (JSON):

json
{
  "action": "GET_PROFILE",
  "error": false,
  "data": {
    "uid": 1024780,
    "avatar": "https://game-load-sa.lobah.net/avatar/1.jpg",
    "userName": "Guest",
    "userCoins": 0,
    "level": 0,
    "gameLevel": 0,
    "rankImg": "",
    "gameIcon": "https://game-load-sa.lobah.net/game/icon/1.png",
    "gender": 0,
    "testAccount": false,
    "guest": true
  }
}

الحقول الرئيسية:

الحقلالنوعالوصف
uidnumberالمعرّف الفريد للمستخدم
avatarstringرابط الصورة الرمزية
userNamestringاسم المستخدم / الاسم المعروض
userCoinsnumberرصيد العملات الحالي للمستخدم
levelnumberمستوى نمو المستخدم (نظام الحساب)
gameLevelnumberالرتبة (مبنية على مباريات اللعبة / التصنيف)؛ 0 تعني لا توجد رتبة (مثل لاعب جديد، لم يشارك في التصنيف)
rankImgstringرابط صورة الرتبة؛ سلسلة فارغة عندما لا تكون للمستخدم رتبة
gameIconstringرابط أيقونة اللعبة
gendernumberتعداد الجنس: 0 = غير محدد، 1 = ذكر، 2 = أنثى
testAccountbooleanما إذا كان حساب اختبار / حساب مراجعة المنصة
guestbooleanما إذا كان ضيفًا (لم يسجّل دخوله بحساب حقيقي)

1.2.2 جلب ملفات مستخدمين متعددة دفعةً واحدة

يُستخدم في لوائح المتصدرين، قوائم الأصدقاء، تسوية المباريات، أو أي سيناريو يحتاج لجلب معلومات أساسية لـعدة مستخدمين في آنٍ واحد، تجنبًا لاستدعاء getProfile بشكل متكرر.

javascript
/**
 * جلب ملفات مستخدمين متعددة
 * @param uids  مطلوب؛ مصفوفة uid للاستعلام (يُنصح بـ 1–50 في كل استدعاء)
 */
GameTokSDK.getProfiles({ uids: [1234567, 1234568, 1234569] })
  .then(({ action, error, data }) => {
    console.log('نجح الجلب المتعدد:', data);
    // data مصفوفة Profile؛ يمكن التكرار عليها أو تحويلها لـ Map حسب uid
    const map = new Map(data.map(p => [p.uid, p]));
    console.log('1234567 ->', map.get(1234567));
  })
  .catch((e) => {
    console.error('فشل الجلب المتعدد:', e);
  });

المعاملات:

الحقلالنوعمطلوبالوصف
uidsnumber[]نعمقائمة uid للاستعلام؛ الطول المُوصى به 1–50. إذا كانت مصفوفة فارغة، أو غير مصفوفة، أو تحتوي على عناصر غير صالحة (مثل "abc"، null، أعداد سالبة، كسور)، يُرفض الـ SDK فورًا بـ reject(SDKError)، رمز الخطأ INVALID_PARAMS = 1003، ولن يُرسل للأصل

مثال الاستجابة (JSON):

json
{
  "action": "GET_PROFILES",
  "error": false,
  "data": [
    {
      "uid": 1024780,
      "avatar": "https://game-load-sa.lobah.net/avatar/1.jpg",
      "userName": "Guest",
      "userCoins": 0,
      "level": 0,
      "gameLevel": 0,
      "rankImg": "",
      "gameIcon": "https://game-load-sa.lobah.net/game/icon/1.png",
      "gender": 0,
      "testAccount": false,
      "guest": true
    },
    {
      "uid": 1024781,
      "avatar": "https://game-load-sa.lobah.net/avatar/2.jpg",
      "userName": "Alice",
      "userCoins": 200,
      "level": 3,
      "gameLevel": 5,
      "rankImg": "https://game-load-sa.lobah.net/rank/5.png",
      "gameIcon": "https://game-load-sa.lobah.net/game/icon/2.png",
      "gender": 2,
      "testAccount": false,
      "guest": false
    }
  ]
}

1.2.3 الشراء داخل التطبيق (مثل شراء عنصر)

javascript
/**
 * شراء داخل التطبيق
 * @param productId معرّف المنتج؛ يجب تعريفه مسبقًا في لوحة المطور (https://developer.lobah.net/)
 */
GameTokSDK.purchase({ productId: 'HAB.WATER.10.COINS' })
  .then(({ data }) => {
    // يُحدَّد النجاح أو الفشل من الرمز المُعاد هنا
    console.log('اكتملت عملية الشراء:', data);
  })
  .catch((e) => {
    console.error('خطأ:', e);
  });

مثال الاستجابة (JSON):

json
{
  "action": "PURCHASE",
  "error": false,
  "data": {
    "purchaseResultCode": 0,
    "testAccount": false,
    "userBalance": 22514
  }
}

قيم purchaseResultCode:

  • 0: نجاح الشراء
  • 11: product_id غير صحيح
  • 12: رصيد العملات غير كافٍ
  • 13: reference_id مكرر أو غير صالح
  • 14: المستخدم ضيف؛ الضيوف لا يمكنهم الشراء
  • 16: product_id غير صحيح
  • 20: خطأ آخر

1.2.4 تخزين مفتاح/قيمة

javascript
/**
 * تخزين مفتاح/قيمة
 * @param key   مفتاح مخصص
 * @param value قيمة؛ قد تكون نصًا أو كائنًا
 */
GameTokSDK.storageSet({ key: 'settings', value: { theme: 'dark', volume: 0.8 } })
  .then(() => {
    console.log('تم التخزين بنجاح');
  })
  .catch((e) => {
    console.error('فشل التخزين:', e);
  });

مثال الاستجابة (JSON):

json
{
  "action": "STORAGE_SET",
  "error": false,
  "data": null
}

1.2.5 قراءة مفتاح/قيمة

javascript
/**
 * قراءة مفتاح/قيمة
 * @param key المفتاح المعرّف مسبقًا
 */
GameTokSDK.storageGet({ key: 'settings' })
  .then((result) => {
    console.log('قراءة ناجحة:', result.data.value); // كائن أو نص
  })
  .catch((e) => {
    console.error('فشلت القراءة:', e);
  });

مثال الاستجابة (JSON):

json
{
  "action": "STORAGE_GET",
  "error": false,
  "data": {
    "value": {
        "theme": "dark",
        "volume": 0.8
    }
  }
}

1.2.6 إرسال النتيجة

javascript
/**
 * حسب اللعبة قد تُرسل نتيجة، مرحلة، أو مستوى.
 * للنتيجة: { score: 300, scoreType: 'score', remark: 'score' }
 * للمستوى: { score: 1, scoreType: 'level', remark: 'level' }
 * @param score     القيمة المطلوب إرسالها؛ عدد صحيح موجب
 * @param scoreType فئة البيانات (وصف يحدده العمل)
 * @param remark    ملاحظة اختيارية (قد تطابق scoreType)
 */

GameTokSDK.addScore({ score: 10, scoreType: 'score', remark: 'score' })
  .then(() => {
    console.log('تم إرسال النتيجة');
  })
  .catch((e) => {
    console.error('فشل الإرسال:', e);
  });

مثال الاستجابة (JSON):

json
{
  "action": "ADD_SCORE",
  "error": false,
  "data": null
}

1.2.7 إذن الميكروفون

javascript
/**
 * طلب إذن الميكروفون (أندرويد)
 * للاستخدام داخل اللعبة لطلب الإذن (مثل الميزات الصوتية).
 * بلا قيمة إرجاع؛ يُظهر فقط واجهة الأذونات الأصلية.
 */
GameTokSDK.getPermissionMic();

1.2.8 الشحن (TOPUP)

مهم: إلغاء المستخدم، فشل الدفع، وما شابه ما زالت تُحلّ عبر then. داخل then افحص data.code لمعرفة النجاح. catch مخصّص لأعطال الـ SDK، انتهاء المهلة، وما شابه.

javascript
/**
 * شحن الرصيد
 * @param amount المبلغ (الوحدات حسب الاتفاق مع العميل)
 */
GameTokSDK.topup({ amount: 100 })
  .then(({ action, error, data }) => {
    const code = data && data.code;
    if (code === 200) {
      console.log('نجح الشحن:', data);
    } else {
      console.warn('الشحن غير مكتمل أو فشل، الرمز:', code, data);
    }
  })
  .catch((e) => {
    console.error('خطأ في طلب الشحن (مثل عدم توفر الـ SDK):', e);
  });

مثال الاستجابة (JSON، نجاح):

json
{
  "action": "TOPUP",
  "error": false,
  "data": {
    "code": 200,
    "message": "ok"
  }
}

قيم code:
200 نجاح
401 فشل الشحن (خطأ)
402 فشل الشحن (إلغاء من المستخدم)

1.2.9 بداية الجولة (ROUND_START)

يُستدعى مرة واحدة بعد بدء الجولة فعليًا. round_id إلزامي (معرّف فريد للجولة من جهة اللعبة). اختياريًا timestamp (بالميلي ثانية؛ إن حُذف يُستخدم الوقت الحالي). بلا إرجاع Promise — إرسال دون انتظار.

javascript
/**
 * إشعار بداية الجولة
 * @param round_id معرّف فريد لهذه الجولة
 * @param timestamp اختياري، وقت البدء (ملي ثانية)
 */
GameTokSDK.roundStart({
  round_id: 'round-' + Date.now(),
  timestamp: Date.now(),
});

إن لم يُمرَّر round_id، يُحذّر الـ SDK في وحدة التحكم ولا يُرسل إلى الأصل.

1.2.10 نهاية الجولة (ROUND_END)

يُستدعى عند انتهاء الجولة. يجب أن يطابق round_id roundStart لنفس الجولة. اختياريًا timestamp. بلا إرجاع Promise.

javascript
/**
 * إشعار نهاية الجولة
 * @param round_id نفس قيمة roundStart لهذه الجولة
 * @param timestamp اختياري، وقت الانتهاء (ملي ثانية)
 */
const roundId = 'round-' + Date.now();
GameTokSDK.roundStart({ round_id: roundId });
// ... منطق الجولة ...
GameTokSDK.roundEnd({ round_id: roundId, timestamp: Date.now() });

1.2.11 جلب النتيجة المستهدفة للقسيمة (GET_COUPON_TARGET_SCORE)

عند بدء اللعبة أو أثناء تشغيلها، يمكن للعبة الاستعلام من عميل المنصة عن النتيجة المستهدفة اللازمة لعرض نافذة القسيمة. بعد أن يُعيد العميل قيمة النتيجة، تستخدم اللعبة ذلك لتحديد ما إذا كانت ستستدعي showCouponDialog.

javascript
/**
 * جلب النتيجة المستهدفة للقسيمة
 * عادةً بلا معاملات إضافية
 */
GameTokSDK.getCouponTargetScore()
  .then(({ action, error, data }) => {
    console.log('النتيجة المستهدفة للقسيمة:', data.target_score);
    // عند وصول نتيجة اللعبة إلى data.target_score، استدعِ showCouponDialog
  })
  .catch((e) => {
    console.error('فشل جلب النتيجة المستهدفة للقسيمة:', e);
  });

مثال الاستجابة (JSON):

json
{
  "action": "GET_COUPON_TARGET_SCORE",
  "error": false,
  "data": {
    "target_score": 5000
  }
}

شرح الحقول:

الحقلالنوعالوصف
target_scorenumberعتبة النتيجة المستهدفة لعرض نافذة القسيمة

1.2.12 عرض نافذة القسيمة (SHOW_COUPON_DIALOG)

عندما تحتاج اللعبة إلى توجيه المستخدمين لاستلام أو استخدام قسيمة، استدعِ هذه الواجهة لإخطار عميل المنصة بعرض نافذة القسيمة. تُعرض النافذة من قِبل المنصة في طبقة حاوية اللعبة؛ ولا تحتاج اللعبة إلى التعامل مع واجهة النافذة. بلا إرجاع Promise — إرسال دون انتظار.

javascript
/**
 * عرض نافذة القسيمة
 * @param coupon_id اختياري، معرّف القسيمة؛ إن لم يُمرَّر، تقرر المنصة المحتوى المعروض
 * @param scene     اختياري، سيناريو التفعيل (يُعرَّف حسب العمل، مثل round_end أو level_up)
 */
GameTokSDK.showCouponDialog({
  coupon_id: 'coupon-001',
  scene: 'round_end',
});

// أو الاستدعاء بلا معاملات لعرض القسيمة الافتراضية للمنصة
GameTokSDK.showCouponDialog();

مثال الحمولة المُرسَلة (JSON):

json
{
  "action": "SHOW_COUPON_DIALOG",
  "data": {
    "coupon_id": "coupon-001",
    "scene": "round_end"
  }
}

1.2.13 مستمعو أحداث الصوت (onAudioSuspend / offAudioSuspend / onAudioResume / offAudioResume)

عندما تطلب المنصة من اللعبة الكتم (مثل دخول المستخدم غرفة بث مباشر، ورود مكالمة نظام، إلخ)، يُرسَل لها حدث تعليق الصوت؛ وعند السماح باستئناف الصوت، يُرسَل حدث الاستئناف. يجب على اللعبة إيقاف/استئناف جميع المؤثرات الصوتية والموسيقى الخلفية فور استقبال هذه الأحداث.

هذه واجهة مستمع أحداث، وليست واجهة Promise، ولا تُعيد أي قيمة.

javascript
// الاستماع لحدث تعليق الصوت
function handleAudioSuspend(payload) {
  console.log('تم استقبال إشعار الكتم:', payload);
  // كتم جميع المؤثرات الصوتية والموسيقى الخلفية في اللعبة
  myGame.muteAll();
}

GameTokSDK.onAudioSuspend(handleAudioSuspend);

// الاستماع لحدث استئناف الصوت
function handleAudioResume(payload) {
  console.log('تم استقبال إشعار استئناف الصوت:', payload);
  // استئناف جميع المؤثرات الصوتية والموسيقى الخلفية في اللعبة
  myGame.unmuteAll();
}

GameTokSDK.onAudioResume(handleAudioResume);

إزالة المستمعين:

javascript
// إزالة مستمع تعليق الصوت (مرّر نفس مرجع الدالة المستخدمة عند التسجيل)
GameTokSDK.offAudioSuspend(handleAudioSuspend);

// إزالة مستمع استئناف الصوت
GameTokSDK.offAudioResume(handleAudioResume);

معامل options.sync (مزامنة الحالة):

يدعم onAudioSuspend وonAudioResume معاملًا اختياريًا ثانيًا options، حيث يعالج sync (الافتراضي true) حالة تسجيل المستمع بعد انطلاق الحدث:

  • sync: true (الافتراضي) — إذا كان الصوت في حالة تعليق/استئناف عند تسجيل المستمع، سيُستدعى رد الاتصال فوريًا مرة واحدة في المهمة الدقيقة التالية، لضمان عدم إغفال اللعبة لتغييرات الحالة السابقة.
  • sync: false — يستمع فقط للأحداث الجديدة اللاحقة؛ بلا مزامنة للحالة عند التسجيل.
javascript
// الافتراضي sync: true — إذا كان الصوت معلقًا بالفعل، يُطلق رد الاتصال فورًا مرة واحدة
GameTokSDK.onAudioSuspend((payload) => {
  myGame.muteAll();
});

// تعطيل مزامنة الحالة، الاستماع للأحداث اللاحقة فقط
GameTokSDK.onAudioSuspend((payload) => {
  myGame.muteAll();
}, { sync: false });

الاستخدام الموصى به: سجّل المستمعين في أقرب وقت ممكن خلال مرحلة تهيئة اللعبة، مع إبقاء sync: true (الافتراضي)، بحيث تتمكن اللعبة من مزامنة الحالة بشكل صحيح حتى لو أرسلت المنصة تعليمات الكتم قبل التسجيل.

المعاملات وشكل الاستجابة

  • أمثلة معاملات شائعة (مرجعية؛ التفاصيل لكل واجهة):
    • getProfile: `` (غالبًا بلا معاملات إضافية)
    • getProfiles: { uids: number[] } (مطلوب، مصفوفة غير فارغة؛ تُعيد Profile[]، نفس بنية getProfile.data على شكل مصفوفة)
    • purchase: { productId: string }
    • storageSet: { key: string, value: any }
    • storageGet: { key: string }
    • addScore: { score: number, scoreType: string }
    • getPermissionMic: `` (غالبًا بلا معاملات إضافية)
    • topup: { amount: number, ... } (حقول أخرى حسب بروتوكول العميل)
    • roundStart / roundEnd: { round_id: string, timestamp?: number }
    • getCouponTargetScore: `` (عادةً بلا معاملات إضافية)
    • showCouponDialog: { coupon_id?: string, scene?: string }
    • onAudioSuspend / onAudioResume: (handler: Function, options?: { sync?: boolean })
    • offAudioSuspend / offAudioResume: (handler: Function)
  • عند النجاح، الاستجابة الموحّدة:
typescript
{
  action: string;      // اسم الإجراء (مثل 'GET_PROFILE')
  error: false;        // false عند النجاح (عند الفشل يُرفض الـ Promise)
  data: any;           // حمولة الأصل؛ الحقول حسب الإجراء
}
  • سلوك الفشل:
    • topup: معظم نتائج العمل (نجاح/فشل/إلغاء) تُحلّ في then عبر data.code؛ catch مخصّص لأخطاء الـ SDK وانتهاء المهلة.
    • واجهات Promise الأخرى: عند الفشل يُرفض الـ Promise بكائن Error؛ يُفضّل التعامل في .catch (تسجيل، إعادة محاولة، أو تنبيه).

ملاحظات مهمة

لا تستبدل ولا تحذف الكائنات العامة التالية وإلا يتعطّل الـ SDK:

  • window.GameTokSDK (نقطة الدخول الرئيسية)

مثال (لا تفعل هذا):

javascript
// خطر — يعطل الجسر مع iOS / Android
window.GameTokSDK = {};
window.GameTokSDK = null;
delete window.GameTokSDK;

مثال كامل

javascript
// جلب الملف الشخصي
GameTokSDK.getProfile({})
  .then((resp) => {
    console.log('Profile:', resp.data);
  })
  .catch((e) => {
    console.error('فشل جلب الملف الشخصي:', e);
  });

// جلب ملفات مستخدمين متعددة (للوائح المتصدرين، قوائم الأصدقاء، إلخ)
GameTokSDK.getProfiles({ uids: [10001, 10002, 10003] })
  .then(({ data }) => {
    const map = new Map(data.map(p => [p.uid, p]));
    console.log('ملف 10001:', map.get(10001));
  })
  .catch((e) => {
    console.error('فشل الجلب المتعدد:', e);
  });

// تخزين
GameTokSDK.storageSet({ key: 'config', value: { lang: 'ar' } })
  .then(() => {
    console.log('تم التخزين بنجاح');
  })
  .catch((e) => {
    console.error('فشل التخزين:', e);
  });

// قراءة
GameTokSDK.storageGet({ key: 'config' })
  .then((resp) => {
    console.log('Config:', resp.data.value);
  })
  .catch((e) => {
    console.error('فشلت القراءة:', e);
  });

// شراء
GameTokSDK.purchase({ productId: 'HAB.WATER.10.COINS' })
  .then((resp) => {
    console.log('Purchase:', resp.data);
  })
  .catch((e) => {
    console.error('فشل الشراء:', e);
  });

// نتيجة
GameTokSDK.addScore({ score: 5, scoreType: 'score' })
  .then(() => {
    console.log('Score added');
  })
  .catch((e) => {
    console.error('فشل إرسال النتيجة:', e);
  });

// الميكروفون (أندرويد، بلا إرجاع)
GameTokSDK.getPermissionMic();

// الشحن: افحص data.code في then (200 = نجاح)
GameTokSDK.topup({ amount: 100 })
  .then(({ data }) => {
    if (data && data.code === 200) {
      console.log('نجح الشحن', data);
    } else {
      console.log('لم ينجح الشحن', data);
    }
  })
  .catch((e) => console.error('خطأ في الشحن', e));

// إشعارات الجولة (نفس round_id)
const rid = 'r-' + Date.now();
GameTokSDK.roundStart({ round_id: rid });
GameTokSDK.roundEnd({ round_id: rid });

// جلب النتيجة المستهدفة للقسيمة، ثم عرض النافذة عند الوصول إليها
GameTokSDK.getCouponTargetScore()
  .then(({ data }) => {
    console.log('النتيجة المستهدفة للقسيمة:', data.target_score);
    if (myGame.score >= data.target_score) {
      GameTokSDK.showCouponDialog({ scene: 'score_reached' });
    }
  })
  .catch((e) => console.error('فشل جلب النتيجة المستهدفة للقسيمة', e));

// مستمعو أحداث الصوت (يُسجَّلان خلال تهيئة اللعبة)
GameTokSDK.onAudioSuspend((payload) => {
  console.log('كتم', payload);
  myGame.muteAll();
});

GameTokSDK.onAudioResume((payload) => {
  console.log('استئناف الصوت', payload);
  myGame.unmuteAll();
});

Swipe & Play Endless Game Together