إنها ساذجة. سوبر: كود وبنية لعبة بسيطة

نحن نعيش في عالم معقد ، ويبدو أنه بدأ ينسى الأشياء البسيطة. على سبيل المثال ، بخصوص ماكينة حلاقة أوكام ، ومبدأها هو: "ما يمكن القيام به على أساس عدد أقل لا يجب أن يتم على أساس عدد أكبر". في هذه المقالة سأتحدث عن الحلول البسيطة وليس الأكثر موثوقية التي يمكن استخدامها في تطوير الألعاب البسيطة.



بحلول فصل الخريف DotNext في موسكو ، قررنا تطوير لعبة. كان اختلاف تكنولوجيا المعلومات للكيمياء الشعبية. كان على اللاعبين جمع 128 مفاهيم تتعلق بتكنولوجيا المعلومات والبيتزا ودودو من العناصر الأربعة المتاحة. وكنا بحاجة إلى تحقيق ذلك من فكرة إلى لعبة عمل في أكثر من شهر بقليل.

في مقال سابق ، كتبت عن عنصر التصميم في العمل: التخطيط ، fakapy والعواطف. وتتناول هذه المقالة الجزء الفني. سيكون هناك حتى رمز!

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

رمز العميل


باختصار حول بنية المشروع: كان لدينا عملاء متنقلون لنظامي Android و iOS على Unity وخادم خلفية على ASP.NET مع CosmosDB كسعة تخزين.

عميل على الوحدة يمثل فقط التفاعل مع واجهة المستخدم. ينقر اللاعب على العناصر ، وينتقل حول الشاشة بطريقة ثابتة. عندما يتم إنشاء عنصر جديد ، تظهر نافذة مع وصفها.




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



لقد استخدمت مكتبة UnitRx الرائعة جدًا لكتابة كود الوحدة بأسلوب غير متزامن تمامًا. في البداية حاولت استخدام مهمتي الأصلية ، لكنهم تصرفوا بشكل غير مستقر على إصدارات iOS. لكن UniRx.Async عملت كالساعة.

يتم استدعاء أي إجراء يتطلب الرسوم المتحركة من خلال فئة AnimationRunner:

public static class AnimationRunner
   {
       private const int MinimumIntervalMs = 20;
     public static async UniTask Run(Action<float> action, float durationInSeconds)
       {
           float t = 0;
           var delta = MinimumIntervalMs / durationInSeconds / 1000;
           while (t <= 1)
           {
               action(t);
               t += delta;
               await UniTask.Delay(TimeSpan.FromMilliseconds(MinimumIntervalMs));
           }
       }
   }

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

public async UniTask HandleUiOperation(UniTask uiOperation)
       {
           _inputLocked = true;
           await uiOperation;
           _inputLocked = false;
       }

وفقًا لذلك ، في جميع عناصر التحكم ، يتم التحقق من قيمة InputLocked أولاً ، وفقط إذا كان خطأ ، يتفاعل عنصر التحكم.

جعل هذا الأمر سهلاً بما يكفي لتنفيذ آلة الحالة الموضحة أعلاه ، بما في ذلك مكالمات الشبكة و I / O ، باستخدام نهج غير متزامن / انتظار مع المكالمات المتداخلة ، كما هو الحال في دمية روسية.

السمة المهمة الثانية للعميل هي أن جميع مقدمي الخدمات الذين تلقوا بيانات عن العناصر تم إجراؤهم في شكل واجهات. بعد المؤتمر ، عندما أوقفنا الواجهة الخلفية ، سمحت حرفياً في إحدى الأمسيات بإعادة كتابة رمز العميل بحيث أصبحت اللعبة غير متصلة تمامًا. يمكن تنزيل هذا الإصدار الآن من Google Play .

التفاعل مع العميل


الآن دعونا نتحدث عن القرارات التي اتخذناها أثناء تطوير بنية خادم العميل.



تم تخزين الصور على العميل ، وكان الخادم مسؤولاً عن كل المنطق. عند بدء التشغيل ، قام الخادم بقراءة ملف csv بمعرف وأوصاف جميع العناصر وتخزينها في ذاكرته. بعد ذلك ، كان على استعداد للذهاب.

كانت طرق API الحد الأدنى الضروري - خمسة فقط. لقد نفذوا كل منطق اللعبة. كل شيء بسيط للغاية ، لكني سأخبرك عن نقطتين مثيرتين للاهتمام.

عناصر المصادقة والمبتدئين


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



العيوب الواضحة لهذا الحل هي:

  1. إذا قام المستخدم بهدم التطبيق وإعادة تثبيته ، فسيتم اعتباره لاعبًا جديدًا ، وسيفقد كل تقدمه.
  2. لا يمكنك متابعة اللعبة على جهاز آخر.

كانت هذه افتراضات متعمدة ، لأننا فهمنا أن الناس سيلعبون في الكونفور من جهاز واحد فقط. ولن يكون لديهم الوقت للتبديل إلى جهاز آخر.

وهكذا ، في السيناريو المخطط ، اتصل العميل بطريقة الخادم AddNewUserمرة واحدة فقط.

عند تحميل شاشة اللعبة GetBaseElements، تم استدعاء الطريقة أيضًا مرة واحدة ، والتي أعادت id ، اسم sprite ووصف للعناصر الأساسية الأربعة. وجد العميل العفاريت الضرورية في موارده ، وأنشأ كائنات العناصر ، وكتبها لنفسه محليًا ورسم على الشاشة.

عند عمليات الإطلاق المتكررة ، لم يعد العميل مسجلاً على الخادم ولم يطلب عناصر البدء ، ولكنه أخذها من التخزين المحلي. ونتيجة لذلك ، تم فتح شاشة اللعبة على الفور.

دمج العناصر


عندما يحاول اللاعب توصيل عنصرين ، يتم استدعاء طريقة MergeElementsإما إرجاع معلومات حول العنصر الجديد أو الإبلاغ عن عدم جمع هذين العنصرين. إذا قام اللاعب بجمع عنصر جديد ، يتم تسجيل المعلومات حول هذا في قاعدة البيانات.



قمنا بتطبيق الحل الواضح: لتقليل الحمل على الخادم ، بعد أن يحاول اللاعب إضافة عنصرين ، يتم تخزين النتيجة مؤقتًا على العميل (في الذاكرة و csv). إذا حاول لاعب إعادة تكديس العناصر ، يتم فحص ذاكرة التخزين المؤقت أولاً. وفقط إذا لم تكن النتيجة موجودة ، يتم إرسال الطلب إلى الخادم.

وبالتالي ، نعلم أن كل لاعب يمكنه إجراء عدد محدود من التفاعلات مع ظهره ، وأن عدد الإدخالات في قاعدة البيانات لا يتجاوز عدد العناصر المتاحة (التي كان لدينا 128 منها). تمكنا من تجنب مشاكل العديد من تطبيقات المؤتمرات الأخرى ، والتي غالبًا ما كان يتم دعمها بعد التدفق الكبير والمتزامن للمشاركين.

جدول درجات عالية


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



لتشكيل جدول السجلات، وأساليب الماضيين تستخدم GetCurrentLadderو GetUser.هناك أيضا فارق بسيط غريبة. إذا كان اللاعب ضمن أفضل 20 نتيجة ، فسيتم تمييز اسمه في الجدول. كانت المشكلة أن معلومات هاتين الطريقتين لم تكن ذات صلة مباشرة. تصل

الطريقة GetCurrentLadderإلى المجموعة Statsوتحصل على 20 نتيجة وتقوم بذلك بسرعة. GetUserتصل الطريقة إلى المجموعة.Usersبواسطة UserId ويقوم بذلك بسرعة كبيرة. دمج النتائج موجود بالفعل على جانب العميل. هذا فقط لأننا لم نرغب في تألق UserId في النتائج ، لذا فهي ليست هناك. تمت المقارنة حسب اسم اللاعب وعدد النقاط المسجلة. في حالة الآلاف من اللاعبين ، فإن التصادمات ستكون حتمًا. لكننا اعتمدنا على حقيقة أنه من غير المحتمل أن يكون من بين جميع اللاعبين لاعبون بنفس الأسماء والنقاط. في حالتنا ، هذا النهج له ما يبرره تماما.

انتهت اللعبة


كانت مهمتنا الأولية هي تجميع لعبة لعقد مؤتمر لمدة يومين في شهر واحد. كل تلك القرارات التي قمنا بتجسيدها في العمارة والتعليمات البرمجية تبرر نفسها تمامًا. لم يستلق الخادم ، وتم معالجة جميع الطلبات بشكل صحيح ، ولم يشكو اللاعبون من الأخطاء في التطبيق.

بحلول موسكو DotNext التالية ، من المرجح أن نثير لعبة أخرى ، لأنها أصبحت الآن تقاليدنا الجيدة ( CMAN-2018 ، IT-Alchemy-2019 ). اكتب في التعليقات أي قاتل للوقت أنت على استعداد لتبادل التقارير الصارمة من نجوم التطوير. :)
لنفس السذاجة والمهتمين ، قمنا بنشر كود العميل الخاص بالكيمياء الخاصة بتكنولوجيا المعلومات في المجال العام .

وانظر إلى قناة البرقية ، حيث أكتب كل شيء عن التنمية والحياة والرياضيات والفلسفة.

All Articles