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

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

- الغوغاء والكائنات الثابتة القابلة للتدمير ، المتحركة باستمرار وتتكون من العفاريت منفصلة في كمية من بضع إلى بضع عشرات.

- الأصداف الخاصة والممثلة في معظم الحالات إما بعفريت واحد أو بنظام من الجزيئات ، وفي هذه الحالة الأخيرة لم يكن هناك حاجة إلى ظل ؛

- خزان يتكون من عدة أجزاء تم تجميعها وفقًا لنفس المخطط مثل الغوغاء ؛

- الجدران مع العديد من الحالات الثابتة ، والتي ، مرة أخرى ، هي مجموعة من العفاريت منفصلة.

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

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

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

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

وإذا كان في حالة أرجل الغول لا يزال بإمكانك تغيير وضع الظل قليلاً وإخفاء المشكلة ، فعندئذٍ لا توجد فرصة لعشرات من جذوع الأشجار. يجب جعل كل كائنات الموقع التي كان من المفترض أن تلقي بظلالها على GameObject منفصلة. هذا هو بالضبط ما فعلته بوضع نسخ من الكائنات القابلة للتدمير المقابلة على الموقع الجاهز وتعطيل البرامج النصية غير المستخدمة في هذا الموضع. في الوقت نفسه ، وبفضل هذا ، أصبح من الممكن تضمينها في التصنيف العام لأشياء المشهد ، ولم تعد القذائف التي تطير خارج الموقع مرسومة بشكل صارم فوق جميع الكائنات ، ولكنها طارت بينها. بالإضافة إلى ذلك ، أصبح من الممكن جعل الكائنات نفسها متحركة.
ولكن بعد ذلك مشكلة جديدة تنتظرني. مع الظلال والعشرات من الكائنات الجديدة ، فإن العدد الأقصى من GameObjects في وقت واحد على المسرح ، ومعهما مكونات Animator و SpriteRenderer ، تضاعف أكثر من الضعف. عندما أطلقت الموجة الكاملة من الغوغاء على الموقع ، والتي بلغت حوالي 150 قطعة ، أظهر لي بروفيلر بشكل متقارب حوالي 40 مللي ثانية ، والتي ذهبت فقط للتقديم والرسوم المتحركة ، وتفاوت معدل الإطارات ككل حوالي 10. لقد قمت بتحسين نصوصي الخاصة ، وأقاتل من أجل كل مللي ثانية ، لكن ذلك لم يكن كافيا.
بحثًا عن أدوات تحسين إضافية ، صادفت الوثائق والأدلة الواسعة للتجميع الديناميكي.
أكثر قليلا عن الخلطباختصار ، يعد التجميع آلية لتقليل عدد مكالمات السحب ، ومعها يتم قضاء الوقت في تقديم الإطار على التفاعل بين وحدة المعالجة المركزية ووحدة معالجة الرسومات. عند استخدامها بدلاً من إرسال كل عنصر على حدة للتقديم ، يتم تجميع العناصر المتشابهة وتجميعها في وقت واحد. في حالة الوحدة ، يحاول المحرك نفسه الاستفادة القصوى من هذه الآلية ولا يتطلب الأمر أي إجراء إضافي من المطور.
أظهر مصحح الإطارات أن لدي ، في أحسن الأحوال ، تفاصيل كل كائن أو غوغاء بشكل منفصل. بعد أن قمت بإنشاء sprites للأول والثاني للأطلس ، حققت تظليل الظل من خلال عدد قليل من مكالمات السحب ، لكن أصحاب هذه الظلال رفضوا بعناد معارضة أنفسهم.
أظهرت التجارب على مشهد منفصل أن الخلط الديناميكي يكسر عندما تحتوي الكائنات على مكون SortingGroup ، والذي استخدمته لفرز عرض الكيانات على الشاشة. كان من الممكن الاستغناء عنه ، من الناحية النظرية ، فإن تحديد قيم الفرز لكل نظام شبح وجسيم في كائن منفصل يمكن أن يكون أكثر تكلفة من عدم وجود مجموعة.
ولكن هناك شيء مسكون لي. كائن الظل ، كونه سليل كائن مضيف في المشهد الحقيقي ، ينتمي تقنيًا إلى نفس مجموعة الفرز ، ومع ذلك ، لا توجد أي مشاكل مع تظليل كائنات الظل الديناميكية. كان الاختلاف الوحيد هو أن الكائنات المضيفة تم رسمها مباشرة على الشاشة بواسطة الكاميرا الرئيسية ، وكانت كائنات الظل تُعرض لأول مرة في RenderTexture.
كان هذا هو المصيد. ما هو بالضبط سبب هذا السلوك غير معروف للإنترنت ، ولكن عند تقديم صور الكاميرا في RenderTexture ، لم يعد SortingGroup يكسر الدُفعة. بدا القرار غريبًا جدًا ، وغير منطقي ، وبصورة عامة الأكثر عكازًا. ولكن من خلال تنفيذ تجسيد الكيانات باستخدام نفس طريقة تجسيد الظلال ، ومن ثم الحصول على طبقة كيان ، بالإضافة إلى طبقة الظل ، فقد حققت بالفعل قيم أداء مقبولة تمامًا.
تعرض لقطة الشاشة أدناه مثالًا على تقديم طبقة كيان.

بشكل عام ، فإن تقديم كيان معين في إحداثي Y يبدو كالتالي:
- يتم وضع الكيان في Y - 20 ؛
- يتم تقديم الكيان بواسطة كاميرا مراقبة هذا الإحداثيات في RenderTexture للكيانات ؛
- يتم وضع ظل الكيان على Y + 20 ؛
- يتم رسم ظل كيان بواسطة كاميرا مراقبة هذا الإحداثيات في RenderTexture للظلال ؛
- تقوم الكاميرا الرئيسية برسم الموقع الرئيسي على الشاشة - العنصر الوحيد الذي يتم عرضه حاليًا مباشرة على الشاشة ؛
- ترسم الكاميرا الرئيسية طائرة على الشاشة مع ظلال RenderTexture كمادة ؛
- تقوم الكاميرا الرئيسية برسم طائرة على الشاشة مع RenderTexture للكيانات كمواد.
هذه كعكة طبقة.
في لقطة الشاشة أدناه ، تم ضبط كاميرا المحرر على الوضع ثلاثي الأبعاد لإظهار موقع الطبقات بالنسبة إلى بعضها البعض.

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

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

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

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

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

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