<?xml version="1.0"?>
<rss version="2.0"><channel><title>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;: &#x635;&#x646;&#x627;&#x639;&#x629; &#x627;&#x644;&#x623;&#x644;&#x639;&#x627;&#x628;</title><link>https://academy.hsoub.com/programming/game-development/page/6/?d=2</link><description>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;: &#x635;&#x646;&#x627;&#x639;&#x629; &#x627;&#x644;&#x623;&#x644;&#x639;&#x627;&#x628;</description><language>ar</language><item><title>&#x625;&#x639;&#x62F;&#x627;&#x62F; &#x627;&#x644;&#x645;&#x634;&#x647;&#x62F; &#x648;&#x627;&#x644;&#x643;&#x627;&#x645;&#x64A;&#x631;&#x627; &#x641;&#x64A; Unity3D</title><link>https://academy.hsoub.com/programming/game-development/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A7%D9%84%D9%85%D8%B4%D9%87%D8%AF-%D9%88%D8%A7%D9%84%D9%83%D8%A7%D9%85%D9%8A%D8%B1%D8%A7-%D9%81%D9%8A-unity3d-r131/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2015_07/unity3d-scene-camera_(2).png.d179930662206652401b59c58789bde3.png" /></p>
<p dir="rtl">
	قمنا في الدرس السابق <a href="https://academy.hsoub.com/programming/general/%D9%86%D8%A8%D8%B0%D8%A9-%D8%B9%D9%86-%D8%B5%D9%86%D8%A7%D8%B9%D8%A9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D9%88%D9%85%D8%AD%D8%B1%D9%83-unity3d-r126/" rel="">بالتعرف على محرك Unity</a> وكيفية تحميله وإنشاء مشروع جديد عليه، سنبدأ في هذا الدرس في مشروع عملي لتعلم كيفية صناعة لعبة حقيقية كاملة. سنتكلم في الدرس الأول عن إعداد مشهد اللعبة والتحكم بالكاميرا.
</p>

<p dir="rtl" style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2015_07/unity3d-scene-camera_(2).png.42af9c92d2965f10565c87d402360d4e.png" data-fileid="3422" data-fileext="png" rel=""><img alt="unity3d-scene-camera_(2).thumb.png.b93f8" class="ipsImage ipsImage_thumbnailed" data-fileid="3422" src="https://academy.hsoub.com/uploads/monthly_2015_07/unity3d-scene-camera_(2).thumb.png.b93f88f59360050e899544130114d98f.png"></a>
</p>

<p dir="rtl">
	<strong>ملاحظة</strong>: يمكن تحميل الملفات المصدرية لكامل هذه السلسلة عبر <a href="https://github.com/HsoubAcademy/Unity3D/tree/master/AngryAnimals%20Game" rel="external nofollow">حساب أكاديمية حسوب على Github</a>، يمكن أيضا تحميل <a href="https://github.com/HsoubAcademy/Unity3D/releases/tag/v0.1.0" rel="external nofollow">ملف APK لتجريب اللعبة على أجهزة Android</a>.
</p>

<h2 dir="rtl">
	استيراد الصور وتجهيزها
</h2>

<p dir="rtl">
	سأستخدم في هذا المثال مجموعة من الرسوم المطروحة مجانا تحت رخصة المشاع الإبداعي <a href="https://creativecommons.org/publicdomain/zero/1.0/" rel="external nofollow">CC0</a> وهي متوفرة على الموقع <a href="http://kenney.nl" rel="external nofollow">http://kenney.nl</a>. <a href="http://www.kenney.nl/assets/physics-assets" rel="external nofollow">المجموعة الأولى</a> عبارة عن رسومات خاصة بالألعاب الفيزيائية مثل Angry Birds، وهي اللعبة التي نحاول أن نحاكي ميكانيكياتها عبر هذه السلسلة. أما <a href="http://www.kenney.nl/assets/animal-pac" rel="external nofollow">المجموعة الثانية</a> فهي عبارة عن رسوم حيوانات بسيطة سنستخدمها بدلا عن الطيور الموجودة في اللعبة الأصلية بحيث تمثل المقذوفات. إلا أننا سنضيف بعض الرسومات الأخرى وملفات الأصوات حسب الحاجة. سأعتمد في هذه السلسلة وضع كل مصدر (ملف صورة أو ملف صوت) داخل مجلد يحمل اسم الموقع الذي تم جلب المصدر منه، بحيث يمكنك الرجوع لاحقا لهذه المواقع وتحميل الملفات.
</p>

<p dir="rtl">
	بعد تحميل هذه الرسومات وفك ضغطها قم بنسخها إلى داخل المجلد <span style="font-family:courier new,courier,monospace;">Assets</span> الخاص بمشروعك، يمكنك نسخها عن طريق نظام التشغيل أو بسحبها مباشرة إلى داخل المجلد في مستعرض Unity. بمجرد أن تضيف هذه الملفات للمشروع سيتعرف عليها Unity على أنها رسومات شبحية sprites، وهي أنواع الرسوم الأكثر شيوعا في الألعاب ثنائية الأبعاد. من أجل الحصول على أعلى جودة ممكنة سنقوم بتعديل بعض خصائص الرسومات، سأضرب مثالا هنا على مجلد واحد ومن ثم علينا تكرار العملية لجميع  مجلدات الرسومات.
</p>

<p dir="rtl">
	<strong>ملاحظة:</strong> تحتوي مجموعات الرسومات على طريقتين مختلفتين للتخزين: الأولى تخزين كل صورة على حدى في ملف مستقل، والثانية جمع الصور في ملف واحد يسمى ورقة الرسومات <span style="font-family:courier new,courier,monospace;">sprite sheet.</span> سنتناول في هذه الدروس الطريقة الأولى وهي الأشكال المنفصلة؛ وذلك لسهولة العمل عليها. علما بأن الطريقة الثانية أكثر كفاءة من ناحية الأداء وأسهل في حالة الرسومات المتحركة.
</p>

<p dir="rtl">
	كما ترى في الشكل التالي فإنني قمت باختيار جميع عناصر مجلد رسومات الوحوش والتي ستمثل الخصوم في هذه اللعبة.
</p>

<p dir="rtl" style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2015_07/image00.png.9b228b1d9a0760e0affa5e9e45ded816.png" data-fileid="3386" data-fileext="png" rel=""><img alt="image00.thumb.png.2a02c20d0b98d000a86cf7" class="ipsImage ipsImage_thumbnailed" data-fileid="3386" src="https://academy.hsoub.com/uploads/monthly_2015_07/image00.thumb.png.2a02c20d0b98d000a86cf774c4f51c74.png"></a>
</p>

<p dir="rtl">
	بعد ذلك قمت بتعديل خصائص استيراد هذه الرسومات لتصبح كالتالي:
</p>

<p dir="rtl" style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2015_07/image01.png.ce6db41e531c9edfea2342bf9932189d.png" data-fileid="3387" data-fileext="png" rel=""><img alt="image01.thumb.png.0bdcebeb5e71111f7acd5a" class="ipsImage ipsImage_thumbnailed" data-fileid="3387" src="https://academy.hsoub.com/uploads/monthly_2015_07/image01.thumb.png.0bdcebeb5e71111f7acd5a5e0afd1bea.png"></a>
</p>

<p dir="rtl">
	بتغيير نوع المرشِّح إلى Trilinear وتنسيق الألوان إلى Truecolor فإننا نطلب من Unity ألا يقوم بضغط هذه الصور بحيث تبقى بحجمها الأصلي وتحافظ على جودتها. على الرغم من أن الضغط أفضل للأداء، إلا أنك ستلاحظ أن Unity يخبرك أن معظم هذه الرسومات غير قابلة للضغط كونها لا تحقق قاعدة أن يكون الطول والعرض مساويين لأرقام لوغاريتمية للأساس 2 (مثلا 64، 128، 512، إلخ). لا تنس أن تضغط على Apply لحفظ التغييرات بعد الانتهاء منها.
</p>

<h2>
	بناء المشهد وتجهيز الخلفية
</h2>

<p dir="rtl">
	بعد تغيير الخصائص لجميع الرسومات التي استوردناها يمكننا أن نبدأ بإعداد المشهد. الخطوة الأولى ستكون بإضافة الخلفية والأرضية التي ستدور أحداث اللعبة عليها. إحدى المواصفات المهمة في عملية الاستيراد هي Pixels Per Unit. الرقم 100 هنا يعني أن خطا بطول 100 بكسل في الصورة الأصلية يغطي مسافة قدرها وحدة واحدة في فضاء Unity. فلو افترضت أن الوحدة هي متر واحد، فإن صورة صندوق بحجم 10×10 بكسل تساوي صندوقا بحجم 10×10 سم في فضاء اللعبة. لحسن حظنا فإن مجموعة الصور التي استوردناها ذات أحجام مناسبة لبعضها البعض، فمثلا صور الوحوش هي ذات مقاس 70×70 بكسل بينما الخلفيات أكبر من ذلك بكثير وتصل إلى 1024×1024 بكسل.
</p>

<p dir="rtl">
	لنأخذ بعين الاعتبار منذ البداية أن هذه السلسلة ليست لتعلم برمجة الألعاب فقط، كما أنها ليست لتعلم محرك Unity فقط، بل يمكنك القول أنها ستعلمك أيضا أفضل الممارسات البرمجية والتنظيمية للمشهد لتحصل على أفضل نتيجة باستخدام هذا المحرك. من أجل ذلك سنقوم بإعداد مشهد اللعبة بطريقة خاصة بحيث يحتوي المشهد على كائن واحد نسميه الكائن الجذري للمشهد <span style="font-family:courier new,courier,monospace;">SceneRoot</span> ومن ثم سنضيف كل عناصر المشهد على شكل أبناء وأحفاد لهذا الكائن. لإضافة كائن جديد كل ما عليك هو اختيار القائمة:
</p>

<p dir="rtl" style="text-align: center;">
	<strong>Game Object &gt; Create Empty </strong>
</p>

<p dir="rtl">
	وسيظهر بعدها الكائن الجديد في هرمية المشهد باسم GameObject، قم باختياره وتغيير اسمه إلى <span style="font-family:courier new,courier,monospace;">SceneRoot</span> وموقعه إلى نقطة الأصل (0 ,0). كما ذكرت في مقدمة هذا الفصل يمكنك تغيير هذه القيم من نافذة الخصائص Inspector كما هو موضح في الصورة أدناه. لاحظ أن هذا الكائن "فارغ" كما يدل اسمه، أي أنه موجود في الذاكرة فقط ولا يمكن رؤيته في المشهد رغم أنه موجود في وسطه، سنأتي إلى هذه التفاصيل حسب حاجتنا لها.
</p>

<p dir="rtl" style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2015_07/image02.png.ec55941c3eadc816edf07e90a95675c8.png" data-fileid="3388" data-fileext="png" rel=""><img alt="image02.thumb.png.ce465b4723533bf52d239b" class="ipsImage ipsImage_thumbnailed" data-fileid="3388" src="https://academy.hsoub.com/uploads/monthly_2015_07/image02.thumb.png.ce465b4723533bf52d239b6db16574f5.png"></a>
</p>

<p dir="rtl">
	بعد ذلك سنضيف كائنا فارغا آخر ليمثل خلفية المشهد، كل ما عليك هو أن تضغط بزر الفأرة الأيمن على الكائن SceneRoot في الهرمية وتختار Create Empty من أجل أن تضيف كائنا فرعيا (ابنا) للكائن الجذري ولنقم بتسميته <span style="font-family:courier new,courier,monospace;">Background</span>. أخيرا عليك أن تقوم بسحب كائن الكاميرا Main Camera إلى داخل الكائن الجذري بحيث تصبح هرمية المشهد بهذا الشكل.
</p>

<p dir="rtl" style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2015_07/image03.png.07cf090f5f570917830821294fbb1d08.png" data-fileid="3389" data-fileext="png" rel=""><img alt="image03.thumb.png.f62d7a6648ae0ddd366ff5" class="ipsImage ipsImage_thumbnailed" data-fileid="3389" src="https://academy.hsoub.com/uploads/monthly_2015_07/image03.thumb.png.f62d7a6648ae0ddd366ff5c2614a48f4.png"></a>
</p>

<p dir="rtl">
	حان الوقت الآن لحفظ المشهد الذي نعمل عليه، كما هو الحال في 99% من البرامج يمكنك الحفظ عن طريق <span style="font-family:courier new,courier,monospace;">Control+S</span> وهو أمر خاص بحفظ المشهد وليس حفظ المشروع كاملا، حيث أن المشروع ما هو إلا مجموعة ملفات يتم تعديلها وحفظها على حدى. قم بإنشاء مجلد جديد داخل <span style="font-family:courier new,courier,monospace;">Assets</span> وقم بتسميته <span style="font-family:courier new,courier,monospace;">Scenes</span> ومن ثم احفظ المشهد داخله باسم <span style="font-family:courier new,courier,monospace;">GameScene</span>.
</p>

<p dir="rtl">
	لنتفق من الآن على أننا سنقوم ببناء مشهد واحد لكل اللعبة، وسنعمل على تغيير كل من الخلفية والأرضية ومواقع الكائنات لصنع مراحل مختلفة. الكائن الأول سيكون خلفية المشهد، والذي توجد عدة صور يمكن استخدامها له. هذه الصور عددها 8 وهي موجودة في المجلد <span style="font-family:courier new,courier,monospace;">Backgrounds</span> في مجموعة الصور الأولى كما ترى في الصورة أدناه. ما يميز صور الخلفيات هذه هو أن كلا منها ينطبق طرفها الأيمن على طرفها الأيسر تماما مما يجعلنا قادرين على تكرارها أفقيا بشكل متتابع دون أن يلاحظ اللاعب وجود حدود بينها.
</p>

<p dir="rtl" style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2015_07/image04.png.1c70b99d4758184691f99066c14295e8.png" data-fileid="3390" data-fileext="png" rel=""><img alt="image04.thumb.png.6d31b24b5ddfa8d97a71b5" class="ipsImage ipsImage_thumbnailed" data-fileid="3390" src="https://academy.hsoub.com/uploads/monthly_2015_07/image04.thumb.png.6d31b24b5ddfa8d97a71b55a69426363.png"></a>
</p>

<p dir="rtl">
	بما أننا نتعامل مع لعبة ذات مشهد عرضي فإن علينا أن نكرر الخلفية أكثر من مرة بشكل أفقي، ولتكن 4 مرات. قبل أن نبدأ بإضافة الخلفيات للمشهد، لنسهل الأمر على أنفسنا بجعل نقطة الارتكاز Pivot لجميع صور الخلفيات هي الطرف الأيسر من الصورة بدل الأوسط، وذلك كما ترى في الصورة التالية:
</p>

<p dir="rtl" style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2015_07/image05.png.2eca7c611215c46cf5e421fec2cea6d5.png" data-fileid="3391" data-fileext="png" rel=""><img alt="image05.thumb.png.4ba765d2e5601aa1c69c57" class="ipsImage ipsImage_thumbnailed" data-fileid="3391" src="https://academy.hsoub.com/uploads/monthly_2015_07/image05.thumb.png.4ba765d2e5601aa1c69c57cbee0a3503.png"></a>
</p>

<p dir="rtl">
	ولكن ماذا يعني أن تكون نقطة ارتكاز الصورة هي طرفها الأيسر وليس وسطها؟ الصورتان في الأسفل تجيبان على هذا السؤال.
</p>

<p>
	في كلتا الحالتين فإن موقع الصورة لم يتغير وهو (0 ,0) أي في منتصف المشهد. في الوضع الافتراضي (الصورة اليسرى) فإن ارتكاز الصورة يكون في منتصفها، أي أن موقع الصورة في الفضاء هو موقع منتصفها، ويمتد طرفا الصورة الأيسر والأيمن بين النقطتين <span style="font-family:courier new,courier,monospace;">x=-5.12</span> و <span style="font-family:courier new,courier,monospace;">x=+5.12</span>. أما في الحالة الثانية قمنا بتغيير الارتكاز لأقصى يسار الصورة أفقيا مع بقائه في منتصفها عموديا. بهذا الشكل أصبحت الصورة تمتد من النقطة <span style="font-family:courier new,courier,monospace;">x=0</span> يسارا إلى النقطة <span style="font-family:courier new,courier,monospace;">x=+10.24</span> يمينا. لاحظ أن قيم هذه النقاط متوافقة مع حقيقة أن الوحدة الواحدة <span style="font-family:courier new,courier,monospace;">=100 بكسل</span> حيث يكون عرض الصورة بالوحدات هو 10.2=1024/100
</p>

<p dir="rtl">
	بهذا أصبح بإمكاننا أن نحسب مواقع الصور الأربعة التي سنقوم برصّها بجانب بعضها البعض من اليسار لليمين حول نقطة الأصل، وهي كالتالي:
</p>

<ul dir="rtl">
	<li>
		الصورة في أقصى اليسار ستكون في الموقع<span style="font-family:courier new,courier,monospace;"> 20.48- </span>وتمتد إلى <span style="font-family:courier new,courier,monospace;">10.24-.</span>
	</li>
	<li>
		الصورة على يسار نقطة الأصل ستكون في الموقع <span style="font-family:courier new,courier,monospace;">10.24- </span>وتمتد إلى <span style="font-family:courier new,courier,monospace;">0.0.</span>
	</li>
	<li>
		الصورة على يمين نقطة الأصل ستكون في الموقع <span style="font-family:courier new,courier,monospace;">0.0</span> وتمتد إلى <span style="font-family:courier new,courier,monospace;">10.24+.</span>
	</li>
	<li>
		الصورة في أقصى اليمين ستكون في الموقع <span style="font-family:courier new,courier,monospace;">10.24+</span> وتمتد إلى <span style="font-family:courier new,courier,monospace;">20.48+.</span>
	</li>
</ul>

<p dir="rtl">
	بدلا من أن نقوم بوضع هذه الصورة يدويا، سنقوم بكتابة بُريمج صغير script بحيث نعطيه صورة ونطلب منه أن يكررها عددا من المرات ليصنع لنا الخلفية. هذا البريمج سيعمل بالشكل التالي: سنقوم بإعطائه جميع صور الخلفيات الموجودة لدينا على شكل مصفوفة بحيث تحمل كل صورة رقما خاصا بها، إضافة لذلك سنكون قادرين على تزويده بعدد مرات التكرار المطلوبة للصورة من أجل رسم الخلفية. عند تشغيل اللعبة فإنه يجب علينا أن نقوم باختيار رقم الخلفية التي نرغب بظهورها على الشاشة، وهذا الرقم سيكون من 0 إلى 7. سنعود لموضوع البريمج هذا بعد قليل.
</p>

<p dir="rtl">
	قبل الخوض في التفاصيل أود التنويه إلى أن الكائنات في محرك Unity جميعها تتشابه، وهي في وضعها الأصلي فارغة غير مرئية، وما يميز كل كائن عن غيره ويعطيه سماته الخاصة هو مجموعة المكوّنات التي تتم إضافتها إليه. المكوّن الوحيد الموجود في الكائن الفارغ هو <span style="font-family:courier new,courier,monospace;">Transform</span>، وهو موجود في جميع كائنات المحرك دون استثناء، حيث أنه يعطي الكائن موقعه ودورانه وحجمه في الفضاء ثنائي الأبعاد (أو ثلاثي الأبعاد) إضافة إلى أنه يحدد علاقاته بالكائنات الأخرى كعلاقة الأب والأبناء التي نراها في هرمية المشهد. بناء على هذه الحقيقة، فإن ما تراه في شاشة الخصائص Inspector حين تختار أي كائن هو مجموعة المكوّنات المضافة إليه.
</p>

<p dir="rtl">
	ما سنقوم به الآن هو بناء قالب prefab، وهو عبارة عن كائن نضيف إليه بعض المكوّنات، ومن ثم يمكننا إنشاء عدة نسخ منه أثناء تشغيل اللعبة. الأمر الجيد في هذه القوالب هو أنه يمكن تعديلها من مكان واحد مهما بلغ عدد الكائنات التي يتم إنشاؤها منها، حيث ينعكس أي تعديل في القالب الأصلي (كإضافة مكون أو حذفه) على جميع الكائنات التي تتبع هذا القالب. القالب الذي سنقوم به خاص بالكائن الذي سيعرض صورة الخلفية، والذي سيحتاج مبدئيا لمكوّن واحد وهو<span style="font-family:courier new,courier,monospace;"> Sprite Renderer </span>الخاص بتصيير الصور ثنائية الأبعاد على الشاشة. لإنشاء القالب لنقم أولا بإضافة كائن فارغ للمشهد ومن ثم نضيف له المكوّن المذكور وذلك عن طريق الزر Add Component والذي يعطيك قائمة بجميع المكوّنات المتوفرة، ولتسهيل الوصول يمكنك البحث عن طريق كتابة اسم المكوّن <span style="font-family:courier new,courier,monospace;">Sprite Renderer</span> ومن ثم اختياره كما يلي:
</p>

<p dir="rtl" style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2015_07/image06.png.2b0a49bc9998a90f687013f7f2ea2a3d.png" data-fileid="3392" data-fileext="png" rel=""><img alt="image06.thumb.png.ae50fbe6e8803893ca6cdd" class="ipsImage ipsImage_thumbnailed" data-fileid="3392" src="https://academy.hsoub.com/uploads/monthly_2015_07/image06.thumb.png.ae50fbe6e8803893ca6cdd84d516c841.png"></a>
</p>

<p dir="rtl">
	بعد إضافة المكوّن أصبحنا جاهزين لتحويل هذا الكائن إلى قالب، وذلك عن طريق سحبه من الهرمية إلى داخل أي مجلد في المشروع، في هذه الحالة سأقوم بإنشاء مجلد جديد داخل <span style="font-family:courier new,courier,monospace;">Assets</span> وأسميه <span style="font-family:courier new,courier,monospace;">Prefabs</span> لأننا سنحتاج في هذا العمل إلى عدد كبير من القوالب كما سنرى. بعدها قم بتغيير اسم القالب ليصبح <span style="font-family:courier new,courier,monospace;">BGElement</span>. بعد إنشاء القالب لم نعد بحاجة للكائن في المشهد بالتالي يمكننا حذفه باستخدام مفتاح Delete. انتبه هنا أنك تحذف الكائن من الهرمية ولا تحذف القالب من المشروع فهما يحملان نفس الاسم لكن الفرق كبير بين الأمرين!
</p>

<p dir="rtl">
	لإضافة بريمج جديد للمشروع لنقم أولا بإنشاء مجلد خاص بالبريمجات داخل Assets ولنسمه Scripts. بعدها يمكنك إضافة بريمج جديد بلغة #C وذلك عن طريق الضغط على المجلد بزر الفأرة الأيمن ومن ثم اختيار:
</p>

<p dir="rtl" style="text-align: center;">
	<strong> Create &gt; C# Script.</strong>
</p>

<p dir="rtl">
	أدخل الاسم <span style="font-family:courier new,courier,monospace;">BackgroundManager</span> ومن ثم اضغط <span style="font-family:courier new,courier,monospace;">Enter</span> وبعدها قم بفتح الملف على محرر MonoDevelop أو Visual Studio وذلك بالنقر المزدوج. لنشاهد البريمج الذي سنكتبه في السرد التالي ثم نناقشه معا بالتفصيل.
</p>

<pre class="javascript ipsCode prettyprint prettyprinted" data-pbcklang="javascript" data-pbcktabsize="4" style=""><span class="kwd">using</span><span class="pln"> </span><span class="typ">UnityEngine</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">using</span><span class="pln"> </span><span class="typ">System</span><span class="pun">.</span><span class="typ">Collections</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">BackgroundManager</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="typ">MonoBehaviour</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    
    </span><span class="com">//القالب المستخدم لبناء عناصر الخلفية</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">GameObject</span><span class="pln"> bgElementPrefab</span><span class="pun">;</span><span class="pln">
    
    </span><span class="com">//مصفوفة تحتوي على جميع صور الخلفيات المتاحة</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">Sprite</span><span class="pun">[]</span><span class="pln"> selectionList</span><span class="pun">;</span><span class="pln">

    </span><span class="com">//عدد مرات تكرار صورة الخلفية</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> repeatCount </span><span class="pun">=</span><span class="pln"> </span><span class="lit">4</span><span class="pun">;</span><span class="pln">

    </span><span class="com">//صورة الخلفية المعروضة حاليا</span><span class="pln">
    </span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> selectedIndex </span><span class="pun">=</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">;</span><span class="pln">

    </span><span class="com">//"Background" مرجع للكائن الابن المسمى</span><span class="pln">
    </span><span class="kwd">private</span><span class="pln"> </span><span class="typ">Transform</span><span class="pln"> bgGameObject</span><span class="pun">;</span><span class="pln">

    </span><span class="com">//تستدعى مرة واحدة عند بداية التشغيل</span><span class="pln">
    </span><span class="kwd">void</span><span class="pln"> </span><span class="typ">Start</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="com">//"Background" ابحث في الأبناء على الكائن الذي يحمل الاسم</span><span class="pln">
        bgGameObject </span><span class="pun">=</span><span class="pln"> transform</span><span class="pun">.</span><span class="typ">FindChild</span><span class="pun">(</span><span class="str">"Background"</span><span class="pun">);</span><span class="pln">
        </span><span class="com">//قم مبدئيا باختيار الصورة الأولى كخلفية</span><span class="pln">
        </span><span class="typ">ChangeBackground</span><span class="pun">(</span><span class="lit">0</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    
    </span><span class="com">//تستدعى مرة واحدة عند تصيير كل إطار</span><span class="pln">
    </span><span class="kwd">void</span><span class="pln"> </span><span class="typ">Update</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    
    </span><span class="pun">}</span><span class="pln">

    </span><span class="com">//تقوم بتغيير صورة الخلفية المعروضة حاليا</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> </span><span class="typ">ChangeBackground</span><span class="pun">(</span><span class="kwd">int</span><span class="pln"> newIndex</span><span class="pun">){</span><span class="pln">
        </span><span class="kwd">if</span><span class="pun">(</span><span class="pln">newIndex </span><span class="pun">==</span><span class="pln"> selectedIndex</span><span class="pun">){</span><span class="pln">
            </span><span class="com">//لا حاجة لتغيير الصورة</span><span class="pln">
            </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="com">//قم بحذف صور الخلفية الحالية</span><span class="pln">
        </span><span class="kwd">for</span><span class="pun">(</span><span class="kwd">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> bgGameObject</span><span class="pun">.</span><span class="pln">childCount</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++){</span><span class="pln">
            </span><span class="typ">Transform</span><span class="pln"> bgSprite </span><span class="pun">=</span><span class="pln"> bgGameObject</span><span class="pun">.</span><span class="typ">GetChild</span><span class="pun">(</span><span class="pln">i</span><span class="pun">);</span><span class="pln">
            </span><span class="typ">Destroy</span><span class="pun">(</span><span class="pln">bgSprite</span><span class="pun">.</span><span class="pln">gameObject</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="com">//قم بتخزين صورة الخلفية الجديدة من المصفوفة حسب الموقع المعطى</span><span class="pln">
        </span><span class="typ">Sprite</span><span class="pln"> newSprite </span><span class="pun">=</span><span class="pln"> selectionList</span><span class="pun">[</span><span class="pln">newIndex</span><span class="pun">];</span><span class="pln">

        </span><span class="com">//ما هو عرض صورة الخلفية بالبكسل؟</span><span class="pln">
        </span><span class="kwd">float</span><span class="pln"> width </span><span class="pun">=</span><span class="pln"> newSprite</span><span class="pun">.</span><span class="pln">rect</span><span class="pun">.</span><span class="pln">width</span><span class="pun">;</span><span class="pln">
        
        </span><span class="com">//ما هو ارتفاع صورة الخلفية بالبكسل؟</span><span class="pln">
        </span><span class="kwd">float</span><span class="pln"> height </span><span class="pun">=</span><span class="pln"> newSprite</span><span class="pun">.</span><span class="pln">rect</span><span class="pun">.</span><span class="pln">height</span><span class="pun">;</span><span class="pln">

        </span><span class="com">//كم عدد البكسلات في الوحدة الواحدة؟</span><span class="pln">
        </span><span class="kwd">float</span><span class="pln"> ppu </span><span class="pun">=</span><span class="pln"> newSprite</span><span class="pun">.</span><span class="pln">pixelsPerUnit</span><span class="pun">;</span><span class="pln">

        </span><span class="com">//احسب الطول والعرض مستخدما الوحدات</span><span class="pln">
        width </span><span class="pun">=</span><span class="pln"> width </span><span class="pun">/</span><span class="pln"> ppu</span><span class="pun">;</span><span class="pln">
        height </span><span class="pun">=</span><span class="pln"> height </span><span class="pun">/</span><span class="pln"> ppu</span><span class="pun">;</span><span class="pln">

        </span><span class="com">// قم بحساب الموقع الأفقي للصورة الأولى في أقصى اليسار</span><span class="pln">
        </span><span class="kwd">float</span><span class="pln"> posX </span><span class="pun">=</span><span class="pln"> </span><span class="pun">-</span><span class="pln">width </span><span class="pun">*</span><span class="pln"> repeatCount </span><span class="pun">*</span><span class="pln"> </span><span class="lit">0.5f</span><span class="pun">;</span><span class="pln">
        
        </span><span class="com">//قم بحساب حدود المشهد الجديدة</span><span class="pln">
        </span><span class="typ">Vector2</span><span class="pln"> boundsSize </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Vector2</span><span class="pun">(</span><span class="pln">width </span><span class="pun">*</span><span class="pln"> repeatCount</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">);</span><span class="pln">
        </span><span class="typ">Bounds</span><span class="pln"> newBounds </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Bounds</span><span class="pun">(</span><span class="typ">Vector2</span><span class="pun">.</span><span class="pln">zero</span><span class="pun">,</span><span class="pln"> boundsSize</span><span class="pun">);</span><span class="pln">
        
        </span><span class="com">//قم بإرسال رسالة تخبر بتغير حدود المشهد</span><span class="pln">
        </span><span class="typ">BroadcastMessage</span><span class="pun">(</span><span class="str">"SceneBoundsChanged"</span><span class="pun">,</span><span class="pln"> newBounds</span><span class="pun">);</span><span class="pln">

        </span><span class="com">//يمكننا الآن البدء ببناء الخلفية الجديدة</span><span class="pln">
        </span><span class="kwd">for</span><span class="pun">(</span><span class="kwd">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> repeatCount</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++){</span><span class="pln">
            </span><span class="com">//قم ببناء كائن جديد مستخدما القالب</span><span class="pln">
            </span><span class="typ">GameObject</span><span class="pln"> bg </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="typ">GameObject</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Instantiate</span><span class="pun">(</span><span class="pln">bgElementPrefab</span><span class="pun">);</span><span class="pln">
            </span><span class="com">//قم بتحديد اسم الكائن</span><span class="pln">
            bg</span><span class="pun">.</span><span class="pln">name </span><span class="pun">=</span><span class="pln"> </span><span class="str">"BG_"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> i</span><span class="pun">;</span><span class="pln">
            </span><span class="com">//قم بجلب مكوّن تصيير الصور الموجود في الكائن</span><span class="pln">
            </span><span class="typ">SpriteRenderer</span><span class="pln"> sr </span><span class="pun">=</span><span class="pln"> bg</span><span class="pun">.</span><span class="typ">GetComponent</span><span class="pun">&lt;</span><span class="typ">SpriteRenderer</span><span class="pun">&gt;();</span><span class="pln">
            </span><span class="com">//قم بتحديد الصورة التي سيتم تصييرها لتكون الصورة المختارة من المصفوفة</span><span class="pln">
                   sr</span><span class="pun">.</span><span class="pln">sprite </span><span class="pun">=</span><span class="pln"> newSprite</span><span class="pun">;</span><span class="pln">
            </span><span class="com">//قم بضبط الموقع الأفقي مستخدما القيمة التي سبق وحسبناها</span><span class="pln">
            bg</span><span class="pun">.</span><span class="pln">transform</span><span class="pun">.</span><span class="pln">position </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Vector2</span><span class="pun">(</span><span class="pln">posX</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">
                   </span><span class="com">//قم بإرجاع صورة الخلفية خطوة للخلف في ترتيب الرسم</span><span class="pln">
                   sr</span><span class="pun">.</span><span class="pln">sortingOrder </span><span class="pun">=</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">;</span><span class="pln">
            </span><span class="com">//كأب لعنصر الخلفية الذي تم إنشاؤه "Background" قم بتحديد الكائن الفارغ</span><span class="pln">
            bg</span><span class="pun">.</span><span class="pln">transform</span><span class="pun">.</span><span class="pln">parent </span><span class="pun">=</span><span class="pln"> bgGameObject</span><span class="pun">;</span><span class="pln">
            </span><span class="com">//قم بإضافة مكون تصادم على شكل مربع</span><span class="pln">
            bg</span><span class="pun">.</span><span class="typ">AddComponent</span><span class="pun">&lt;</span><span class="typ">BoxCollider2D</span><span class="pun">&gt;();</span><span class="pln">
            </span><span class="com">//قم بإضافة عرض الصورة إلى قيمة الموقع الأفقي لحساب الموقع الجديد للعنصر التالي</span><span class="pln">
            posX </span><span class="pun">+=</span><span class="pln"> width</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        
        </span><span class="com">//أرسل رسالة تخبر بتغير صورة الخلفية وأرفق معها موقع الصورة الجديدة</span><span class="pln">
        </span><span class="typ">BroadcastMessage</span><span class="pun">(</span><span class="str">"BackgroundChanged"</span><span class="pun">,</span><span class="pln"> newIndex</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p dir="rtl">
	يتعامل محرك Unity مع البريمجات على أنها مكوّنات يمكن إضافتها إلى أي كائن في المشهد أو قالب في المشروع (القوالب في النهاية هي كائنات). بالنسبة لنا فهذا بريمج أساسي يتعلق بإدارة المشهد ككل وليس باللاعب أو الخصم، بالتالي سنضيفه إلى الكائن الجذري وذلك ببساطة عن طريق سحب ملف<span style="font-family:courier new,courier,monospace;"> BackgroundManager.cs </span>من مستعرض المشروع إلى الكائن <span style="font-family:courier new,courier,monospace;">SceneRoot</span> سواء في الهرمية أو في شاشة خصائصه.
</p>

<p dir="rtl">
	بعد إضافته ستلاحظ أن البريمج ظهر في شاشة خصائص الكائن الجذري وظهرت المتغيرات العامة <span style="font-family:courier new,courier,monospace;">bgElementPrefab</span> و <span style="font-family:courier new,courier,monospace;">selectionList</span> و <span style="font-family:courier new,courier,monospace;">repeatCount</span> بحيث يظهر الأول على شكل خانة فارغة ويظهر الثاني على شكل قائمة يمكنك تحديد عدد عناصرها من الخانة size، والثالث على شكل مربع نص يقبل قيمة رقمية.
</p>

<p dir="rtl">
	لنر الآن كيف يمكننا تحديد قيم هذه المتغيرات وستكون البداية مع<span style="font-family:courier new,courier,monospace;"> bgElementPrefab</span>. كما هو متوقع فهذه الخانة مخصصة لتحديد القالب الذي سيستخدم لاحقا لإنشاء عناصر الخلفية، وهو<span style="font-family:courier new,courier,monospace;"> BGElement</span> الذي سبق وأنشأناه. لربط القالب بهذا المتغير كل ما عليك هو أن تقوم بسحبه من مستعرض المشروع إلى داخل الخانة التي تراها في شاشة الخصائص. بعد ذلك قم بتغيير عدد العناصر في القائمة <span style="font-family:courier new,courier,monospace;">selectionList</span> إلى 8 ومن ثم اسحب لكل خانة منها واحدة من صور الخلفية الموجودة في المجلد <span style="font-family:courier new,courier,monospace;">Backgrounds</span>، أما المتغير الثالث <span style="font-family:courier new,courier,monospace;">repeactCount</span> فقد أخذ قيمة افتراضية هي 4 وهي القيمة التي استخدمناها عند تعريفه. سيصبح شكل الكائن الجذري الآن كالتالي:
</p>

<p dir="rtl" style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2015_07/image07.png.7d31103a3899ecaa8106e5147a6b7cd1.png" data-fileid="3393" data-fileext="png" rel=""><img alt="image07.thumb.png.5e5375bba0ca22e1f09f9c" class="ipsImage ipsImage_thumbnailed" data-fileid="3393" src="https://academy.hsoub.com/uploads/monthly_2015_07/image07.thumb.png.5e5375bba0ca22e1f09f9cac68f9ed39.png"></a>
</p>

<p dir="rtl">
	لو جربت تشغيل اللعبة الآن ستلاحظ ظهور صورة الخلفية الموجودة في العنصر الأول من القائمة، كما ستلاحظ أنها تكررت أفقيا 4 مرات بشكل صحيح كما ترى أدناه، وتلاحظ أيضا الكائنات الأربعة لهذه الصور  والتي تمت إضافتها كأبناء للكائن <span style="font-family:courier new,courier,monospace;">Background</span> في الهرمية.
</p>

<p dir="rtl" style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2015_07/image08.png.23d1b31f9090256c810ada4acaa19b19.png" data-fileid="3394" data-fileext="png" rel=""><img alt="image08.thumb.png.8f596aea52fa0c4660567c" class="ipsImage ipsImage_thumbnailed" data-fileid="3394" src="https://academy.hsoub.com/uploads/monthly_2015_07/image08.thumb.png.8f596aea52fa0c4660567c7e7f9e1012.png"></a>
</p>

<p dir="rtl">
	سنناقش الآن بشكل سريع أهم آليات عمل البريمج <span style="font-family:courier new,courier,monospace;">BackgroundManager</span>، جميع البريمجات في محرك Unity تأخذ تقريبا نفس التنسيق، حيث تبدأ بتعريف المتغيرات اللازمة، ومن ثم الدّالتين الأكثر استخداما وهما<span style="font-family:courier new,courier,monospace;"> ()Start</span> و <span style="font-family:courier new,courier,monospace;">()Update</span>. كما هو موضح في التعليقات على الكود فإنّ الدّالة <span style="font-family:courier new,courier,monospace;">()Start</span> تستدعى مرة واحدة فقط عند بداية التشغيل إن كان الكائن موجودا في المشهد أصلا، أو بمجرد إضافته للمشهد إن لم يكن موجودا من قبل. بعد ذلك تدخل اللعبة فيما نسميها حلقة التحديث أو حلقة تصيير الإطارات حيث يتم استدعاء الدّالة <span style="font-family:courier new,courier,monospace;">()Update</span> عند تصيير كل إطار طالما أن اللعبة تعمل. في حالتنا هذه لا يلزمنا استخدام<span style="font-family:courier new,courier,monospace;"> ()Update</span>.
</p>

<p dir="rtl">
	لاحظ أننا بدأنا البريمج بتعريف عدد من المتغيرات بعضها عام public وبعضها الآخر خاص private. بالنسبة لمحرك Unity فإن المتغيرات العامة ذات أهمية خاصة لأنه يعرضها على شكل خانات في شاشة الخصائص مما يسمح لك بتغيير قيمها دون تعديل الكود كما سبق ورأينا. فيما يلي شرح هذه المتغيرات:
</p>

<ul dir="rtl">
	<li>
		<span style="font-family:courier new,courier,monospace;">bgElementPrefab</span> يستخدم كمرجع للوصول للقالب الذي سنستخدمه لإنشاء عناصر الخلفية عند بنائها.
	</li>
	<li>
		<span style="font-family:courier new,courier,monospace;">selectionList</span> مصفوفة تحتوي على عناصر من نوع Sprite حيث تقوم بتخزين قائمة بجميع الخلفيات التي يمكن استخدامها.
	</li>
	<li>
		<span style="font-family:courier new,courier,monospace;">repeatCount</span> عدد صحيح يمثل عدد مرات تكرار الخلفية أفقيا، بطبيعة الحال فزيادة هذا العدد سيؤدي لزيادة عرض الخلفية النهائية.
	</li>
	<li>
		<span style="font-family:courier new,courier,monospace;">selectedIndex</span> يمثل موقع الخلفية المعروضة حاليا في القائمة، حيث تمثل القيمة 0 العنصر الأول.
	</li>
	<li>
		<span style="font-family:courier new,courier,monospace;">bgGameObject</span> سنستخدم هذا المتغير للوصول إلى الكائن الفارغ Background والذي خصصناه ليكون أبا لجميع صور الخلفية الموجودة في المشهد. الجدير بالذكر هنا هو أننا في معظم الحالات نستخدم متغيرا من نوع Transform للوصول للكائنات، ولذلك فوائد عديدة سنعرفها.
	</li>
</ul>

<p dir="rtl">
	عند بداية التشغيل يتم استدعاء الدّالة <span style="font-family:courier new,courier,monospace;">()Start</span> والتي تقوم أولا بالبحث في الأبناء عن الكائن المسمى <span style="font-family:courier new,courier,monospace;">Background</span> وتخزينه في المتغير bgGa<span style="font-family:courier new,courier,monospace;">m</span>eObject. الخطوة الثانية التي تقوم بها هذه الدّالة هي اختيار العنصر الأول في القائمة تلقائيا ليكون خلفية المشهد وذلك عن طريق استدعاء الدّالة <span style="font-family:courier new,courier,monospace;">()ChangeBackground</span> مع القيمة 0.
</p>

<p dir="rtl">
	الدّالة<span style="font-family:courier new,courier,monospace;"> ()ChangeBackground</span> هي الأهم في هذا البريمج حيث تعمل على تغيير الخلفية حسب القيمة التي تعطى لها حين الاستدعاء newIndex. إذا كانت هذه القيمة أصلا تساوي قيمة الخلفية المعروضة حاليا selectedIndex فلا حاجة لتغيير أي شيء بالتالي يتوقف تنفيذ الدّالة. عدا ذلك فإن الدّالة تحتاط لوجود خلفية معروضة حاليا فتقوم بحذفها قبل كل شيء. عملية الحذف تتم عبر المرور على جميع أبناء الكائن <span style="font-family:courier new,courier,monospace;">bgGameObject</span> واستدعاء الدّالة <span style="font-family:courier new,courier,monospace;">()Destroy</span> لها وذلك ليتم حذفها من المشهد. حالة حذف الكائن هي أحد الاستثناءات لقاعدة التعامل مع الكائنات عن طريق المكون <span style="font-family:courier new,courier,monospace;">Transform</span>، حيث أن الحذف يجب أن يتم على مستوى الكائن كاملا وليس على مكوّن بعينه، بالتالي نستدعي<span style="font-family:courier new,courier,monospace;"> bgSprite.gameObject</span> حين الحذف.
</p>

<p dir="rtl">
	بعد حذف الخلفية السابقة (إن وجدت) نحتاج لإضافة الجديدة. بالتالي فالخطوة التالية ستكون تخزين قيمة الصورة الموجودة في العنصر newIndex في المتغير <span style="font-family:courier new,courier,monospace;">newSprite</span> ومن ثم حساب عرض الصورة الأصلية بوحدات فضاء Unity وذلك بقسمة عرضها بوحدة البكسل على عدد البكسلات في كل وحدة. بما أن تكرار صورة الخلفية سوف يتمحور حول نقطة الأصل، فإن أقصى نقطة إلى اليسار ستكون نصف عرض جميع الصور التي سنكررها، بالتالي نحسب القيمة <span style="font-family:courier new,courier,monospace;">posX</span> عن طريق ضرب نصف العدد الكلي للصور <span style="font-family:courier new,courier,monospace;">repeatCount</span> بعرض الصورة الواحدة <span style="font-family:courier new,courier,monospace;">width</span>، ونحول الناتج للقيمة السالبة حتى نبدأ من اليسار (تذكر أننا قمنا باختيار يسار الصورة ليكون نقطة ارتكازها في الفضاء).
</p>

<p dir="rtl">
	إضافة لنقطة البداية قمنا بحساب الطول والعرض الكلي للمشهد بعد بناء الخلفية الجديدة، ومن ثم قمنا بتخزين هذه القيم في متغير من نوع <span style="font-family:courier new,courier,monospace;">Bounds</span>، هذا المتغير يخزن ببساطة نقطة في الفضاء وامتدادات لهذه النقطة على المحورين <span style="font-family:courier new,courier,monospace;">x</span> و <span style="font-family:courier new,courier,monospace;">y</span>. لاحظ أننا عرّفنا متغير الامتداد على شكل متجه ثنائي الأبعاد وحددنا قيمة <span style="font-family:courier new,courier,monospace;">x</span> بأنها عدد مرات تكرار صورة الخلفية مضروبة في عرض الصورة الواحدة و قيمة <span style="font-family:courier new,courier,monospace;">y</span> على أنها ارتفاع الصورة الواحدة. بعد ذلك قمنا بإرسال رسالة لجميع البريمجات الأخرى سواء الموجودة على الكائن الجذري أو أي كائن في الأبناء وذلك لإعلامها بأن حدود المشهد تغيرت، واستخدمنا لذلك الدّالة <span style="font-family:courier new,courier,monospace;">()BroadcastMessage</span>. عند استدعاء هذه الدّالة فإننا نعطيها اسم الرسالة وهو هنا <span style="font-family:courier new,courier,monospace;">SceneBoundsChanged</span> ونرفق معها أيضا المتغير <span style="font-family:courier new,courier,monospace;">newBounds</span> الذي يحتوي على حدود المشهد الجديدة. سنرى لاحقا ما معنى أن ترسل رسالة، وكيف تستقبلها، ولماذا هي مهمة. خلال مجموعة الدروس هذه سنتعامل بشكل مستفيض مع الرسائل، لذا لا بأس من التعرف مبكرا على كيفية إرسالها. يتم إرسال الرسائل في محرك Unity باستخدام أحد الدّوال التالية:
</p>

<ul dir="rtl">
	<li>
		<span style="font-family:courier new,courier,monospace;">BroadcastMessage</span> والتي تقوم بإرسال رسالة لجميع البريمجات الموجودة على الكائن الحالي إضافة إلى جميع كائنات الأبناء والأحفاد المتفرعة منه.
	</li>
	<li>
		<span style="font-family:courier new,courier,monospace;">SendMessage</span> والتي تقوم بإرسال الرسالة إلى جميع البريمجات الموجودة على الكائن الحالي فقط.
	</li>
	<li>
		<span style="font-family:courier new,courier,monospace;">SendMessageUpwards</span> والتي تقوم بإرسال الرسالة إلى جميع بريمجات الكائن الحالي إضافة إلى جميع آبائه وأجداده في الهرمية.
	</li>
</ul>

<p dir="rtl">
	الحلقة التكرارية التي تلي هذه الخطوات هي التي يتم عبرها بناء وتوزيع صور الخلفية في الفضاء بالشكل الصحيح. حيث نبدأ بإنشاء كائن جديد <span style="font-family:courier new,courier,monospace;">bg</span> مستخدمين القالب <span style="font-family:courier new,courier,monospace;">bgElementPrefab</span> ثم نعطيه اسما متسلسلا حسب موقعه في مجموعة الخلفيات من اليسار لليمين (مثلا BG_0 سيكون اسم الخلفية في أقصى اليسار و BG_3 في أقصى اليمين). بعد ذلك نستدعي المكوّن <span style="font-family:courier new,courier,monospace;">Sprite Renderer</span> ونحدد له الصورة التي سيقوم بتصييرها، وهي هنا <span style="font-family:courier new,courier,monospace;">newSprite</span> التي سبق واستخرجناها من المصفوفة <span style="font-family:courier new,courier,monospace;">selectionList</span>. مرة أخرى تعاملنا مع الكائن مباشرة وليس مع مكون <span style="font-family:courier new,courier,monospace;">Transform</span>، وذلك لأن الهدف هنا هو إضافة مكون جديد له.
</p>

<p dir="rtl">
	أما الخطوتان التاليتان فهما تتعاملان مع <span style="font-family:courier new,courier,monospace;">bg.transform</span> كما ترى، فالأولى تحدد موقعه في الفضاء <span style="font-family:courier new,courier,monospace;">gb.transform.position</span> والثانية تحدد الكائن الأب له وهو هنا <span style="font-family:courier new,courier,monospace;">bgGameObject</span> الذي سبق وتحدثنا عنه. بالنسبة للموقع فإن الخلفية تتوسط الشاشة عموديا كما ترى حيث <span style="font-family:courier new,courier,monospace;">y=0</span>، أما أفقيا فالموقع هو القيمة <span style="font-family:courier new,courier,monospace;">posX</span> التي سبق وحسبناها. من المهم هنا إدراك حقيقة أن صور الخلفية يجب أن تظهر خلف باقي الصور الموجودة في فضاء اللعبة. من أجل ذلك علينا أن نخبر مكوّن التصيير أن يقوم برسم الخلفية قبل أن يرسم أي صورة أخرى وذلك حتى لا تغطي الخلفية على عناصر المشهد. هذا الأمر يمكننا القيام به عن طريق تقليل قيمة المتغير <span style="font-family:courier new,courier,monospace;">sr.sortingOrder</span> حيث القيمة الافتراضية هي صفر. الصور ذات القيمة الأقل للمتغير <span style="font-family:courier new,courier,monospace;">sortingOrder</span> يتم تصييرها أولا تتبعها الصور ذات القيمة الأعلى فالأعلى.
</p>

<p dir="rtl">
	بعد ذلك نضيف مكوّن اكتشاف التصادمات Box Collider 2D حتى نتمكن من اكتشاف قيام اللاعب بلمس الخلفية أو النقر عليها بالفأرة. الخطوة الأخيرة هي زيادة <span style="font-family:courier new,courier,monospace;">posX</span> بمقدار عرض الصورة <span style="font-family:courier new,courier,monospace;">width</span> حتى يكون موقع الصورة التالية على يمين الحالية مباشرة وملاصقة لطرفها الأيمن. بعد بناء الخلفية نرسل رسالة أخرى هي <span style="font-family:courier new,courier,monospace;">BackgroundChanged</span> ونرفق معها رقم عنصر الخلفية الجديدة، والتي سنرى لاحقا أهميتها.
</p>

<h2>
	بناء نظام التحكم بالكاميرا
</h2>

<p dir="rtl">
	لو قمت بتشغيل اللعبة ستلاحظ أن الكاميرا تنظر مباشرة لوسط المشهد، وما سنقوم به الآن هو إضافة آلية لتحريكها يمينا ويسارا، إضافة إلى إمكانية التقريب والإبعاد. قبل البدء في تفاصيل التحكم بالكاميرا علينا أن نحدد ما هي نسبة عرض الشاشة إلى طولها aspect ratio. هذه القيمة مهمة لأنها تؤثر مباشرة على حجم المستطيل الذي يحدد مجال رؤية الكاميرا. بما أن هذه اللعبة مصممة للهواتف وتلعب بشكل عرضي، فإن النسبة التي سنعتمدها هي <span style="font-family:courier new,courier,monospace;">16:9</span>، ولاختيارها انتقل من شاشة المشهد Scene إلى شاشة اللعبة Game وقم باختيار هذه النسبة من أعلى يسار الشاشة كما ترى هنا:
</p>

<p dir="rtl" style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2015_07/image09.png.56ea02c704414d7d6292e9e9db5c6dd5.png" data-fileid="3395" data-fileext="png" rel=""><img alt="image09.thumb.png.3acf4fca436c1a4b447737" class="ipsImage ipsImage_thumbnailed" data-fileid="3395" src="https://academy.hsoub.com/uploads/monthly_2015_07/image09.thumb.png.3acf4fca436c1a4b4477373090f9d25a.png"></a>
</p>

<p dir="rtl">
	قبل البدء في كتابة بريمج التحكم بالكاميرا، علينا أن نعرف طبيعة هذا التحكم وماذا يمكن للاعب أن يفعل من خلاله. طريقة التحكم ستكون ثنائية البعد، أي أن اللاعب سيتمكن من نحريك الكاميرا يمينا ويسارا ولأعلى وأسفل. إضافة لذلك فإن اللاعب يجب أن يكون قادرا على تقريب وإبعاد الكاميرا، وكل ذلك ضمن حدود معينة. سنطّلع بعد قليل على بريمج التحكم بالكاميرا ومن ثم نشرح بعض تفاصيله، وأخيرا سنرى كيف يمكننا ربط وظائف هذا البريمج مع مدخلات اللاعب.
</p>

<p dir="rtl">
	لكن قبل ذلك لنتعلم أحد المبادئ المهمة في برمجة الألعاب وخاصة مع محرك Unity وهي فصل الاهتمامات separation of concerns. معنى هذا أن البريمج الواحد مسؤول عن مهمة واحدة، وهو الوحيد المخول بالتحكم بتفاصيل هذه المهمة. بالتالي فإننا سنقوم أولا بكتابة بريمج للتحكم بالكاميرا، لكنه منفصل تماما عن مدخلات اللاعب. هذه المدخلات سنكتبها في بريمج آخر خاص بها، والذي سيستقبلها وبناء عليها "يطلب" من بريمج التحكم بالكاميرا تحريكها. هذا الطلب قد يستجاب له وقد لا يستجاب له وفقا لمعطيات يقدرها بريمج التحكم بالكاميرا. مثلا إذا حرك اللاعب الكاميرا لأقصى يمين المشهد ومن ثم حاول تحريكها لليمين متجاوزا حدود المشهد، فسيقوم بريمج الكاميرا برفض هذا الطلب وستبقى الكاميرا في مكانها.
</p>

<p dir="rtl">
	لنبدأ الآن مع البريمج<span style="font-family:courier new,courier,monospace;"> CameraControl</span> والذي تراه في السرد التالي، هذا البريمج يجب أن تتم إضافته إلى كائن الكاميرا حتى يقوم بالتحكم بها:
</p>

<pre class="html ipsCode prettyprint prettyprinted" data-pbcklang="html" data-pbcktabsize="4" style=""><span class="kwd">using</span><span class="pln"> </span><span class="typ">UnityEngine</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">using</span><span class="pln"> </span><span class="typ">System</span><span class="pun">.</span><span class="typ">Collections</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">CameraControl</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="typ">MonoBehaviour</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    
    </span><span class="com">//أقل حجم للكاميرا في حال التقريب</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">float</span><span class="pln"> minSize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2.5f</span><span class="pun">;</span><span class="pln">
    
    </span><span class="com">//ارتفاع المستطيل المحدد لمجال الرؤية</span><span class="pln">
    </span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">float</span><span class="pln"> camHeight</span><span class="pun">;</span><span class="pln">
    
    </span><span class="com">//عرض المستطيل المحدد لمجال الرؤية</span><span class="pln">
    </span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">float</span><span class="pln"> camWidth</span><span class="pun">;</span><span class="pln">
    
    </span><span class="com">//مرجع إلى مكوّن الكاميرا</span><span class="pln">
    </span><span class="typ">Camera</span><span class="pln"> cam</span><span class="pun">;</span><span class="pln">
    
    </span><span class="com">//متغير لتخزين حدود المشهد الحالية</span><span class="pln">
    </span><span class="kwd">private</span><span class="pln"> </span><span class="typ">Bounds</span><span class="pln"> sceneBounds</span><span class="pun">;</span><span class="pln">
        
    </span><span class="com">//يتم تنفيذها مرة عند بداية التشغيل</span><span class="pln">
    </span><span class="kwd">void</span><span class="pln"> </span><span class="typ">Start</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="com">//اجلب مكوّن الكاميرا الموجود على نفس الكائن</span><span class="pln">
        cam </span><span class="pun">=</span><span class="pln"> </span><span class="typ">GetComponent</span><span class="pun">&lt;</span><span class="typ">Camera</span><span class="pun">&gt;();</span><span class="pln">
        </span><span class="com">//قم بتحديث أبعاد الكاميرا</span><span class="pln">
        </span><span class="typ">UpdateCamDimensions</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    
    </span><span class="com">//يتم استدعاؤها مرة عند تصيير كل إطار</span><span class="pln">
    </span><span class="kwd">void</span><span class="pln"> </span><span class="typ">Update</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        
    </span><span class="pun">}</span><span class="pln">
    
    </span><span class="com">//قم باستقبال الرسالة التي تخبر بتغير حدود المشهد</span><span class="pln">
    </span><span class="kwd">void</span><span class="pln"> </span><span class="typ">SceneBoundsChanged</span><span class="pun">(</span><span class="typ">Bounds</span><span class="pln"> newBounds</span><span class="pun">){</span><span class="pln">
        sceneBounds </span><span class="pun">=</span><span class="pln"> newBounds</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    
    </span><span class="com">//تقوم هذه الدّالة بتحريك الكاميرا بمقدار محدد</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> </span><span class="typ">Move</span><span class="pun">(</span><span class="typ">Vector2</span><span class="pln"> distance</span><span class="pun">){</span><span class="pln">
        </span><span class="com">//قم بحساب حدود الحركة المسموحة</span><span class="pln">
        </span><span class="kwd">float</span><span class="pln"> maxX </span><span class="pun">=</span><span class="pln"> </span><span class="typ">GetRightLimit</span><span class="pun">();</span><span class="pln">
        </span><span class="kwd">float</span><span class="pln"> minX </span><span class="pun">=</span><span class="pln"> </span><span class="typ">GetLeftLimit</span><span class="pun">();</span><span class="pln">
        </span><span class="kwd">float</span><span class="pln"> maxY </span><span class="pun">=</span><span class="pln"> </span><span class="typ">GetUpperLimit</span><span class="pun">();</span><span class="pln">
        </span><span class="kwd">float</span><span class="pln"> minY </span><span class="pun">=</span><span class="pln"> </span><span class="typ">GetLowerLimit</span><span class="pun">();</span><span class="pln">

            </span><span class="com">//يهمنا في هذه الحالة الخاصّة الموقع ثلاثي الأبعاد</span><span class="pln">
              </span><span class="com">//z = -10 وذلك حتى نضمن أن الكاميرا في الموقع</span><span class="pln">
               </span><span class="com">//دائما</span><span class="pln">
            </span><span class="typ">Vector3</span><span class="pln"> dist3D </span><span class="pun">=</span><span class="pln"> distance</span><span class="pun">;</span><span class="pln">

        </span><span class="com">//احسب الموقع الجديد</span><span class="pln">
              </span><span class="typ">Vector3</span><span class="pln"> newPos </span><span class="pun">=</span><span class="pln"> transform</span><span class="pun">.</span><span class="pln">position </span><span class="pun">+</span><span class="pln"> dist3D</span><span class="pun">;</span><span class="pln">
        
        </span><span class="com">//التزم بحدود الحركة</span><span class="pln">
        newPos</span><span class="pun">.</span><span class="pln">x </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Mathf</span><span class="pun">.</span><span class="typ">Clamp</span><span class="pun">(</span><span class="pln">newPos</span><span class="pun">.</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> minX</span><span class="pun">,</span><span class="pln"> maxX</span><span class="pun">);</span><span class="pln">
           newPos</span><span class="pun">.</span><span class="pln">y </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Mathf</span><span class="pun">.</span><span class="typ">Clamp</span><span class="pun">(</span><span class="pln">newPos</span><span class="pun">.</span><span class="pln">y</span><span class="pun">,</span><span class="pln"> minY</span><span class="pun">,</span><span class="pln"> maxY</span><span class="pun">);</span><span class="pln">
           newPos</span><span class="pun">.</span><span class="pln">z </span><span class="pun">=</span><span class="pln"> </span><span class="pun">-</span><span class="lit">10.0f</span><span class="pun">;</span><span class="pln">
        </span><span class="com">//قم بتغيير الموقع</span><span class="pln">
        transform</span><span class="pun">.</span><span class="pln">position </span><span class="pun">=</span><span class="pln"> newPos</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    
    </span><span class="com">//تقوم هذه الدّالة بتغيير تقريب الكاميرا</span><span class="pln">
    </span><span class="com">//القيمة الموجبة تعني تقريب الكاميرا من المشهد</span><span class="pln">
    </span><span class="com">//القيمة السالبة تعني إبعاد الكاميرا عن المشهد</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> </span><span class="typ">Zoom</span><span class="pun">(</span><span class="kwd">float</span><span class="pln"> amount</span><span class="pun">){</span><span class="pln">
        </span><span class="kwd">float</span><span class="pln"> newSize </span><span class="pun">=</span><span class="pln"> cam</span><span class="pun">.</span><span class="pln">orthographicSize </span><span class="pun">-</span><span class="pln"> amount</span><span class="pun">;</span><span class="pln">
        
        </span><span class="com">//التزم بحدود التقريب والإبعاد</span><span class="pln">
        </span><span class="kwd">float</span><span class="pln"> maxSize </span><span class="pun">=</span><span class="pln"> </span><span class="typ">GetMaxSize</span><span class="pun">();</span><span class="pln">
        newSize </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Mathf</span><span class="pun">.</span><span class="typ">Clamp</span><span class="pln"> </span><span class="pun">(</span><span class="pln">newSize</span><span class="pun">,</span><span class="pln"> minSize</span><span class="pun">,</span><span class="pln"> maxSize</span><span class="pun">);</span><span class="pln">
        
        </span><span class="com">//قم بتغيير حجم مجال الرؤية وبالتالي يتغير التقريب</span><span class="pln">
        cam</span><span class="pun">.</span><span class="pln">orthographicSize </span><span class="pun">=</span><span class="pln"> newSize</span><span class="pun">;</span><span class="pln">
        
        </span><span class="com">//قم بتحريك الكاميرا بمقدار صفر حتى تضمن تنفيذ الالتزام بحدود المشهد</span><span class="pln">
        </span><span class="typ">UpdateCamDimensions</span><span class="pun">();</span><span class="pln">
           </span><span class="typ">Move</span><span class="pun">(</span><span class="typ">Vector2</span><span class="pun">.</span><span class="pln">zero</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    
    </span><span class="com">//تقوم بتحديث عرض وارتفاع الكاميرا</span><span class="pln">
    </span><span class="kwd">void</span><span class="pln"> </span><span class="typ">UpdateCamDimensions</span><span class="pun">(){</span><span class="pln">
        camHeight </span><span class="pun">=</span><span class="pln"> cam</span><span class="pun">.</span><span class="pln">orthographicSize </span><span class="pun">*</span><span class="pln"> </span><span class="lit">2</span><span class="pun">;</span><span class="pln">
        camWidth </span><span class="pun">=</span><span class="pln"> camHeight </span><span class="pun">*</span><span class="pln"> cam</span><span class="pun">.</span><span class="pln">aspect</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    
    </span><span class="com">//تحسب قيمة أعلى ارتفاع للكاميرا</span><span class="pln">
    </span><span class="kwd">float</span><span class="pln"> </span><span class="typ">GetUpperLimit</span><span class="pun">(){</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> sceneBounds</span><span class="pun">.</span><span class="pln">max</span><span class="pun">.</span><span class="pln">y </span><span class="pun">-</span><span class="pln"> camHeight </span><span class="pun">*</span><span class="pln"> </span><span class="lit">0.5f</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    
    </span><span class="com">//تحسب قيمة أدنى ارتفاع للكاميرا</span><span class="pln">
    </span><span class="kwd">float</span><span class="pln"> </span><span class="typ">GetLowerLimit</span><span class="pun">(){</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> sceneBounds</span><span class="pun">.</span><span class="pln">min</span><span class="pun">.</span><span class="pln">y </span><span class="pun">+</span><span class="pln"> camHeight </span><span class="pun">*</span><span class="pln"> </span><span class="lit">0.5f</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    
    </span><span class="com">//تحسب الحد الأقصى لموقع الكاميرا يمينا</span><span class="pln">
    </span><span class="kwd">float</span><span class="pln"> </span><span class="typ">GetRightLimit</span><span class="pun">(){</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> sceneBounds</span><span class="pun">.</span><span class="pln">max</span><span class="pun">.</span><span class="pln">x </span><span class="pun">-</span><span class="pln"> camWidth </span><span class="pun">*</span><span class="pln"> </span><span class="lit">0.5f</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    
    </span><span class="com">//تحسب الحد الأقصى لموقع الكاميرا يسارا</span><span class="pln">
    </span><span class="kwd">float</span><span class="pln"> </span><span class="typ">GetLeftLimit</span><span class="pun">(){</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> sceneBounds</span><span class="pun">.</span><span class="pln">min</span><span class="pun">.</span><span class="pln">x </span><span class="pun">+</span><span class="pln"> camWidth </span><span class="pun">*</span><span class="pln"> </span><span class="lit">0.5f</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    
    </span><span class="com">//تقوم بحساب أكبر حجم مسموح للكاميرا حين الإبعاد</span><span class="pln">
    </span><span class="com">//وذلك اعتمادا على حدود المشهد</span><span class="pln">
    </span><span class="kwd">float</span><span class="pln"> </span><span class="typ">GetMaxSize</span><span class="pun">(){</span><span class="pln">
        </span><span class="kwd">float</span><span class="pln"> maxHeight </span><span class="pun">=</span><span class="pln"> sceneBounds</span><span class="pun">.</span><span class="pln">max</span><span class="pun">.</span><span class="pln">y </span><span class="pun">-</span><span class="pln"> sceneBounds</span><span class="pun">.</span><span class="pln">min</span><span class="pun">.</span><span class="pln">y</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> maxHeight </span><span class="pun">*</span><span class="pln"> </span><span class="lit">0.5f</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	المتغير العام الوحيد في هذا البريمج هو <span style="font-family:courier new,courier,monospace;">minSize</span> والذي يحدد أقل حجم مسموح للكاميرا عند التقريب. لشرح آلية التقريب والإبعاد لاحظ أولا أن الكاميرا لا ترى كل المشهد في كل وقت ولكن لها مجال رؤية محدد بشكل مستطيل، ويظهر هذا المستطيل حين اختيار الكاميرا كما في الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2015_07/image10.png.8f992495603a4d3953f4948fc7da07af.png" data-fileid="3396" data-fileext="png" rel=""><img alt="image10.thumb.png.f4c141b46b8d2a62dff545" class="ipsImage ipsImage_thumbnailed" data-fileid="3396" src="https://academy.hsoub.com/uploads/monthly_2015_07/image10.thumb.png.f4c141b46b8d2a62dff545c58d67e4df.png"></a>
</p>

<p dir="rtl">
	كلما زاد حجم هذا المستطيل فإن المساحة التي يراها من المشهد تزيد، بالتالي يبدو الأمر وكأن الكاميرا تبتعد، أما في حالة تقليل حجمه فإن العكس يحصل وهو تقليل المساحة المرئية بحيث يبدو الأمر كأن الكاميرا تقترب. يمكنك تجربة ذلك أثناء تشغيل اللعبة عن طريق تعديل قيمة المتغير size في مكوّن الكاميرا من شاشة الخصائص حيث قيمته الافتراضية هي 5.
</p>

<p dir="rtl">
	معنى هذا أننا نسمح بتقريب الكاميرا أي تصغير حجمها إلى أن يصل إلى 2.5 ويمكنك السماح بتقريبها أكثر إن قمت بتقليل هذا الرقم. المتغيران الآخران المهمان هما <span style="font-family:courier new,courier,monospace;">camHeight</span> و <span style="font-family:courier new,courier,monospace;">camWidth</span> ويمثلان طول وعرض المستطيل الذي يحدد مجال الرؤية، لكنهما محسوبان بقيم فضاء Unity وسنرى أهميتهما بعد قليل. أيضا نحتاج لمرجع لمكوّن الكاميرا وهو cam. تذكر ما ذكرته سابقا وهو أن المكوّنات هي التي تميز كائنا عن الآخر، ووجود هذا المكوّن هو الذي يجعل الكائن المسمى<span style="font-family:courier new,courier,monospace;"> Main Camera</span> عبارة عن كاميرا وليس شيئا آخر. أخيرا لدينا متغير من نوع <span style="font-family:courier new,courier,monospace;">Bounds</span> وهو <span style="font-family:courier new,courier,monospace;">sceneBounds</span> والذي سيقوم بتخزين حدود المشهد والتي سنحصر حركة الكاميرا دخلها.
</p>

<p dir="rtl">
	سأبدأ هذه المرة مع الدّالة <span style="font-family:courier new,courier,monospace;">()SceneBoundsChanged</span> وذلك لإكمال شرح فكرة الرسائل. بالعودة إلى البريمج <span style="font-family:courier new,courier,monospace;">BackgroundManager</span> سترى أنه يقوم بإرسال رسالة اسمها <span style="font-family:courier new,courier,monospace;">SceneBoundsChanged</span> ويرفقها بمتغير من نوع <span style="font-family:courier new,courier,monospace;">Bounds</span>. إذا قمت بتعريف دالّة تحمل نفس اسم الرسالة وتأخذ متغيرا من نفس نوع المرفق، فإنك بذلك حددت هذه الدّالة كأحد مستقبلي هذه الرسالة (يمكن أن يستقبل الرسالة عدد غير محدد من البريمجات). بالتالي فالبريمج <span style="font-family:courier new,courier,monospace;">CameraControl</span> يستقبل هذه الرسالة ويخزن قيمة الحدود الجديدة للمشهد في المتغير <span style="font-family:courier new,courier,monospace;">sceneBounds</span>.
</p>

<p dir="rtl">
	بالعودة إلى <span style="font-family:courier new,courier,monospace;">()Start</span> فإن ما تقوم به هو تخزين قيمة مكوّن الكاميرا في المتغير <span style="font-family:courier new,courier,monospace;">cam</span> ومن ثم تقوم باستدعاء <span style="font-family:courier new,courier,monospace;">()UpdateCamDimensions</span> والتي تحسب عرض وارتفاع الكاميرا بوحدات فضاء Unity. لنتحدث قليلا عن كيفية حساب هذه القيم. بالرجوع إلى توثيق محرك Unity وتحديدا <a href="http://docs.unity3d.com/ScriptReference/Camera-orthographicSize.html" rel="external nofollow">هذه الصفحة</a> سنعرف أن قيمة المتغير<span style="font-family:courier new,courier,monospace;"> cam.orthographicSize</span> والذي يمثل حجم الكاميرا هو عبارة عن منتصف ارتفاعها. بالتالي يمكننا ضرب هذه القيمة في 2 لنحصل على ارتفاع الكاميرا. بالنسبة لعرضها فهو يعتمد على نسبة العرض إلى الارتفاع aspect ratio، والذي يمكننا الوصول إليه عبر المتغير <span style="font-family:courier new,courier,monospace;">cam.aspect</span>، حيث يمكننا ضرب هذا المتغير في الارتفاع لنحصل على عرض الكاميرا.
</p>

<p dir="rtl">
	بحصولنا على حدود المشهد وارتفاع الكاميرا وعرضها، يمكننا أن نحسب الحدود التي يُسمح للكاميرا بالحركة ضمنها. سيُسمح للكاميرا بالحركة يمينا إلى أن يصل طرف مستطيل مجال الرؤية الأيمن إلى أقصى حد في يمين المشهد. بالتالي فإن أقصى موقع للكاميرا يمينا هو طرف المشهد الأيمن مطروح منه نصف عرض الكاميرا. هذه القيمة نحسبها عن طريق الدّالة<span style="font-family:courier new,courier,monospace;"> ()GetRightLimit</span>، وبطريقة مشابهة نحسب الحدود الأخرى في اليسار والأعلى والأسفل عبر <span style="font-family:courier new,courier,monospace;">()GetLeftLimit</span> و <span style="font-family:courier new,courier,monospace;">()GetUpperLimit</span> و <span style="font-family:courier new,courier,monospace;">()GetLowerLimit</span>. لاحظ أننا في هذه الدّوال استخدمنا <span style="font-family:courier new,courier,monospace;">sceneBounds.min</span> و <span style="font-family:courier new,courier,monospace;">sceneBounds.max</span> وهما المتجهان اللذان يعبران عن الحد الأدنى (أسفل ويسارا) والحد الأقصى (أعلى ويمينا) لحدود المشهد.
</p>

<p dir="rtl">
	قبل الدخول في الوظائف الرئيسية للبريمج وهما التحريك والتقريب، لنتعرف على الدّالة المتبقية ضمن الدّوال المساعدة وهي<span style="font-family:courier new,courier,monospace;"> ()GetMaxSize</span>. تعمل هذه الدّالة على حساب الحد الأقصى المسموح لحجم الكاميرا حين تنفيذ الإبعاد، وذلك اعتمادا على الحدين العلوي والسفلي للمشهد إضافة لارتفاع الكاميرا. فالارتفاع الأقصى المسموح به هو المسافة بين حد المشهد الأعلى والأسفل، والحجم المسموح به بالتالي هو نصف هذه المسافة، ذلك لأن حجم الكاميرا هو نصف ارتفاعها كما سبق وعرفنا.
</p>

<p dir="rtl">
	نأتي الآن على الوظائف الأساسية لهذا البريمج وهي تحريك الكاميرا وتقريبها وإبعادها. البداية مع التحريك والذي يتم عن طريق الدّالة <span style="font-family:courier new,courier,monospace;">()Move</span> والتي تأخذ متجها يمثل مقدار الإزاحة التي نرغب بتحريك الكاميرا بها. أول خطوة هي حساب القيم القصوى والدنيا للحركة المسموح بها، وتخزينها في المتغيرات الأربع <span style="font-family:courier new,courier,monospace;">maxX</span>, <span style="font-family:courier new,courier,monospace;">minX</span>, <span style="font-family:courier new,courier,monospace;">maxY</span>, <span style="font-family:courier new,courier,monospace;">minY</span>. بعد ذلك نقوم بتعريف متجه ثلاثي الأبعاد هو <span style="font-family:courier new,courier,monospace;">dist3D</span> وذلك لأن موقع الكاميرا على محور الثالث <span style="font-family:courier new,courier,monospace;">z</span> مهم بخلاف الكائنات الأخرى، حيث يجب أن تبقى هذه القيمة سالبة أي أن الكاميرا بعيدة عن المشهد نحو الخارج وتنظر إليه داخل الشاشة. أما إذا حسبنا موقعها مستخدمين الأبعاد الثنائية فقط فإن هذا سيؤدي لضياع القيمة <span style="font-family:courier new,courier,monospace;">z</span> للموقع وتصبح الكاميرا في الموقع <span style="font-family:courier new,courier,monospace;">z=0</span> وبالتالي لن يمكنها رؤية أي كائن في المشهد.
</p>

<p dir="rtl">
	بعد حساب الموقع الجديد نقوم بتخزينه في الموقع newPosition وكل ما علينا الآن هو التأكد من أن قيمة <span style="font-family:courier new,courier,monospace;">newPos.x</span> محصورة بين <span style="font-family:courier new,courier,monospace;">minX</span> و <span style="font-family:courier new,courier,monospace;">maxX</span> وكذلك الأمر بالنسبة لقيمة <span style="font-family:courier new,courier,monospace;">newPos.y</span> التي يجب أن تنحصر بين <span style="font-family:courier new,courier,monospace;">minY</span> و <span style="font-family:courier new,courier,monospace;">maxY</span>. لاحظ هنا استخدام الدّالة <span style="font-family:courier new,courier,monospace;">()Mathf.Clamp</span> والتي تقوم بالتأكد من أن الرقم الأول محصور بين الرقمين الثاني والثالث وتعيد الحد الأقصى أو الأدنى في حال كان الرقم يتجاوز هذين الحدين. أخيرا نقوم بتغيير موقع الكاميرا للموقع الجديد newPos.
</p>

<p dir="rtl">
	الوظيفة الرئيسية الثانية هي تقريب الكاميرا وإبعادها، والتي تتم عن طريق الدّالة<span style="font-family:courier new,courier,monospace;"> ()Zoom</span>، هذه الدّالة تأخذ رقما واحدا وهو مقدار التقريب (إن كان موجبا) أو الإبعاد (إن كان سالبا). طريقة التقريب والإبعاد تتم عن طريق طرح القيمة amount من حجم الكاميرا الحالي، وذلك لأن العلاقة بين حجم الكاميرا وتقريبها عكسية، فزيادة الحجم تعني تقليل التقريب. بعدها يتم حصر القيمة الجديدة بين <span style="font-family:courier new,courier,monospace;">minSize</span> وهو المتغير الذي يمكننا تحديده كما نشاء و <span style="font-family:courier new,courier,monospace;">maxSize</span> والذي يتم حسابه عن طريق الدّالة<span style="font-family:courier new,courier,monospace;"> ()GetMaxSize</span> التي سبق شرحها. بعد التأكد من أن الحجم الجديد ضمن الحدود يتم تغيير قيمة <span style="font-family:courier new,courier,monospace;">cam.orthogonalSize</span> للقيمة الجديدة <span style="font-family:courier new,courier,monospace;">newSize</span>. أخيرا يجب أن نعيد حساب أبعاد الكاميرا حيث أن حجمها تغير لذا نستدعي <span style="font-family:courier new,courier,monospace;">()UpdateCamDimensions</span> إضافة إلى إزاحتها بمقدار صفر احتياطا من إمكانية كون تغيير حجمها قد أدى لتجاوز حدودها لحدود المشهد خاصة في حالة الإبعاد حيث يزيد حجم الكاميرا؛ حيث أن الإزاحة بغض النظر عن مقدارها ستُبقي الكاميرا ضمن حدود المشهد دائما.
</p>

<p dir="rtl">
	بهذا تكون آلية التحكم بالكاميرا جاهزة، ويبقى علينا أن نربط استدعاء كل من <span style="font-family:courier new,courier,monospace;">()Move</span> و<span style="font-family:courier new,courier,monospace;"> ()Zoom</span> بمدخلات اللاعب. البداية ستكون مع الفأرة وذلك لأن المحرك يعمل على الحاسب بالتالي من السهل تجربتها مباشرة، بينما سنؤجل شاشة اللمس والهواتف الذكية لوقت لاحق. تحريك الكاميرا سيتم عن طريق الضغط بزر الفأرة الأيسر على الخلفية ومن ثم تحريكها في الاتجاهات الأربع، بينما يستخدم الزر الأيمن للتقريب والإبعاد، حيث يضغط اللاعب بالزر الأيمن ويحرك الفأرة لليمين للتقريب ولليسار للإبعاد. هذه الوظائف سيقوم بها البريمج <span style="font-family:courier new,courier,monospace;">CameraMouseInput</span> والذي يجب أن نضيفه لقالب الخلفية <span style="font-family:courier new,courier,monospace;">BGElement</span>، حيث يجب أن تكون كل صور الخلفية قادرة على استقبال مدخلات الفأرة. فيما يلي سرد بهذا البريمج.
</p>

<pre class="html ipsCode prettyprint prettyprinted" data-pbcklang="html" data-pbcktabsize="4" style=""><span class="kwd">using</span><span class="pln"> </span><span class="typ">UnityEngine</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">using</span><span class="pln"> </span><span class="typ">System</span><span class="pun">.</span><span class="typ">Collections</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">CameraMouseInput</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="typ">MonoBehaviour</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    
    </span><span class="com">//سرعة حركة الكاميرا</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">float</span><span class="pln"> movementSpeed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0.1f</span><span class="pun">;</span><span class="pln">
    
    </span><span class="com">//سرعة التقريب والإبعاد</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">float</span><span class="pln"> zoomingSpeed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0.01f</span><span class="pun">;</span><span class="pln">
    
    </span><span class="com">//أثناء التقريب والإبعاد trueتكون قيمة هذا المتغير</span><span class="pln">
    </span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">bool</span><span class="pln"> zoomingAcitve </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">
    
    </span><span class="com">//تقوم بتخزين موقع مؤشر الفأرة في الإطار السابق</span><span class="pln">
       </span><span class="kwd">private</span><span class="pln"> </span><span class="typ">Vector2</span><span class="pln"> lastPosition</span><span class="pun">;</span><span class="pln">
    
    </span><span class="com">//مرجع لبريمج التحكم بالكاميرا</span><span class="pln">
    </span><span class="kwd">private</span><span class="pln"> </span><span class="typ">CameraControl</span><span class="pln"> camControl</span><span class="pun">;</span><span class="pln">
    
    </span><span class="com">//يتم تنفيذ هذه الدّالة مرة عند بداية التشغيل</span><span class="pln">
    </span><span class="kwd">void</span><span class="pln"> </span><span class="typ">Start</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="com">//اجلب بريمج التحكم بالكاميرا</span><span class="pln">
        camControl </span><span class="pun">=</span><span class="pln"> </span><span class="typ">FindObjectOfType</span><span class="pun">&lt;</span><span class="typ">CameraControl</span><span class="pun">&gt;();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    
    </span><span class="com">//يتم استدعاؤها عند تصيير كل إطار لكن في وقت متأخر</span><span class="pln">
    </span><span class="kwd">void</span><span class="pln"> </span><span class="typ">LateUpdate</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">UpdateZooming</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    
    </span><span class="com">//تقوم بقراءة مدخلات التقريب والإبعاد</span><span class="pln">
    </span><span class="kwd">void</span><span class="pln"> </span><span class="typ">UpdateZooming</span><span class="pun">(){</span><span class="pln">
        </span><span class="com">//قم بفحص الضغط على زر الفأرة الأيمن</span><span class="pln">
        </span><span class="kwd">if</span><span class="pun">(</span><span class="typ">Input</span><span class="pun">.</span><span class="typ">GetMouseButtonDown</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)){</span><span class="pln">
            zoomingAcitve </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
            lastPosition </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">mousePosition</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        
        </span><span class="com">//قم بتحديث التقريب والإبعاد بناء على الحركة الأفقية للفأرة</span><span class="pln">
        </span><span class="kwd">if</span><span class="pun">(</span><span class="pln">zoomingAcitve</span><span class="pun">){</span><span class="pln">
            </span><span class="kwd">float</span><span class="pln"> amount </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">mousePosition</span><span class="pun">.</span><span class="pln">x </span><span class="pun">-</span><span class="pln"> lastPosition</span><span class="pun">.</span><span class="pln">x</span><span class="pun">;</span><span class="pln">
            camControl</span><span class="pun">.</span><span class="typ">Zoom</span><span class="pun">(</span><span class="pln">amount </span><span class="pun">*</span><span class="pln"> zoomingSpeed</span><span class="pun">);</span><span class="pln">
            lastPosition </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">mousePosition</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        
        </span><span class="com">//عند رفع الضغط عن زر الفأرة الأيمن false إلى  zoomingActive قم بإرجاع قيمة</span><span class="pln">
        </span><span class="kwd">if</span><span class="pun">(</span><span class="typ">Input</span><span class="pun">.</span><span class="typ">GetMouseButtonUp</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)){</span><span class="pln">
            zoomingAcitve </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    
    </span><span class="com">//يتم استدعاء هذه الدّالة عند الضغط بزر الفأرة الأيمن على الكائن</span><span class="pln">
    </span><span class="kwd">void</span><span class="pln"> </span><span class="typ">OnMouseDown</span><span class="pun">(){</span><span class="pln">
        </span><span class="com">//لا تسمح بالحركة أثناء التقريب والإبعاد</span><span class="pln">
        </span><span class="kwd">if</span><span class="pun">(!</span><span class="pln">zoomingAcitve</span><span class="pun">){</span><span class="pln">
            lastPosition </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">mousePosition</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    
    </span><span class="com">//يتم استدعاء هذه الدّالة عند السحب بزر الفأرة الأيسر فوق الكائن</span><span class="pln">
    </span><span class="kwd">void</span><span class="pln"> </span><span class="typ">OnMouseDrag</span><span class="pun">(){</span><span class="pln">
        </span><span class="com">//امنع الحركة أثناء التقريب والإبعاد</span><span class="pln">
        </span><span class="kwd">if</span><span class="pun">(!</span><span class="pln">zoomingAcitve</span><span class="pun">){</span><span class="pln">
                   </span><span class="typ">Vector2</span><span class="pln"> movement </span><span class="pun">=</span><span class="pln"> lastPosition </span><span class="pun">-</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Vector2</span><span class="pun">)</span><span class="typ">Input</span><span class="pun">.</span><span class="pln">mousePosition</span><span class="pun">;</span><span class="pln">
            camControl</span><span class="pun">.</span><span class="typ">Move</span><span class="pun">(</span><span class="pln">movement </span><span class="pun">*</span><span class="pln"> movementSpeed</span><span class="pun">);</span><span class="pln">
            lastPosition </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">mousePosition</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span></pre>

<p dir="rtl">
	كما ذكرت سابقا يجب أن تتم إضافة هذا البريمج على قالب صور الخلفية، وذلك لأن اللاعب سيضغط عليها بالفأرة إذا أراد تحريك الكاميرا. عند بداية تشغيل البريمج فإنه يبحث في المشهد عن البريمج الذي يتحكم بالكاميرا <span style="font-family:courier new,courier,monospace;">CameraController</span> وذلك عن طريق <span style="font-family:courier new,courier,monospace;">()FindObjectOfType</span>، وبما أن هذا البريمج موجود مرة واحدة في المشهد بالتالي ستكون نتيجة البحث هي قطعا البريمج الموجود على الكاميرا. بالتالي يمكننا الآن استخدام المتغير <span style="font-family:courier new,courier,monospace;">camController</span> للتحكم بالكاميرا.
</p>

<p dir="rtl">
	عندما يضغط اللاعب بزر الفأرة الأيسر على كائن ما يتم إرسال الرسالة <span style="font-family:courier new,courier,monospace;">OnMouseDown</span> مرة واحدة إلى ذلك الكائن، لذا فإننا هنا نستقبل هذه الرسالة عبر الدّالة التي تحمل نفس الاسم حيث نقوم بتخزين موقع الفأرة الحالي في المتغير <span style="font-family:courier new,courier,monospace;">lastPosition</span>. إذا بقي اللاعب ضاغطا على الزر الأيسر وقام بتحريك الفأرة، فحينها يتم إرسال الرسالة <span style="font-family:courier new,courier,monospace;">OnMouseDrag</span> إلى الكائن في كل إطار يتم فيه تحريك المؤشر أثناء الضغط. في الدّالة<span style="font-family:courier new,courier,monospace;"> ()OnMouseDrag</span> نقوم بثلاثة أشياء:
</p>

<ul dir="rtl">
	<li>
		أولا نحسب مقدار الإزاحة عن موقع الضغط وذلك بطرح الموقع الحالي من الموقع السابق. بهذه الطريقة فإننا نجعل حركة الكاميرا معاكسة لحركة المؤشر، فإن تحرك المؤشر يمينا تتحرك الكاميرا يسارا، مما يعطي اللاعب شعورا بأنه لا يحرك الكاميرا، وإنما يمسك المشهد ويحركه يمينا ويسارا وهذه الطريقة أسهل عليه في التحكم خاصة مع شاشة اللمس.
	</li>
	<li>
		الخطوة الثانية هي استدعاء الدّالة <span style="font-family:courier new,courier,monospace;">()Move </span>وتزويدها بمقدار الإزاحة المحسوبة مضروبة في سرعة الحركة. لاحظ أن سرعة الحركة قليلة نسبيا وذلك لأن قيمة إزاحة الفأرة عالية مقارنة مع مقدار الإزاحة المطلوبة للكاميرا.
	</li>
	<li>
		أخيرا وبعد تنفيذ الإزاحة نقوم بتخزين موقع المؤشر الحالي في <span style="font-family:courier new,courier,monospace;">lastPosition</span> حتى نكون جاهزين لحساب الإزاحة القادمة انطلاقا من الموقع الجديد.
	</li>
</ul>

<p dir="rtl">
	لاحظ أن جميع الخطوات في الدّالتين <span style="font-family:courier new,courier,monospace;">()OnMouseDown</span> و<span style="font-family:courier new,courier,monospace;"> ()OnMouseDrag</span> مشروطتان بأن تكون قيمة <span style="font-family:courier new,courier,monospace;">zoomingActive</span> هي <span style="font-family:courier new,courier,monospace;">false</span>. هذا المتغير يخبرنا ما إذا كان اللاعب يقوم حاليا بتقريب أو إبعاد الكاميرا مستخدما الزر الأيمن، وبذلك نمنع التحريك والتقريب في آن واحد. الدّالة <span style="font-family:courier new,courier,monospace;">()LateUpdate</span> التي عرّفناها هنا بدلا من <span style="font-family:courier new,courier,monospace;">()Update</span> تختلف عن هذه الأخيرة في شيء واحد، وهو أنها تُستدعى متأخرة عنها. فعند تحديث كل إطار يقوم Unity بتحديث جميع البريمجات عن طريق استدعاء <span style="font-family:courier new,courier,monospace;">()Update</span> ومن ثم يعاود التحديث مجددا باستخدام <span style="font-family:courier new,courier,monospace;">()LateUpdate</span>. بالتالي فإن استدعاء أي خطوات خلال <span style="font-family:courier new,courier,monospace;">()LateUpdate</span> هو مضمون أن يتم تنفيذه بعد تحديث جميع عناصر المشهد من خلال <span style="font-family:courier new,courier,monospace;">()Update</span>. الفائدة من ذلك هو أن نضمن أن كل العناصر أصبحت جاهزة للإطار الجديد قبل أن نقوم بتحريك الكاميرا، بالتالي عادة ما يتم استخدام <span style="font-family:courier new,courier,monospace;">()LateUpdate</span> مع أي شيء له علاقة بالتحكم بالكاميرا.
</p>

<p dir="rtl">
	ما نفعله في <span style="font-family:courier new,courier,monospace;">()LateUpdate</span> هو استدعاء <span style="font-family:courier new,courier,monospace;">()UpdateZooming</span> والتي تفحص ما إذا ضغط اللاعب على زر الفأرة الأيمن عن طريق الدّالة <span style="font-family:courier new,courier,monospace;">()Input.GetMouseButtonDown</span> والتي تفحص أزرار الفأرة بناء على أرقامها؛ فالرقم 0 للأيسر و 1 للأيمن و 2 للأوسط. أول ما نقوم به عند اكتشاف الضغط على الزر الأيمن هو تغيير قيمة <span style="font-family:courier new,courier,monospace;">zoomingActive</span> إلى <span style="font-family:courier new,courier,monospace;">true</span> مما يمنع استقبال مدخلات التحريك. بعدها نقوم بتخزين الموقع الحالي للمؤشر في <span style="font-family:courier new,courier,monospace;">lastPosition</span> تماما كما فعلنا مع حالة الضغط بالزر الأيسر. بناء على قيمة <span style="font-family:courier new,courier,monospace;">zoomingActive</span> نقوم بتحديث التقريب والإبعاد، فنحسب قيمة الإزاحة الأفقية بطرح الإحداثي x للموقع السابق من الإحداثي <span style="font-family:courier new,courier,monospace;">x</span> للموقع الحالي، فإذا تحرك مؤشر الفأرة يمينا ستكون النتيجة موجبة مما يجعل قيمة <span style="font-family:courier new,courier,monospace;">amount * zoomingSpeed</span>  المزودة للدّالة <span style="font-family:courier new,courier,monospace;">()camControl.Zoom</span> موجبة وينتج عنه تقريب الكاميرا، والعكس يحدث حين تحريك الفأرة يسارا. بعد تحديث قيمة التقريب نقوم بتخزين الموقع الحالي في <span style="font-family:courier new,courier,monospace;">lastPosition</span> من أجل حساب الإزاحة القادمة. آخر خطوة في هذه الدّالة هي فحص ما إذا كان اللاعب قد رفع الضغط عن زر الفأرة الأيمن وإعادة قيمة <span style="font-family:courier new,courier,monospace;">zoomingSpeed</span> إلى fal<span style="font-family:courier new,courier,monospace;">s</span>e في هذه الحالة.
</p>

<h2 dir="rtl">
	بناء أرضية المشهد
</h2>

<p dir="rtl">
	بعد أن انتهينا من تجهيز خلفية المشهد وأصبحنا قادرين على تحريك الكاميرا للتجول فيه بحرية، حان الوقت لإضافة عناصر المشهد، وهي الكائنات ثنائية الأبعاد التي سنبني منها اللعبة. ستكون البداية مع الأرضية التي ستقف عليها باقي الكائنات، والتي سنبنيها بطريقة مشابهة لطريقة بناء الخلفية. سنقوم أولا بإضافة كائن فارغ كابن للكائن الجذري ونسميه <span style="font-family:courier new,courier,monospace;">Ground</span>. هذا الكائن سيكون أبا لجميع كائنات الأرضية. بعدها سنقوم بإنشاء قالب خاص بكائنات الأرضية ونضيف له المكوّن <span style="font-family:courier new,courier,monospace;">sprite renderer</span> والبريمج <span style="font-family:courier new,courier,monospace;">CameraMouseInput</span>، مما يجعل التحكم بالكاميرا عن طريق الضغط على الأرضية ممكنا تماما كما هو الحال مع الخلفية. سنسمي هذا القالب <span style="font-family:courier new,courier,monospace;">GroundElement</span> ومن ثم سنكتب البريمج الذي يعمل على إضافة كائنات الأرضية اعتمادا على الخلفية وحدود المشهد. هذا البريمج هو <span style="font-family:courier new,courier,monospace;">GroundManager</span> والموضح في السرد التالي، علما أن هذا البريمج يجب أن نضيفه أيضا للكائن الجذري<span style="font-family:courier new,courier,monospace;"> SceneRoot</span>.
</p>

<pre class="html ipsCode prettyprint prettyprinted" data-pbcklang="html" data-pbcktabsize="4" style=""><span class="kwd">using</span><span class="pln"> </span><span class="typ">UnityEngine</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">using</span><span class="pln"> </span><span class="typ">System</span><span class="pun">.</span><span class="typ">Collections</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">GroundManager</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="typ">MonoBehaviour</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    
    </span><span class="com">//قالب كائن عناصر الأرضية</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">GameObject</span><span class="pln"> gePrefab</span><span class="pun">;</span><span class="pln">
    
    </span><span class="com">//مصفوفة بصور الأرضية المتوفرة</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">Sprite</span><span class="pun">[]</span><span class="pln"> selectionList</span><span class="pun">;</span><span class="pln">
    
    </span><span class="com">//متغير لتخزين حدود المشهد الحالية</span><span class="pln">
    </span><span class="kwd">private</span><span class="pln"> </span><span class="typ">Bounds</span><span class="pln"> sceneBounds</span><span class="pun">;</span><span class="pln">
    
    </span><span class="com">//موقع صورة الخلفية المعروضة حاليا</span><span class="pln">
    </span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">float</span><span class="pln"> selectedIndex </span><span class="pun">=</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">;</span><span class="pln">
    
    </span><span class="com">//تستدعى مرة واحدة عند بداية التشغيل</span><span class="pln">
    </span><span class="kwd">void</span><span class="pln"> </span><span class="typ">Start</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">        
    </span><span class="pun">}</span><span class="pln">
    
    </span><span class="com">//تستدعى مرة واحدة عند تصيير كل إطار</span><span class="pln">
    </span><span class="kwd">void</span><span class="pln"> </span><span class="typ">Update</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    
    </span><span class="com">//تقوم باستقبال رسالة تغير صورة الخلفية</span><span class="pln">
    </span><span class="kwd">void</span><span class="pln"> </span><span class="typ">BackgroundChanged</span><span class="pun">(</span><span class="kwd">int</span><span class="pln"> newIndex</span><span class="pun">){</span><span class="pln">
        </span><span class="typ">ChangeGround</span><span class="pun">(</span><span class="pln">newIndex</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    
    </span><span class="com">//تقوم باستقبال رسالة تغير حدود المشهد</span><span class="pln">
    </span><span class="kwd">void</span><span class="pln"> </span><span class="typ">SceneBoundsChanged</span><span class="pun">(</span><span class="typ">Bounds</span><span class="pln"> newBounds</span><span class="pun">){</span><span class="pln">
        sceneBounds </span><span class="pun">=</span><span class="pln"> newBounds</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
        
    </span><span class="com">//تقوم بتغيير صورة الأرضية المعروضة بناء على رقم العنصر المحدد</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> </span><span class="typ">ChangeGround</span><span class="pun">(</span><span class="kwd">int</span><span class="pln"> newIndex</span><span class="pun">){</span><span class="pln">
        </span><span class="com">//"Ground" ابحث عن الكائن الابن المسمى</span><span class="pln">
        </span><span class="typ">Transform</span><span class="pln"> groundGameObject </span><span class="pun">=</span><span class="pln"> transform</span><span class="pun">.</span><span class="typ">FindChild</span><span class="pun">(</span><span class="str">"Ground"</span><span class="pun">);</span><span class="pln">
        
        </span><span class="com">//قم بحذف عناصر الأرضية الحالية</span><span class="pln">
        </span><span class="kwd">for</span><span class="pun">(</span><span class="kwd">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> groundGameObject</span><span class="pun">.</span><span class="pln">childCount</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++){</span><span class="pln">
            </span><span class="typ">Transform</span><span class="pln"> ge </span><span class="pun">=</span><span class="pln"> groundGameObject</span><span class="pun">.</span><span class="typ">GetChild</span><span class="pun">(</span><span class="pln">i</span><span class="pun">);</span><span class="pln">
            </span><span class="typ">Destroy</span><span class="pun">(</span><span class="pln">ge</span><span class="pun">.</span><span class="pln">gameObject</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        
        </span><span class="com">//قم بحساب عرض المشهد</span><span class="pln">
        </span><span class="kwd">float</span><span class="pln"> sceneWidth </span><span class="pun">=</span><span class="pln"> sceneBounds</span><span class="pun">.</span><span class="pln">max</span><span class="pun">.</span><span class="pln">x </span><span class="pun">-</span><span class="pln"> sceneBounds</span><span class="pun">.</span><span class="pln">min</span><span class="pun">.</span><span class="pln">x</span><span class="pun">;</span><span class="pln">
        
        </span><span class="com">//قم بحساب النقطة في أقصى اليسار</span><span class="pln">
        </span><span class="kwd">float</span><span class="pln"> posX </span><span class="pun">=</span><span class="pln"> sceneBounds</span><span class="pun">.</span><span class="pln">min</span><span class="pun">.</span><span class="pln">x</span><span class="pun">;</span><span class="pln">
        
        </span><span class="com">//قم بجلب صورة الأرضية من المصفوفة</span><span class="pln">
        </span><span class="typ">Sprite</span><span class="pln"> newGround </span><span class="pun">=</span><span class="pln"> selectionList</span><span class="pun">[</span><span class="pln">newIndex</span><span class="pun">];</span><span class="pln">
        
        </span><span class="com">//قم بحساب عرض وارتفاع صورة الأرضية</span><span class="pln">
        </span><span class="kwd">float</span><span class="pln"> width </span><span class="pun">=</span><span class="pln"> newGround</span><span class="pun">.</span><span class="pln">rect</span><span class="pun">.</span><span class="pln">width</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">float</span><span class="pln"> height </span><span class="pun">=</span><span class="pln"> newGround</span><span class="pun">.</span><span class="pln">rect</span><span class="pun">.</span><span class="pln">height</span><span class="pun">;</span><span class="pln">
        
        </span><span class="com">//قم بحساب العرض والارتفاع بالوحدات</span><span class="pln">
        width </span><span class="pun">=</span><span class="pln"> width </span><span class="pun">/</span><span class="pln"> newGround</span><span class="pun">.</span><span class="pln">pixelsPerUnit</span><span class="pun">;</span><span class="pln">
        height </span><span class="pun">=</span><span class="pln"> height </span><span class="pun">/</span><span class="pln"> newGround</span><span class="pun">.</span><span class="pln">pixelsPerUnit</span><span class="pun">;</span><span class="pln">
        
        </span><span class="com">//قم بحساب الموقع العمودي للأرضية</span><span class="pln">
        </span><span class="kwd">float</span><span class="pln"> posY </span><span class="pun">=</span><span class="pln"> sceneBounds</span><span class="pun">.</span><span class="pln">min</span><span class="pun">.</span><span class="pln">y </span><span class="pun">+</span><span class="pln"> height </span><span class="pun">*</span><span class="pln"> </span><span class="lit">0.5f</span><span class="pun">;</span><span class="pln">
        
        </span><span class="com">//كم مرة يجب أن نكرر صورة الأرضية؟</span><span class="pln">
        </span><span class="kwd">int</span><span class="pln"> repeats </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Mathf</span><span class="pun">.</span><span class="typ">RoundToInt</span><span class="pun">(</span><span class="pln">sceneWidth </span><span class="pun">/</span><span class="pln"> width</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">
        
        </span><span class="kwd">for</span><span class="pun">(</span><span class="kwd">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> repeats</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++){</span><span class="pln">
            </span><span class="com">//قم بإضافة كائن عنصر أرضية جديد</span><span class="pln">
            </span><span class="typ">GameObject</span><span class="pln"> ge </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="typ">GameObject</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Instantiate</span><span class="pun">(</span><span class="pln">gePrefab</span><span class="pun">);</span><span class="pln">
            ge</span><span class="pun">.</span><span class="pln">name </span><span class="pun">=</span><span class="pln"> </span><span class="str">"GE_"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> i</span><span class="pun">;</span><span class="pln">
            
            </span><span class="com">//ضع الكائن في الموقع الصحيح</span><span class="pln">
                  ge</span><span class="pun">.</span><span class="pln">transform</span><span class="pun">.</span><span class="pln">position </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Vector2</span><span class="pun">(</span><span class="pln">posX</span><span class="pun">,</span><span class="pln"> posY</span><span class="pun">);</span><span class="pln">
            
            </span><span class="com">//قم بتحديد الأب</span><span class="pln">
            ge</span><span class="pun">.</span><span class="pln">transform</span><span class="pun">.</span><span class="pln">parent </span><span class="pun">=</span><span class="pln"> groundGameObject</span><span class="pun">;</span><span class="pln">
            
            </span><span class="com">//قم بوضع الصورة على مكوّن تصيير الكائن الذي أضفناه</span><span class="pln">
            </span><span class="typ">SpriteRenderer</span><span class="pln"> sr </span><span class="pun">=</span><span class="pln"> ge</span><span class="pun">.</span><span class="typ">GetComponent</span><span class="pun">&lt;</span><span class="typ">SpriteRenderer</span><span class="pun">&gt;();</span><span class="pln">
            sr</span><span class="pun">.</span><span class="pln">sprite </span><span class="pun">=</span><span class="pln"> newGround</span><span class="pun">;</span><span class="pln">
            
            </span><span class="com">//قم بإضافة مكوّن التصادم</span><span class="pln">
            ge</span><span class="pun">.</span><span class="typ">AddComponent</span><span class="pun">&lt;</span><span class="typ">BoxCollider2D</span><span class="pun">&gt;();</span><span class="pln">
            
            </span><span class="com">//قم بزيادة قيمة الموقع الأفقي استعدادا للعنصر التالي</span><span class="pln">
            posX </span><span class="pun">+=</span><span class="pln"> width</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p dir="rtl">
	ما نلاحظه للوهلة الأولى هو التشابه الشديد بين <span style="font-family:courier new,courier,monospace;">GroundManager</span> و <span style="font-family:courier new,courier,monospace;">BackgroundManager</span> حثي أن كلا منهما يقوم ببناء عدة كائنات مستخدما قالبا محددا ومن ثم رص هذه الكائنات بجانب بعضها البعض من اليسار لليمين. الفرق هنا هو أن هذا البريمج يعتمد في عمله على استقبال الرسالة <span style="font-family:courier new,courier,monospace;">BackgroundChanged</span> بحيث يختار صورة الأرضية الموجودة في الموقع المساوي لموقع الخلفية التي تم اختيارها. معنى ذلك أنه لصور الخلفية الثمانية الموجودة سنحتاج لصور ثماني أرضيات. صور الأرضيات موجودة في المجلد Other في مجموعة الصور وعددها هو 4 كما ترى في الصورة التالية، مما يعني أن نفس الأرضية يمكن أن تتكرر مع أكثر من خلفية:
</p>

<p dir="rtl" style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2015_07/image11.png.d8739dc6aa298b5991370f57e624d0fd.png" data-fileid="3397" data-fileext="png" rel=""><img alt="image11.thumb.png.dc6776c4bd6e8898ac0c80" class="ipsImage ipsImage_thumbnailed" data-fileid="3397" src="https://academy.hsoub.com/uploads/monthly_2015_07/image11.thumb.png.dc6776c4bd6e8898ac0c809d6a4406d3.png"></a>
</p>

<p dir="rtl">
	أول خطوة علينا القيام بها هي تغيير نقطة الارتكاز لهذه الصور الأربعة بحيث تصبح في اليسار تماما كما فعلنا مع صور الخلفية. ومن ثم سنقوم بسحبها إلى المواقع الثمانية داخل المصفوفة <span style="font-family:courier new,courier,monospace;">selectionList</span>. أثناء إضافتي لصورة الأرضية المقابلة لكل خلفية، حاولت أن أختار لونا متميزا عن لون الجزء الأسفل من الخلفية، بحيث يسهل على اللاعب تمييز أرضية المشهد عن خلفيته، وكانت النتيجة كالتالي. لا تنس أيضا بعد إضافة البريمج للكائن <span style="font-family:courier new,courier,monospace;">SceneRoot</span> أن تقوم بتحديد القالب <span style="font-family:courier new,courier,monospace;">GroundElement</span> ليكون هو مصدر بناء كائنات الأرضية.
</p>

<p dir="rtl" style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2015_07/image12.png.8844173208f41f6a3ab5ae5d0fa9ae20.png" data-fileid="3398" data-fileext="png" rel=""><img alt="image12.thumb.png.dc8c916c338e6ff35b378f" class="ipsImage ipsImage_thumbnailed" data-fileid="3398" src="https://academy.hsoub.com/uploads/monthly_2015_07/image12.thumb.png.dc8c916c338e6ff35b378f4448b591e1.png"></a>
</p>

<p dir="rtl">
	اختلاف آخر بين البريمجين نلحظه في موقع وضع الكائنات، حيث توضع كائنات الخلفية عموديا في المنتصف بينما توضع كائنات الأرضية في الأسفل. الفرق الآخر المهم هو أن عدد مرات التكرار بالنسبة للخلفية متغير يمكننا تحديد قيمته، بينما في حالة الأرضية يتم حسابه تلقائيا بقسمة عرض المشهد على عرض صورة الأرضية، ومن ثم إضافة 1 لتغطية أي ثغرة متبقية في أقصى اليمين نتيجة للكسور.
</p>

<p dir="rtl">
	إلى هذه النقطة نكون قد انتهينا من بناء الخلفية والأرضية، بالتالي لو حاولنا تشغيل اللعبة الآن سنحصل على النتيجة التالية:
</p>

<p dir="rtl" style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2015_07/image13.png.e896f16e325edce947a0930757f43e71.png" data-fileid="3399" data-fileext="png" rel=""><img alt="image13.thumb.png.8c6605f4f5877beb4b51a4" class="ipsImage ipsImage_thumbnailed" data-fileid="3399" src="https://academy.hsoub.com/uploads/monthly_2015_07/image13.thumb.png.8c6605f4f5877beb4b51a40a76082d2c.png"></a>
</p>

<p dir="rtl">
	قمنا في هذا الدرس بتجهيز الخلفية و الأرضية للعبة كما قمنا بالتحكم في الكاميرا. في الدرس القادم سنقوم بإنشاء الوحدات البنائية للعبة.
</p>

<h2 dir="rtl">
	اقرأ أيضًا
</h2>

<ul dir="rtl">
	<li>
		<p>
			<a href="https://academy.hsoub.com/programming/game-development/%D9%84%D8%BA%D8%A7%D8%AA-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8/" rel="">تعرف على أشهر لغات برمجة الألعاب</a>
		</p>
	</li>
</ul>
]]></description><guid isPermaLink="false">131</guid><pubDate>Thu, 30 Jul 2015 18:52:00 +0000</pubDate></item><item><title>&#x646;&#x628;&#x630;&#x629; &#x639;&#x646; &#x635;&#x646;&#x627;&#x639;&#x629; &#x627;&#x644;&#x623;&#x644;&#x639;&#x627;&#x628; &#x648;&#x645;&#x62D;&#x631;&#x643; Unity3D</title><link>https://academy.hsoub.com/programming/game-development/%D9%86%D8%A8%D8%B0%D8%A9-%D8%B9%D9%86-%D8%B5%D9%86%D8%A7%D8%B9%D8%A9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D9%88%D9%85%D8%AD%D8%B1%D9%83-unity3d-r126/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2015_07/unity3D-intro.jpg.1eef333a724c24ead1691a918e6b50bb.jpg" /></p>
<p dir="rtl" style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="3318" href="https://academy.hsoub.com/uploads/monthly_2015_07/unity3D-intro.jpg.e571e16275203a1aac77b316cf2c159e.jpg" rel="" data-fileext="jpg"><img alt="unity3D-intro.thumb.jpg.56579b9566ade56c" class="ipsImage ipsImage_thumbnailed" data-fileid="3318" src="https://academy.hsoub.com/uploads/monthly_2015_07/unity3D-intro.thumb.jpg.56579b9566ade56c14a9a47f0bbb7850.jpg"></a>
</p>

<h2 dir="rtl">
	نبذة مختصرة عن صناعة الألعاب وتطويرها
</h2>

<p dir="rtl">
	لعل صناعة الألعاب هي إحدى أصعب الصناعات في هذا العصر، وذلك من عدة نواح تبدأ بالتحديات التقنية، مرورا بجمهور صعب الإرضاء ولا يرحم حتى كبريات الشركات إن لم تكن منتجاتها بالمستوى المطلوب، وليس انتهاءا بالمنافسة الشرسة ونسب الفشل العالية وصعوبة تحقيق أرباح تغطي تكاليف الإنتاج المرتفعة.
</p>

<p dir="rtl">
	على الجانب الآخر يوجد ميزات لهذه الصناعة تجعل من النجاة فيها أمرا ممكنا، فعلى الجانب التقني مثلا، لا تخلوا الغالبية العظمى من الألعاب من وظائف متشابهة وأنماط متكررة من معالجة البيانات، مما يجعل إعادة استخدام الوحدات البرمجية للألعاب السابقة من أجل إنشاء ألعاب جديدة أمرا ممكنا، وهذا بدوره يساهم في تذليل العقبات التقنية واختصار الوقت والجهد.
</p>

<p dir="rtl">
	عندما تتحدث عن صناعة لعبة، فأنت هنا تذكر العملية الكبرى والتي تنطوي على عشرات وربما مئات المهام التي يجب أن تنجزها في مجالات عدة. فصناعة لعبة تعني إنتاجها وتسويقها ونشرها وكل ما يتعلق بهذه العمليات من إجراءات وخطوات إدارية وتقنية وفنية ومالية وقانونية. على أية حال فإن ما يهمنا في سلسلة الدروس هذه هو الجانب التقني وهو تطوير اللعبة، وهي عملية بناء المنتج البرمجي النهائي بكافة مكوّناته. هذه العملية لا تشمل بالضرورة تصميم اللعبة، حيث أن عملية التصميم ذات منظور أوسع وتركز على أمور مثل القصة والسمة العامة للعبة وأشكال المراحل وطبيعة الخصوم، بالإضافة إلى قواعد اللعبة وأهدافها وشروط الفوز والخسارة.
</p>

<p dir="rtl">
	بالعودة لعملية تطوير اللعبة، نجد أن العديد من الاختصاصات والمهارات تساهم في هذه العملية، فهناك الرسامون ومصممو النماذج وفنيو التحريك ومهندسو الصوت والمخرج، إضافة – بالطبع – للمبرمجين. هذه النظرة الشاملة مهمة لنعرف أن دور المبرمج في إنتاج اللعبة ليس سوى دورا مكمّلا لأدوار غيره من أعضاء الفريق، ولو أن هذه الصورة بدأت تتغير بظهور المطورين المستقلين Indie Developers والذين يقومون بالعديد من المهام إلى جانب البرمجة.
</p>

<h2 dir="rtl">
	لماذا نستخدم محركات الألعاب؟
</h2>

<p dir="rtl">
	لو أردنا الحديث بتفصيل أكبر عن دور المبرمجين في صناعة الألعاب، سنجد أنه حتى على مستوى البرمجة نفسها هناك أدوار عديدة يجب القيام بها: فهناك برمجة الرسومات وهناك أنظم الإدخال وأنظمة استيراد الموارد والذكاء الاصطناعي ومحاكاة الفيزياء وغيرها مثل مكتبات الصوت والأدوات المساعدة. كل هذه المهام يمكن إنجازها على شكل وحدات برمجية قابلة لإعادة الاستخدام كما سبق وذكرت، وبالتالي فهذه الوحدات تشكل معا ما يعرف ب<a href="https://academy.hsoub.com/programming/game-development/%D9%85%D8%AD%D8%B1%D9%83%D8%A7%D8%AA-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-game-engines/" rel="">محرك الألعاب Game Engine</a><span style="font-family:courier new,courier,monospace;">.</span> باستخدامك للمحرك والمكتبات البرمجية التي يتكون منها، فأنت تختصر على نفسك الجهد اللازم لبناء نظام الإدخال والإخراج والاستيراد ومحاكاة الفيزياء، وحتى جزء من الذكاء الاصطناعي. وما يتبقى عليك هو كتابة منطق لعبتك الخاصة وإبداع ما يميزها عن غيرها من الألعاب. هذه النقطة الأخيرة هي ما ستدور حوله سلسلة الدروس القادمة، وبالرغم من أن المهمة تبدو صغيرة جدا مقارنة بتطوير اللعبة كاملة، إلا أنها على صغرها تحتاج لمجهود معتبر في التصميم والتنفيذ كما سنرى.
</p>

<h2>
	خطوات سريعة لتبدأ مع محرك Unity
</h2>

<p dir="rtl">
	إن لم تكن ذا خبرة سابقة بهذا المحرك يمكنك قراءة هذه المقدمة السريعة، كما يمكنك تخطيها إن كنت تعاملت مع هذا المحرك سابقا. لن أطيل شرح هذه الخطوات حيث هناك الكثير من الدروس سواء بالعربية أو الإنجليزية تتناولها، لكنها هنا لنتأكد من أن كل قارئ للسلسلة على نفس الدرجة من المعرفة الأولية قبل البدء.
</p>

<h3>
	الخطوة الأولى: تحميل وتنصيب المحرك
</h3>

<p dir="rtl">
	لتنزيل الإصدار الأحدث من المحرك وهو 5 ادخل مباشرة إلى الموقع <a href="http://unity3d.com/" rel="external nofollow">http://unity3d.com</a> ومن ثم قم بتحميل النسخة المناسبة لنظام التشغيل الذي تستخدمه، علما بأن النسخة المجانية من المحرك ذات إمكانات كبيرة وهي تفي بالغرض بالنسبة لمشروعنا في سلسلة الدروس هذه.
</p>

<h3>
	الخطوة الثانية: إنشاء المشروع
</h3>

<p dir="rtl">
	بمجرد تشغيل المحرك بعد تنصيبه ستظهر لك شاشة البداية، قم بالضغط على <span style="font-family:courier new,courier,monospace;">New Project ل</span>تظهر لك شاشة كالتي تراها في الصورة أدناه. كل ما عليك هو اختيار النوع 2D ومن ثم اختيار اسم وموقع المشروع الجديد الذي ستقوم بإنشائه، ومن ثم الضغط على <span style="font-family:courier new,courier,monospace;">Create Project</span>.
</p>

<p style="text-align: center;">
	 
</p>

<h3 dir="rtl">
	الخطوة الثالثة: التعرف على نوافذ البرنامج الرئيسية
</h3>

<p dir="rtl">
	تهمنا في البداية 4 نوافذ رئيسية في محرك Unity، وفيما يلي ملخص لوظائفها:
</p>

<p dir="rtl" style="text-align: center;">
	 
</p>

<ul dir="rtl">
	<li>
		<strong>نافذة المشهد Scene</strong>: وهي التي تستخدمها لبناء مشهد اللعبة وإضافة الكائنات المختلفة إليه وتوزيعها في الفضاء ثنائي الأبعاد. تحتوي هذه النافذة مبدئيا على كائن واحد وهو الكاميرا.
	</li>
	<li>
		<strong>هرمية المشهد Hierarchy</strong>: تحتوي على ترتيب شجري يحوي كافة الكائنات التي تمت إضافتها للمشهد ويساعدك في تنظيم العلاقات بينها، حيث أنه من الممكن أن تضيف كائنات كأبناء لكائنات أخرى بحيث يتأثر الكائن الابن بالكائن الأب كما سنرى. تحتوي هذه النافذة مبدئيا على كائن واحد وهو الكاميرا.
	</li>
	<li>
		<strong>مستعرض المشروع Project</strong>: يقوم بعرض جميع الملفات الموجودة داخل مجلد المشروع، سواء تلك التي تمت إضافتها للمشهد أم التي لم تُضف. يحتوي المشروع مبدئيا على مجلد واحد يسمى Assets، وسنضيف داخله كافة الملفات والمجلدات الأخرى.
	</li>
	<li>
		<strong>نافذة الخصائص Inspector</strong>: عند اختيار أي كائن من هرمية المشهد أو نافذة المشهد أو مستعرض المشروع، فإن خصائصه ستظهر في هذه النافذة ويمكنك تغييرها من هناك.
	</li>
</ul>

<p>
	استعرضنا في هذا الدرس ما يظهر من واجهة Unity3D للوهلة الأولى، مع مقدمة بسيطة حول صناعة الألعاب، سنشرع في <a href="https://academy.hsoub.com/tags/%D9%85%D8%AF%D8%AE%D9%84%20%D8%A5%D9%84%D9%89%20unity3d/" rel="">الدروس القادمة</a> في مشروع عملي نتعلم من خلاله كيفية صناعة لعبة كاملة حقيقية. فترقبوا!
</p>
]]></description><guid isPermaLink="false">126</guid><pubDate>Thu, 16 Jul 2015 12:02:00 +0000</pubDate></item></channel></rss>
