كيف تم ترتيب الرسومات NES؟

صورة

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

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

تفترض هذه المقالة أنك على دراية برياضيات الكمبيوتر ، وبصفة خاصة حقيقة أن 8 بتات = 1 بايت و 8 بتات يمكن أن تمثل 256 قيمة. من الضروري فهم كيفية عمل التدوين السداسي عشري. لكن حتى بدون هذه المعرفة التقنية ، قد تبدو المقالة مثيرة للاهتمام.

مراجعة قصيرة



أعلاه صورة من المشهد الأول لـ Castlevania (1986): البوابة المؤدية إلى القلعة ، حيث ستقام اللعبة. هذه الصورة بحجم 256 × 240 بكسل وتستخدم 10 ألوان مختلفة. لوصف هذه الصورة في الذاكرة ، يجب علينا الاستفادة من لوحة الألوان المحدودة ، وتوفير مساحة عن طريق تخزين قدر ضئيل فقط من المعلومات. تتمثل إحدى الطرق الساذجة في استخدام لوحة مفهرسة يكون لكل بكسل فيها وحدة تخزين من 4 بتات ، أي يتم وضع بكسلين في بايت. سيتطلب ذلك 256 * 240/2 = 30720 بايت ، ولكن كما سنرى قريبًا ، تستطيع NES التعامل مع هذه المهمة بكفاءة أكبر.

المفاهيم الرئيسية في موضوع الرسومات NES هي البلاط والكتل [1]. تبلغ مساحة البلاطة 8 × 8 بكسل ، والكتلة تبلغ 16 × 16 بكسل ، ويتم ربط كل منها بشبكة بنفس حجم الخلية. بعد إضافة هذه الشبكات ، يمكننا أن نرى هيكل الرسومات. هنا هو مدخل القلعة مع شبكة في التكبير المزدوج.


في هذه الشبكة ، تظهر الكتل باللون الأخضر الفاتح ، والبلاط باللون الأخضر الداكن. تحتوي المساطر على المحاور على قيم ست عشرية يمكن إضافتها للعثور على موضع ؛ على سبيل المثال ، يبلغ القلب في شريط الحالة 15 دولارًا + 60 دولارًا = 75 دولارًا ، والذي يتكون من رقم عشري 117. تحتوي كل شاشة على 16 × 15 كتل (240) و 32 × 30 بلاطة (960). الآن دعونا نرى كيف يتم وصف هذه الصورة والبدء في الرسومات بكسل الخام.

لجنة حقوق الإنسان


تصف بنية CHR رسومات البكسل "الخام" بدون لونها وموضعها ، ويتم تعيينها في بلاطات. تحتوي صفحة الذاكرة بالكامل على 256 وحدة تجميعة CHR ، ولكل مربع عمق 2 بت. هنا هي الرسومات القلب:


وإليك كيفية وصفها في CHR [2]:

بكسل قلب مركز حقوق الإنسان

يأخذ هذا الوصف 2 بت لكل بكسل ، أي بحجم 8 × 8 ، يتضح 8 * 8 * 2 = 128 بت = 16 بايت. ثم تأخذ الصفحة بأكملها 16 * 256 = 4096 بايت. هنا جميع حقوق الإنسان المستخدمة في الصورة من Castlevania.


تذكر أن ملء الصورة يتطلب 960 تجانبًا ، لكن CHR تسمح فقط 256. وهذا يعني أن معظم المربعات تتكرر ، في المتوسط ​​، 3.75 مرة ، ولكن في كثير من الأحيان يتم استخدام عدد صغير منها فقط (على سبيل المثال ، خلفية فارغة ، تجانب أحادي اللون أو تكرار الأنماط). تستخدم الصورة من Castlevania العديد من البلاط الفارغ ، وكذلك اللون الأزرق الصلب. لنرى كيف يتم تعيين البلاط ، ونحن نستخدم nametables.

NAMETABLE


يعين جدول الاسم ملف CHR لكل موضع على الشاشة ، ويبلغ إجماليه 960. يتم تحديد كل موضع في بايت واحد ، أي أن جدول الاسم بأكمله يستغرق ما يصل إلى 960 بايت. يتم تعيين البلاط بالترتيب من اليسار إلى اليمين ، من أعلى إلى أسفل ، وتتوافق مع الموضع المحسوب الموجود عن طريق إضافة القيم من المساطر الموضحة أعلاه. أي أن الموضع في الزاوية اليسرى العليا هو 0 دولار ، وعلى يمينه 1 دولار ، وأدناه 20 دولار.

تعتمد القيم في nametable على الترتيب الذي يتم به تعبئة CHR. إليك أحد الخيارات [3]:


في هذه الحالة ، تبلغ قيمة القلب (في الموضع 75 دولارًا) 13 دولارًا.

بعد ذلك ، لإضافة لون ، نحن بحاجة إلى تحديد لوحة.

لوحة


يحتوي NES على لوحة نظام من 64 لونًا [4] ، ومنه نختار اللوحات التي سيتم استخدامها في العرض. تحتوي كل لوحة على 3 ألوان فريدة بالإضافة إلى لون الخلفية الكلي. تحتوي الصورة على 4 لوحات بحد أقصى ، والتي تشغل 16 بايتًا في المجموع. فيما يلي اللوحات الخاصة بالصورة من Castlevania:

عبة Castlevania بال

لا يمكن استخدام اللوحات بشكل تعسفي. يتم تطبيق لوحة واحدة فقط لكل كتلة. وبسبب هذه الحاجة إلى فصل كل مساحة 16 × 16 وفقًا للوحة الألوان الخاصة باللعبة التي تتمتع بـ NES لديها مثل هذا الشكل "البلوك". يمكن تجنب الرسومات المنفذة ببراعة ، على سبيل المثال ، من شاشة البداية في Castlevania ، عن طريق مزج الألوان عند حواف الكتل ، والتي تخفي وجود الشبكة.

يتم تنفيذ اختيار لوحة لكل كتلة باستخدام آخر مكون - السمات.

سمات


تشغل السمات 2 بت لكل كتلة. أنها تحدد أي من لوحات 4 للاستخدام. تعرض هذه الصورة أي اللوحات المعرفة بواسطة السمات تستخدم كتل مختلفة [5]:


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

مع 2 بت لكل كتلة أو 4 كتل لكل بايت ، تشغل سمات الصورة فقط 240/4 = 60 بايت ، ولكن نظرًا لطريقة تشفيرها ، يتم إهدار 4 بايت أخرى ، أي في إجمالي 64 بايت. هذا يعني أن الصورة بأكملها ، بما في ذلك CHR و nametable واللوحات والسمات ، تشغل 4096 + 960 + 16 + 64 = 5136 بايت - أفضل بكثير من 30720 المذكور أعلاه.

MAKECHR


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

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

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


في الغالب المعرفة حول البرمجة لـ NES ، وخاصة حول إنشاء الرسومات ، لقد حصلت على من المصادر التالية:


الملاحظات


[1] المصطلحات - في بعض الوثائق ، تسمى الكتل "بلاطات التلوي" ، والتي تبدو شخصياً أقل فائدة بالنسبة لي.

[2] تشفير CHR - لا يتم تخزين 2 بت لكل بكسل بجانب بعضها البعض. يتم حفظ الصورة الكاملة أولاً مع البتات المنخفضة فقط ، ثم يتم حفظها مرة أخرى فقط مع البتات العالية.

وهذا هو ، سيتم تخزين القلب مثل هذا:

بكسل القلب منخفضةبكسل القلب عالية

كل سطر هو بايت واحد. وهذا هو ، 01100110 هو 66 دولار ، 01111111 هو 7f دولار. في المجموع ، تبدو بايتات القلب كما يلي:

$ 66 $ 7f $ ff $ ff $ ff $ 7e $ 3c $ 18 $ 66 $ 5f $ bf $ bf $ ff $ 7e $ 3c $ 18

[3] Nametable - في هذا المخطط في اللعبة ، يتم استخدام جدول الاسم بشكل مختلف. عادة ، يتم حفظ الحروف الأبجدية في الذاكرة في الحي ، بما في ذلك Castlevania.

[4] لا يستخدم نظام الألوان - NES لوحة ألوان RGB ، والألوان الفعلية التي يتم تقديمها تعتمد على جهاز تلفزيون معين. عادةً ما تستخدم برامج محاكاة الألوان لوحات RGB مختلفة تمامًا. الألوان في هذه المقالة تتوافق مع لوحة مكتوبة في makechr.

[5] سمة ترميز - يتم تخزين السمات في ترتيب غريب. لا ينتقلون من اليسار إلى اليمين ، من أعلى إلى أسفل - يتم تشفير منطقة الكتلة 2 × 2 مع بايت واحد ، في شكل الحرف Z. ولهذا السبب تُهدر 4 بايت ؛ خلاصة القول هي 8 بايت كامل.

PAL-كتلة مجموعة

على سبيل المثال ، يتم تخزين كتلة بقيمة 308 دولارات بـ 30 دولارًا و 348 دولارًا و 34 دولارًا أمريكيًا. قيم لوح الألوان هي 1 و 2 و 3 و 3 ، ويتم تخزينها بالترتيب من أدنى موضع إلى أعلى موضع ، أو 11 :: 11 :: 10 :: 01 = 11111001. لذلك ، فإن قيمة البايت لهذه السمات هي f9 دولار.

الجزء 2


في الجزء الأول ، تحدثنا عن مكونات رسومات الخلفية NES - CHR ، nametable ، اللوحات والسمات. ولكن هذا ليس سوى نصف القصة.

بادئ ذي بدء ، هناك بالفعل جدولان للاسم [6]. كل واحد منهم لديه سماته الخاصة لإعداد اللون ، ولكن لديهم نفس CHR. تحدد معدات الخرطوشة موقعها: إما بجانب بعضها البعض ، أو واحد فوق الآخر. فيما يلي أمثلة لنوعين مختلفين من المواقع - Lode Runner (1984) و Bubble Bobble (1988).


التمرير فقاعة مزركشة،

التمرير


للاستفادة من وجود جدولين للاسماء ، تدعم PPU القدرة على التمرير بالبكسل في وقت واحد على طول محوري X و Y. يتم التحكم به بواسطة سجل به ذاكرة في 2005 دولار: كتابة وحدتي بايت فقط في هذا العنوان ينقل الشاشة بأكملها إلى العدد المطلوب من البكسلات [7] . في وقت إصدار NES ، كانت هذه الميزة الرئيسية على غيرها من لوحات المفاتيح المنزلية ، والتي كان عليها في كثير من الأحيان للتمرير إعادة كتابة ذاكرة الفيديو بالكامل. أدى مثل هذا المخطط سهل الاستخدام إلى ظهور عدد كبير من منصات اللعب والرماة ، وأصبح السبب الرئيسي لهذا النجاح الكبير للنظام.

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


العفاريت


بالإضافة إلى التمرير خلال جداول الأسماء ، كان لدى NES أيضًا جانب مختلف تمامًا من الرسومات: العفاريت. على عكس جداول الأسماء التي تحتاج إلى محاذاة في الشبكات ، يمكن وضع العفاريت بشكل تعسفي ، بحيث يمكن استخدامها لعرض شخصيات اللاعب والعقبات والمقذوفات وأي كائنات بحركة معقدة. على سبيل المثال ، في المشهد أعلاه من Mega Man (1987) لعرض شخصية اللاعب. يتم استخدام نقاط وشرائط الطاقة العفاريت ، والتي تتيح لهم الخروج من شبكة جداول الأسماء عند التمرير على الشاشة.

Sprites لها صفحة CHR خاصة بها [8] ومجموعة من 4 لوحات. بالإضافة إلى ذلك ، يشغلونها صفحة ذاكرة 256 بايت. الذي يسرد موضع ومظهر كل العفريت (كما اتضح ، ذاكرة الفيديو NES أكبر مرتين ونصف من المذكورة في الجزء الأول من المقالة). تنسيق هذه السجلات غير معتاد إلى حد ما - فهي تحتوي أولاً على موضع في Y ، ثم رقم تجانب ، ثم سمة ، ثم موضع في X [9]. نظرًا لأن كل سجل يستغرق 4 بايت ، هناك قيود صارمة: على الشاشة لا يمكن أن يكون هناك أكثر من 256/4 = 64 نقش في وقت واحد.

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

قيود


تسمح الأنظمة الحديثة بالعمل مع العفاريت من أي حجم تعسفي ، ولكن على NES ، كان يجب أن يكون حجم العفريت بسبب قيود CHR 8 × 8 [11]. تتكون الكائنات الأكبر حجمًا من عدة نقوش ، ويجب أن يتأكد البرنامج من أن جميع الأجزاء الفردية يتم تقديمها بجوار بعضها البعض. على سبيل المثال ، يمكن أن يصل حجم حرف Megaman إلى 10 أشكال ، مما يسمح لك أيضًا باستخدام المزيد من الألوان ، خاصة لعينيه البيضاء ولون البشرة.


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

المتعري وميض

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

الملاحظات


[6] من الناحية النظرية ، هناك في الواقع أربعة جداول أسماء ، لكنها معكوسة بطريقة تحتوي فقط 2 منها على رسومات فريدة. عند وضعها جنبًا إلى جنب ، يُسمى ذلك النسخ المتطابق الرأسي ، وعندما توجد جداول الأسماء واحدة فوق الأخرى ، النسخ المتطابق الأفقي.

[7] يوجد أيضًا سجل يحدد جدول الأسماء الذي سيبدأ التقديم به ، أي أن التمرير هو في الواقع قيمة 10 بت أو 9 بت ، مع مراعاة النسخ المتطابق.

[8] ليس هذا هو الحال دائمًا. يمكن تكوين PPU لاستخدام نفس الصفحة CHR لجداول الأسماء كما هو الحال مع العفاريت.

[9] ربما تم استخدام هذا الترتيب لأنه يتوافق مع البيانات التي تحتاجها وحدة معالجة البرامج لمعالجة التقديم الفعال.

[10] تُستخدم هذه القطعة في تأثيرات مختلفة ، على سبيل المثال ، لتحريك Mario أسفل الكتل البيضاء في Super Mario Bros 3 ، أو لتكوين ضباب فوق العفاريت في Castlevania 3.

[11] تحتوي PPU أيضًا على خيار تمكين 8 × 16 sprites ، والذي يستخدم في ألعاب مثل Contra ، حيث توجد شخصيات طويلة. ومع ذلك ، يتم تطبيق جميع القيود الأخرى.

الجزء 3


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

أداء



النقطية تقديم مع وقفة ل vblank

مثل أجهزة الكمبيوتر القديمة الأخرى ، تم تصميم NES للعمل مع أجهزة تلفزيون CRT. يرسمون خطوط المسح على الشاشة ، واحدة في كل مرة ، من اليسار إلى اليمين ، من أعلى إلى أسفل ، باستخدام بندقية الإلكترون الذي ينتقل فعليا إلى نقطة على الشاشة حيث يتم رسم هذه الخطوط. بعد الوصول إلى الزاوية السفلية ، تحدد فترة زمنية تسمى "الفراغ العمودي" (أو vblank): يعود مسدس الإلكترون إلى الزاوية اليسرى العليا للتحضير لرسم الإطار التالي. داخل NES ، تقوم وحدة معالجة الصورة (PPU) بمعالجة عرض البيانات النقطية تلقائيًا ، في كل إطار ، ويقوم الكود الذي يعمل في وحدة المعالجة المركزية بجميع المهام التي يجب أن تؤديها اللعبة. يسمح Vblank للبرنامج باستبدال البيانات الموجودة في ذاكرة PPU ، وإلا فسيتم استخدام هذه البيانات للتقديم. في معظم الأحيان ، يتم إجراء تغييرات على جدول الأسماء ولوحات PPU أثناء هذه النافذة الصغيرة.

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

تقسيم الشاشة



التمرير المستوى ، والواجهة في الجزء العلوي من الشاشة لا تزال ثابتة

أولاً ، تحتوي وحدة PPU على أجهزة مضمنة تقوم بمعالجة sprite في موضع الذاكرة الصفرية بطريقة خاصة. عند تقديم هذا العفريت ، إذا تداخل أحد وحدات البكسل الخاصة به مع الجزء المرئي من الخلفية ، يتم تعيين بت يسمى "إشارة sprite0". يمكن لرمز اللعبة أولاً وضع هذا العفريت حيث يجب أن يحدث انقسام الشاشة ، ثم الانتظار في حلقة ، والتحقق من قيمة علامة sprite0. لذلك ، عند الخروج من الحلقة ، ستعرف اللعبة بالتأكيد الخط النقطي الذي يتم تقديمه حاليًا. تُستخدم هذه التقنية لتنفيذ مشاركة الشاشة البسيطة في العديد من ألعاب NES ، بما في ذلك Ninja Gaiden (1989) ، الموضح أعلاه [12]

النينجا هود

يقع Sprite0 عند Y $ 26 ، X $ a0. عند تقديم صف البكسل السفلي ، يتم تعيين علامة sprite0

في بعض الألعاب ، يتم دمج إشارة sprite0 مع تقنية أخرى - حلقة ذات توقيت يمكن التنبؤ به ("دورة ذات توقيت يمكن التنبؤ به"): ينتظر البرنامج حتى يتم تقديم بضعة خطوط إضافية لتقسيم الشاشة إلى أجزاء أكثر. على سبيل المثال ، يتم استخدام هذه التقنية في العديد من شاشات توقف Ninja Gaiden لإنشاء تأثيرات جذرية ، على سبيل المثال ، حقل يحركه الرياح أو صورة لقلعة في البعد. , , , sprite0 , timed loops.

ninjas-in-field

قلعة عرض

, , . ( , (memory mapping)), [13], . , . NES, , .

على مستوى القطار

Ninja Gaiden 2, , , . , ; . , .


, . , , [14]. ( ), CHR, , . , . . Ninja Gaiden , , CHR.

goofall-BG

,

goofall-الشمالي

,


CHR. ,

الذين، هي، أنها القاع البنوك

CHR.

, ( ) . , , . , . , [15]. , CHR.

المعادن عاصفة-BG

Metal Storm (1991 )

المعادن عاصفة الإقليم الشمالي



CHR — , . , ; . , CHR , , . , , , .


نائب النار

Vice: Project Doom (1991 ) , . , .

السيف رئيسية

Sword Master (1990 ) , .

شكر


, FCEUX. , sprite0 NesDev:


الملاحظات


[12] , Ninja Gaiden . 8×16 sprites — , PPU, . sprite0 , sprite1 . z- , , .

[13] . . PPU , , . (IRQ), , , .

[14] , , . , , - 4 8 .

[15] CHR : , . , , 1 , .

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


All Articles