قبل بضعة أسابيع ، قررت العمل في لعبة لـ Game Boy ، والتي منحتني إبداعها سرورًا كبيرًا. اسم العمل هو أكوا والرماد. اللعبة مفتوحة المصدر ويتم نشرها على
جيثب . الجزء السابق من المقال
هنا .
العفاريت رائعة وحيث يعيشون
في الجزء الأخير ، انتهيت من تقديم العديد من العفاريت على الشاشة. وقد تم ذلك بطريقة تعسفية للغاية والفوضى. في الواقع ، كان علي أن أشير في الكود إلى ماذا وأين أريد عرضه. هذا جعل إنشاء الرسوم المتحركة مستحيلًا تقريبًا ، وقضى الكثير من وقت وحدة المعالجة المركزية ودعم التعليمات البرمجية المعقدة. كنت بحاجة إلى طريقة أفضل.
على وجه التحديد ، كنت بحاجة إلى نظام يمكنني فيه ببساطة تكرار رقم الحركة ورقم الإطار والموقت لكل رسم متحرك فردي. إذا كنت بحاجة إلى تغيير الرسوم المتحركة ، أود فقط تغيير الرسوم المتحركة وإعادة ضبط عداد الإطار. يجب أن يختار إجراء الحركة في كل إطار ببساطة العفاريت المناسبة لعرضها ورميها على الشاشة دون أي مجهود من جانبي.
وكما اتضح ، تم حل هذه المهمة عمليا. ما كنت أبحث عنه يسمى
تعيينات العفريت . خرائط العفريت هي هياكل بيانات تحتوي (بشكل تقريبي) على قائمة من العفاريت. كل خريطة العفريت تحتوي على جميع العفاريت لتقديم كائن واحد. ترتبط أيضًا بها
خرائط الرسوم المتحركة (تعيينات الرسوم المتحركة) ، وهي قوائم خرائط العفاريت مع معلومات حول كيفية تنفيذ الحلقة.
إنه لأمر مضحك أنه في شهر مايو ، أضفت محرر خرائط رسوم متحركة إلى محرر خرائط sprite الجاهزة لألعاب Sonic ذات 16 بت حول Sonic. (إنه
هنا ، يمكنك الدراسة) لم يكتمل بعد ، لأنه خشن إلى حد ما ، وبطئ مؤلم وغير مريح للاستخدام. ولكن من الناحية الفنية ، فإنه يعمل. ويبدو
لي أنه رائع ... (أحد أسباب الخشونة هو أنني عملت حرفيًا أولاً مع إطار JavaScript). Sonic هي لعبة قديمة ، لذا فهي مثالية كأساس لعبتي القديمة الجديدة.
سونيك 2 تنسيق بطاقة
كنت أنوي استخدام المحرر في Sonic 2 لأنني أردت إنشاء أداة اختراق لـ Genesis. Sonic 1 و 3K متشابهان بشكل أساسي تقريبًا ، لكن حتى لا أقوم بالتعقيد ، سأقتصر على قصة الجزء الثاني.
أولاً ، دعنا ننظر إلى خرائط العفريت. إليكم شبح Tails نموذجي ، جزء من الرسوم المتحركة الوامضة.
وحدة التحكم Genesis يخلق العفاريت بشكل مختلف قليلا. بلاط Genesis (يطلق عليه معظم المبرمجين "النمط") 8 × 8 ، تمامًا مثل Game Boy. يتكون العفريت من مستطيل يصل إلى 4 × 4 بلاطات ، يشبه إلى حد كبير وضع العفريت 8x16 على Game Boy ، ولكنه أكثر مرونة. الحيلة هنا هي أنه في الذاكرة هذه البلاط يجب أن تكون بجانب بعضها البعض. أراد مطورو Sonic 2 إعادة استخدام أكبر عدد ممكن من الإطارات لإطار Tails الوامض من إطار Tails دائم. لذلك ، يتم تقسيم Tails إلى 2 عدوانية للأجهزة ، تتكون من بلاطات 3 × 2 - واحدة للرأس ، والأخرى للجسم. وتظهر في الشكل أدناه.
الجزء العلوي من مربع الحوار هذا هو سمات العفريت الأجهزة. يحتوي على موضعه بالنسبة لنقطة البداية (يتم قطع الأرقام السالبة ؛ في الواقع ، هذه هي -16 و -12 للعفريت الأول و -12 للثاني) ، التجانب الأولي المستخدم في VRAM ، عرض وارتفاع العفريت ، وكذلك مختلف بتات الحالة لـ صورة طبق الأصل من العفريت واللوحة.
يتم عرض البلاط في الأسفل عند تحميلها من ROM إلى VRAM. لا توجد مساحة كافية لتخزين جميع رموز Tails في VRAM ، لذلك يجب نسخ البلاط الضروري إلى الذاكرة في كل إطار. يطلق عليهم
العظة تحميل نمط الديناميكي . ومع ذلك ، في حين يمكننا تخطيها ، لأنها مستقلة تقريبًا عن خرائط العفاريت ، وبالتالي يمكن إضافتها بسهولة لاحقًا.
بالنسبة للرسوم المتحركة ، كل شيء هنا أسهل قليلاً. خريطة الرسوم المتحركة في Sonic هي قائمة بخرائط العفريت مع قطعتين من البيانات الوصفية - قيمة السرعة والإجراء الذي يجب اتخاذه بعد انتهاء الرسوم المتحركة. الإجراءات الثلاثة الأكثر شيوعًا هي: حلقة فوق جميع الإطارات ، حلقة فوق إطارات N الأخيرة ، أو الانتقال إلى رسم متحرك مختلف تمامًا (على سبيل المثال ، عند التبديل من رسم متحرك سونيك واقعي إلى رسم متحرك ينقر بفقد صبره على قدمه). هناك بضعة أوامر تحدد الإشارات الداخلية في ذاكرة الكائنات ، لكن ليس هناك الكثير من الكائنات التي تستخدمها. (الآن حدث لي أنه يمكنك تعيين بعض الشيء في ذاكرة الوصول العشوائي للكائن إلى قيمة عند تكرار الحركة. سيكون ذلك مفيدًا للمؤثرات الصوتية وغيرها من الأشياء.)
إذا نظرت إلى رمز
Sonic 1 المفكك (رمز Sonic 2 كبير جدًا بحيث لا يمكن الارتباط به) ، ستلاحظ أن الرابط إلى الرسوم المتحركة لا يتم بواسطة أي معرف. يتم إعطاء كل كائن قائمة بالرسوم المتحركة ، ويتم تخزين فهرس الرسوم المتحركة في الذاكرة. لتقديم رسوم متحركة محددة ، تأخذ اللعبة فهرسًا وتبحث عنها في قائمة الرسوم المتحركة ، ثم تعرضها. هذا يجعل العمل أسهل قليلاً ، لأنك لا تحتاج إلى مسح الرسوم المتحركة للعثور على ما تحتاجه.
نحن تنظيف الحساء من الهياكل
لنلقِ نظرة على أنواع البطاقات:
- خرائط العفاريت: قائمة العفاريت التي تتكون من تجانب أولي ، وعدد التجانبات ، والموضع ، وحالة الانعكاس (العاكسة معكوسة أم لا) ولوحة.
- DPLC: قائمة بلاطات ROM التي يجب تحميلها في VRAM. يتكون كل عنصر في DPLC من بلاطة أولية وطول ؛ يتم وضع كل عنصر في VRAM بعد الأخير.
- خرائط الرسوم المتحركة: قائمة الرسوم المتحركة التي تتألف من قائمة خرائط العفاريت ، وقيم السرعة ، وإجراءات الدورة.
- قائمة الرسوم المتحركة: قائمة المؤشرات إلى عمل كل الرسوم المتحركة.
نظرًا لأننا نعمل مع Game Boy ، يمكن إجراء بعض التبسيطات. نحن نعلم أنه في خرائط العفريت في العفريت 8x16 سيكون هناك دائما اثنين من البلاط. ومع ذلك ، يجب الحفاظ على كل شيء آخر. في الوقت الحالي ، يمكننا التخلي عن DPLC تمامًا وتخزين كل شيء في VRAM. هذا حل مؤقت ، لكن ، كما قلت ، ستكون هذه المشكلة سهلة الحل. أخيرًا ، يمكننا تجاهل قيمة السرعة إذا افترضنا أن كل رسوم متحركة تعمل بنفس السرعة.
لنبدأ في معرفة كيفية تطبيق نظام مماثل في لعبتي.
تحقق مع الالتزام
2e5e5b7 !
لنبدأ بخرائط العفريت. يجب أن يعكس كل عنصر في الخريطة OAM (ذاكرة سمة الكائن - sprite VRAM) وبالتالي فإن حلقة بسيطة و memcpy ستكون كافية لعرض الكائن. اسمحوا لي أن أذكركم بأن
عنصرًا في OAM يتكون من Y و X و تجانب أولي و بايت سمة . أنا فقط بحاجة إلى إنشاء قائمة منهم. باستخدام المشغلة الزائفة المجمعة EQU ، قمت بإعداد بايت السمة مقدمًا بحيث كان لدي اسم قابل للقراءة لكل مجموعة ممكنة من السمات. (يمكنك أن ترى أنه في الالتزام السابق ، استبدلت بلاطة Y / X في البطاقات. حدث هذا لأنني قرأت مواصفات OAM عن غير قصد. لقد أضفت أيضًا عدادًا للعفاريت لمعرفة المدة التي يجب أن تستغرقها الحلقة.)
ستلاحظ أن الجسم والذيل من الثعلب القطبي يتم تخزينها بشكل منفصل. إذا تم تخزينها معًا ، فسيكون هناك
الكثير من التكرار ، لأنه يجب تكرار كل رسم متحرك لكل حالة خلفية. وسوف يزداد حجم التكرار بسرعة. في Sonic 2 ، نشأت نفس المشكلة مع Tails. لقد قاموا بحلها هناك ، مما يجعل ذيول Tails كائنًا منفصلًا مع حالة الحركة الخاصة به وجهاز ضبط الوقت. لا أريد القيام بذلك لأنني لا أحاول حل مشكلة الحفاظ على موضع الذيل الصحيح بالنسبة إلى الثعلب.
أنا حل المشكلة من خلال خرائط الرسوم المتحركة. إذا نظرت إلى خريطة الرسوم المتحركة (الفردية) الخاصة بي ، فهناك ثلاثة أجزاء من البيانات الوصفية فيها. يعرض عدد بطاقات الرسوم المتحركة ، لذلك أعرف متى ستنتهي. (في Sonic ، تم التحقق من أن الرسوم المتحركة التالية غير صالحة ، على غرار مفهوم صفر بايت في خطوط C. حل من Sonic يحرر القضية ، لكنه يضيف مقارنة من شأنها أن تعمل ضدي.) بالطبع ، لا يزال هناك عمل حلقة. (لقد حولت دارة Sonic ثنائية البايت إلى رقم 1 بايت يكون بت 7 فيه هو بت الوضع.) لكن لدي أيضًا عدد
بطاقات العفريت ، لكنها لم تكن في Sonic. يتيح لي وجود خرائط sprite متعددة لكل إطار رسوم متحركة إعادة استخدام الرسوم المتحركة في العديد من الرسوم المتحركة ، والتي ، في رأيي ، ستوفر الكثير من المساحة الثمينة. يمكنك أيضًا ملاحظة أن الرسوم المتحركة مكررة لكل اتجاه. هذا لأن خرائط كل اتجاه مختلفة ، وتحتاج إلى إضافتها.
الرقص مع السجلات
الرجوع إلى
هذا الملف في 1713848.
لنبدأ برسم نقش واحد على الشاشة. لذلك ، أنا أعترف ، كذبت. دعني أذكرك بأنه لا يمكننا التسجيل على الشاشة خارج VBlank. وهذه العملية برمتها طويلة جدًا لتناسبها في VBlank. لذلك ، نحتاج إلى تسجيل مساحة الذاكرة التي سنخصصها لـ DMA. في النهاية ، لا يغير أي شيء ، من المهم أن تسجل في المكان الصحيح.
لنبدأ عد السجلات. يحتوي المعالج GBZ80 على 6 سجلات ، من A إلى E و H و L. H و L هي سجلات خاصة ، لذلك فهي مناسبة تمامًا لأداء التكرار من الذاكرة. (نظرًا لاستخدامهم معًا ، يطلق عليهم HL.) في شفرة تشغيل واحدة ، يمكنني الكتابة إلى عنوان الذاكرة الموجود في HL وإضافة واحد إليه. هذا صعب التعامل معه. يمكنك استخدامه إما كمصدر أو كوجهة. لقد استخدمتها كعناوين ، والجمع بين سجلات BC كمصدر ، لأنها كانت أكثر ملاءمة. لدينا فقط A و D و E. أحتاج إلى تسجيل A للعمليات الرياضية وما شابه ذلك. ماذا يمكن أن تستخدم DE ل؟ يمكنني استخدام D بمثابة عداد حلقة ، و E كمساحة عمل. وهذا هو المكان الذي انتهت فيه السجلات.
دعنا نقول لدينا 4 العفاريت. قمنا بتعيين السجل D (عداد الدورة) إلى 4 ، وسجل HL (الوجهة) وعنوان المخزن المؤقت OAM ، و BC (المصدر) الموقع في ROM حيث يتم تخزين بطاقاتنا. الآن أود الاتصال بـ memcpy. ومع ذلك ، تنشأ مشكلة صغيرة. تذكر إحداثيات X و Y؟ يتم الإشارة إليها بالنسبة لنقطة البداية ، يتم استخدام مركز الكائن للتصادم وما شابه. إذا سجلناها كما هي ، فسيتم عرض كل كائن في الركن الأيسر العلوي من الشاشة. هذا لا يناسبنا. لإصلاح ذلك ، نحتاج إلى إضافة إحداثيات X و Y للكائن إلى X و Y للعفريت.
ملاحظة قصيرة: أتحدث عن "الأشياء" ، لكنني لم أشرح لك هذا المفهوم. الكائن هو مجرد مجموعة من السمات المرتبطة بالكائن في اللعبة. السمات هي الموقف والسرعة والاتجاه. وصف العنصر ، إلخ. أنا أتحدث عن هذا لأنني بحاجة لاستخراج بيانات X و Y من هذه الكائنات ، ولعمل ذلك ، نحتاج إلى مجموعة ثالثة من السجلات تشير إلى مكان RAM في الكائنات التي توجد بها الإحداثيات. ثم نحتاج إلى تخزين X و Y في مكان ما ، وينطبق الشيء نفسه على الاتجاه ، لأنه يساعدنا في تحديد الاتجاه الذي تبحث فيه العفاريت. بالإضافة إلى ذلك ، نحتاج إلى تقديم
جميع الكائنات ، لذلك تحتاج أيضًا إلى عداد حلقة. ونحن لم نصل إلى الرسوم المتحركة حتى الآن! كل شيء بسرعة يخرج عن نطاق السيطرة ...
مراجعة القرار
لذلك ، أنا أركض بعيدًا جدًا. دعنا نعود ونفكر في كل جزء من البيانات أحتاج إلى تتبعه ، ومكان كتابته.
للبدء ، دعنا نقسم هذا إلى "خطوات". يجب أن تتلقى كل خطوة بيانات للخطوة التالية فقط ، باستثناء الخطوة الأخيرة التي تنفذ النسخة.
- كائن (حلقة) - يكتشف ما إذا كان يجب تقديم الكائن ، وعرضه.
- قائمة الرسوم المتحركة - تحدد الرسوم المتحركة التي سيتم عرضها. كما يحصل على سمات كائن.
- الرسوم المتحركة (حلقة) - تحدد قائمة الخرائط المراد استخدامها ، وتجعل كل خريطة منها.
- خريطة (دورة) - يمر كل عفاري في قائمة العفاريت
- العفريت - نسخ سمات العفريت إلى OAM العازلة
بالنسبة لكل مرحلة من المراحل التي قمت بإدراجها في قائمة المتغيرات التي يحتاجون إليها ، والأدوار التي يلعبونها وأماكن تخزينها. هذا الجدول يبدو مثل هذا.
الوصف | الحجم | المرحلة | استخدام | من أين | المكان | إلى أين |
---|
OAM العازلة | 2 | العفريت | المؤشر | Hl | Hl | |
مصدر الخريطة | 2 | العفريت | المؤشر | قبل الميلاد | قبل الميلاد | |
البايت الحالي | 1 | العفريت | مساحة العمل | مصدر الخريطة | هـ | |
اكس | 1 | العفريت | متغير | حيرام | أ | |
نعم | 1 | العفريت | متغير | حيرام | أ | |
|
بداية خريطة الرسوم المتحركة | 2 | خريطة العفريت | المؤشر | Stack3 | دي | |
مصدر الخريطة | 2 | خريطة العفريت | المؤشر | [DE] | قبل الميلاد | |
العفاريت المتبقية | 1 | خريطة العفريت | خدش | مصدر الخريطة | د | |
OAM العازلة | 1 | خريطة العفريت | المؤشر | Hl | Hl | المكدس 1 |
|
بداية خريطة الرسوم المتحركة | 2 | الرسوم المتحركة | مساحة العمل | قبل الميلاد / Stack3 | قبل الميلاد | Stack3 |
البطاقات المتبقية | 1 | الرسوم المتحركة | مساحة العمل | بدء الرسوم المتحركة | حيرام | |
إجمالي عدد البطاقات | 1 | الرسوم المتحركة | متغير | بدء الرسوم المتحركة | حيرام | |
اتجاه الكائن | 1 | الرسوم المتحركة | متغير | حيرام | حيرام | |
بطاقات في الإطار | 1 | الرسوم المتحركة | متغير | بدء الرسوم المتحركة | لا تستخدم !!! | |
رقم الإطار | 1 | الرسوم المتحركة | متغير | حيرام | أ | |
خريطة المؤشر | 2 | الرسوم المتحركة | المؤشر | AnimStart + Dir * TMC + MpF * F # | قبل الميلاد | دي |
OAM العازلة | 2 | الرسوم المتحركة | المؤشر | المكدس 1 | Hl | |
|
بداية جدول الرسوم المتحركة | 2 | قائمة الرسوم المتحركة | مساحة العمل | مجموعة صعبة | دي | |
مصدر الكائن | 2 | قائمة الرسوم المتحركة | المؤشر | Hl | Hl | Stack2 |
رقم الإطار | 1 | قائمة الرسوم المتحركة | متغير | مصدر الكائن | حيرام | |
رقم الرسوم المتحركة | 1 | قائمة الرسوم المتحركة | مساحة العمل | مصدر الكائن | أ | |
كائن X | 1 | قائمة الكائنات | متغير | مصدر الكائن | حيرام | |
كائن ص | 1 | قائمة الرسوم المتحركة | متغير | مصدر الكائن | حيرام | |
اتجاه الكائن | 1 | قائمة الرسوم المتحركة | متغير | Obj src | حيرام | |
بداية خريطة الرسوم المتحركة | 2 | قائمة الرسوم المتحركة | المؤشر | [Anim Table + Anim #] | قبل الميلاد | |
OAM العازلة | 2 | قائمة الرسوم المتحركة | المؤشر | دي | المكدس 1 | |
|
مصدر الكائن | 2 | دورة الكائن | علامة | مجموعة الثابت / Stack2 | Hl | |
الأشياء المتبقية | 1 | دورة الكائن | متغير | محسوبة | ب | |
حقل بت نشط للكائن | 1 | دورة الكائن | متغير | محسوبة | ج | |
OAM العازلة | 2 | دورة الكائن | المؤشر | مجموعة صعبة | دي | |
نعم ، مربكة للغاية. لكي أكون صادقًا تمامًا ، قمت بوضع هذا الجدول للنشر فقط ، لشرح أكثر وضوحًا ، لكنه بدأ بالفعل مفيدًا. سأحاول شرح ذلك ، لنبدأ من النهاية ونبدأ من البداية. سترى كل جزء من البيانات التي أبدأ بها: مصدر الكائن ، المخزن المؤقت OAM ، ومتغيرات الحلقة precomputed. في كل دورة ، نبدأ بهذا وهذا فقط ، باستثناء أنه يتم تحديث مصدر الكائن في كل دورة.
لكل كائن نعرضه ، من الضروري تحديد الرسوم المتحركة المعروضة. أثناء قيامنا بذلك ، يمكننا أيضًا حفظ سمات X و Y و Frame # و Direction قبل زيادة مؤشر الكائن إلى الكائن التالي وحفظها على بنية تخزين العناصر لتسترجعها عند الخروج. نحن نستخدم رقم الرسوم المتحركة بالاقتران مع جدول الرسوم المتحركة الثابت في الكود لتحديد أين تبدأ خريطة الرسوم المتحركة. (هنا أبسط ، بافتراض أن كل كائن له نفس جدول الرسوم المتحركة. هذا يحدني بـ 256 من الرسوم المتحركة لكل لعبة ، لكن من غير المحتمل أن تتجاوز هذه القيمة.) يمكننا أيضًا كتابة مخزن مؤقت لـ OAM لحفظ بعض السجلات.
بعد استخراج خريطة الرسوم المتحركة ، نحتاج إلى العثور على مكان وجود قائمة خرائط العفاريت للإطار والاتجاه المحدد ، وكذلك عدد الخرائط التي يجب تقديمها. قد تلاحظ أن متغير البطاقة لكل إطار غير مستخدم. حدث ذلك لأنني لم أفكر وتعيين القيمة الثابتة 2. أنا بحاجة إلى إصلاحه. نحتاج أيضًا إلى استخراج المخزن المؤقت OAM من المكدس. قد تلاحظ أيضًا نقصًا تامًا في التحكم في الدورة. يتم تنفيذه في إجراء فرعي منفصل أبسط بكثير ، والذي يسمح لك بالتخلص من شعوذة مع السجلات.
بعد ذلك ، يصبح كل شيء بسيطًا جدًا. الخريطة عبارة عن مجموعة من العفاريت ، لذلك ندور حولها في حلقة ونرسم على أساس إحداثيات X و Y المخزنة.
ما هي النتيجة النهائية لكل هذا؟ بالضبط نفس الشيء من قبل: ثعلب قطبي يلوح بذيله في الظلام. لكن إضافة الرسوم المتحركة الجديدة أو العفاريت أصبح الآن أكثر سهولة. في الجزء التالي ، سأتحدث عن خلفيات معقدة والتمرير المنظر.
الجزء 4. المنظر الخلفية
واسمحوا لي أن أذكرك ، في المرحلة الحالية ، بأن لدينا نقوش متحركة على خلفية سوداء صلبة. إذا لم أخطط لإنشاء لعبة أركيد في السبعينيات ، فمن الواضح أن هذا لن يكون كافيًا. أحتاج إلى نوع من صورة الخلفية.
في الجزء الأول ، عندما كنت أرسم الرسومات ، قمت أيضًا بإنشاء عدة مربعات خلفية. لقد حان الوقت لاستخدامها. سيكون لدينا ثلاثة أنواع "أساسية" من البلاط (السماء والعشب والأرض) واثنين من البلاط الانتقالي. يتم تحميل كل منهم في VRAM وجاهزة للاستخدام. الآن علينا فقط أن نكتبها في الخلفية.
الخلفية
يتم تخزين الخلفيات الموجودة على Game Boy في الذاكرة في صفيف 32 × 32 من مربعات 8 × 8. كل 32 بايت يتوافق مع سطر واحد من البلاط.
حتى الآن ، أخطط لتكرار نفس
عمود المربعات في مساحة 32x32 بأكملها. هذا رائع ، لكنه يخلق مشكلة صغيرة: سأحتاج إلى تعيين
كل مربع 32 مرة على التوالي. سيكون وقت طويل للكتابة.
غريزي ، قررت استخدام الأمر REPT لإضافة 32 بايت / سطر ، ثم استخدم memcpy لنسخ الخلفية إلى VRAM.
REPT 32 db BG_SKY ENDR REPT 32 db BG_GRASS ENDR ...
ومع ذلك ، فهذا يعني أنه يتعين عليك تخصيص 256 بايت لخلفية واحدة فقط ، وهذا كثير جدًا. تتفاقم هذه المشكلة إذا كنت تتذكر أن نسخ خريطة خلفية تم إنشاؤها مسبقًا باستخدام memcpy لن يسمح لك بإضافة أنواع أخرى من الأعمدة (على سبيل المثال ، البوابات والعقبات) دون تعقيد كبير وأكوام من خرطوشة ROM الضائعة.
بدلاً من ذلك ، قررت إعداد عمود واحد كما يلي:
db BG_SKY, BG_SKY, BG_SKY, ..., BG_GRASS
ثم استخدم حلقة بسيطة لنسخ كل عنصر في هذه القائمة 32 مرة. (انظر
LoadGFX
ملف LoadGFX
من الالتزام 739986a .)
راحة هذا النهج هي أنه في وقت لاحق يمكنني إضافة قائمة انتظار لكتابة شيء مثل هذا:
BGCOL_Field: db BG_SKY, ... BGCOL_LeftGoal: db BG_SKY, ... BGCOL_RightGoal: db BG_SKY, ... ... BGMAP_overview: db 1 dw BGCOL_LeftGoal db 30 dw BGCOL_Field db 1 dw BGCOL_RightGoal db $FF
إذا قررت تقديم BGMAP_overview ، فسوف يرسم عمودًا واحدًا من LeftGoal ، وبعد ذلك سيكون هناك 30 عمودًا من الحقول وعمود واحد من RightGoal. إذا كان
BGMAP_overview
في ذاكرة الوصول العشوائي ،
BGMAP_overview
حسب موضع الكاميرا في X.
الكاميرا والموقف
أوه نعم ، الكاميرا. هذا مفهوم مهم لم أتحدث عنه حتى الآن. نحن هنا نتعامل مع العديد من الإحداثيات ، لذلك قبل الحديث عن الكاميرا ، سنحلل كل هذا أولاً.
نحن بحاجة إلى العمل مع نظامين تنسيق. الأول هو
إحداثيات الشاشة . هذه مساحة 256 × 256 يمكن تضمينها في VRAM لوحدة تحكم Game Boy. يمكننا التمرير للجزء المرئي من الشاشة ضمن 256 × 256 ، ولكن عندما نتجاوز الحدود ، فإننا ننهار.
في العرض ، أحتاج إلى أكثر من 256 بكسل ، لذلك أود أن أضيف
إحداثيات العالم ، والتي في هذه اللعبة سيكون لها أبعاد 65536x256. (لا أحتاج إلى ارتفاع إضافي في Y ، لأن اللعبة تجري في حقل مسطح.) هذا النظام منفصل تمامًا عن نظام تنسيق الشاشة. يجب إجراء جميع الفيزياء والاصطدامات في إحداثيات العالم ، لأنه وإلا فإن الكائنات ستصطدم بالكائنات على الشاشات الأخرى.
مقارنة بين إحداثيات الشاشة والعالمنظرًا لتمثيل مواضع كل الكائنات في إحداثيات العالم ، فيجب تحويلها إلى إحداثيات الشاشة قبل التقديم. عند الحافة اليسرى من العالم ، تتزامن إحداثيات العالم مع إحداثيات الشاشة. إذا احتجنا إلى عرض الأشياء على اليمين على الشاشة ، فعلينا أخذ كل شيء في إحداثيات العالم ونقله إلى اليسار بحيث يكون في إحداثيات الشاشة.
للقيام بذلك ، قمنا بتعيين متغير "الكاميرا X" ، والتي يتم تعريفها على أنها الحد الأيسر من الشاشة في العالم. على سبيل المثال ، إذا كانت
camera X
هي 1000 ، فيمكننا أن نرى إحداثيات العالم 1000-1192 ، لأن الشاشة المرئية يبلغ عرضها 192 بكسل.
لمعالجة الكائنات ، نأخذ موقعها ببساطة في X (على سبيل المثال ، 1002) ، ونطرح موضع الكاميرا يساوي 1000 ، ونرسم الكائن في الموضع المحدد بواسطة الفرق (في حالتنا ، 2). بالنسبة إلى الخلفية غير الموجودة في الإحداثيات العالمية ، ولكن الموضحة بالفعل في إحداثيات الشاشة ، فإننا نقوم بتعيين الموضع مساوٍ للبايت الأدنى لمتغير
camera X
بفضل هذا ، ستنتقل الخلفية إلى اليسار واليمين باستخدام الكاميرا.
المنظر
النظام الذي أنشأناه يبدو مسطحًا إلى حد ما. كل طبقة خلفية تتحرك في نفس السرعة. لا يشعر ثلاثي الأبعاد ، ونحن بحاجة إلى إصلاحه.
هناك طريقة بسيطة لإضافة محاكاة ثلاثية الأبعاد تسمى التمرير المنظر. تخيل أنك تقود سيارتك على طول الطريق وأنت متعب للغاية. نفد البطاريات لدى Game Boy وعليك أن تنظر من نافذة السيارة. إذا نظرت إلى الأرض بجانبك ، فسترى. أنها تتحرك بسرعة 70 ميلا في الساعة. ومع ذلك ، إذا نظرت إلى الحقول الموجودة على مسافة ، يبدو أنها تتحرك بشكل أبطأ. وإذا نظرت إلى الجبال البعيدة ، يبدو أنها بالكاد تتحرك.
يمكننا محاكاة هذا التأثير بثلاثة أوراق. إذا قمت برسم سلسلة جبال على ورقة واحدة ، فإن الحقل في الثانية والطريق على الورقة الثالثة ، ووضعها فوق بعضها مثل هذا. بحيث تكون كل طبقة مرئية ، سيكون تقليدًا لما نراه من نافذة السيارة. إذا كنا نرغب في نقل "السيارة" إلى اليسار ، فنحن نحرك الورقة العليا (مع الطريق) إلى أقصى اليمين ، السيارة التالية هي إلى اليمين قليلاً ، والأخيرة هي قليلاً إلى اليمين.
ومع ذلك ، عند تطبيق مثل هذا النظام على Game Boy ، تنشأ مشكلة صغيرة. تحتوي وحدة التحكم على طبقة خلفية واحدة فقط. هذا مشابه لحقيقة أن لدينا ورقة واحدة فقط. لا يمكنك إنشاء تأثير المنظر باستخدام ورقة واحدة فقط. أم أنه ممكن؟
H فارغة
يتم تقديم شاشة Game Boy سطرا سطرا. نتيجة لمحاكاة سلوك
أجهزة تلفزيون CRT القديمة ، هناك تأخير بسيط بين كل سطر. ماذا لو استطعنا استخدامه بطريقة ما؟ اتضح أن Game Boy لديها مقاطعة خاصة للأجهزة خصيصا لهذا الغرض.
على غرار مقاطعة VBlank ، التي اعتدنا عليها دائمًا الانتظار حتى نهاية الإطار للتسجيل في VRAM ، هناك مقاطعة HBlank. من خلال تعيين بت 6 من السجل على
$FF41
، وتشغيل مقاطعة
LCD STAT
، وكتابة رقم السطر على
$FF45
، يمكننا إخبار Game Boy ببدء مقاطعة
LCD STAT
عندما يكون على وشك رسم الخط المحدد (وعندما يكون في HBlank الخاص به).
خلال هذا الوقت ، يمكننا تغيير أي متغيرات VRAM. هذا ليس
كثيرًا من الوقت ، لذلك لا يمكننا تغيير أكثر من سجلين ، لكن لا يزال لدينا بعض الاحتمالات. نريد تغيير سجل التمرير الأفقي عند
$FF43
. في هذه الحالة ، سينتقل كل شيء على الشاشة أسفل الخط المحدد بمقدار معين من التغيير ، مما يخلق تأثيرًا مشابهًا.
إذا عدت إلى مثال الجبل ، يمكنك ملاحظة مشكلة محتملة. الجبال والسحب والزهور ليست خطوط مسطحة! لا يمكننا نقل الخط المحدد لأعلى ولأسفل أثناء عملية التقديم ؛ إذا اخترنا ذلك ، فسيظل كما هو على الأقل حتى HBlank التالي. وهذا هو ، يمكننا فقط قطع الخطوط المستقيمة.
لحل هذه المشكلة ، علينا أن نفعل أكثر ذكاء قليلا. يمكننا إعلان سطر ما في الخلفية كخط لا يمكن أن يعبره أي شيء ، مما يعني تغيير أنماط الكائنات أعلاه وتحته ، ولن يتمكن اللاعب من ملاحظة أي شيء. على سبيل المثال ، هذا هو المكان الذي توجد فيه هذه الخطوط مع الجبل.
أنا هنا صنعت شرائح أعلى وأسفل الجبل مباشرة. كل شيء من الأعلى إلى السطر الأول يتحرك ببطء ، كل شيء إلى السطر الثاني يتحرك بسرعة متوسطة ، وكل شيء أسفل هذا الخط يتحرك بسرعة. هذه خدعة بسيطة ولكنها ذكية. وللتعرف على ذلك ، يمكنك أن تلاحظه في العديد من الألعاب القديمة ، خاصة بالنسبة لـ Genesis / Mega Drive ، ولكن أيضًا على لوحات المفاتيح الأخرى. أحد الأمثلة الأكثر وضوحا هو
جزء الكهف من ميكي هوس. يمكنك أن تلاحظ أنه يتم فصل الصواعد والهوابط في الخلفية
تمامًا على طول خط أفقي مع حدود سوداء واضحة بين الطبقات.
أدركت نفس الشيء في خلفيتي. ومع ذلك ، هناك خدعة واحدة. افترض أن المقدمة تتحرك بسرعة واحدة تلو الأخرى تتزامن مع حركة الكاميرا ، وأن سرعة الخلفية هي ثلث حركة البكسل في الكاميرا ، أي أن الخلفية تتحرك مثل ثلث المقدمة. ولكن ، بالطبع ، ثلث بكسل غير موجود. لذلك ، أحتاج إلى تحريك الخلفية بمقدار بكسل واحد لكل ثلاث وحدات بكسل للحركة.
إذا كنت تعمل مع أجهزة الكمبيوتر القادرة على إجراء حسابات رياضية ، فستأخذ موضع الكاميرا وتقسيمه على 3 ويجعل هذه القيمة إزاحة خلفية. لسوء الحظ ، فإن Game Boy غير قادر على القيام بالتقسيم ، ناهيك عن حقيقة أن تقسيم البرنامج عملية بطيئة ومؤلمة للغاية. لا يبدو أن إضافة جهاز لتقسيمه (أو ضربه) إلى وحدة المعالجة المركزية الضعيفة لوحدة الترفيه المحمولة في الثمانينات خطوة فعالة من حيث التكلفة ، لذلك يتعين علينا اختراع طريقة أخرى.
في الكود ، قمت بما يلي: بدلاً من قراءة موضع الكاميرا من متغير ، طلبت أن تزيد أو تنقص. بفضل هذا ، مع كل زيادة ثالثة ، يمكنني إجراء زيادة في موضع الخلفية ومع كل زيادة أولية - زيادة في الموضع الأمامي. يؤدي هذا إلى تعقيد التمرير إلى موضع من الحافة الأخرى للحقل قليلاً (أسهل طريقة لإعادة تعيين مواضع الطبقات بعد انتقال معين) ، ولكنها تمنعنا من الانقسام.
النتيجة
بعد
كل هذا ، حصلت على ما يلي:
في لعبة Game Boy ، هذا رائع حقًا. بقدر ما أعرف ، ليس كل منهم لديهم التمرير المنظر تنفيذها مثل هذا.