كيف فعلنا وحدتنا الصغيرة من الصفر



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

  • تقديم.
  • العمل مع SDK ؛
  • العمل مع نظام التشغيل ؛
  • مع الشبكة والموارد.

ومع ذلك ، فقد كانت تفتقر إلى ما تستحقه Unity ، وهو نظام مناسب لتنظيم المشاهد وكائنات اللعبة ، وكذلك المحررين لها.

هنا أريد أن أقول كيف قدمنا ​​كل هذه وسائل الراحة وما توصلنا إليه.

ما هو الان


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

لدينا كائنات مرئية يتم تخزينها في المشاهد. تتكون هذه الكائنات من العقد التي يتم تنظيمها في تسلسل هرمي ويمكن أن تحتوي كل عقدة على عدد من الكيانات ، مثل:

  • تحويل - تحول العقدة ؛
  • المكون - يعمل في التقديم ويمكن أن يكون هناك واحد فقط أو لا يوجد على الإطلاق. المكونات هي العفريت ، شبكة ، الجسيمات وغيرها من الكيانات التي يمكن عرضها. الأقرب المكافئ للوحدة هو Renderer ؛
  • السلوك - المسؤول عن السلوك ، وقد يكون هناك عدة. هذا هو تناظرية مباشرة من MonoBehaviour في الوحدة ، أي منطق مكتوب فيها.
  • الفرز هو كيان مسؤول عن الترتيب الذي يتم به عرض العقد في مشهد ما. نظرًا لأن نظامنا كان يجب أن يكون سهل الاندماج في الألعاب التي تعمل بالفعل ، مع المنطق الحالي والمتنوع لعرض الكائنات ، فقد كان من الضروري أن تكون قادرًا على دمج كيانات جديدة في الكيانات القديمة. يتيح لك الفرز نقل التحكم في ترتيب العرض إلى الكود الخارجي.

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

/// @category(VSO.Basic) class SpriteComponent : public MaterialComponent { VISUAL_CLASS(MaterialComponent) public: /// @getter const std::string& GetId() const; /// @setter void SetId(const std::string& id); protected: void OnInit() override; void Draw() override; protected: /// @property Color _color = Color::WHITE; /// @property Sprite _sprite; }; 

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

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

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

الكتل الرئيسية




كود الشفرة


لكي تعمل العديد من الأنظمة ، تحتاج إلى كتابة الكثير من التعليمات البرمجية الروتينية ، وهو أمر ضروري بسبب عدم وجود انعكاس في C ++ ( الانعكاس - القدرة على الوصول إلى معلومات حول الأنواع الموجودة في رمز البرنامج). لذلك ، نقوم بإنشاء معظم هذا الرمز الفني.

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

يمكننا إنشاء رمز للأنظمة الفرعية التالية:

  • التسلسل - يستخدم لحفظ / تحميل البيانات من القرص أو عند الإرسال عبر شبكة. سيتم النظر في مزيد من التفاصيل في وقت لاحق.
  • روابط مكتبة الانعكاس - تُستخدم لعرض المحرر تلقائيًا على البيانات. سوف نناقش في الفصل على المحرر.
  • رمز لاستنساخ الكيانات - يستخدم لاستنساخ الكيانات في المحرر وفي اللعبة.
  • رمز للتفكير وقت التشغيل خفيفة الوزن لدينا.

→ يمكن العثور على مثال للرمز الذي تم إنشاؤه لفئة واحدة هنا.

تحليل ج ++


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

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

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

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

مولد رمز


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

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

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

التسلسل


للتسلسل ، تم اعتبار المكتبات المختلفة: protobuf ، FlexBuffers ، الحبوب ، إلخ.

المكتبات التي تم إنشاؤها باستخدام الكود (Protobuf و FlatBuffers وغيرها) لم تكن مناسبة ، لأن لدينا هياكل مكتوبة بخط اليد ولا توجد طريقة لدمج الهياكل التي تم إنشاؤها في كود المستخدم. ومضاعفة عدد الفصول لمجرد التسلسل أمر تبذير للغاية.

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

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

كانت الفكرة الرئيسية مأخوذة من الحبوب ، وهي تستند إلى المحفوظات الأساسية لقراءة البيانات وكتابتها. يتم إنشاء ورثة مختلفين منهم يقومون بتنفيذ السجل بتنسيقات مختلفة: xml و json و binary. ويتم إنشاء رمز التسلسل بواسطة الفئات ويستخدم هذه المحفوظات لكتابة البيانات.



المحرر


نستخدم مكتبة ImGui للمحررين ، حيث كتبنا عليها جميع نوافذ المحرر الرئيسية: محتويات المشهد ، عارض الملفات والأصول ، مفتش الأصول ، محرر الرسوم المتحركة ، إلخ.

تتم كتابة الكود الرئيسي للمحرر يدويًا ، ولكن لعرض خصائص فئات معينة وتحريرها ، نستخدم مكتبة rttr ، والتخزين المؤقت المتولد لها وكود المفتش المعمم الذي يمكنه العمل مع rttr.

مكتبة الانعكاس - rttr


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

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

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

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

→ مثال رمز لإعلان فئة في rttr يمكن العثور عليها هنا

مفتش


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

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

أيضًا ، يتولى رمز المفتش دعم إلغاء العمليات - عند تغيير القيم ، يتم إنشاء أمر لتغيير البيانات ، والتي يمكن استعادتها بعد ذلك.

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

ويندوز والمحررين


في الوقت الحالي ، تم إنشاء العديد من النظم الفرعية والمحررين على أساس المحررين لدينا ، وإنشاء الشفرة ، وأنظمة إنشاء الأصول:

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

عند تطوير كل هذه النظم الفرعية والمحررين ، نظرنا عن كثب في Unity و Unreal Engine وحاولنا الاستفادة منها بشكل أفضل. وبعض هذه النظم الفرعية مصنوعة على جانب مشاريع اللعبة.

لتلخيص


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

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

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

Source: https://habr.com/ru/post/ar467827/


All Articles