في
مقال سابق ، تحدثت عن كيفية صنع لعبة بسيطة باستخدام مكتبة SFML. في نهاية المقال ، وعدتُ بالمتابعة وإظهار كيفية جعل رمز البرنامج في شكل أكثر صحة. لذلك ، لقد حان وقت إعادة بيع العقارات.
بادئ ذي بدء ، اكتشفت ما هي الفئات التي احتاجها للعبة. اتضح أنني بحاجة إلى فصل للعمل مع موارد اللعبة - الأصول. من بين الموارد ، لدي الآن خط قابل للتنزيل فقط ، ولكن في المستقبل ، يمكن إضافة موارد أخرى ، مثل الصور والموسيقى وما إلى ذلك. لقد جعلت الفصل منفردًا ، حيث يعمل هذا القالب بشكل رائع بالنسبة لفئة الأصول. وقد اتخذ الأساس من قبل سينغلتون المعروفة مايرز.
بعد ذلك ، تحتاج في الواقع إلى فئة اللعبة المسؤولة عن منطق البرنامج وتخزين حالته. من وجهة نظر أيديولوجية MVC ، هذه الفئة هي نموذج. لذلك دعوتها - GameModel.
لعرض اللعبة بصريًا ، فأنت بحاجة إلى فصل مسؤول عن عرض الشاشة. في الأيديولوجية ، MVC هو عرض. قمت بتسمية هذه الفئة GameRender ورثتها من الفئة التجريبية Drawable ، والتي تعد جزءًا من مكتبة SFML.
حسنًا ، الفئة الأخيرة التي نحتاجها - هذه هي الفئة المسؤولة عن التفاعل مع اللاعب - هذه هي وحدة التحكم. يجب أن يقال هنا أنه في الإيديولوجية الكلاسيكية لـ MVC ، يجب ألا تتفاعل وحدة التحكم مع التمثيل مباشرة. يجب أن يعمل فقط على النموذج ، ويقرأ العرض البيانات من النموذج من تلقاء نفسه ، أو بواسطة إشارة نظام الرسائل. لكن هذا النهج بدا لي زائداً إلى حد ما في هذه الحالة ، لذلك قمت بتوصيل وحدة التحكم مع العرض مباشرة. لذلك سيكون من الأصح افتراض أننا لا نملك وحدة تحكم ، ولكن مقدم العرض ، وفقًا لإيديولوجية MVP. الصف ومع ذلك ، دعوت GameController.
حسنًا ، دعنا الآن ننتقل إلى الأمر الممتع المتمثل في نشر التعليمات البرمجية الخاصة بنا في هذه الفئات.
الأصول الصفية
#pragma once #include <SFML/Graphics.hpp> class Assets { public: sf::Font font; public: static Assets& Instance() { static Assets s; return s; } void Load(); private: Assets() {}; ~Assets() {}; Assets(Assets const&) = delete; Assets& operator= (Assets const&) = delete; };
من ما أضفته إلى الفصل الدراسي ، هذا هو إعلان عضو من فئة الخط لتخزين الخط المحمل ، وطريقة التحميل لتحميله. كل شيء آخر هو تنفيذ المفرد مايرز.
طريقة التحميل بسيطة للغاية أيضًا:
void Assets::Load() { if (!font.loadFromFile("calibri.ttf")) throw; }
يحاول تحميل الخط calibri ويلقي استثناء إذا فشل هذا.
GameModel الدرجة
#pragma once enum class Direction { Left = 0, Right = 1, Up = 2, Down = 3 }; class GameModel { public: static const int SIZE = 4;
يتم وضع جميع المنطق وجميع بيانات اللعبة في الفصل. يجب أن يكون الفصل ، من حيث المبدأ ، مستقلًا عن البيئة الخارجية قدر الإمكان ، وبالتالي ، يجب ألا يكون لديه أي روابط لإخراج الشاشة أو تفاعل المستخدم. عند التطوير ، تذكر أن نموذج اللعبة يجب أن يظل قيد التشغيل حتى لو تغير تطبيق فئات العرض التقديمي أو وحدة التحكم.
تم وصف جميع أساليب النموذج ، من حيث المبدأ ، في مقال سابق ، لذلك لن أكرر هنا. الشيء الوحيد الذي سأقوله هو أنه تمت إضافة حالتين IsSolved و Elements إلى النموذج لتلبية احتياجات فئة العرض التقديمي.
GameRender الدرجة
#pragma once #include <SFML/Graphics.hpp> #include "GameModel.h" class GameRender : public sf::Drawable, public sf::Transformable { GameModel *m_game; sf::RenderWindow m_window; sf::Text m_text; public: GameRender(GameModel *game); ~GameRender(); sf::RenderWindow& window() { return m_window; }; bool Init(); void Render(); public: virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const; };
والغرض من فئة GameRender هو تجميع جميع البيانات في حد ذاتها لعرض نافذة اللعبة وتقديم الملعب. للتواصل مع طراز اللعبة ، يتم نقل مؤشر لكائن النموذج للتمثيل المحدد وتخزينه في المُنشئ. يقوم المنشئ أيضًا باستدعاء طريقة التهيئة ، والتي تنشئ وتهيئ نافذة اللعبة.
GameRender::GameRender(GameModel *game) { m_game = game; Init(); } bool GameRender::Init() { setPosition(50.f, 50.f);
سيتم استدعاء أسلوب Render () من حلقة معالجة الرسائل في وحدة التحكم في اللعبة لرسم النافذة وحالة الملعب:
void GameRender::Render() { m_window.clear(); m_window.draw(*this); m_window.draw(m_text); m_window.display(); } void GameRender::draw(sf::RenderTarget& target, sf::RenderStates states) const { states.transform *= getTransform(); sf::Color color = sf::Color(200, 100, 200);
فئة لعبة التحكم
الفئة الأخيرة المستخدمة في اللعبة هي وحدة التحكم.
#pragma once #include <SFML/Graphics.hpp> #include "GameRender.h" class GameController { GameModel *m_game; GameRender *m_render; public: GameController(GameModel *game, GameRender *render); ~GameController(); void Run(); };
الفصل بسيط للغاية ويحتوي على طريقة واحدة فقط تطلق اللعبة - طريقة Run (). في المُنشئ ، تقبل وحدة التحكم المؤشرات وتخزينها على مثيلات فئة الطراز وفئة عرض اللعبة.
تحتوي طريقة Run () على الدورة الرئيسية للعبة - معالجة الرسائل وتقديم نافذة الاتصال في فئة العرض التقديمي.
void GameController::Run() { sf::Event event; int move_counter = 0;
وأخيراً ، تبقى الوظيفة الرئيسية ()
#include "Assets.h" #include "GameModel.h" #include "GameRender.h" #include "GameController.h" int main() { Assets::Instance().Load();
في ذلك ، مجرد خلق الأشياء وإطلاق اللعبة.
نتيجة لذلك ، بعد إعادة التوطين ، حصلنا على رمز مقسم إلى فئات ، لكل منها هدف وظيفي خاص بها. في حالة إدخال تحسينات على اللعبة ، سيكون من الأسهل بالنسبة لنا تغيير محتويات الأجزاء الفردية من البرنامج.
في الختام ، سأحاول صياغة بعض القواعد لفترة وجيزة لتحليل الشفرة.
- يجب أن يكون لكل فصل هدف واحد. لا تحتاج إلى تكوين فئة فائقة الجودة يمكنها فعل أي شيء ، وإلا فلن تتمكن من التغلب عليها في المستقبل
- عند تسليط الضوء على الفئات ، تأكد من أن الطبقات متقاربة. تخيل عقليا دائما أن تنفيذ الفصل يمكن أن يكون مختلفا جذريا. ومع ذلك ، يجب ألا يؤثر هذا على فئات المشاريع الأخرى. استخدام واجهات للتفاعل بين الطبقات.
- استخدم أنماط التصميم عند الضرورة. هذا سوف تجنب الأخطاء غير الضرورية في تنفيذ الحلول الراسخة.
جميع مصادر البرنامج يمكن أن تؤخذ
هنا .