
مقدمة
بدأت القصة مع hackathon القائم على blockchain. في بداية الحدث ، قابلت رجلاً يصنع ألعاباً لوحية على شكل هواية (كنت في لعبة واحدة من هذه اللعبة) ، تعاوننا معًا ووجدنا فريقًا "أعمى" معه لعبة استراتيجية بسيطة خلال عطلة نهاية الأسبوع. مرت hackathon ، ولكن بقي الحماس. وقد توصلنا إلى فكرة لعبة الورق متعددة اللاعبين حول السعادة والمجتمع العالمي والانتخابات.
في سلسلة المقالات ، سنعكس طريقنا إلى إنشاء لعبة ، مع وصف للخطأ الذي بدأناه بالفعل وسنتقدم ونحن نمضي قدمًا.
تحت خفض سيكون:
- ملخص اللعبة
- كيف تم اتخاذ القرار بشأن ما يجب القيام به الخلفية. أين "سيعيش" حتى لا يدفع ثمنه في مرحلة التطوير
- الخطوات الأولى في التطوير - مصادقة اللاعب وتنظيم التوفيق
- خطط أخرى
ما هي اللعبة عنه
لقد سئمت البشرية من الحروب العالمية واستنزاف الموارد والمنافسة المستمرة. وافقت الفصائل الرئيسية على استخدام التكنولوجيا الحديثة لاختيار قيادة واحدة. في الوقت المحدد ، يجب على الناخبين في العالم أن يقرروا اختيار جزء يحكم الكوكب للألفية القادمة. تشارك الفصائل الرئيسية في صراع السلطة "النزيه". في جلسة اللعب ، يمثل كل لاعب نسبة ضئيلة.
هذه اللعبة هي ورقة حول الانتخابات. كل فصيل لديه ميزانية لإجراء سباق الانتخابات ، ومصادر الدخل وزيادة الميزانية وبدء التصويت. في بداية اللعبة ، يتم خلط ورق اللعب مع بطاقات الحركة ويتم إصدار 4 بطاقات لكل مشارك. كل منعطف ، يمكن للاعبين تنفيذ ما يصل إلى اثنين من الإجراءات اللعبة. لاستخدام البطاقة ، يضعها اللاعب على الطاولة ، وإذا لزم الأمر ، يحدد الهدف ويخصم تكلفة استخدام البطاقة من الميزانية. بعد نهاية الجولة ، يمكن للاعب الاحتفاظ بإحدى البطاقات غير المستخدمة فقط. في بداية كل جولة ، يحصل اللاعبون على بطاقات من مجموعة الورق ، بحيث يكون لكل لاعب في بداية كل جولة 4 بطاقات عمل في متناول اليد.
في نهاية الجولات 3 و 6 و 9 ، تتم إزالة اللاعب الذي لديه أقل عدد من الأصوات من اللعبة. إذا كان لدى العديد من اللاعبين نفس العدد الأدنى من الأصوات ، فسيتم حذف جميع اللاعبين الذين لديهم هذه النتيجة من اللعبة. أصوات هؤلاء اللاعبين تذهب إلى التجمع العام للناخبين.
في نهاية الجولة 12 ، يكون الفائز هو صاحب أكبر عدد من الأصوات.
اختيار أداة للخلفية
من وصف اللعبة التالي:
- هذا متعدد اللاعبين
- من الضروري تحديد اللاعبين بطريقة أو بأخرى وإدارة الحسابات
- إن وجود عنصر اجتماعي سيفيد اللعبة - الأصدقاء والمجتمعات (العشائر) والدردشة والإنجازات (الإنجازات)
- ستكون لوحات المتصدرين ووظائف التوفيق مطلوبة.
- ستكون وظيفة إدارة البطولة مفيدة في المستقبل
- نظرًا لأن اللعبة عبارة عن لعبة ورق ، فأنت بحاجة إلى إدارة كتالوج البطاقات ، فقد تحتاج إلى تخزين البطاقات المتاحة للاعب والطوابق المترجمة
- في المستقبل ، قد يكون الاقتصاد في اللعبة مطلوبًا ، بما في ذلك العملة داخل اللعبة ، وتبادل السلع الافتراضية (البطاقات)
بعد إلقاء نظرة على قائمة الاحتياجات ، توصلت فوراً إلى استنتاج مفاده أن جعل الواجهة الخلفية الخاصة بي في المرحلة الأولية غير منطقية وتوجهت إلى google ماهي الخيارات الأخرى. لذلك اكتشفت أن هناك خلفيات خلفية للألعاب السحابية ، من بينها PlayFab (اشترتها Microsoft) و GameSparks (اشترتها Amazon).
بشكل عام ، فهي متشابهة وظيفيا وتغطي الاحتياجات الأساسية. علاوة على ذلك ، تختلف البنية الداخلية الخاصة بهم اختلافًا كبيرًا ، ويتم حل نفس المهام بشكل مختلف قليلاً ، ويصعب تتبع المراسلات الصريحة في الميزات. فيما يلي الميزات الإيجابية والسلبية لكل منصة والاعتبارات المتعلقة بموضوع الاختيار.
PlayFab
ميزات إيجابية:
- يتم دمج الحسابات من الألعاب المختلفة في حساب رئيسي
- يتم وصف اقتصاد الألعاب بدون سطر واحد من الكود ، بما في ذلك السعر إلى متجر افتراضي منفصل
- واجهة المستخدم ودية
- مايكروسوفت تحصل على المنتج بعد الاستحواذ
- تبلغ تكلفة الملكية في الإنتاج من خلال اشتراك Indie Studio 99 دولارًا (بحد أقصى 100 ألف مللي أمبير) ، عند التبديل إلى اشتراك Professional 1K MAU سيكلف 8 دولارات (الحد الأدنى للحساب 300 دولار)
الميزات السلبية:
- يقتصر تخزين بيانات اللعبة بشكل صارم ، على سبيل المثال ، في اشتراك مجاني لتخزين البيانات لجلسة لعبة معينة (إذا فهمت كل شيء بشكل صحيح ، يتم استخدام مجموعات الكيان لهذا) 3 فتحات لكل 500 بايت متاحة
- لتنظيم Multiplayer ، تحتاج إلى الاتصال بخوادم الجهات الخارجية التي ستقوم بمعالجة الأحداث من العملاء وحساب منطق اللعبة. هذا إما فوتون على أجهزتك أو Azure Thunderhead ، ولا تحتاج فقط إلى تنظيم الخادم ، ولكن أيضًا ترقية اشتراكك إلى Indie Studio على الأقل
- من الضروري أن نتحمل حقيقة أن رمز السحابة بدون الإكمال التلقائي وليس هناك طريقة لاقتحام الوحدات (أو لم تجد؟)
- لا يوجد مصحح أخطاء عادي ، يمكنك فقط كتابة السجلات في CloudScript وطريقة العرض
GameSparks
ميزات إيجابية:
- لعبة تخزين البيانات. ليس فقط هناك العديد من الأماكن التي يمكنك فيها حفظ البيانات (بيانات تعريف اللعبة العامة ، السلع الافتراضية ، ملف تعريف اللاعب ، جلسات متعددة اللاعبين ، إلخ.) ، كما يوفر النظام الأساسي قاعدة بيانات كاملة كخدمة كخدمة غير مرتبطة بأي شيء ، علاوة على ذلك ، يتوفر كل من MongoDB و Redis على الفور لأنواع مختلفة من البيانات. في بيئة التطوير ، يمكنك تخزين 10 ميغابايت ، في المعركة 10 جيجابايت
- متعددة اللاعبين متوفر في اشتراك مجاني (تطوير) بحد أقصى 10 اتصالات متزامنة و 10 طلبات في الثانية
- عمل مريح مع CloudCode ، بما في ذلك أداة مدمجة للاختبار والتصحيح (Test Harness)
الميزات السلبية:
- الشعور بأنه منذ شراء شركة Amazon (شتاء 2018) للركود ، لم تعد هناك ابتكارات
- مرة أخرى ، بعد الاستحواذ على Amazon ، أصبحت التعريفات أسوأ ؛ في وقت سابق كان من الممكن استخدام ما يصل إلى 10،000 MAU في الإنتاج مجانًا
- تكلفة الإنتاج للملكية تبدأ من 300 دولار (اشتراك قياسي)
التفكير
أولاً عليك التحقق من مفهوم اللعبة. للقيام بذلك ، أريد أن أبني نموذجًا أوليًا للعصي وشريط سكوتش دون استثمارات نقدية وبدء لعب اختبارات ميكانيكا اللعبة. لذلك ، في المقام الأول عند الاختيار ، أثير الفرصة لتطوير واختبار ميكانيكي على اشتراك مجاني.
يفي GameSparks بهذا المعيار ، لكن PlayFab لا يلبي ذلك ، لأنك ستحتاج إلى خادم يتعامل مع أحداث عملاء اللعبة والاشتراك على مستوى استوديو Indie (99 دولارًا).
في الوقت نفسه ، أقبل خطر عدم قيام أمازون بتطوير GameSparks ، مما يعني أنه قد "يموت". نظرًا لهذا ولا تزال تكلفة الملكية في الإنتاج ، أضع في الاعتبار الحاجة المحتملة للانتقال إلى منصة أخرى أو إلى الواجهة الخلفية الخاصة بي.
الخطوات الأولى في التنمية
الاتصال والمصادقة
لذلك ، وقع الاختيار على GameSparks كواجهة خلفية في مرحلة النماذج الأولية. الخطوة الأولى هي معرفة كيفية الاتصال بالمنصة ومصادقة اللاعب. النقطة المهمة هي أن المستخدم يجب أن يكون قادرًا على اللعب دون تسجيل ورسالة نصية قصيرة فور تثبيت اللعبة. للقيام بذلك ، توفر GameSparks خيار إنشاء ملف تعريف مجهول عن طريق استدعاء أسلوب DeviceAuthenticationRequest ، في وقت لاحق على أساس ملف تعريف مجهول ، يمكنك إنشاء ملف كامل من خلال الاتصال ، على سبيل المثال ، بحسابك في Google.
بالنظر إلى أن لديّ TDD في الدماغ ، فقد بدأت بإنشاء اختبار لربط العميل باللعبة. نظرًا لأن CloudCode في المستقبل سوف تحتاج إلى كتابتها في JS ، فسأجري اختبارات التكامل في JS باستخدام mocha.js و chai.js. الاختبار الأول اتضح مثل هذا:
var expect = require("chai").expect; var GameClientModule = require("../src/gameClient"); describe("Integration test", function () { this.timeout(0); it("should connect client to server", async function () { var gameClient = new GameClientModule.GameClient(); expect(gameClient.connected()).is.false; await gameClient.connect(); expect(gameClient.connected()).is.true; }); })
افتراضيًا ، تكون المهلة في mocha.js هي ثانيتين ، فأنا لا أجعلها على الفور لا تنتهي ، لأن الاختبارات عبارة عن تكامل. في الاختبار ، أقوم بإنشاء عميل لعبة لم يتم تنفيذه بعد ، وتحقق من عدم وجود اتصال بالخادم ، واتصل بالأمر للاتصال بالواجهة الخلفية ، وتحقق من اتصال العميل بنجاح.
لكي يتحول الاختبار إلى اللون الأخضر ، تحتاج إلى تنزيل وإضافة GameSparks JS SDK إلى المشروع ، وكذلك توصيل تبعياته (crypto-js و ws) ، وبطبيعة الحال ، تطبيق GameClientModule:
var GameSparks = require("../gamesparks-javascript-sdk-2018-04-18/gamesparks-functions"); var config = new require("./config.json"); exports.GameClient = function () { var gamesparks = new GameSparks(); this.connected = () => (gamesparks.connected === true); this.connect = function () { return new Promise(function (resolve, reject) { gamesparks.initPreview({ key: config.gameApiKey, secret: config.credentialSecret, credential: config.credential, onInit: () => resolve(), onMessage: onMessage, onError: (error) => reject(error), logger: console.log }); }); } function onMessage(message) { console.log("GAME onMessage: " + JSON.stringify(message)); } }
في بدء تطبيق عميل اللعبة ، تتم قراءة المفاتيح اللازمة للترخيص الفني لإنشاء اتصال من تطبيق العميل من التكوين. التفاف الأسلوب المتصل نفس الحقل من SDK. يحدث الشيء الأكثر أهمية في طريقة الاتصال ، حيث تقوم بإرجاع وعد مع عمليات الاسترجاعات للاتصال الناجح أو خطأ ، كما يربط معالج onMessage بنفس رد الاتصال. سيكون onMessage بمثابة مدير معالجة الرسائل من الواجهة الخلفية ، فلنسمح له الآن بتسجيل الرسائل إلى وحدة التحكم.
يبدو أن العمل قد اكتمل ، ولكن الاختبار لا يزال أحمر. اتضح أن GameSparks JS SDK لا يعمل مع node.js ؛ لأنه ، كما ترى ، يفتقر إلى سياق المتصفح. لنجعله يعتقد أن العقدة هي Chrome على الخشخاش. نذهب إلى gamesparks.js وفي البداية أضف:
if (typeof module === 'object' && module.exports) {
تحول الاختبار الأخضر ، والمضي قدما.
كما كتبت سابقًا ، يجب أن يكون اللاعب قادرًا على بدء اللعب فورًا بمجرد دخوله اللعبة ، بينما أريد أن أبدأ في تجميع التحليلات في النشاط. للقيام بذلك ، نلتزم إما بمعرف الجهاز أو بمعرف تم إنشاؤه عشوائيًا. تحقق هذا سيكون مثل هذا الاختبار:
it("should auth two anonymous players", async function () { var gameClient1 = new GameClientModule.GameClient(); expect(gameClient1.playerId).is.undefined; var gameClient2 = new GameClientModule.GameClient(); expect(gameClient2.playerId).is.undefined; await gameClient1.connect(); await gameClient1.authWithCustomId("111"); expect(gameClient1.playerId).is.equals("5b5f5614031f5bc44d59b6a9"); await gameClient2.connect(); await gameClient2.authWithCustomId("222"); expect(gameClient2.playerId).is.equals("5b5f6ddb031f5bc44d59b741"); });
قررت التحقق على الفور من عميلين للتأكد من قيام كل عميل بإنشاء ملف التعريف الخاص به على الواجهة الخلفية. للقيام بذلك ، سيحتاج عميل اللعبة إلى طريقة يمكنك من خلالها نقل معرف معين خارج GameSparks ، ثم تحقق من اتصال العميل بملف تعريف اللاعب المطلوب. الملفات الشخصية المعدة مسبقًا على بوابة GameSparks.
للتنفيذ في GameClient ، أضف:
this.playerId = undefined; this.authWithCustomId = function (customId) { return new Promise(resolve => { var requestData = { "deviceId": customId , "deviceOS": "NodeJS" } sendRequest("DeviceAuthenticationRequest", requestData) .then(response => { if (response.userId) { this.playerId = response.userId; resolve(); } else { reject(new Error(response)); } }) .catch(error => { console.error(error); }); }); } function sendRequest(requestType, requestData) { return new Promise(function (resolve) { gamesparks.sendWithData(requestType, requestData, (response) => resolve(response)); }); }
يرجع التطبيق إلى إرسال طلب DeviceAuthenticationRequest ، وتلقي معرف اللاعب من الاستجابة ، ووضعه في خاصية العميل. على الفور ، في طريقة منفصلة ، أرسل المساعد طلبات إلى GameSparks مع غلاف في promis.
كلا الاختبارين أخضر ، يبقى لإضافة إغلاق الاتصال و refactor.
في GameClient ، أضفت طريقة تغلق الاتصال بالخادم (قطع الاتصال) و connectAsAnonymous تجمع بين الاتصال و authWithCustomId. من ناحية ، ينتهك connectAsAnonymous مبدأ المسؤولية الفردية ، لكنه لا يبدو أنه ينتهك ... في الوقت نفسه ، فإنه يضيف قابلية الاستخدام ، لأنه في الاختبارات سيكون من الضروري في الغالب مصادقة العملاء. ما رأيك في هذا؟
في الاختبارات ، أضاف مساعدًا لطريقة المصنع ينشئ مثيلًا جديدًا لعميل اللعبة ويضيف إلى مجموعة العملاء الذين تم إنشاؤها. في معالج mocha الخاص ، بعد كل اختبار قيد التشغيل للعملاء في الصفيف ، أسمي طريقة قطع الاتصال وقم بمسح هذه الصفيف. لا أحب "الأوتار السحرية" في الشفرة حتى الآن ، لذلك أضفت قاموسًا به معرفات مخصصة تستخدم في الاختبارات.
يمكن الاطلاع على الكود النهائي في المستودع ، وهو رابط سأقدمه في نهاية المقال.
منظمة البحث عن لعبة (التوفيق)
سأبدأ تشغيل ميزة المطابقة ، والتي تعد مهمة للغاية بالنسبة إلى اللاعبين المتعددين. يبدأ هذا النظام في العمل عندما نضغط على الزر "Find a game" في اللعبة. انها تلتقط إما منافسيها ، أو زملائه ، أو كليهما (اعتمادا على اللعبة). كقاعدة عامة ، في مثل هذه الأنظمة ، كل لاعب لديه مؤشر رقمي MMR (مطابقة نسبة المباراة) - تصنيف شخصي للاعب ، والذي يستخدم لاختيار لاعبين آخرين بنفس المستوى من المهارة.
لاختبار هذه الوظيفة ، توصلت للاختبار التالي:
it("should find match", async function () { var gameClient1 = newGameClient(); var gameClient2 = newGameClient(); var gameClient3 = newGameClient(); await gameClient1.connectAsAnonymous(playerCustomIds.id1); await gameClient2.connectAsAnonymous(playerCustomIds.id2); await gameClient3.connectAsAnonymous(playerCustomIds.id3); await gameClient1.findStandardMatch(); expect(gameClient1.state) .is.equals(GameClientModule.GameClientStates.MATCHMAKING); await gameClient2.findStandardMatch(); expect(gameClient2.state) .is.equals(GameClientModule.GameClientStates.MATCHMAKING); await gameClient3.findStandardMatch(); expect(gameClient3.state) .is.equals(GameClientModule.GameClientStates.MATCHMAKING); await sleep(3000); expect(gameClient1.state) .is.equals(GameClientModule.GameClientStates.CHALLENGE); expect(gameClient1.challenge, "challenge").is.not.undefined; expect(gameClient1.challenge.challengeId).is.not.undefined; expect(gameClient2.state) .is.equals(GameClientModule.GameClientStates.CHALLENGE); expect(gameClient2.challenge.challengeId) .is.equals(gameClient1.challenge.challengeId); expect(gameClient3.state) .is.equals(GameClientModule.GameClientStates.CHALLENGE); expect(gameClient3.challenge.challengeId) .is.equals(gameClient1.challenge.challengeId); });
ثلاثة عملاء متصلين باللعبة (في المستقبل يعد هذا الحد الأدنى الضروري للتحقق من بعض السيناريوهات) ويتم تسجيلهم للبحث عن اللعبة. بعد تسجيل اللاعب الثالث على الخادم ، يتم تشكيل جلسة لعبة ويجب على اللاعبين الاتصال بها. في الوقت نفسه ، تتغير حالة العملاء ، ويظهر سياق جلسة اللعبة بنفس المعرف.
أولا ، إعداد الخلفية. توجد في GameSparks أداة جاهزة لتخصيص البحث عن الألعاب ، وهي متاحة على المسار "Configurator-> Matches". يمكنني إنشاء واحدة جديدة والمضي قدما في الإعداد. بالإضافة إلى المعلمات القياسية مثل الرمز واسم المباراة ووصفها ، يشار إلى الحد الأدنى والحد الأقصى لعدد اللاعبين المطلوب لوضع اللعب المخصص. سأخصص رمز "StandardMatch" للمباراة التي تم إنشاؤها وأشير إلى عدد اللاعبين من 2 إلى 3.
تحتاج الآن إلى تكوين قواعد اختيار اللاعبين في قسم "العتبات". لكل عتبة ، تتم الإشارة إلى وقت عملها ، اكتب (مطلق ، نسبي وفي المئة) والحدود.

لنفترض أن اللاعب الذي يحمل MMR من 19 يبدأ البحث. في المثال أعلاه ، ستكون أول 10 ثوانٍ هي اختيار لاعبين آخرين مع MMR من 19 إلى 21. إذا لم يتم تحديد اللاعبين ، يتم تنشيط حد البحث الثاني ، مما يمدد نطاق البحث من 16 إلى 20 ثانية التالية ( 19-3) إلى 22 (19 + 3). بعد ذلك ، يتم تضمين الحد الثالث ، والذي سيتم خلاله البحث لمدة 30 ثانية في النطاق من 14 (19-25٪) إلى 29 (19 + 50٪) ، في حين يُعتبر الانتهاء مكتملاً إذا تم تجميع الحد الأدنى المطلوب من اللاعبين (قبول علامة الحد الأدنى) اللاعبين).
في الواقع ، فإن الآلية أكثر تعقيدًا ، لأنها تأخذ في الاعتبار معدل معدل ضربات القلب لجميع اللاعبين الذين تمكنوا من الانضمام إلى مباراة معينة. سأقوم بتحليل هذه التفاصيل عندما يحين الوقت لوضع تصنيف اللعبة (وليس في هذه المقالة). بالنسبة لوضع اللعب القياسي ، حيث لا أخطط لاستخدام MMR حتى الآن ، أحتاج إلى عتبة واحدة فقط من أي نوع.
عندما يتم اختيار جميع اللاعبين ، تحتاج إلى إنشاء جلسة لعبة وتوصيل اللاعبين بها. في GameSparks ، وظيفة جلسة اللعبة هي "التحدي". كجزء من هذا الكيان ، يتم تخزين بيانات جلسة اللعبة ، ويتم تبادل الرسائل بين عملاء اللعبة. لإنشاء نوع جديد من التحدي ، تحتاج إلى السير على طول المسار "Configurator-> Challenges". هناك ، أضفت نوعًا جديدًا يحتوي على الكود "StandardChallenge" وأشير إلى أن هذا النوع من جلسات اللعبة هو Turn Based ، أي يتناوب اللاعبون ، ليس في وقت واحد. GameSparks في نفس الوقت يتحكم في تسلسل التحركات.
لكي يسجل عميل ما للبحث عن لعبة ، يمكنك استخدام طلب من نوع MatchmakingRequest ، لكنني لا أوصي به ، لأن قيمة MMR الخاصة باللاعب مطلوبة كأحد المعلمات. يمكن أن يؤدي هذا إلى حدوث احتيال من جانب عميل اللعبة ، ويجب ألا يعرف العميل أي معدل وفيات MMR ، فهذه هي الأعمال الخلفية. للتسجيل بشكل صحيح للبحث عن اللعبة ، أقوم بإنشاء حدث تعسفي من العميل. يتم ذلك في قسم "Configurator-> الأحداث". أدعو الحدث FindStandardMatch دون سمات. أنت الآن بحاجة إلى تكوين رد الفعل على هذا الحدث ، لأنني ذاهب إلى قسم "Configurator-> Cloud Code" في الكود السحابي ، أكتب المعالج التالي لـ FindStandardMatch في قسم "الأحداث":
var matchRequest = new SparkRequests.MatchmakingRequest(); matchRequest.matchShortCode = "StandardMatch"; matchRequest.skill = 0; matchRequest.Execute();
تسجل هذه الشفرة لاعبًا في StandardMatch باستخدام MMR 0 ، وبالتالي فإن أي لاعب مسجل للبحث عن لعبة قياسية سيكون مناسبًا لإنشاء جلسة لعبة. عند اختيار تطابق التصنيف ، قد يكون هناك جاذبية للبيانات الخاصة لملف تعريف اللاعب للحصول على MMR لهذا النوع من التطابق.
عندما يكون هناك عدد كاف من اللاعبين لبدء جلسة لعب ، سترسل GameSparks رسالة MatchFoundMessage إلى جميع اللاعبين المحددين. هنا يمكنك إنشاء جلسة لعبة تلقائيًا وإضافة لاعبين إليها. للقيام بذلك ، في "رسائل المستخدم-> MatchFoundMessage" ، أضف الكود:
var matchData = Spark.getData(); if (Spark.getPlayer().getPlayerId() != matchData.participants[0].id) { Spark.exit(); } var challengeCode = ""; var accessType = "PRIVATE"; switch (matchData.matchShortCode) { case "StandardMatch": challengeCode = "StandardChallenge"; break; default: Spark.exit(); } var createChallengeRequest = new SparkRequests.CreateChallengeRequest(); createChallengeRequest.challengeShortCode = challengeCode; createChallengeRequest.accessType = accessType; var tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); createChallengeRequest.endTime = tomorrow.toISOString(); createChallengeRequest.usersToChallenge = []; var participants = matchData.participants; var numberOfPlayers = participants.length; for (var i = 1; i < numberOfPlayers; i++) { createChallengeRequest.usersToChallenge.push(participants[i].id) } createChallengeRequest.Send();
يتحقق الكود أولاً من أنه اللاعب الأول في قائمة المشاركين. بعد ذلك ، نيابة عن اللاعب الأول ، يتم إنشاء مثيل لـ StandardChallenge ويتم دعوة اللاعبين الباقين. يتم إرسال اللاعبين المدعوين إلى رسالة ChallengeIssuedMessage. هنا يمكنك تصور السلوك عندما يتم عرض دعوة للانضمام إلى اللعبة على العميل وتتطلب تأكيدًا عن طريق إرسال AcceptChallengeRequest ، أو يمكنك قبول الدعوة في الوضع الصامت. لذلك سأقوم بذلك ، لذلك في "رسائل المستخدم-> ChallengeIssuedMessage" سأضيف الكود التالي:
var challangeData = Spark.getData(); var acceptChallengeRequest = new SparkRequests.AcceptChallengeRequest(); acceptChallengeRequest.challengeInstanceId = challangeData.challenge.challengeId; acceptChallengeRequest.message = "Joining"; acceptChallengeRequest.SendAs(Spark.getPlayer().getPlayerId());
الخطوة التالية ، ترسل GameSparks حدث ChallengeStartedMessage. يعد المعالج العالمي لهذا الحدث ("الرسائل العالمية-> ChallengeStartedMessage") مكانًا مثاليًا لتهيئة جلسة لعبة ، وسأعتني بهذا عند تنفيذ منطق اللعبة.
لقد حان الوقت لتطبيق العميل. التغييرات في وحدة العميل:
exports.GameClientStates = { IDLE: "Idle", MATCHMAKING: "Matchmaking", CHALLENGE: "Challenge" } exports.GameClient = function () { this.state = exports.GameClientStates.IDLE; this.challenge = undefined; function onMessage(message) { switch (message["@class"]) { case ".MatchNotFoundMessage": this.state = exports.GameClientStates.IDLE; break; case ".ChallengeStartedMessage": this.state = exports.GameClientStates.CHALLENGE; this.challenge = message.challenge; break; default: console.log("GAME onMessage: " + JSON.stringify(message)); } } onMessage = onMessage.bind(this); this.findStandardMatch = function () { var eventData = { eventKey: "FindStandardMatch" } return new Promise(resolve => { sendRequest("LogEventRequest", eventData) .then(response => { if (!response.error) { this.state = exports.GameClientStates.MATCHMAKING; resolve(); } else { console.error(response.error); reject(new Error(response)); } }) .catch(error => { console.error(error); reject(new Error(error)); }); }); } }
وفقًا للاختبار ، ظهر حقلان على العميل - الحالة والتحدي. اكتسبت طريقة onMessage نظرة ذات مغزى وتستجيب الآن للرسائل المتعلقة ببدء جلسة اللعبة ورسالة تفيد بأنه لم يكن من الممكن التقاط لعبة. تمت إضافة طريقة findStandardMatch أيضًا ، والتي ترسل الطلب المقابل إلى الواجهة الخلفية. الاختبار أخضر ، لكنني راضٍ عن اختيار الألعاب المتقنة.
ما التالي؟
في المقالات التالية سوف أصف عملية تطوير منطق اللعبة ، من تهيئة جلسة اللعبة إلى معالجة التحركات. سأحلل ميزات تخزين أنواع مختلفة من البيانات - وصف البيانات الوصفية للعبة ، وخصائص عالم اللعبة ، وبيانات من جلسات اللعبة ، وبيانات عن اللاعبين. سيتم تطوير منطق اللعبة من خلال نوعين من الاختبارات - الوحدة والتكامل.
سوف أقوم بتحميل المصادر على جيثب في أجزاء مرتبطة بالمقالات.
هناك فهم أنه من أجل التقدم الفعال في إنشاء لعبة ، تحتاج إلى توسيع فريقنا المتحمسين. سوف ينضم الفنان / المصمم قريبًا. والمعلم ، على سبيل المثال ، Unity3D ، الذي سيصنع الواجهة لمنصات الهواتف المحمولة ، لا يزال موجودًا.