<?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/3/?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;&#x646;&#x634;&#x627;&#x621; &#x634;&#x62E;&#x635;&#x64A;&#x627;&#x62A; &#x62B;&#x644;&#x627;&#x62B;&#x64A;&#x629; &#x627;&#x644;&#x623;&#x628;&#x639;&#x627;&#x62F; &#x641;&#x64A; &#x62C;&#x648;&#x62F;&#x648; Godot</title><link>https://academy.hsoub.com/programming/game-development/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B4%D8%AE%D8%B5%D9%8A%D8%A7%D8%AA-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2508/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_02/Godot.png.cf2fcd119b611d73e2db5c701b6c1cbb.png" /></p>
<p>
	تعرّفنا في <a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D8%B3%D8%AA%D9%8A%D8%B1%D8%A7%D8%AF-%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-r2502/" rel="">المقال السابق</a> على طريقة استيراد كائنات ثلاثية الأبعاد لمحرك الألعاب جودو وكيفية ترتيبها في مشهد اللعبة، وسنضيف في هذا المقال مزيدًا من الكائنات إلى مشهد اللعبة، وسنشرح طريقة إنشاء شخصية ثلاثية الأبعاد يتحكم فيها المستخدم.
</p>

<h2 id="">
	بناء المشهد
</h2>

<p>
	سنستمر في هذا المقال استخدام مجموعة الملحقات assets التي توفرها منصة Kenney على <a href="https://kenney.nl/assets/platformer-kit" rel="external nofollow">هذا الرابط</a> والتي شرحنا طريقة تنزيلها واستيرادها في <a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D8%B3%D8%AA%D9%8A%D8%B1%D8%A7%D8%AF-%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-r2502/" rel="">مقال سابق</a>. نفتح مشروع اللعبة ثلاثية الأبعاد التي بدأناها، ونحدّد الآن جميع ملفات <code>block*.glb</code>، وننتقل إلى التبويب <strong>استيراد Import</strong> ونضبط نوع الجذر Root Type الخاص بهذه الملفات على <code>StaticBody3D</code> ، بعدها ننقر على زر <strong>إعادة الاستيراد Reimport</strong> كما في الصورة أدناه.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="PNG" data-fileid="167147" href="https://academy.hsoub.com/uploads/monthly_2025_02/001---.PNG.e1b6e05cfb00c392ce962c5c70273e7c.PNG" rel=""><img alt="001 ضبط نوع الجذر" class="ipsImage ipsImage_thumbnailed" data-fileid="167147" data-unique="9uku848ar" style="width: 300px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_02/001---.thumb.PNG.99a89368c5514f82377967fc59885729.PNG"> </a>
</p>

<p>
	بعدها، نحدّد الكائن <code>block-grass-large.glb</code> بزر الفأرة الأيمن ونختار <strong>إنشاء مشهد موروث جديد</strong>، ستظهر عقدة جديدة باسم block-grass-large في المشهد، وعقدة ابن لها تُمثّل المجسم Mesh، نحدد العقدة الابن وننقر فوق خيار <strong>مجسم Mesh </strong>في القائمة العلوية الظاهرة في <a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%85%D8%AD%D8%B1%D8%B1-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%EF%BB%B7%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot-r2183/" rel="">محرر جودو</a>، ثم نحدد خيار <strong>إنشاء شكل تصادم Create Collision Shape</strong> ونعيّن قيمة الحقل Collision Shape Placement لتكون Sibling وقيمة الحقل Collision Shape Type لتكون Trimmesh كما فعلنا بالضبط في <a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D8%B3%D8%AA%D9%8A%D8%B1%D8%A7%D8%AF-%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-r2502/" rel="">المقال السابق</a>.
</p>

<p>
	عند الضغط على زر الإنشاء سيضيف جودو تلقائيًا العقدة <code>CollionShape3D</code> مع شكل تصادم يطابق المجسم Mesh.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="PNG" data-fileid="167148" href="https://academy.hsoub.com/uploads/monthly_2025_02/002-----.PNG.9eadefba07f03e7fce43551dc832a4fd.PNG" rel=""><img alt="002 مجسم وتصادم للكائن ثلاثي الأبعاد" class="ipsImage ipsImage_thumbnailed" data-fileid="167148" data-unique="gfsjuxn9x" style="width: 500px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_02/002-----.thumb.PNG.1aa6897cb86eee506cf76b4dbefb0209.PNG"> </a>
</p>

<p>
	يمكننا الآن حفظ المشهد باسم <code>BlockLarge</code>، ويفضل إنشاء مجلد منفصل وليكن <code>platform_objects</code> لحفظ المشهد وكافة المشاهد الأخرى التي تمثل أجزاء منصة اللعبة بأشكالها المختلفة.
</p>

<p>
	نفتح مشهد الأرضية Ground مع الصناديق Crates الذي عملنا عليه في <a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D8%B3%D8%AA%D9%8A%D8%B1%D8%A7%D8%AF-%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-r2502/" rel="">المقال السابق</a> ونحذف كافة الصناديق المضافة ونستبدلها بالمشهد الموروث الذي حفظناه للتو، بهذا سنتمكّن من وضع عدة كتل بجانب بعضها البعض بحيث تكون في صف واحد وتشكل منصة اللعبة أو عالم اللعبة. بعدها نحدّد العقدة BlockLarge وننقر خيار تعديل المحاذاة Configure Snap من القائمة العلوية <strong>التحوّل Transform</strong> الظاهرة أعلى نافذة العرض كالتالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="167149" href="https://academy.hsoub.com/uploads/monthly_2025_02/003--.png.eab58eedaa2cf4490f4a20d9effb6e58.png" rel=""><img alt="003 تعديل المحاذاة" class="ipsImage ipsImage_thumbnailed" data-fileid="167149" data-unique="jakz2r5xq" style="width: 500px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_02/003--.thumb.png.f7d9cd91d571d81b907debe55ca09e8b.png"> </a>
</p>

<p>
	نضبط خيار ترجمة المحاذاة Translate Snap على القيمة 0.5، ثم ننقر على زر استخدام المحاذاة Snap Mode أو نضغط على مفتاح <code>Y</code>، ثم نكرّر الكتلة عدة مرات ونسحبها لترتيبها ضمن المشهد بالشكل المناسب. يمكن أيضًا إضافة مشاهد لبعض كتل المنصة الأخرى وترتيبها في أيّ شكل نريده.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="167150" href="https://academy.hsoub.com/uploads/monthly_2025_02/004--.png.1fe43d91f17bef3a39bdf91599d92da7.png" rel=""><img alt="004 مشهد المنصة" class="ipsImage ipsImage_thumbnailed" data-fileid="167150" data-unique="87fe38397" style="width: 500px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_02/004--.png.1fe43d91f17bef3a39bdf91599d92da7.png"> </a>
</p>

<h2 id="character">
	إضافة شخصية Character
</h2>

<p>
	سننشئ الآن شخصية يمكنها التجول على المنصة التي أنشأناها، لذا نفتح مشهدًا جديدًا ونبدأ باستخدام العقدة <code>CharacterBody3D</code> بالاسم Character، حيث تتصرف عقدة <code>PhysicsBody</code> بطريقة مشابهة جدًا لنظيرتها ثنائية الأبعاد، وتحتوي على التابع <code>move_and_slide()‎</code> الذي سنستخدمه لإجراء الحركة و<a href="https://academy.hsoub.com/programming/game-development/%D9%83%D8%B4%D9%81-%D8%A7%D9%84%D8%AA%D8%B5%D8%A7%D8%AF%D9%85%D8%A7%D8%AA-r770/" rel="">كشف التصادم</a>.
</p>

<p>
	نضيف عقدة <code>MeshInstance3D</code> على شكل كبسولة، وعقدة <code>CollionShape3D</code> مطابقة لها، ولنتذكّر أن بإمكاننا إضافة عقدة <code>StandardMaterial3D</code> إلى المجسم Mesh وضبط خاصية اللون Color في القسم Albedo. شكل الكبسولة جميل ومناسب للشخصية، ولكن سيكون من الصعب معرفة الاتجاه الذي يمثل الوجه، لذا لنضيف مجسم Mesh آخر على شكل مخروطي <code>CylinderMesh3D</code>، ونضبط نصف قطره العلوي Top Radius على القيمة 0.2 ونصف قطره السفلي Bottom Radius على القيمة 0.001 وارتفاعه Height على القيمة 0.5، ثم نضبط دورانه حول المحور x على ‎-90 درجة. أصبح لدينا الآن شكل مخروطي جميل، لذا نرتّبه بحيث يشير لخارج الجسم على طول محور z السالب، إذ يمكن بسهولة معرفة الاتجاه السالب لأن أسهم أداة Gizmo تشير إلى الاتجاه الموجب.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="167151" href="https://academy.hsoub.com/uploads/monthly_2025_02/005_character_01.png.d48874eb7a101c5bde93029bb5871874.png" rel=""><img alt="005 character 01" class="ipsImage ipsImage_thumbnailed" data-fileid="167151" data-unique="8eborep7e" style="width: 250px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_02/005_character_01.png.d48874eb7a101c5bde93029bb5871874.png"> </a>
</p>

<p>
	ملاحظة: أضفنا في هذه الشخصية أيضًا مجسمين كرويين بنفس الطريقة لتمثيل عيني الشخصية، ويمكن إضافة أية تفاصيل نريدها. 
</p>

<p>
	لنضف الآن عقدة كاميرا <code>Camera3D</code> إلى المشهد لتتبع الشخصية. نضع الكاميرا خلف الشخصية وفوقها مع توجيهها للأسفل قليلًا، ثم ننقر على زر معاينة Preview للتحقق من عرض الكاميرا وظهورالشخصية بالشكل المناسب.
</p>

<h2 id="-1">
	كود التحكم في الشخصية
</h2>

<p>
	قبل إضافة سكربت برمجي للتحكم في الشخصية، سوف نفتح <strong>إعدادات المشروع</strong> <strong>Project Settings</strong>، ونضيف المدخلات التالية في تبويب خريطة الإدخال Input Map:
</p>

<table>
	<thead>
		<tr>
			<th>
				إجراء الإدخال Input Action
			</th>
			<th>
				المفتاح Key
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				التحرك للأمام move_forward
			</td>
			<td>
				المفتاح W
			</td>
		</tr>
		<tr>
			<td>
				التحرك للخلف move_back
			</td>
			<td>
				المفتاح S
			</td>
		</tr>
		<tr>
			<td>
				الانعطاف لليمين strafe_right
			</td>
			<td>
				المفتاح D
			</td>
		</tr>
		<tr>
			<td>
				الانعطاف لليسار strafe_left
			</td>
			<td>
				المفتاح A
			</td>
		</tr>
		<tr>
			<td>
				القفز jump
			</td>
			<td>
				المسافة Space
			</td>
		</tr>
	</tbody>
</table>

<p>
	يمكننا الآن كتابة سكربت لتحريك شخصيتنا ثلاثية الأبعاد كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3587_17" style=""><span class="pln">extends </span><span class="typ">CharacterBody3D</span><span class="pln">

var gravity </span><span class="pun">=</span><span class="pln"> </span><span class="typ">ProjectSettings</span><span class="pun">.</span><span class="pln">get_setting</span><span class="pun">(</span><span class="str">"physics/3d/default_gravity"</span><span class="pun">)</span><span class="pln">
var speed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">4.0</span><span class="pln">  </span><span class="com"># سرعة الحركة</span><span class="pln">
var jump_speed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">6.0</span><span class="pln">  </span><span class="com"># تحديد ارتفاع القفزة</span><span class="pln">
var mouse_sensitivity </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0.002</span><span class="pln">  </span><span class="com"># سرعة الدوران</span><span class="pln">


func get_input</span><span class="pun">():</span><span class="pln">
    var input </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">get_vector</span><span class="pun">(</span><span class="str">"strafe_left"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"strafe_right"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"move_forward"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"move_back"</span><span class="pun">)</span><span class="pln">
    velocity</span><span class="pun">.</span><span class="pln">x </span><span class="pun">=</span><span class="pln"> input</span><span class="pun">.</span><span class="pln">x </span><span class="pun">*</span><span class="pln"> speed
    velocity</span><span class="pun">.</span><span class="pln">z </span><span class="pun">=</span><span class="pln"> input</span><span class="pun">.</span><span class="pln">y </span><span class="pun">*</span><span class="pln"> speed

func _physics_process</span><span class="pun">(</span><span class="pln">delta</span><span class="pun">):</span><span class="pln">
    velocity</span><span class="pun">.</span><span class="pln">y </span><span class="pun">+=</span><span class="pln"> </span><span class="pun">-</span><span class="pln">gravity </span><span class="pun">*</span><span class="pln"> delta
    get_input</span><span class="pun">()</span><span class="pln">
    move_and_slide</span><span class="pun">()</span></pre>

<p>
	نلاحظ أن الشيفرة البرمجية في الدالة <code>‎_physics_process()‎</code> بسيطة، حيث نضيف الجاذبية للتسارع في الاتجاه الموجب للمحور Y إلى الأسفل، ثم نستدعي الدالة <code>get_input()‎</code> للتحقق من الإدخال أي معرفة المفتاح الذي ضغط المستخدم عليه، ثم نستخدم التابع <code>move_and_slide()‎</code> للتحرك في اتجاه متجه السرعة. نشغّل اللعبة لاختبارها، وسنرى النتيجة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="gif" data-fileid="167152" href="https://academy.hsoub.com/uploads/monthly_2025_02/006_3d_move_01.gif.22f69cc84b91cc109642c5d5ce8bc3e2.gif" rel=""><img alt="006 3d move 01" class="ipsImage ipsImage_thumbnailed" data-fileid="167152" data-unique="7qo3r3u9w" src="https://academy.hsoub.com/uploads/monthly_2025_02/006_3d_move_01.gif.22f69cc84b91cc109642c5d5ce8bc3e2.gif"> </a>
</p>

<p>
	هذا يبدو جيدًا، ولكن يجب أن نكون قادرين على تدوير الشخصية باستخدام الفأرة، لذا سنضيف الشيفرة البرمجية التالية إلى سكربت الشخصية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3587_19" style=""><span class="pln">func _unhandled_input</span><span class="pun">(</span><span class="pln">event</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> event </span><span class="kwd">is</span><span class="pln"> </span><span class="typ">InputEventMouseMotion</span><span class="pun">:</span><span class="pln">
        rotate_y</span><span class="pun">(-</span><span class="pln">event</span><span class="pun">.</span><span class="pln">relative</span><span class="pun">.</span><span class="pln">x </span><span class="pun">*</span><span class="pln"> mouse_sensitivity</span><span class="pun">)</span></pre>

<p>
	عندما نحرك الفأرة أفقيًا في اتجاه المحور x سيعمل السكربت بتدوير الشخصية حول المحور الرأسي Y، وبالتالي إذا حركنا الفأرة إلى اليمين، سيؤدي ذلك إلى تدوير الشخصية إلى اليسار. وإذا حركنا الفأرة إلى اليسار ستدور الشخصية إلى اليمين.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="gif" data-fileid="167153" href="https://academy.hsoub.com/uploads/monthly_2025_02/007_3d_move_02.gif.24b5692824cde9eb9aca44fea9394ba2.gif" rel=""><img alt="007 3d move 02" class="ipsImage ipsImage_thumbnailed" data-fileid="167153" data-unique="bc0a4n0mj" src="https://academy.hsoub.com/uploads/monthly_2025_02/007_3d_move_02.gif.24b5692824cde9eb9aca44fea9394ba2.gif"> </a>
</p>

<p>
	لدينا هنا مشكلة، إذ يحرّكنا الضغط على مفتاح W على طول المحور Z لعالم اللعبة بغض النظر عن الاتجاه الذي تتجه له الشخصية، لأن اللعبة تستخدم هنا الإحداثيات العالمية Global Coordinates، ولكننا بحاجة إلى التحرك بناء على اتجاه الكائن، ويمكن حل هذه المشكلة باستخدام التحويلات.
</p>

<h2 id="transforms">
	الاستفادة من التحويلات Transforms
</h2>

<p>
	التحويل هو مصفوفة رياضية تحتوي على معلومات حول انتقال الكائن ودورانه وتغيير حجمه في آن واحد، ويُخزَّنه جودو في نوع البيانات <code>Transform</code>، حيث تُسمَّى معلومات الموضع بالاسم <code>transform.origin</code> وتكون معلومات الاتجاه في <code>transform.basis</code>.
</p>

<p>
	تشير محاور X و Y و Z الخاصة بأداة Gizmo على طول محاور الكائن نفسه عندما تكون في وضع الحيّز المحلي Local Space Mode، ويشابه ذلك الأساس <code>basis</code> الخاص بالتحويل، حيث يحتوي هذا الأساس على ثلاثة كائنات <code>Vector3</code> تسمى <code>x</code> و <code>y</code> و <code>z</code> تمثل هذه الاتجاهات، ويمكننا استخدامها لضمان أن الضغط على مفتاح W سيؤدي إلى التحرك في الاتجاه الأمامي للكائن دائمًا.
</p>

<p>
	نعدّل الدالة <code>get_input()‎</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3587_21" style=""><span class="pln">func get_input</span><span class="pun">():</span><span class="pln">
    var input </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">get_vector</span><span class="pun">(</span><span class="str">"strafe_left"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"strafe_right"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"move_forward"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"move_back"</span><span class="pun">)</span><span class="pln">
    var movement_dir </span><span class="pun">=</span><span class="pln"> transform</span><span class="pun">.</span><span class="pln">basis </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">(</span><span class="pln">input</span><span class="pun">.</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> input</span><span class="pun">.</span><span class="pln">y</span><span class="pun">)</span><span class="pln">
    velocity</span><span class="pun">.</span><span class="pln">x </span><span class="pun">=</span><span class="pln"> movement_dir</span><span class="pun">.</span><span class="pln">x </span><span class="pun">*</span><span class="pln"> speed
    velocity</span><span class="pun">.</span><span class="pln">z </span><span class="pun">=</span><span class="pln"> movement_dir</span><span class="pun">.</span><span class="pln">z </span><span class="pun">*</span><span class="pln"> speed</span></pre>

<p>
	نطبّق هذا التحويل على المتجه من خلال ضرب متجه الإدخال في أساس التحويل <code>transform.basis</code>. يمثّل الأساس دوران الكائن، لذا لنحوّل الآن اتجاه الأمام والخلف للإشارة على طول المحور Z للكائن، ونحوّل مفاتيح التحريك لليمين واليسار للإشارة على طول المحور X الخاص بالكائن.
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="167153" data-ratio="100.00" data-unique="s5lrb6e5v" width="250" alt="007_3d_move_02.gif" src="https://academy.hsoub.com/uploads/monthly_2025_02/007_3d_move_02.gif.24b5692824cde9eb9aca44fea9394ba2.gif">
</p>

<h2 id="jumping">
	القفز Jumping
</h2>

<p>
	سنضيف الآن حركة أخرى إلى اللاعب وهي القفز، لتحقيق ذلك نضيف الأسطر التالية إلى نهاية الدالة <code>‎_unhandled_input()‎</code>:
</p>

<pre class="ipsCode">if event.is_action_pressed("jump") and is_on_floor():
    velocity.y = jump_speed
</pre>

<h2 id="-2">
	تحسين الكاميرا
</h2>

<p>
	إذا وقفت الشخصية بالقرب من عائق ما، فيمكن للكاميرا الالتصاق بالكائن بطريقة غير لطيفة. وبالرغم من أن برمجة كاميرا ثلاثية الأبعاد قد تكون أمرًا معقدًا بحد ذاته، ولكن يمكننا استخدام عقد جودو المضمنة للحصول على حل جيد لذلك. نحذف العقدة <code>Camera3D</code> من مشهد الشخصية ونضيف العقدة <code>SpringArm3D</code> التي تعمل كذراع متحركة تحمل الكاميرا أثناء كشف التصادمات، وستقرّب الكاميرا عند وجود عقبة، ثم نضبط خاصية طول الذراع Spring Length على القيمة 5، ونضبط خاصية الموضع Position على القيم ‎(0, 1, 0)‎. نلاحظ ظهور خط بلون أصفر يشير إلى طول النابض Spring Length، حيث ستتحرك الكاميرا على طول هذا الخط، ولكنها ستقترب عند وجود عقبة حتى نهايته كلما أمكن ذلك.
</p>

<p>
	نعيد إضافة العقدة <code>Camera3D</code> كابن للعقدة <code>SpringArm3D</code>، ونحاول تشغيل اللعبة مرة أخرى، ويمكن تجربة تدوير ذراع النابض حول محوره X ليشير إلى الأسفل قليلًا حتى الوصول لنتيجة مرضية.
</p>

<h2 id="-3">
	الخلاصة
</h2>

<p>
	شرحنا في هذا المقال كيفية بناء مشهد ثلاثي الأبعاد أكثر تعقيدًا وكتابة الشيفرة البرمجية لحركة شخصية يتحكم فيها المستخدم، وتعلمنا أيضًا مفهوم التحويلات Transforms التي تعد مفهومًا مهمًا جدًا في الألعاب ثلاثية الأبعاد، والتي ستستخدمها بكثرة في المقالات القادمة.
</p>

<p>
	ترجمة -وبتصرّف- للقسم <a href="https://kidscancode.org/godot_recipes/4.x/g101/3d/101_3d_03/index.html" rel="external nofollow">Creating a 3D Character</a> من توثيقات Kidscancode.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D8%B3%D8%AA%D9%8A%D8%B1%D8%A7%D8%AF-%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-r2502/" rel="">استيراد الكائنات ثلاثية الأبعاد في جودو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%AD%D8%B1%D9%8A%D9%83-%D8%A7%D9%84%D8%B4%D8%AE%D8%B5%D9%8A%D8%A9-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-3d-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AD%D8%B1%D8%B1-%D8%A7%D9%84%D8%AA%D8%AD%D8%B1%D9%8A%D9%83-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-r2477/" rel="">تحريك الشخصية في لعبة 3D باستخدام محرر التحريك في جودو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AD%D8%B1%D8%B1-%D8%AC%D9%88%D8%AF%D9%88-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-r2498/" rel="">استخدام محرر جودو ثلاثي الأبعاد</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%B3%D9%83%D8%B1%D8%A8%D8%AA%D8%A7%D8%AA-gdscript-%D9%88%D8%A5%D8%B1%D9%81%D8%A7%D9%82%D9%87%D8%A7-%D8%A8%D8%A7%D9%84%D8%B9%D9%82%D8%AF-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-r2495/" rel="">كتابة سكربتات GDScript وإرفاقها بالعقد في جودو</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2508</guid><pubDate>Tue, 11 Feb 2025 15:05:08 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x64A;&#x631;&#x627;&#x62F; &#x627;&#x644;&#x643;&#x627;&#x626;&#x646;&#x627;&#x62A; &#x62B;&#x644;&#x627;&#x62B;&#x64A;&#x629; &#x627;&#x644;&#x623;&#x628;&#x639;&#x627;&#x62F; &#x641;&#x64A; &#x62C;&#x648;&#x62F;&#x648;</title><link>https://academy.hsoub.com/programming/game-development/%D8%A7%D8%B3%D8%AA%D9%8A%D8%B1%D8%A7%D8%AF-%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-r2502/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_02/Godot.png.9582fcd69c09713f7aeed118aa180bbf.png" /></p>
<p>
	نشرح في هذا المقال كيفية استيراد كائنات ثلاثية الأبعاد موجودة مسبقًا أنشأناها أو نزّلناها من مصدر خارجي إلى داخل محرك ألعاب جودو Godot، ونوضح المزيد حول كيفية استخدام العقد ثلاثية الأبعاد في جودو.
</p>

<h2 id="">
	استيراد الكائنات ثلاثية الأبعاد
</h2>

<p>
	في حال كنا على دراية ببرامج النمذجة ثلاثية الأبعاد مثل <a href="https://academy.hsoub.com/design/3d/blender/" rel="">بلندر Blender</a>، فيمكننا إنشاء نماذجنا الخاصة لاستخدامها في لعبتنا. وفي حال لم نكن كذلك، فهناك العديد من المصادر التي توفر لنا تنزيل الكائنات لأنواع معينة من الألعاب، ومن بينها منصة <a href="https://kenney.nl/" rel="external nofollow">Kenney</a> التي توفر الكثير من الموارد والملحقات assets المجانية عالية الجودة لصنّاع الألعاب. سنستخدم في أمثلتنا التالية مجموعة ملحقات Platformer Kit للعبة منصات من Kenney، والتي يمكن تنزيلها من <a href="https://kenney.nl/assets/platformer-kit" rel="external nofollow">هذا الرابط</a><span style="display: none;"> </span>، حيث تحتوي مجموعة واسعة من الكائنات ثلاثية الأبعاد 3D. وفيما يلي عينة توضّح هذه الملحقات:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="166614" href="https://academy.hsoub.com/uploads/monthly_2025_02/01_____Kenny.png.de2e213f5746b06cbdcb0d20f77dca01.png" rel=""><img alt="01 عينة منصة ثلاثية الأبعاد kenny" class="ipsImage ipsImage_thumbnailed" data-fileid="166614" data-ratio="56.00" data-unique="yitlos5aj" style="width: 400px; height: auto;" width="400" src="https://academy.hsoub.com/uploads/monthly_2025_02/01_____Kenny.thumb.png.cfa16a039decab831f41eef72376648c.png"> </a>
</p>

<p>
	سنجد بعد التنزيل مجلدًا باسم <code>Models</code> يتضمن مجموعة متنوعة من الصيغ التي يمكن التعامل معها في <a href="https://academy.hsoub.com/programming/game-development/%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot/" rel="">محرك ألعاب جودو</a>، ولكن صيغة <strong>GLTF </strong>مفضلة عن الصيغ الأخرى.
</p>

<h3 id="-1">
	صيغ الملفات ثلاثية الأبعاد
</h3>

<p>
	يجب أن نحفظ نماذجنا ثلاثية الأبعاد بصيغة يمكن لجودو استخدامها سواءً أردنا إنشاء نماذجنا الخاصة أو تنزيلها، حيث يدعم جودو صيغ الملفات التالية للنماذج ثلاثية الأبعاد، ولكل منها ميزاته وقيوده:
</p>

<ul>
	<li>
		<strong>GlTF</strong>: صيغ نماذج ثلاثية الأبعاد مدعومة في كل من الإصدارات النصية <code>‎.gltf</code> والثنائية <code>‎.glb</code>
	</li>
	<li>
		<strong>DAE Collada‎</strong>: صيغة أقدم لكنها لا تزال مدعومة
	</li>
	<li>
		<strong>OBJ Wavefront</strong>‎: صيغة أقدم مدعومة، ولكنها محدودة مقارنة بالخيارات الحديثة
	</li>
	<li>
		<strong>FBX</strong>: صيغة تجارية لها دعم محدود
	</li>
</ul>

<p>
	تُعَد صيغة GlTF هي الصيغة الموصى بها كما وضحنا سابقًا، لكونها تحتوي على معظم الميزات ودعمها جيد في جودو، سنضع المجلد <code>GLB format</code> في الموجود ضمن المجلد <code>Models</code>  في مجلد مشروع جودو الخاص بنا ونعيد تسميته إلى platformer_kit.
</p>

<p style="text-align: center;">
	<img alt="المجلد Models" class="ipsImage ipsImage_thumbnailed" data-fileid="167134" data-ratio="87.57" data-unique="0ptwfxhw3" style="width: 169px; height: auto;" width="169" src="https://academy.hsoub.com/uploads/monthly_2025_02/Models.PNG.63a12cb389e732351ae94cc2d492800e.PNG">
</p>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="166616" href="https://academy.hsoub.com/uploads/monthly_2025_02/03----.png.0b9ad205c84ea8ac2790b92496351a85.png" rel=""><img alt="03 شريط تقدم استيراد  الملحقات" class="ipsImage ipsImage_thumbnailed" data-fileid="166616" data-ratio="48.00" data-unique="x43508xfb" style="width: 300px; height: auto;" width="300" src="https://academy.hsoub.com/uploads/monthly_2025_02/03----.png.0b9ad205c84ea8ac2790b92496351a85.png"> </a>
</p>

<p>
	لننقر نقرًا مزدوجًا على أحد هذه الكائنات وليكن <code>crate.glb</code> في تبويب نظام الملفات FileSystem والذي يمثل صندوق ثلاثي الأبعاد كالتالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="166617" href="https://academy.hsoub.com/uploads/monthly_2025_02/04_3d_import.png.f0ca5c66ea831d1c24028a3de832a898.png" rel=""><img alt="04 3d import" class="ipsImage ipsImage_thumbnailed" data-fileid="166617" data-ratio="56.25" data-unique="5grqob407" style="width: 400px; height: auto;" width="400" src="https://academy.hsoub.com/uploads/monthly_2025_02/04_3d_import.thumb.png.b37467e5333df1b6ab7b3f8908ff247f.png"> </a>
</p>

<h2 id="-2">
	خطوات استيراد كائن ثلاثي الأبعاد وتعديل عقدة الجذر
</h2>

<p>
	عند النقر على كائن ثلاثي الأبعاد يمكننا رؤية خيارات استيراد هذا الكائن في التبويب استيراد Import بجانب تبويب المشهد Scene مع إمكانية ضبط الخاصية نوع الجذر Root Type وتعديل اسم الجذر Root Name، دعونا نضبط نوع الجذر على <code>RigidBody3D</code> ونطلق عليه اسم Crate للتعبير عن كونه يمثل صندوق ثلاثي الأبعاد، ثم ننقر على زر إعادة الاستيراد Reimport.
</p>

<p style="text-align: center;">
	<img alt="التبويب استيراد" class="ipsImage ipsImage_thumbnailed" data-fileid="167135" data-ratio="174.44" data-unique="xodq91xr3" style="width: 180px; height: auto;" width="271" src="https://academy.hsoub.com/uploads/monthly_2025_02/1405928251_.PNG.341466521da1c576e41a75cfcf1bc2e0.PNG">
</p>

<p>
	ننقر بعدها بزر الفأرة الأيمن على crate.glb ونحدّد خيار مشهد موروث جديد New Inherited Scene. أصبح لدينا كائن لعبة كلاسيكي هو الصندوق Crate، وعقدة جذر للمشهد هي <code>RigidBody3D</code> بالاسم Crate كما أردنا تمامًا، لكن سنلاحظ ظهور تحذير يشير بأن هذه العقدة لا تحتوي مكون التصادم Collision الضروري للتفاعل مع الكائنات الأخرى.
</p>

<p>
	لذا فإن الخطوة التالية التي علينا القيام بها هي إضافة شكل تصادم إلى الكائن ثلاثي الأبعاد، ويمكننا تطبيق ذلك من خلال إضافة عقدة من نوع <code>CollionShape3D</code> كما نفعل عادة في <a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D8%A8%D8%AF%D8%A1-%D8%A8%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-%D8%A7%D9%84%D8%A8%D8%B9%D8%AF-%D8%B9%D8%A8%D8%B1-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-godot-r2280/" rel="">الألعاب ثنائية الأبعاد 2D</a>، ولكن سننجزها هنا بطريقة أسرع.
</p>

<p>
	نحدّد العقدة <code>crate</code>، وسنلاحظ ظهور شريط قوائم في الجزء العلوي من نافذة العرض، ننقر على أيقونة المجسم ونحدّد إنشاء شكل تصادم Create Collision shape ونحدد قيمة الحقل Collision Shape Placement لتكون Sibling أي أن أن شكل التصادم سيضاف كعقدة أخ للعقدة الحالية، وقيمة الحقل Collision Shape Type لتكون Trimmesh أي التصادم سيتم بناءً على المجسم ثلاثي الأبعاد للكائن، وعند الضغط على زر الإنشاء سيضيف جودو تلقائيًا العقدة <code>CollionShape3D</code> مع شكل تصادم يطابق Mesh.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="166628" href="https://academy.hsoub.com/uploads/monthly_2025_02/06---.png.e7a910321cb1fb897556834ba865d314.png" rel=""><img alt="06 إنشاء شكل تصادم" class="ipsImage ipsImage_thumbnailed" data-fileid="166628" data-ratio="63.50" data-unique="hwnb44p0n" style="width: 400px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2025_02/06---.thumb.png.0812537ffff85dcfa8a55ee33feddf52.png"></a>
</p>

<p>
	انتهينا الآن من إعداد كائن الصندوق <code>Crate</code>، لنحفظ المشهد الخاص به ونتعرّف كيف يمكننا استخدامه في اللعبة أو المشروع.
</p>

<h2 id="-3">
	بناء مشهد ثلاثي الأبعاد
</h2>

<p>
	ننشئ مشهدًا جديدًا باستخدام عقدة الجذر <code>Node3D</code>، وأول ابن سنضيفه هو الأرضية لوضع بعض كائنات الصناديق عليها، لذا نضيف عقدة <code>StaticBody3D</code> ونسميها Ground، ونضيف إليها عقدة ابن من نوع <code>MeshInstance3D</code>. ونحدّد خيار <strong>BoxMesh جديدة </strong>في الخاصية Mesh ضمن قسم الفاحص.
</p>

<p style="text-align: center;">
	<img alt="BoxMesh جديدة" class="ipsImage ipsImage_thumbnailed" data-fileid="167136" data-ratio="215.00" data-unique="dooh9r6u4" style="width: 180px; height: auto;" width="269" src="https://academy.hsoub.com/uploads/monthly_2025_02/BoxMesh.png.96f7bd7e144608e4547dfe06c2d74ae6.png">
</p>

<p>
	ثم نضبط حجمها على القيم التالية ‎<code>(10,0.1,10)</code>‎ بحيث يكون لدينا أرضية كبيرة، ستظهر الأرضية باللون الأبيض بشكل افتراضي وسيبدو شكلها أفضل إن غيرناها للون البني أو الأخضر، وللقيام بذلك ننتقل للخاصية Material الموجودة في قسم خاصيات Mesh فهي تساعدنا على تحديد مظهر الكائن. سنحدّد الخيار <strong>StandardMaterial3D جديدة</strong> كقيمة للخاصية Mesh، ثم ننقر عليها لنستعرض قائمة كبيرة من الخاصيات، ما يهمنا هو خاصية اللون Color ضمن قسم Albedo لضبط الأرضية باللون الأخضر الداكن.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="PNG" data-fileid="167137" href="https://academy.hsoub.com/uploads/monthly_2025_02/417407828_.PNG.41510d07ea8c434d68b958a65c5c7b29.PNG" rel=""><img alt="خاصية اللون Color ضمن قسم Albedo " class="ipsImage ipsImage_thumbnailed" data-fileid="167137" data-ratio="225.56" data-unique="ck6ch8o2h" style="width: 180px; height: auto;" width="266" src="https://academy.hsoub.com/uploads/monthly_2025_02/.thumb.PNG.c1d4daf6e713e741508444a5d38b77ca.PNG"></a>
</p>

<p>
	الآن إذا أضفنا صندوقًا، فسيسقط عبر الأرضية، لذا يجب إعطاؤها شكل تصادم من خلال إضافة عقدة التصادم <code>CollisionShape3D</code> كعقدة ابن للأرضية <code>Ground</code> ونحدد قيمة الخاصية Shape لتكون <strong>BoxShape3 جديدة</strong>، ثم نضبط حجم صندوق التصادم Size ليكون بنفس حجم Mesh.
</p>

<p>
	عند إضافة صندوق إلى المشهد، سيسقط تلقائياً عبر الأرضية Ground نظراً لغياب خصائص التصادم الفيزيائي. لحل هذه المشكلة، نحتاج إلى إضافة عقدة <code>CollisionShape3D</code> كعقدة فرعي للأرضية Ground. بمجرد إضافة العقدة، علينا تعيين خاصية Shape لها باختيار <strong>BoxShape3D </strong>جديدة ، ثم نضبط أبعاد صندوق التصادم ليتطابق مع حجم الشبكة. بهذه الطريقة، ستصبح الأرضية قادرة على منع الأجسام من السقوط عبرها.
</p>

<p>
	ننشئ الآن عددًا من الصناديق في المشهد ونرتبها في كومة تقريبية. ونضيف كاميرا <code>Camera</code> ونضعها في مكان يوفر لنا رؤية جيدة للصناديق، ونشغّل المشهد ونشاهد الصناديق تتدحرج.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="166622" href="https://academy.hsoub.com/uploads/monthly_2025_02/08_____.png.d561c8d478ecf977cd85ca92ce93392d.png" rel=""><img alt="08 مشهد ثلاثي الأبعاد دون إضاءة" class="ipsImage ipsImage_thumbnailed" data-fileid="166622" data-unique="dglepfzc6" style="width: 400px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_02/08_____.png.d561c8d478ecf977cd85ca92ce93392d.png"> </a>
</p>

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

<h2 id="-4">
	الإضاءة
</h2>

<p>
	تتوفر عقد إضاءة متعددة في المشاهد ثلاثية الأبعاد يمكننا استخدامها لإنشاء مجموعة متنوعة من تأثيرات الإضاءة. سنبدأ بالعقدة <code>DirectionalLight3D</code> أولًا، ولكن سنجعل جودو يستخدم العقدة نفسها التي يستخدمها في نافذة المحرر بدلًا من إضافتها يدويًا. نلاحظ وجود أيقونتين في الجزء العلوي فوق نافذة العرض يتحكمان في إضاءة المعاينة Preview Lighting وبيئة المعاينة Preview Environment. إذا نقرنا على النقاط الثلاث بجوارهما، فيمكنك رؤية إعداداتهما.
</p>

<p style="text-align: center;">
	<img alt="08-إضاءة-مشهد-العمل" class="ipsImage ipsImage_thumbnailed" data-fileid="166629" data-ratio="87.75" data-unique="bf7myvqiq" style="width: 300px; height: auto;" width="616" src="https://academy.hsoub.com/uploads/monthly_2025_02/08_____.png.b2b78d7ca009f9c8825fe1941bb10e0c.png">
</p>

<p>
	ننقر على زر إضافة الشمس إلى المشهد Add Sun to Scene، وبهذا سيضيف جودو العقدة <code>DirectionalLight3D</code> إلى المشهد مباشرة. ننقر على زر إضافة بيئة إلى المشهد Add Environment to Scene، وسيفعل جودو الشيء نفسه مع معاينة السماء من خلال إضافة عقدة <code>WorldEnvironment</code>. نشغّل المشهد مرة أخرى، وسنتمكن من رؤية الصناديق تتساقط بشكل أفضل.
</p>

<h2 id="-5">
	تدوير الكاميرا
</h2>

<p>
	لنجعل الآن الكاميرا تدور ببطء حول المشهد، لذا نحدّد العقدة الجذر ونضيف عقدة <code>Node3D</code>، والتي ستكون موجودة عند النقطة <code>‎(0,0,0)‎</code> ونطلق على هذه العقدة اسم CameraHub. نسحب الكاميرا في شجرة المشهد لجعلها ابنًا لهذه العقدة الجديدة، حيث إذا دارت عقدة <code>CameraHub</code> حول المحور y، فسنسحب الكاميرا معها.
</p>

<p>
	نضيف سكربتًا إلى العقدة الجذر ونضع فيه ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4209_20" style=""><span class="pln">extends </span><span class="typ">Node3D</span><span class="pln">

func _process</span><span class="pun">(</span><span class="pln">delta</span><span class="pun">):</span><span class="pln">
    $CameraHub</span><span class="pun">.</span><span class="pln">rotate_y</span><span class="pun">(</span><span class="lit">0.6</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> delta</span><span class="pun">)</span></pre>

<p>
	يعمل السكربت أعلاه على تدوير العقدة <code>CameraHub</code> حول المحور <code>Y</code> بشكل مستمر أثناء اللعبة. وتعتمد سرعة التدوير على الزمن بين الإطارات <code>delta</code> ما يجعل الحركة أكثر سلاسة بغض النظر عن أداء الجهاز.
</p>

<h2 id="-6">
	الخلاصة
</h2>

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

<p>
	ترجمة -وبتصرّف- للقسم <a href="https://kidscancode.org/godot_recipes/4.x/g101/3d/101_3d_02/index.html" rel="external nofollow">Importing 3D Objects</a> من توثيقات Kidscancode.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AD%D8%B1%D8%B1-%D8%AC%D9%88%D8%AF%D9%88-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-r2498/" rel="">استخدام محرر جودو ثلاثي الأبعاد</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%AD%D8%B1%D9%8A%D9%83-%D8%A7%D9%84%D8%B4%D8%AE%D8%B5%D9%8A%D8%A9-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-3d-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AD%D8%B1%D8%B1-%D8%A7%D9%84%D8%AA%D8%AD%D8%B1%D9%8A%D9%83-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-r2477/" rel="">تحريك الشخصية في لعبة 3D باستخدام محرر التحريك في جودو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D9%85%D8%B7%D9%88%D8%B1-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8/" rel="">مطور الألعاب: من هو وما هي مهامه</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%AD%D8%B1%D9%8A%D9%83-%D8%A7%D9%84%D8%B4%D8%AE%D8%B5%D9%8A%D8%A9-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-3d-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AD%D8%B1%D8%B1-%D8%A7%D9%84%D8%AA%D8%AD%D8%B1%D9%8A%D9%83-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-r2477/" rel="">تحريك الشخصية في لعبة 3D باستخدام محرر التحريك في جودو</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2502</guid><pubDate>Tue, 04 Feb 2025 15:02:00 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x645;&#x62D;&#x631;&#x631; &#x62C;&#x648;&#x62F;&#x648; &#x62B;&#x644;&#x627;&#x62B;&#x64A; &#x627;&#x644;&#x623;&#x628;&#x639;&#x627;&#x62F;</title><link>https://academy.hsoub.com/programming/game-development/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AD%D8%B1%D8%B1-%D8%AC%D9%88%D8%AF%D9%88-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-r2498/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_01/Godot_.png.4cb76a399027ac48e6be70aef24d3ea5.png" /></p>
<p>
	سنلقي نظرة في هذا المقال على كيفية البدء في استخدام محرّر جودو Godot ثلاثي الأبعاد، حيث ستتعلم كيفية التنقل في هذا المحرّر، وكيفية إنشاء الكائنات ثلاثية الأبعاد ومعالجتها، وكيفية العمل مع بعض العقد الأساسية ثلاثية الأبعاد في جودو مثل الكاميرات والإضاءة.
</p>

<p>
	قد يكون تطوير الألعاب ثلاثية الأبعاد أكثر تعقيدًا من تطوير الألعاب ثنائية الأبعاد، حيث يمكن تطبيق العديد من المبادئ نفسها مثل العمل مع العقد، وكتابة السكربتات والتعامل مع المنطق البرمجي، ولكن يتطلب التطوير ثلاثي الأبعاد عدد من الاعتبارات الأخرى، لذا يُفضَّل تعلم تطوير الألعاب ثنائية الأبعاد للمبتدئين، والانتقال إلى تعلم تطوير الألعاب ثلاثية الأبعاد بعد امتلاك الفهم الجيد لمبادئ تطوير الألعاب، يفترض هذا المقال وجود معرفة جيدة بإنشاء لعبة متكاملة ثنائية الأبعاد في جودو Godot 2D على الأقل.
</p>

<h2 id="-1">
	البدء في تطوير الألعاب ثلاثية الأبعاد
</h2>

<p>
	تتمثل إحدى نقاط قوة جودو في قدرته على التعامل مع الألعاب ثنائية الأبعاد وثلاثية الأبعاد بكفاءة. يمكننا تطبيق الكثير مما نتعلّمته من العمل على المشاريع ثنائية الأبعاد كالعقد nodes والمشاهد scenes والإشارات signals وما إلى ذلك على الألعاب ثلاثية الأبعاد، ولكن سنجد بعض التعقيدات والمميزات الجديدة في التطوير ثلاثي الأبعاد، وسنوضح في فقراتنا التالية أهم الميزات الإضافية المتوفرة في نافذة <a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%85%D8%AD%D8%B1%D8%B1-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%EF%BB%B7%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot-r2183/" rel="">محرر جودو</a> ثلاثي الأبعاد.
</p>

<h2 id="orienting">
	التوجيه Orienting في الفضاء ثلاثي الأبعاد
</h2>

<p>
	عندما نفتح مشروعًا جديدًا في جودو لأول مرة فسنرى عرض المشروع ثلاثي الأبعاد وسنلاحظ وجود أدوات وخصائص مُعدة خصيصًا للتطوير ثلاثي الأبعاد 3D كما في الصورة التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="166349" href="https://academy.hsoub.com/uploads/monthly_2025_01/01_3d_workspace.png.7f23907f48dc3a5dc4e374a7b9618851.png" rel=""><img alt="01 3d workspace" class="ipsImage ipsImage_thumbnailed" data-fileid="166349" data-ratio="51.40" data-unique="n7hekt3qw" style="width: 500px; height: auto;" width="500" src="https://academy.hsoub.com/uploads/monthly_2025_01/01_3d_workspace.thumb.png.5ba88ee394ae8330a61832218eca2a09.png"> </a>
</p>

<p>
	أول شيء سنلاحظه هو الخطوط الثلاثة الملونة في المنتصف، والتي هي المحور X باللون الأحمر، والمحور Y باللون الأخضر، والمحور Z باللون الأزرق، وتسمى النقطة التي تلتقي فيها هذه المحاور بنقطة الأصل Origin والتي لها الإحداثيات ‎(0,0,0)‎. وسنلاحظ أن مخطط الألوان هذا يُطبَّق أيضًا في أماكن أخرى من الفاحص Inspector.
</p>

<p>
	<strong>ملاحظة</strong>: قد تستخدم برامج تطوير الألعاب ثلاثية الأبعاد اصطلاحات مختلفة للتوجيه Orienting. ففي محرك جودو، يستخدم المحور Y ليحدد الاتجاه للأعلى، أي يشير المحور Y للأعلى والأسفل، ويشير المحور X إلى اليسار واليمين، ويشير المحور Z للأمام والخلف. بينما في بعض البرامج ثلاثية الأبعاد الأخرى، قد يُستخدم المحور Z ليشير للاتجاه نحو الأعلى. لذا، يجب أن نأخذ هذا الأمر في الاعتبار عند الانتقال لتطبيقات مختلفة.
</p>

<p>
	يمكننا التنقل في المشروع ثلاثي الأبعاد باستخدام الفأرة ولوحة المفاتيح، وفيما يلي عناصر التحكم الأساسية لكاميرا العرض:
</p>

<ul>
	<li>
		نحرك عجلة الفأرة للأعلى أو للأسفل لتكبير أو تصغير المشهد
	</li>
	<li>
		نستخدم الزر الفأرة الأوسط مع السحب لتدوير الكاميرا حول الهدف الحالي
	</li>
	<li>
		نستخدم مفتاح <code>Shift</code> مع الزر الأوسط للفأرة مع السحب لتحريك الكاميرا حول المشهد
	</li>
	<li>
		ننقر بزر الفأرة الأيمن مع السحب لتدوير الكاميرا حول محورها دون تغيير موقعها في المشهد
	</li>
</ul>

<p>
	<strong>ملاحظة</strong>: في بعض الألعاب ثلاثية الأبعاد الشهيرة، يوجد وضع يسمى Freelook يسمح لنا بالتنقل بحرية في المشهد دون قيود. يمكننا تفعيل هذا الوضع أو إيقافه في جودو باستخدام الاختصار Shift + F. عند تفعيل هذا الوضع يمكننا استخدام مفاتيح WASD أي مفتاح W للتقدم للأمام، و A للتحرك لليسار،و S للتحرك للخلف، و D للتحرك لليمين من أجل التحرك حول المشهد بحرية كما نفعل في الألعاب عادة. وفي هذا الوضع، ستتيح لنا الفأرة التوجيه وتحريك الكاميرا حول المشهد أو الهدف كما نريد.
</p>

<p>
	بالنسبة لتغيير عرض الكاميرا، سنجد في الزاوية العلوية اليسرى من الشاشة، اسم توضيحي <strong>منظوري Perspective</strong>. عند النقر عليه، يمكننا تغيير زاوية رؤية الكاميرا، أي يمكننا جعل الكاميرا تتجه إلى اتجاه معين كجعلها تعرض المشهد من الأعلى أو الجوانب أو بأي زاوية نرغب بها.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="166350" href="https://academy.hsoub.com/uploads/monthly_2025_01/02_3d_.png.d177c2f718f59baaa7c0750f915e6fb8.png" rel=""><img alt="02 3d منظور" class="ipsImage ipsImage_thumbnailed" data-fileid="166350" data-unique="a7e6c3gia" src="https://academy.hsoub.com/uploads/monthly_2025_01/02_3d_.png.d177c2f718f59baaa7c0750f915e6fb8.png"> </a>
</p>

<h2 id="-2">
	إضافة كائنات ثلاثية الأبعاد
</h2>

<p>
	لنضف الآن أول عقدة ثلاثية الأبعاد. ترث العقد ثلاثية الأبعاد في جودو من العقدة الأساسية <strong>Node3D</strong>، وهي توفر نفس الخصائص التي ترثها العقدة Node2D في المشاريع الثنائية الأبعاد، مثل خاصية الموضع position لتحديد مكان الكائن في المشهد، وخاصية الدوران rotation لتحديد زاوية دوران الكائن. عند إضافة عقدة ثلاثية الأبعاد Node3D إلى المشهد، سنرى الكائن أن يظهر عند نقطة الأصل (0,0,0) في الفضاء ثلاثي الأبعاد كما في الصورة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="166351" href="https://academy.hsoub.com/uploads/monthly_2025_01/03_3d_gizmo.png.74fd61f90fc6aea411601b4b50c5b0d2.png" rel=""><img alt="03 3d gizmo" class="ipsImage ipsImage_thumbnailed" data-fileid="166351" data-unique="hkcyel87w" src="https://academy.hsoub.com/uploads/monthly_2025_01/03_3d_gizmo.png.74fd61f90fc6aea411601b4b50c5b0d2.png"> </a>
</p>

<p>
	في جودو، عندما نضيف كائن ثلاثي الأبعاد إلى المشهد، لا يُعدّ هذا الكائن عقدة، بل يُسمى <strong>Gizmo ثلاثي الأبعاد</strong>. تُعدّ Gizmo أداة تتيح لنا التحكم في الكائنات الثلاثية الأبعاد، مثل تحريكها أو تدويرها في الفضاء. تستخدم هذه الأداة ثلاث حلقات كي تتحكم في الدوران، وتستخدم ثلاثة أسهم لتحرك الكائن على طول المحاور الثلاثة، وتكون الحلقات والأسهم ملونة لتتناسب مع ألوان المحاور. يمكن تجربة استخدام هذه الأداة والتعرف عليها، واستخدام زر التراجع Undo لاستعادة الوضع السابق.
</p>

<p>
	<strong>ملاحظة</strong>: قد نجد أن أدوات Gizmo المستخدمة لتحريك الكائنات وتدويرها وتغيير حجمها مزعجة أو متداخلة مع بعضها أثناء العمل. ويمكننا حل هذه المشكلة بسهولة واختيار نوع واحد فقط من التعديلات بالنقر على أيقونات الوضع للتقيد بنوع واحد فقط من التحويلات أي يمكن أن نختار إما وضع التحريك أو وضع التدوير أو وضع التحجيم، فباختيارنا لأحد هذه الأوضاع، سنتمكن من التركيز على تعديل واحد في كل مرة وسيسهل علينا التعامل مع الكائنات في المشهد.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="166352" href="https://academy.hsoub.com/uploads/monthly_2025_01/04_3d_intro_mode_buttons.png.407c33ce4392dbba8ea7ed7adad477b6.png" rel=""><img alt="04 3d intro mode buttons" class="ipsImage ipsImage_thumbnailed" data-fileid="166352" data-unique="ojas8jpiu" src="https://academy.hsoub.com/uploads/monthly_2025_01/04_3d_intro_mode_buttons.png.407c33ce4392dbba8ea7ed7adad477b6.png"> </a>
</p>

<h2 id="-3">
	الحيز العالمي والحيز المحلي
</h2>

<p>
	الحيز العالمي والحيز المحلي هما طريقتان للتحكم في تحريك الكائنات داخل جودو حيث يعتمد الحيز العالمي Global Space على محاور ثابتة لا تتغير مهما دورنا الكائن أوحركناه وتشير الأسهم دائمًا إلى هذه المحاور الثابتة، أما في الحيز المحلي Local Space فتتحرك المحاور مع حركة الكائن نفسه وتُعدّل بناء على تحريك الكائن أو تدويره.
</p>

<p>
	تعمل عناصر التحكم في أداة Gizmo في الحيز العالمي Global Space افتراضيًا، إذ تبقى أسهم أداة Gizmo تشير على طول المحاور عند تدوير الكائن، ولكن إذا نقرنا على زر استخدام الحيز المحلي Use Local Space، فستتحوّل الأداة إلى تحريك الجسم في حيز محلي.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="166353" href="https://academy.hsoub.com/uploads/monthly_2025_01/05_3d___.png.af8baeb43ec228944125a1b2f88531e4.png" rel=""><img alt="05 3d زر الحيز المحلي" class="ipsImage ipsImage_thumbnailed" data-fileid="166353" data-unique="l8r8apqwt" src="https://academy.hsoub.com/uploads/monthly_2025_01/05_3d___.png.af8baeb43ec228944125a1b2f88531e4.png"> </a>
</p>

<p>
	تشير أسهم أداة Gizmo الآن إلى محاور الكائن نفسه بدلاً من محاور العالم عند تدويره. فيمكن أن يساعدنا التبديل بين الحيز المحلي والعالمي في تحديد موقع الكائن بدقة وفقًا لاحتياجاتنا.
</p>

<h2 id="transforms">
	التحويلات Transforms
</h2>

<p>
	لنبحث عن العقدة <strong><code>Node3D</code></strong> في الفاحص Inspector، حيث سنرى خاصيات الموضع Position والدوران Rotation والتحجيم Scale في قسم التحويل Transform، لذا نسحب الكائن باستخدام أداة Gizmo ونلاحظ كيف تتغير هذه القيم. تكون هذه الخاصيات متعلقة بأب هذه العقدة كما هو الحال في العقد ثنائية الأبعاد 2D.
</p>

<p>
	تشكّل هذه الخاصيات مع بعضها البعض ما يسمى بتحويل العقدة. سنتمكن من الوصول إلى خاصية التحويل <code>transform</code> التي هي كائن جودو <strong><code>Transform3D</code> </strong>عند تغيير الخاصيات المكانية للعقدة في الشيفرة البرمجية، ويكون لهذا الكائن خاصيتان هما <strong><code>origin</code> </strong>و <strong><code>basis</code></strong>. تمثّل الخاصية <code>origin</code> موضع الجسم، بينما تحتوي الخاصية <code>basis</code> على ثلاثة متجهات تحدّد محاور إحداثيات الجسم المحلية، حيث يمكننا التفكير في أسهم المحاور الثلاثة في أداة Gizmo عندما تكون في وضع الحيز المحلي Local Space، وسنوضّح كيفية استخدام هذه الخاصيات لاحقًا.
</p>

<h2 id="meshes">
	الشبكات Meshes
</h2>

<p>
	لا تتمتع عقدة <strong><code>Node3D</code> </strong>بحجم أو مظهر خاص بها مثل عقدة <strong><code>Node2D</code></strong>، لذا يمكننا استخدام العقدة <strong><code>Sprite2D</code> </strong>لإضافة خامة <strong>Texture </strong>إلى العقدة في الألعاب ثنائية الأبعاد 2D، ولكننا سنحتاج إلى إضافة شبكة <strong>Mesh </strong>في الألعاب ثلاثية الأبعاد 3D.
</p>

<p>
	الشبكة هي وصف رياضي لشكل ما، وتتكون من مجموعة من النقاط تسمى الرؤوس Vertices، وترتبط هذه الرؤوس بخطوط تسمى الأضلاع Edges، وتشكل الأضلاع المتعددة مع بعضها البعض وجهًا Face، فمثلًا يتكون المكعب من 8 رؤوس و 12 ضلعًا و 6 أوجه.
</p>

<p style="text-align: center;">
	<img alt="أجزاء المكعب 3d" class="ipsImage ipsImage_thumbnailed" data-fileid="166357" data-ratio="100.00" data-unique="i1mjgv3j1" style="width: 200px; height: auto;" width="300" src="https://academy.hsoub.com/uploads/monthly_2025_01/06_3d_intro_cube_labels.png.16a76af9140d817792c93faee927e57f.png">
</p>

<h3 id="-4">
	إضافة الشبكات
</h3>

<p>
	يمكن إنشاء الشبكات باستخدام برامج النمذجة ثلاثية الأبعاد مثل <a href="https://academy.hsoub.com/design/3d/blender/" rel="">بلندر Blender</a>، ويمكننا أيضًا العثور على العديد من مجموعات النماذج ثلاثية الأبعاد المتاحة للتنزيل إن لم نتمكّن من إنشاء نموذجنا الخاص. قد نحتاج في بعض الأحيان إلى شكل هندسي أساسي فقط مثل المكعب أو الكرة، ويوفر جودو في هذه الحالة طريقة لإنشاء شبكات بسيطة تسمى الأشكال الأولية Primitives.
</p>

<p>
	لنضف عقدة <strong><code>MeshInstance3D</code> </strong>كعقدة ابن للعقدة <strong><code>Node3D</code></strong>، وننقر على الخاصية <strong>Mesh</strong> الخاصة بها في الفاحص Inspector:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="166355" href="https://academy.hsoub.com/uploads/monthly_2025_01/07_3d___.png.b38392b665244115664daade355e5489.png" rel=""><img alt="07 3d أشكال هندسية أولية" class="ipsImage ipsImage_thumbnailed" data-fileid="166355" data-ratio="275.00" data-unique="mcylvqtfv" style="width: 200px; height: auto;" width="200" src="https://academy.hsoub.com/uploads/monthly_2025_01/07_3d___.thumb.png.508e6957cafd5a8457b974d8d1ef5363.png"> </a>
</p>

<p>
	يمكننا الآن رؤية قائمة الأشكال الأولية المتاحة، والتي تمثل مجموعة مفيدة من الأشكال الشائعة المفيدة. سنحدّد خيار <strong>BoxMesh جديدة</strong>، سنرى مكعبًا يظهر على الشاشة.
</p>

<h2 id="-5">
	الكاميرات
</h2>

<p>
	سنحاول تشغيل المشهد مع كائن المكعب، ولكنا لن نرى أي شيء في نافذة عرض اللعبة ثلاثية الأبعاد دون إضافة العقدة <strong><code>Camera3D</code></strong>، لذا لنضفها إلى العقدة الجذر ونستخدم أداة Gizmo الخاصة بالكاميرا لوضعها في اتجاه المكعب كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="166356" href="https://academy.hsoub.com/uploads/monthly_2025_01/08_3d__.png.8c3a79438cf3002703c08eb78ffb1731.png" rel=""><img alt="08 3d موضع الكاميرا" class="ipsImage ipsImage_thumbnailed" data-fileid="166356" data-ratio="61.00" data-unique="xokoz3et6" style="width: 300px; height: auto;" width="400" src="https://academy.hsoub.com/uploads/monthly_2025_01/08_3d__.png.8c3a79438cf3002703c08eb78ffb1731.png"> </a>
</p>

<p>
	يسمى الشكل الهرمي الوردي الأرجواني على الكاميرا <strong>Fustrum </strong>وهو يمثل مجال نظر الكاميرا، ونلاحظ السهم الذي له شكل المثلث الصغير ويمثل اتجاه الكاميرا للأعلى. نضغط على زر المعاينة Preview في الجزء العلوي الأيسر أثناء تحريك الكاميرا لمعرفة ما تراه الكاميرا، ونشغّل المشهد للتأكد من أن كل شيء يعمل كما هو متوقع.
</p>

<h2 id="-6">
	الخلاصة
</h2>

<p>
	تعلمنا في هذا المقال كيفية استخدام محرر جودو ثلاثي الأبعاد، وكيفية إضافة عقد ثلاثية الأبعاد مثل <code>Node3D</code> و <code>MeshInstance3D</code> و <code>Camera3D</code>، وكيفية استخدام أدوات Gizmo لوضع الكائنات الخاصة بنا في مكانها، بالإضافة إلى مجموعة من المصطلحات الجديدة.
</p>

<p>
	سنوضح في المقال التالي كيفية بناء مشهد ثلاثي الأبعاد من خلال استيراد ملفات الأصول ثلاثية الأبعاد 3D Assets وكيفية استخدام مزيد من عقد جودو ثلاثية الأبعاد.
</p>

<p>
	ترجمة -وبتصرّف- للقسم <a href="https://kidscancode.org/godot_recipes/4.x/g101/3d/101_3d_01/index.html" rel="external nofollow">The 3D Editor</a> من توثيقات Kidscancode.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/game-development/%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%B3%D9%83%D8%B1%D8%A8%D8%AA%D8%A7%D8%AA-gdscript-%D9%88%D8%A5%D8%B1%D9%81%D8%A7%D9%82%D9%87%D8%A7-%D8%A8%D8%A7%D9%84%D8%B9%D9%82%D8%AF-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-r2495/" rel="">كتابة سكربتات GDScript وإرفاقها بالعقد في جودو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-r2484/" rel="">تعرف على واجهة محرك الألعاب جودو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%AD%D8%B1%D9%8A%D9%83-%D8%A7%D9%84%D8%B4%D8%AE%D8%B5%D9%8A%D8%A9-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-3d-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AD%D8%B1%D8%B1-%D8%A7%D9%84%D8%AA%D8%AD%D8%B1%D9%8A%D9%83-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-r2477/" rel="">تحريك الشخصية في لعبة 3D باستخدام محرر التحريك في جودو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D8%B9%D9%82%D8%AF-nodes-%D9%88%D8%A7%D9%84%D9%85%D8%B4%D8%A7%D9%87%D8%AF-scenes-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2357/" rel="">العقد Nodes والمشاهد Scenes في جودو Godot</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2498</guid><pubDate>Tue, 28 Jan 2025 15:00:01 +0000</pubDate></item><item><title>&#x643;&#x62A;&#x627;&#x628;&#x629; &#x633;&#x643;&#x631;&#x628;&#x62A;&#x627;&#x62A; GDScript &#x648;&#x625;&#x631;&#x641;&#x627;&#x642;&#x647;&#x627; &#x628;&#x627;&#x644;&#x639;&#x642;&#x62F; &#x641;&#x64A; &#x62C;&#x648;&#x62F;&#x648;</title><link>https://academy.hsoub.com/programming/game-development/%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%B3%D9%83%D8%B1%D8%A8%D8%AA%D8%A7%D8%AA-gdscript-%D9%88%D8%A5%D8%B1%D9%81%D8%A7%D9%82%D9%87%D8%A7-%D8%A8%D8%A7%D9%84%D8%B9%D9%82%D8%AF-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-r2495/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_01/GDScriptGodot.png.c5ae2c7c841eb8a678fdfcc6737f5347.png" /></p>
<p>
	تُعَد كتابة السكربتات البرمجية وربطها بالعقد والكائنات الأخرى الأسلوب الأساسي لبناء سلوك الألعاب في محرك جودو. على سبيل المثال، تعرض العقدة <code>Sprite2D</code> صورة تلقائيًا، ولكن يمكننا من خلال إضافة سكربت مخصص لهذه العقدة تحريك هذه الصورة عبر الشاشة، وتحديد سرعة واتجاه الحركة والعديد من الخصائص الأخرى.
</p>

<h2 id="gdscript">
	ما هي لغة GDScript
</h2>

<p>
	لغة جي دي سكربت GDScript هي لغة مُضمَّنة في جودو لكتابة السكربتات البرمجية والتفاعل مع العقد. وتشير عدة مراجع لأن لغة GDScript تعتمد على لغة بايثون Python، إذ تستخدم لغة GDScript صياغة مبنية على لغة بايثون، ولكنها لغة مميزة مُحسَّنة ومدمجة مع محرك جودو، لذا إذا كنا على دراية باستخدام <a href="https://academy.hsoub.com/programming/python/" rel="">لغة بايثون</a>، فستكون هذه اللغة مألوفة وسهلة الفهم بالنسبة لنا.
</p>

<p>
	<strong>ملاحظة</strong>: يُفترَض امتلاك بعض الخبرة في أساسيات البرمجة كي نتمكن من تعلّم محرّك الألعاب جودو، فإن لم نكن على دراية بالبرمجة على الإطلاق فسيكون لديك عبء إضافي لتعلم البرمجة. فإذا وجدتم صعوبة في فهم الشيفرة البرمجية في هذا المقال، فننصح قبل البدء بمطالعة مقال <a href="https://academy.hsoub.com/programming/python/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2325/" rel="">أساسيات لغة بايثون</a> ومقال <a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9/" rel="">تعلم أساسيات البرمجة</a>.
</p>

<h2 id="gdscript-1">
	بنية ملف GDScript
</h2>

<p>
	يجب أن يكون السطر الأول من أي ملف GDScript هو <code>extends &lt;Class&gt;‎</code> حيث أن <code>&lt;Class&gt;</code> هو إما صنف مُضمَّن موجود مسبقًا، أو صنف يعرّفه المستخدم، فمثلًا إذا أردنا إرفاق سكربت معين بعقدة <code>CharacterBody2D</code>، فسيبدأ السكربت بالعبارة <code>extends CharacterBody2D</code>، وهذا يعني أن السكربت يأخذ جميع وظائف كائن <code>CharacterBody2D</code> المُضمَّن ويوسّعه باستخدام الوظائف الإضافية التي أنشأناها بنفسنا.
</p>

<p>
	يمكننا في بقية السكربت تعريفُ أيّ عدد من المتغيرات المعروفة أيضًا باسم خاصيات الصنف والدوال المعروفة أيضًا باسم توابع الصنف.
</p>

<p>
	<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" frameborder="0" height="360" id="ips_uid_3069_6" referrerpolicy="strict-origin-when-cross-origin" src="https://academy.hsoub.com/applications/core/interface/index.html" title="البرمجة باستخدام لغة GDScript" width="640" data-embed-src="https://www.youtube.com/embed/T3_qtdDvphw"></iframe>
</p>

<h2 id="gdscript-2">
	إنشاء سكربت GDScript
</h2>

<p>
	لننشئ السكربت الأول، لنتذكّر أن بإمكاننا إرفاق سكربت بأيّ عقدة. لذا سنفتح محرّر جودو ونتقل للتبويب مشهد Scene، ونضيف عقدة من نوع <code>Sprite2D</code> إلى المشهد، ثم ننقر بزر الفأرة الأيمن على العقدة الجديدة، ونحدّد خيار إلحاق نص برمجي Attach Script، ويمكننا أيضًا النقر على الأيقونة الموجودة يسار مربع البحث.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="165630" href="https://academy.hsoub.com/uploads/monthly_2025_01/01---.png.7d6d183174a59dc44556f606593e7455.png" rel=""><img alt="01 إلحاق نص برمجي" class="ipsImage ipsImage_thumbnailed" data-fileid="165630" data-unique="14aji87jw" style="width: 200px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_01/01---.png.7d6d183174a59dc44556f606593e7455.png"> </a>
</p>

<p>
	يجب بعد ذلك أن نختار المكان الذي تريد حفظ السكربت البرمجي فيه ونحدد اسمه، وفي حال سمّينا العقدة باسم ما، فسيُسمَّى السكربت تلقائيًا بذلك الاسم، لذا إن لم نغيِّر أي شيء، فسوف يسمى هذا السكربت sprite2d.gd.
</p>

<p>
	نفتح الآن نافذة محرّر السكربت البرمجي سنجد بعض الكود البرمجي فيها، سيكون هذا السكربت مرتبطًا بالشخصية الرسومية Sprite الجديدة الفارغة حتى الآن، فقد وضع جودو تلقائيًا بعض أسطر الشيفرة البرمجية بالإضافة إلى بعض التعليقات التي تشرح الشيفرة.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5425_8" style=""><span class="pln">extends </span><span class="typ">Sprite2D</span><span class="pln">

</span><span class="com"># Called when the node enters the scene tree for the first time.</span><span class="pln">
func _ready</span><span class="pun">()</span><span class="pln"> </span><span class="pun">-&gt;</span><span class="pln"> void</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">pass</span><span class="pln"> </span><span class="com"># Replace with function body.</span><span class="pln">

</span><span class="com"># Called every frame. 'delta' is the elapsed time since the previous frame.</span><span class="pln">
func _process</span><span class="pun">(</span><span class="pln">delta</span><span class="pun">:</span><span class="pln"> float</span><span class="pun">)</span><span class="pln"> </span><span class="pun">-&gt;</span><span class="pln"> void</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">pass</span></pre>

<p>
	أُضيف هذا السكربت إلى العقدة <code>Sprite2D</code>، لذا يكون السطر الأول تلقائيًا هو <code>extends Sprite2D</code> كي يوسّع هذا السكربت الصنف <code>Sprite2D</code> ويكون قادرًا على الوصول إلى جميع الخاصيات والتوابع التي توفرها العقدة <code>Sprite2D</code> ومعالجتها.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="PNG" data-fileid="165631" href="https://academy.hsoub.com/uploads/monthly_2025_01/02-.PNG.a52fc8eeb039b1da9810fa8637221d69.PNG" rel=""><img alt="02 كود برمجي للعقدة" class="ipsImage ipsImage_thumbnailed" data-fileid="165631" data-unique="m8vxd0p1t" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_01/02-.thumb.PNG.f2675c313c67605831d99533041500fb.PNG"> </a>
</p>

<p>
	<strong>ملاحظة</strong>: إن الخاصيات Properties والتوابع Methods هي المتغيرات والدوال المُعرَّفة في الكائن، قد يخلط المبرمجون المبتدئون في استخدام هذه المصطلحات.
</p>

<p>
	سنعرّف بعد ذلك جميع المتغيرات التي سنستخدمها في السكربت، والتي تُسمَّى المتغيرات الأعضاء Member Variables، ولتعريف المتغيرات نستخدم الكلمة المفتاحية <code>var</code>.
</p>

<p>
	سنتجاوز التعليقات الموجودة في السكربت ونتحدث عن الجزء التالي، حيث سنرى في السكربت أعلاه الدالة <code>‎_ready()‎</code>  حيث يبدأ تعريف الدالة في لغة GDScript بالكلمة المفتاحية <code>func</code>. والدالة <code>‎_ready()‎</code> هنا هي دالة خاصة ينفّذها جودو بشكل تلقائي عند إضافة عقدة إلى الشجرة، أو عندما يبدأ المشهد بالعمل بالضغط على تشغيل Play.
</p>

<p>
	لنفترض أننا نريد نقل الشخصية الرسومية إلى موضع محدد عند بدء اللعبة، عندها يجب علينا ضبط خاصية الموضع Position في الفاحص Inspector، توجد هذه الخاصية في القسم Node2D، وبالتالي ستتوفر هذه الخاصية في أي عقدة من نوع <code>Node2D</code>، وليس فقط في العقد من نوع <code>Sprite2D</code>.
</p>

<p>
	لنضبط الآن هذه الخاصية عن طريق الشيفرة البرمجية، إحدى الطرق المتبعة للعثور على اسم الخاصية هي التمرير فوقها في الفاحص Inspector كما يلي:
</p>

<p style="text-align: center;">
	<img alt="خاصية الموضع Position في الفاحص" class="ipsImage ipsImage_thumbnailed" data-fileid="165632" data-ratio="58.67" data-unique="z9lqnaj3z" style="width: 600px; height: auto;" width="774" src="https://academy.hsoub.com/uploads/monthly_2025_01/03----.png.471d36725db18a9f4bfa06d766a9a6c1.png">
</p>

<p>
	يحتوي جودو على أداة بحث مساعد مدمجة رائعة تساعدنا في التعرف على تفاصيل الأصناف التي نتعامل معها، لبدء استخدام هذه الأداة ننقر على التبويب Classes في الجزء العلوي من نافذة السكربت، ثم نبحث عن اسم الصنف <code>Node2D</code>، ستظهر لنا صفحة المساعدة التي تعرض جميع الخاصيات والتوابع المتاحة بالصنف.
</p>

<p>
	إذا مررنا لأسفل قليلاً، سنلاحظ وجود الخاصية <code>position</code> ضمن قسم Member Variables، والتي تُعد من المتغيرات الأعضاء للصنف. ونلاحظ أيضًا أن هذه الخاصية من النوع <code>Vector2</code>، مما يعني أنها تُستخدَم لتخزين الإحداثيات على المحورين <code>X</code> و <code>Y</code>.
</p>

<p style="text-align: center;">
	<img alt="بحث مساعد" class="ipsImage ipsImage_thumbnailed" data-fileid="165633" data-ratio="75.00" data-unique="6zuzej1w4" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2025_01/04--.thumb.png.3cdba6bf8c79e0037fc7b49c9e50bf41.png">
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="" rel=""> </a>
</p>

<p>
	لنرجع إلى السكربت البرمجي ونستخدم هذه الخاصية كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1501_7" style=""><span class="pln">func _ready</span><span class="pun">():</span><span class="pln">
    position </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Vector2</span><span class="pun">(</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">150</span><span class="pun">)</span></pre>

<p>
	نلاحظ كيف يعرض لنا المحرّر اقتراحات أثناء الكتابة فعندما نكتب مثلًا <code>Vector2</code>، سيخبرنا التلميح بوضع عددين عشريين <code>x</code> و <code>y</code>. لدينا الآن سكربت يمثّل ضبط موضع الشخصية الرسومية على القيم ‎<code>(100,150)</code>‎ عند بدء التشغيل، ويمكننا تجربة ذلك بالضغط على زر تشغيل المشهد Play Scene.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="165634" href="https://academy.hsoub.com/uploads/monthly_2025_01/05--.png.43bce3ddd200bbcdba5aebd372123910.png" rel=""><img alt="تشغيل المشهد" class="ipsImage ipsImage_thumbnailed" data-fileid="165634" data-ratio="9.71" data-unique="w10tyl3r7" style="width: 700px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2025_01/05--.thumb.png.36f34e93ae3b776189a0bb6e30421fc1.png"></a>
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="" rel=""> </a>
</p>

<p>
	<strong>ملاحظة</strong>: يستخدم جودو المتجهات أو الأشعة <a href="https://academy.hsoub.com/design/3d/%D8%A7%D9%84%D8%A3%D8%B4%D8%B9%D8%A9-%D9%88%D8%A7%D9%84%D9%86%D9%82%D8%A7%D8%B7-%D9%88%D8%A7%D9%84%D9%85%D8%B5%D9%81%D9%88%D9%81%D8%A7%D8%AA-%D8%A7%D9%84%D8%B9%D9%85%D9%88%D8%AF%D9%8A%D8%A9-%D9%81%D9%8A-%D8%A7%D9%84%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%AD%D8%A7%D8%B3%D9%88%D8%A8%D9%8A%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-r848/" rel="">Vectors</a> للعديد من الأشياء، وسنتحدث عنها بمزيد من التفصيل لاحقًا.
</p>

<p>
	وأخيرًا قد يتساءل المبتدئون في <a href="https://academy.hsoub.com/programming/game-development/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-r2304/" rel="">برمجة الألعاب</a> عن كيفية حفظ جميع هذه الأوامر البرمجية، والجواب هو مثل أي مهارة أخرى تمامًا، إذ لا يتعلق الأمر بالحفظ بل بالممارسة والتطبيق، فعندما نكرر تنفيذ الأشياء مرارًا وتكرارًا ستصبح بديهية بالنسبة لنا. ومن الجيد الاحتفاظ بمستندات مرجعية في متناول اليد مبدئيًا، واستخدام البحث كلما رأينا شيئًا لا نعرفه، وإذا كان لدينا شاشات متعددة، فلنحتفظ بنسخة مفتوحة من <a href="https://docs.godotengine.org/en/latest/" rel="external nofollow">صفحات التوثيق</a> للرجوع إليها بسرعة.
</p>

<h2 id="">
	الخاتمة
</h2>

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

<p>
	ترجمة -وبتصرّف- للقسم <a href="https://kidscancode.org/godot_recipes/4.x/g101/gdscript/gdscript_01/index.html" rel="external nofollow">Introduction to GDScript: Getting started</a> من توثيقات Kidscancode.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%B9%D9%82%D8%AF-nodes-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot-r2490/" rel="">تعرف على العقد في محرك الألعاب Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D8%B9%D9%82%D8%AF-nodes-%D9%88%D8%A7%D9%84%D9%85%D8%B4%D8%A7%D9%87%D8%AF-scenes-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2357/" rel="">العقد Nodes والمشاهد Scenes في جودو Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D9%85%D8%AA%D8%A7%D8%AD%D8%A9-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2371/" rel="">لغات البرمجة المتاحة في جودو Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D8%AC%D9%83-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D9%88%D8%AF%D9%88-godot-r2382/" rel="">كتابة برنامجك الأول باستخدام جودو Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot/" rel="">مدخل إلى محرك الألعاب جودو Godot</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2495</guid><pubDate>Tue, 21 Jan 2025 15:00:00 +0000</pubDate></item><item><title>&#x62F;&#x645;&#x62C; &#x645;&#x62D;&#x631;&#x643; &#x627;&#x644;&#x623;&#x644;&#x639;&#x627;&#x628; &#x62C;&#x648;&#x62F;&#x648; &#x645;&#x639; &#x627;&#x644;&#x630;&#x643;&#x627;&#x621; &#x627;&#x644;&#x627;&#x635;&#x637;&#x646;&#x627;&#x639;&#x64A;</title><link>https://academy.hsoub.com/programming/game-development/%D8%AF%D9%85%D8%AC-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-%D9%85%D8%B9-%D8%A7%D9%84%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A-r2494/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_01/-------OpenAI.png.313730574fc354dad99b45e738cd3a67.png" /></p>
<p>
	أصبح الذكاء الاصطناعي موجودًا في كل مجال ويستفيد منه المطورون لتسريع عملهم وتحقيق نتائج أفضل. وسنعرفكم في مقال اليوم على خطوات تطوير أداة ذكية باستخدام OpenAI تُمكّن مطوري الألعاب من دمج الذكاء الاصطناعي مع محرك الألعاب جودو Godot والاستفادة منها في تحسين الشيفرات البرمجية للألعاب.
</p>

<h2 id="openai">
	فوائد دمج OpenAI مع محرك الألعاب جودو
</h2>

<p>
	يمكننا الحصول على فوائد عديدة من دمج منصة الذكاء الاصطناعي OpenAI مع محرك الألعاب جودو ومن أبرزها ما يلي:
</p>

<h3>
	تلخيص الأكواد
</h3>

<p>
	من خلال تطوير أداة تربط بين محرك جودو و OpenAI، سنتمكن من تلخيص الأكواد البرمجية المتتابعة بسهولة وبضغطة واحدة على زر Summarize. توفر هذه الميزة الكثير من الوقت والجهد، لاسيما عندما نتعامل مع شيفرات برمجية معقدة ومتشابكة ونرغب في تبسيطها وفهمها بسرعة.
</p>

<h3>
	تنفيذ إجراءات على الكود
</h3>

<p>
	أليس من الرائع أن نتمكن من إعطاء <a href="https://academy.hsoub.com/programming/python/%D9%85%D8%AD%D8%B1%D8%B1-%D8%A3%D9%83%D9%88%D8%A7%D8%AF-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86/" rel="">محرر الأكواد</a> أمرًا بكتابة دالة أو وظيفة معينة، أو نطلب منه إعادة كتابة كود دالة ما بطريقة أكثر احترافية؟ هذه واحدة من أهداف الأداة التي سنطورها، فهي تساعدنا على كتابة وتنقيح الكود البرمجي من داخل محرر الأكواد مباشرة.
</p>

<h3>
	مساعدة مطور الألعاب
</h3>

<p>
	تساعدنا أداة التكامل بين <a href="https://academy.hsoub.com/programming/game-development/%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot/" rel="">محرك ألعاب جودو</a> ومنصة الذكاء الاصطناعي OpenAI في توفير شرح مفصل للأكواد التي لا نفهمها أو التي نواجه مشكلات فيها، كل ما علينا هو تظليل الكود المطلوب والضغط على زر help وستتولى الأداة توفير شرح نصي واضح يساعدنا على فهمه وتحليل المشكلة التي تواجهنا.
</p>

<h3>
	التواصل مع مفتاح واجهة برمجة التطبيقات
</h3>

<p>
	تسمح لنا هذه الأداة بالتواصل المباشر مع واجهة برمجة التطبيقات OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>، أي إنها تسمح لنا بالتواصل مع كافة <a href="https://platform.openai.com/docs/models" rel="external nofollow">النماذج Models</a> القوية التي توفرها OpenAI وسؤالها عن أي شيء ضمن الكود من داخل محرر الأكواد نفسه، ويساعدنا ذلك على استكشاف الثغرات وتنقيحها وتحسين أكوادنا البرمجية.
</p>

<p>
	بعد أن تعرفنا على فائدة الأداة التي سنطورها لجودو، لننتقل الآن لشرح عملية التطوير خطوة بخطوة.
</p>

<h2 id="-1">
	الحصول على مفتاح الواجهة البرمجية OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>
</h2>

<p>
	أول خطوة سنبدأ بها لتطوير أداة التكامل بين جودو وOpenAI هي الحصول على مفتاح <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D9%87%D9%8A-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%84%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-api%D8%9F-r1512/" rel="">واجهة برمجة التطبيقات</a> من موقع OpenAI، فإن لم يسبق لنا استخدام خدمات OpenAI من قبل فعلينا بداية إنشاء حساب جديد في <a href="platform.openai.com" rel="">OpenAI </a>وتسجيل الدخول، والانتقال إلى حسابنا الخاص في الموقع، ثم الضغط على زر View <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> Keys كما في الصورة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="165579" href="https://academy.hsoub.com/uploads/monthly_2025_01/View_API_Keys.png.b9ca2a17d61f950edd7e2315c990c564.png" rel=""><img alt="view api keys" class="ipsImage ipsImage_thumbnailed" data-fileid="165579" data-unique="iyhgfm9ha" style="width: 500px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_01/View_API_Keys.thumb.png.8976abf01f824bd9f22841175401ff2a.png"> </a>
</p>

<p>
	نحتاج الآن إلى توليد مفتاح واجهة برمجة تطبيقات جديد وذلك بالضغط على زر Create new secret key، ثم تسمية المفتاح بأي اسم مناسب ونسخه.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="165574" href="https://academy.hsoub.com/uploads/monthly_2025_01/Create_API_Keys.png.e9d0677a7ba10bf62598981b74431320.png" rel=""><img alt="create api keys" class="ipsImage ipsImage_thumbnailed" data-fileid="165574" data-unique="fxjftyxn2" style="width: 500px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_01/Create_API_Keys.png.e9d0677a7ba10bf62598981b74431320.png"> </a>
</p>

<h2 id="godot">
	البدء مع جودو Godot
</h2>

<p>
	الخطوة التالية هي إعداد الإضافة Plugin الخاصة بأداتنا داخل محرك الألعاب جودو، وذلك ببدء مشروع جديد، ثم فتح إعدادات المشروع  Project Settings، ثم الدخول إلى إضافات Plugins وإنشاء إضافة جديدة Create New Plugin.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2025_01/2100937324_.png.db4618753a90008615d0bf50caa4352c.png" data-fileid="165609" data-fileext="png" rel=""><img alt="إنشاء إضافة جديدة" class="ipsImage ipsImage_thumbnailed" data-fileid="165609" data-ratio="61.00" data-unique="gs6jvs2q2" style="width: 500px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2025_01/.thumb.png.84f06ca9f8f1514566901bc334e21cc6.png"></a>
</p>

<p>
	نسمِّي الإضافة باسم مناسب مثل Godot GPT Integration، ونحدد المجلد الفرعي <code>res://addons/GPT_Integration</code>، ونكتب اسم المطور في الحقل author، ونجعل رقم النسخة 1.0، وأخيرًا نختار لغة المشروع لتكون GDScript، كما يمكن اختيار لغة #C ولكن في هذا الحالة لن يتمكن المستخدم من تثبيت الإضافة في الإصدار العادي من جودو وسيحتاج لنسخة Godot Mono.
</p>

<p style="text-align: center;">
	<img alt="خيارات الإضافة" class="ipsImage ipsImage_thumbnailed" data-fileid="165610" data-ratio="103.33" data-unique="ssvu5qsf2" style="width: 300px; height: auto;" width="529" src="https://academy.hsoub.com/uploads/monthly_2025_01/384453613_.PNG.477812509cd9f1a209daf150e09c7ffa.PNG">
</p>

<p>
	بعد الانتهاء من ملء البيانات المطلوبة نضغط على زر أنشئ Create، ستنشأ إضافة محرر Editor Plugin، وإضافات المحرر هي أدوات إضافية تعمل أثناء تشغيل <a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%85%D8%AD%D8%B1%D8%B1-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%EF%BB%B7%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot-r2183/" rel="">محرر جودو</a> لمساعدة المطورين على كتابة الشيفرات بسهولة.
</p>

<h2 id="-2">
	إنشاء واجهة المستخدم
</h2>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="165578" href="https://academy.hsoub.com/uploads/monthly_2025_01/Integration_Scene.png.af590766b673513afa2d3d37818f39c4.png" rel=""><img alt="integration scene" class="ipsImage ipsImage_thumbnailed" data-fileid="165578" data-ratio="59.44" data-unique="hffb9818s" style="width: 500px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2025_01/Integration_Scene.thumb.png.f51a44c4c68b55ad74455495af068284.png"></a>
</p>

<p>
	ننتقل لتطوير واجهة الأداة والتي ستكون مشهد ثنائي الأبعاد يضم صندوق محادثة Chat Box، وحقل نصي Text Field، ومكونات أخرى كما في الصورة أعلاه. لتصميم هذه الواجهة علينا اتباع الخطوات التالية:
</p>

<p>
	ننتقل للتبويب مشهد من داخل محرك جودو، وننشئ <a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%B9%D9%82%D8%AF-nodes-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot-r2490/" rel="">عقدة</a> تحكم رئيسية Control ونعدِّل اسمها إلى Chat، ثم نضغط بزر الفأرة الأيمن على المنطقة الفارغة في تبويب المشهد، ونختار إضافة عقدة فرعية Add Child Node. ستظهر مجموعة من الاختيارات لتحديد نوع العقدة الفرعية، نختار TextEdit، ثم نحدد العقدة الفرعية ونبحث في قائمة الفاحص عن خاصية Anchor Preset في قسم Layout والتي تحدد مكان العقدة داخل المشهد، سنحدد خيار على كامل المستطيل Full Rect ثم نضبط حجم العقدة بشكل مناسب، فهذه العقدة ستكون صندوق الدردشة الذي سنتواصل منه مع نموذج OpenAI.
</p>

<p>
	بعدها نضيف عقدة فرعية أخرى من نوع Button، ثم نضبط الخاصية Anchor Preset لها بالقيمة Bottom Right أي في الأسفل يمينًا، ثم نعدّل أبعاد الزر وأخيرًا نعدل خاصية نص الزر Text إلى Chat، ثم نضيف عقدة فرعية جديدة أخرى نوعها TextEdit، ونحدد الخاصية Anchor Preset لها في الأسفل يمينًا لتكون أسفل يمين الواجهة، ونحدد أبعادها ونجعلها أعلى زر Chat.
</p>

<p>
	بعد ذلك نضيف عقدة فرعية نوعها حاوية HBoxContainer، ونحدد مكانها ضمن Anchor Preset إلى القيمة Bottom Wide أي بالعرض بالأسفل، ثم نضبط أبعادها، بعدها ننقر بالزر الأيمن على العقدة ونضيف لها ثلاث عقد فرعية من نوع Button، ثم نعدل أسماء تلك الأزرار إلى Summary و Action وHelp ونحدد الأزرار الثلاثة وننتقل إلى Layout ومنها إلى Container Sizing ونختار Expand و Fit كي تتوسع هذه الأزرار وتتناسب مع الحاوية HBoxContainer.
</p>

<p>
	أخيرًا نضبط قياسات صندوق الدردشة ومنطقة تحرير النصوص لتتناسب مع أحجام الأزرار، ويمكن إضافة عقدة فرعية من نوع HSeparator لأسباب جمالية، وفيما يلي الشكل الذي ستظهر عليه شجرة العقد ضمن المشهد:
</p>

<p style="text-align: center;">
	<img alt="شجرة عقد المشهد" class="ipsImage ipsImage_thumbnailed" data-fileid="165611" data-ratio="156.00" data-unique="q5ubzdiow" style="width: 250px; height: auto;" width="274" src="https://academy.hsoub.com/uploads/monthly_2025_01/635241924_.png.1875a49216b50b5308968a2faf5f4b66.png">
</p>

<p>
	بعد الانتهاء من تصميم الواجهة، سنحفظها في ملف باسم chat.tscn، والآن صارت واجهتنا جاهزة لبدء كتابة الأكواد البرمجية اللازمة لعمل الإضافة.
</p>

<p>
	أولاً، سنحتاج إلى إنشاء متغير يمثل كائن ضندوق الدردشة الذي سننشؤه داخل محرك جودو، سنسمي هذا المتغير <code>chat_dock</code>. بعد ذلك، سنقوم بإنشاء الكائن وإضافته إلى مكان مناسب في واجهة محرك جودو. وهنا سنختار وضعه في تبويب علوي بجانب تبويب المشهد، ولكن يمكنك اختيار أي مكان نفضله. بمجرد أن نقوم بذلك، يجب أن نرى تبويبًا جديدًا باسم Chat في محرر Godot يمثل نافذة الدردشة الخاصة بنا كلما قمنا بتفعيل الإضافة وسيختفي عند إلغاء تفعيلها.
</p>

<p>
	سيبدو تبويب إضافة الدردشة ضمن محرك جودو بالشكل التالي:
</p>

<p style="text-align: center;">
	<img alt="تبويب صندوق الدردشة.png" class="ipsImage ipsImage_thumbnailed" data-fileid="165612" data-ratio="141.60" data-unique="lztqpk331" style="width: 250px; height: auto;" width="274" src="https://academy.hsoub.com/uploads/monthly_2025_01/1023103958_.png.2cecb8dbcc612b37e963cc56adc866ef.png">
</p>

<h2 id="-3">
	إعداد الكود البرمجي
</h2>

<p>
	حان دور كتابة الكود البرمجي لأداتنا والذي سيوفر تكاملًا مع الواجهة البرمجية OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>، نبدأ بوراثة الصنف <code>EditorPlugin</code>، وتعريف بعض المتغيرات والثوابت، كالآتي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4941_7" style=""><span class="lit">@tool</span><span class="pln"> 
extends </span><span class="typ">EditorPlugin</span><span class="pln"> 
const </span><span class="typ">MySettings</span><span class="pun">:</span><span class="typ">StringName</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">"res://addons/GPTIntegration/settings.json"</span><span class="pln"> 

enum modes </span><span class="pun">{</span><span class="pln"> 
 </span><span class="typ">Action</span><span class="pun">,</span><span class="pln">  
 </span><span class="typ">Summarise</span><span class="pun">,</span><span class="pln"> 
 </span><span class="typ">Chat</span><span class="pun">,</span><span class="pln"> 
 </span><span class="typ">Help</span><span class="pln"> 
</span><span class="pun">}</span><span class="pln"> 

var api_key </span><span class="pun">=</span><span class="pln"> </span><span class="str">""</span><span class="pln"> </span><span class="com">## put the API key here</span><span class="pln">
var max_tokens </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1024</span><span class="pln"> </span><span class="com"># How many tokens can you use to generate your response</span><span class="pln">
var temperature </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0.5</span><span class="pln"> </span><span class="com"># how "crazy" you want it to be</span><span class="pln">
var url </span><span class="pun">=</span><span class="pln"> </span><span class="str">"https://api.openai.com/v1/completions"</span><span class="pln"> </span><span class="com"># the open api url</span><span class="pln">
var headers </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">"Content-Type: application/json"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Authorization: Bearer "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> api_key</span><span class="pun">]</span><span class="pln"> 
var engine </span><span class="pun">=</span><span class="pln"> </span><span class="str">"text-davinachi-003"</span><span class="pln"> </span><span class="com"># What engine you want to use</span><span class="pln">
var chat_dock 
var http_request </span><span class="pun">:</span><span class="typ">HTTPRequest</span><span class="pln"> 
var current_mode 
var cursor_pos 
var code_editor 
var settings_menu</span></pre>

<p>
	عرَّفنا في هذه الشيفرات الثابت <code>MySettings</code>، والذي يشير إلى ملف JSON الذي سنخزن فيه إعداداتنا، كما عرفنا متغير <code>modes </code>الذي يسمح للأداة أن تعمل بأساليب مختلفة فهو متغير من نمط تعدادي enum يعرّف الأوضاع التي يمكن أن تتواجد فيها الإضافة وإن كانت ستُنفذ إجراءً محددًا، أو تلخص نصًا، أو ستجري محادثة مع المستخدم، أو توفر مساعدة.
</p>

<p>
	أنشأنا كذلك المتغيرات <code>api_key</code>  و <code>max_tokens</code>   و <code>tempreture</code> و <code>url</code> و <code>header</code> و <code>engin</code> المسؤولة عن التفاعل مع واجهة برمجة تطبيقات OpenAI، وعرفنا أيضًا متغيرات<em> </em><code>chatdock</code>  و <code>http_request</code> <em> </em> و <code>cursor_pos</code> و  <code>current_mode</code> و <code>codeeditor  </code>و<code>settings_menu </code>وهذه المتغيرات مسؤولة عن التفاعل مع محرك جودو <code> </code>.
</p>

<h2 id="-4">
	الدخول للشجرة
</h2>

<p>
	عند الدخول إلى شجرة المشهد، سنعرض تبويب الدردشة chat dock الذي يتضمن صندوق الدردشة، وإضافته بجانب تبويب المشهد وربط الأزرار في صندوق الدردشة بالوظائف الخاصة بها. كما نرغب في إضافة عنصر إلى قائمة الأدوات Tool Menu لعرض الإعدادات. إليك كيفية القيام بذلك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4941_10" style=""><span class="pln">func _enter_tree</span><span class="pun">():</span><span class="pln"> 
 </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'_enter_tree'</span><span class="pun">)</span><span class="pln"> 
 chat_dock </span><span class="pun">=</span><span class="pln"> preload</span><span class="pun">(</span><span class="str">"res://addons/GPTIntegration/Chat.tscn"</span><span class="pun">).</span><span class="pln">instantiate</span><span class="pun">()</span><span class="pln"> 
 add_control_to_dock</span><span class="pun">(</span><span class="typ">EditorPlugin</span><span class="pun">.</span><span class="pln">DOCK_SLOT_LEFT_UR</span><span class="pun">,</span><span class="pln"> chat_dock</span><span class="pun">)</span><span class="pln"> 
 chat_dock</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"Button"</span><span class="pun">).</span><span class="pln">connect</span><span class="pun">(</span><span class="str">"pressed"</span><span class="pun">,</span><span class="pln"> _on_chat_button_down</span><span class="pun">)</span><span class="pln"> 
 chat_dock</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"HBoxContainer/Action"</span><span class="pun">).</span><span class="pln">connect</span><span class="pun">(</span><span class="str">"pressed"</span><span class="pun">,</span><span class="pln"> _on_action_button_down</span><span class="pun">)</span><span class="pln"> 
 chat_dock</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"HBoxContainer/Help"</span><span class="pun">).</span><span class="pln">connect</span><span class="pun">(</span><span class="str">"pressed"</span><span class="pun">,</span><span class="pln"> _on_help_button_down</span><span class="pun">)</span><span class="pln"> 
 chat_dock</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"HBoxContainer/Summary"</span><span class="pun">).</span><span class="pln">connect</span><span class="pun">(</span><span class="str">"pressed"</span><span class="pun">,</span><span class="pln"> _on_summary_button_down</span><span class="pun">)</span><span class="pln"> 
 add_tool_menu_item</span><span class="pun">(</span><span class="str">"GPT Chat"</span><span class="pun">,</span><span class="pln"> on_show_settings</span><span class="pun">)</span><span class="pln"> 
 load_settings</span><span class="pun">()</span><span class="pln"> 
 </span><span class="kwd">pass</span></pre>

<p>
	نبدأ في هذا الكود بتحميل نافذة الدردشة في التبويب chat dock، ثم نستنسخه ونضيفه إلى التبويب العلوي الأيسر -بجانب تبويب المشهد- بعد ذلك، نحصل على عقد الأزرار <code>Button</code> و <code>Action</code> و <code>Help</code> و <code>Summary </code>من نافذة الدردشة ونربطها حدث الضغط عليها بالدوال المناسبة <code>on_chat_button_down </code>و <code>on_action_button_down</code> و <code>on_help_button_down</code> و <code>on_summary_button_down</code> على التوالي. كما نضيف عنصرًا في قائمة الأدوات باسم GPT Chat الذي يستدعي الدالة <code>on_show_settings</code> عند النقر عليه. وأخيرًا، نستدعي الدالة <code>load_setting</code> لتحميل الإعدادات من ملف JSON.
</p>

<h2 id="-5">
	الخروج من الشجرة
</h2>

<p>
	عند الخروج من شجرة المشهد، سنحتاج لإزالة تبويب نافذة الدردشة chat dock الذي أضفناه في الخطوة السابقة مع حفظ الإعدادات، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4515_19" style=""><span class="pln">func _exit_tree</span><span class="pun">():</span><span class="pln"> 
 remove_control_from_docks</span><span class="pun">(</span><span class="pln">chat_dock</span><span class="pun">)</span><span class="pln"> 
 chat_dock</span><span class="pun">.</span><span class="pln">queue_free</span><span class="pun">()</span><span class="pln"> 
 remove_tool_menu_item</span><span class="pun">(</span><span class="str">"GPT Chat"</span><span class="pun">)</span><span class="pln"> 
 save_settings</span><span class="pun">()</span><span class="pln"> 
 </span><span class="kwd">pass</span></pre>

<p>
	نُزيل في هذه الشيفرة كل ما يتعلق بإضافة روبوت المحادثة ثم نضعه ضمن المحذوفات، كما نُزيل التبويب GPT Chat، وأخيرًا نستدعي الدالة<code>  save_settings</code> لحفظ إعدادات الإضافة إلى ملف JSON.
</p>

<h2 id="-6">
	معالجة أحداث الضغط على الأزرار
</h2>

<p>
	الآن لنعالج ضغطات أزرار الأداة، فحين نضغط على زر Chat مثلًا نريد أن ترسل الأداة طلبًا إلى نموذج OpenAI بالنص أو المُوجّه prompt المكتوب في المساحة النصية TextEdit، وفيما يلي كيفية تنفيذ ذلك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4515_21" style=""><span class="pln">func _on_chat_button_down</span><span class="pun">():</span><span class="pln"> 
 </span><span class="kwd">if</span><span class="pun">(</span><span class="pln">chat_dock</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"TextEdit"</span><span class="pun">).</span><span class="pln">text </span><span class="pun">!=</span><span class="pln"> </span><span class="str">""</span><span class="pun">):</span><span class="pln"> 
  current_mode </span><span class="pun">=</span><span class="pln"> modes</span><span class="pun">.</span><span class="typ">Chat</span><span class="pln"> 
  var prompt </span><span class="pun">=</span><span class="pln"> chat_dock</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"TextEdit"</span><span class="pun">).</span><span class="pln">text 
  chat_dock</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"TextEdit"</span><span class="pun">).</span><span class="pln">text </span><span class="pun">=</span><span class="pln"> </span><span class="str">""</span><span class="pln"> 
  add_to_chat</span><span class="pun">(</span><span class="str">"Me: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> prompt</span><span class="pun">)</span><span class="pln"> 
  call_GPT</span><span class="pun">(</span><span class="pln">prompt</span><span class="pun">)</span></pre>

<p>
	فحصنا بالكود أعلاه إن كانت العقدة TextEdit غير فارغة وتتضمن مُوجّه Prompt مكتوب للتواصل مع نموذج الذكاء الاصطناعي، ففي هذه الحالة نضبط الإضافة إلى الوضع Chat، ثم نستنسخ النص ونضعه في الصياغة <code>Me: prompt</code> ليظهر بهذه الصياغة في واجهة المستخدم، ثم نحذف النص من TextEdit، ونستدعي الدالة <code>call_GPT</code> ونمرر لها المُوجّه الذي نود إيصاله إلى نموذج OpenAI.
</p>

<p>
	ننتقل بعد ذلك إلى معالجة حدث الضغط على زر Action نود أن نرسل إلى نموذج OpenAI طلب لتنفيذ الأمر المكتوب على الشيفرات المحددة، وفيما يلي كيفية تنفيذ ذلك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4515_23" style=""><span class="pln">func _on_action_button_down</span><span class="pun">():</span><span class="pln"> 
 current_mode </span><span class="pun">=</span><span class="pln"> modes</span><span class="pun">.</span><span class="typ">Action</span><span class="pln"> 
 call_GPT</span><span class="pun">(</span><span class="str">"Code this for Godot "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> get_selected_code</span><span class="pun">())</span></pre>

<p>
	حوَّلنا في الشيفرات السابقة وضع الملحق إلى Action، ثم استدعينا الدالة <code>call_GPT</code> مع إعطائها المُوجّه prompt الذي نرغب في تنفيذه.
</p>

<p>
	وبعد ذلك نعالج حدث الضغط على زر Help، حيث نرغب بالحصول على شرح للأكواد المحددة عند الضغط عليه، ولتنفيذ ذلك نكتب:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4515_25" style=""><span class="pln">func _on_help_button_down</span><span class="pun">():</span><span class="pln"> 
 current_mode </span><span class="pun">=</span><span class="pln"> modes</span><span class="pun">.</span><span class="typ">Help</span><span class="pln"> 
 var code </span><span class="pun">=</span><span class="pln"> get_selected_code</span><span class="pun">()</span><span class="pln"> 
 chat_dock</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"TextEdit"</span><span class="pun">).</span><span class="pln">text </span><span class="pun">=</span><span class="pln"> </span><span class="str">""</span><span class="pln"> 
 add_to_chat</span><span class="pun">(</span><span class="str">"Me: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"What is wrong with this GDScript code? "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> code</span><span class="pun">)</span><span class="pln"> 
 call_GPT</span><span class="pun">(</span><span class="str">"What is wrong with this GDScript code? "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> code</span><span class="pun">)</span></pre>

<p>
	بدَّلنا في هذه الشيفرات وضع الإضافة إلى Help، ثم أضفنا المُوجّه المكتوب في TextEdit وفق الصياغة <code>Me: prompt</code> الظاهرة في واجهة المستخدم، ثم حذفناه من العقدة TextEdit واستدعينا الدالة <code>call_GPT</code> لترسل الأمر إلى OpenAI للمساعدة في فهم الشيفرات المحددة.
</p>

<p>
	وأخيرًا، نعالج حدث الضغط على زر Summary نود من نموذج OpenAI أن ترسل تلخيصًا للشيفرات المختارة، وذلك كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4515_27" style=""><span class="pln">func _on_summary_button_down</span><span class="pun">():</span><span class="pln"> 
 current_mode </span><span class="pun">=</span><span class="pln"> modes</span><span class="pun">.</span><span class="typ">Summarise</span><span class="pln"> 
 call_GPT</span><span class="pun">(</span><span class="str">"Summarize this GDScript Code "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> get_selected_code</span><span class="pun">())</span></pre>

<p>
	استبدلنا وضع الأداة إلى Summarise، ثم استدعينا الدالة <code>call_GPT</code> ومررنا لها مُوجِّه لتلخيص الأكواد المحددة.
</p>

<h2 id="-7">
	معالجة الاستجابة
</h2>

<p>
	الآن، لنعالج استجابات الطلبات التي حصلنا عليها من OpenAI  وسنفعل ذلك باستخدام الدالة <code>on_request_completed</code>. وسنعتمد على الوضع الحالي للأداة فهل هو Chat أو Summarise أو Help أو Action لنحدد فيما إذا كنا سنضع الإجابة التي حصلنا عليها في مساحة الدردشة بالروبوت أم ضمن أكواد المحرر مباشرة.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4515_29" style=""><span class="pln">func _on_request_completed</span><span class="pun">(</span><span class="pln">result</span><span class="pun">,</span><span class="pln"> responseCode</span><span class="pun">,</span><span class="pln"> headers</span><span class="pun">,</span><span class="pln"> body</span><span class="pun">):</span><span class="pln"> 
 </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">result</span><span class="pun">,</span><span class="pln"> responseCode</span><span class="pun">,</span><span class="pln"> headers</span><span class="pun">,</span><span class="pln"> body</span><span class="pun">)</span><span class="pln"> 
 var json </span><span class="pun">=</span><span class="pln"> JSON</span><span class="pun">.</span><span class="pln">new</span><span class="pun">()</span><span class="pln"> 
 var parse_result </span><span class="pun">=</span><span class="pln"> json</span><span class="pun">.</span><span class="pln">parse</span><span class="pun">(</span><span class="pln">body</span><span class="pun">.</span><span class="pln">get_string_from_utf8</span><span class="pun">())</span><span class="pln"> 
 </span><span class="kwd">if</span><span class="pln"> parse_result</span><span class="pun">:</span><span class="pln"> 
  printerr</span><span class="pun">(</span><span class="pln">parse_result</span><span class="pun">)</span><span class="pln"> 
  </span><span class="kwd">return</span><span class="pln"> 
 var response </span><span class="pun">=</span><span class="pln"> json</span><span class="pun">.</span><span class="pln">get_data</span><span class="pun">()</span><span class="pln"> 
 </span><span class="kwd">if</span><span class="pln"> response </span><span class="kwd">is</span><span class="pln"> </span><span class="typ">Dictionary</span><span class="pun">:</span><span class="pln"> 
  </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"Response"</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> 
  </span><span class="kwd">if</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">has</span><span class="pun">(</span><span class="str">"error"</span><span class="pun">):</span><span class="pln"> 
   </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"Error"</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">[</span><span class="str">'error'</span><span class="pun">])</span><span class="pln"> 
   </span><span class="kwd">return</span><span class="pln"> 
 </span><span class="kwd">else</span><span class="pun">:</span><span class="pln"> 
  </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"Response is not a Dictionary"</span><span class="pun">,</span><span class="pln"> headers</span><span class="pun">)</span><span class="pln"> 
  </span><span class="kwd">return</span><span class="pln"> 
 var newStr </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">choices</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">text 
 </span><span class="kwd">if</span><span class="pln"> current_mode </span><span class="pun">==</span><span class="pln"> modes</span><span class="pun">.</span><span class="typ">Chat</span><span class="pun">:</span><span class="pln"> 
  add_to_chat</span><span class="pun">(</span><span class="str">"GPT: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> newStr</span><span class="pun">)</span><span class="pln"> 
 </span><span class="kwd">elif</span><span class="pln"> current_mode </span><span class="pun">==</span><span class="pln"> modes</span><span class="pun">.</span><span class="typ">Summarise</span><span class="pun">:</span><span class="pln"> 
  var str </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">choices</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">text</span><span class="pun">.</span><span class="pln">replace</span><span class="pun">(</span><span class="str">"\n"</span><span class="pln"> </span><span class="pun">,</span><span class="pln"> </span><span class="str">""</span><span class="pun">)</span><span class="pln"> 
  newStr </span><span class="pun">=</span><span class="pln"> </span><span class="str">"# "</span><span class="pln"> 
  var lineLength </span><span class="pun">=</span><span class="pln"> </span><span class="lit">50</span><span class="pln"> 
  var currentLineLength </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> 
  </span><span class="kwd">for</span><span class="pln"> i </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="pln">str</span><span class="pun">.</span><span class="pln">length</span><span class="pun">()):</span><span class="pln"> 
   </span><span class="kwd">if</span><span class="pln"> currentLineLength </span><span class="pun">&gt;=</span><span class="pln"> lineLength </span><span class="kwd">and</span><span class="pln"> str</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="str">" "</span><span class="pun">:</span><span class="pln"> 
    newStr </span><span class="pun">+=</span><span class="pln"> </span><span class="str">"\n# "</span><span class="pln"> 
    currentLineLength </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> 
   </span><span class="kwd">else</span><span class="pun">:</span><span class="pln"> 
    newStr </span><span class="pun">+=</span><span class="pln"> str</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]</span><span class="pln"> 
    currentLineLength </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> 
  code_editor</span><span class="pun">.</span><span class="pln">insert_line_at</span><span class="pun">(</span><span class="pln">cursor_pos</span><span class="pun">,</span><span class="pln"> newStr</span><span class="pun">)</span><span class="pln"> 
 </span><span class="kwd">elif</span><span class="pln"> current_mode </span><span class="pun">==</span><span class="pln"> modes</span><span class="pun">.</span><span class="typ">Action</span><span class="pun">:</span><span class="pln"> 
  code_editor</span><span class="pun">.</span><span class="pln">insert_line_at</span><span class="pun">(</span><span class="pln">cursor_pos</span><span class="pun">,</span><span class="pln"> newStr</span><span class="pun">)</span><span class="pln"> 
 </span><span class="kwd">elif</span><span class="pln"> current_mode </span><span class="pun">==</span><span class="pln"> modes</span><span class="pun">.</span><span class="typ">Help</span><span class="pun">:</span><span class="pln"> 
  add_to_chat</span><span class="pun">(</span><span class="str">"GPT: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">choices</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">text</span><span class="pun">)</span><span class="pln"> 
 </span><span class="kwd">pass</span></pre>

<p>
	نحلل في الكود أعلاه الاستجابة التي حصلنا عليها من OpenAI ونتحقق مما إذا كانت عبارة عن قاموس dictionart، فإذا كانت كذلك سنستخرج النص من الاستجابة، وفي حال كان الوضع الحالي المحدد في الأداة هو Chat  نضيف النص إلى الدردشة مع وضع البادئة GPT. وإذا كان الوضع الحالي هو وضع Summarise فسندرجه في محرر الأكواد كنسخة ملخصة من الكود المحدد بحيث لا يتجاوز طول كل سطر 50 حرفًا ويبدأ كل سطر برمز  # ، وإذا كان الوضع الحالي هو Action، سندرج نص الاستجابة في محرر الأكواد مباشرة. أما إذا كان الوضع الحالي هو Help فسنضيف نص الاستجابة إلى الدردشة مع البادئة GPT.
</p>

<h2 id="-8">
	حفظ وتحميل الإعدادات
</h2>

<p>
	لنعمل الآن على حفظ إعدادات الإضافة عن طريق الدالة <code>save_settings </code>حيث سننشئ ملف JSON يحتوي قيم المتغيرات <code>api_key</code> <code>maxtokens</code> و <code>maxtokens</code> و<code>engine</code>، ونحدد المجلد <code>res://addons/GPTIntegration</code> لتخزين هذا الملف، بعدها نحمّل الإعدادات من خلال الدالة <code>load_settings </code>التي تفتح  الملف JSON  وتستخرج بياناته وتعين قيم المتغيرات <code>api_key</code> <code>maxtokens</code> و <code>maxtokens</code> و<code>engine</code> بناء عليها وفيما يلي كيفية تنفيذ ذلك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4515_31" style=""><span class="pln">func save_settings</span><span class="pun">():</span><span class="pln"> 
 var data </span><span class="pun">={</span><span class="pln"> 
  </span><span class="str">"api_key"</span><span class="pun">:</span><span class="pln">api_key</span><span class="pun">,</span><span class="pln"> 
  </span><span class="str">"max_tokens"</span><span class="pun">:</span><span class="pln"> max_tokens</span><span class="pun">,</span><span class="pln"> 
  </span><span class="str">"temperature"</span><span class="pun">:</span><span class="pln"> temperature</span><span class="pun">,</span><span class="pln"> 
  </span><span class="str">"engine"</span><span class="pun">:</span><span class="pln"> engine 
 </span><span class="pun">}</span><span class="pln"> 
 </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln"> 
 var jsonStr </span><span class="pun">=</span><span class="pln"> JSON</span><span class="pun">.</span><span class="pln">stringify</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln"> 
 var file </span><span class="pun">=</span><span class="pln"> </span><span class="typ">FileAccess</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="typ">MySettings</span><span class="pun">,</span><span class="pln"> </span><span class="typ">FileAccess</span><span class="pun">.</span><span class="pln">WRITE</span><span class="pun">)</span><span class="pln"> 
 file</span><span class="pun">.</span><span class="pln">store_string</span><span class="pun">(</span><span class="pln">jsonStr</span><span class="pun">)</span><span class="pln"> 
 file</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln"> </span></pre>

<p>
	أنشأنا في الشيفرات السابقة قاموسًا يضم المتغيرات <code>api_key</code> <code>maxtokens</code> و <code>maxtokens</code> و<code>engine</code>، ثم حولنا هذا القاموس إلى تنسيق JSON، ثم فتحنا ملف JSON على وضع الكتابة وحفظنا فيه البيانات ثم أغلقناه.
</p>

<pre class="ipsCode">func load_settings(): 
 if not FileAccess.file_exists(MySettings): 
  save_settings() 
 var file = FileAccess.open(MySettings, FileAccess.READ) 
 if not file: 
printerr("Unable to create", MySettings, error_string(ERR_CANT_CREATE)) 
  print_stack() 
  return 
 var jsonStr = file.get_as_text() 
 file.close() 
 var data = JSON.parse_string(jsonStr) 
 print(data) 
 api_key = data["api_key"] 
 max_tokens = int(data["max_tokens"]) 
 temperature = float(data["temperature"]) 
 engine = data["engine"]
</pre>

<p>
	فحصنا في الشيفرات السابقة ما إذا كان ملف الإعدادات موجودًا، فإن لم يكن موجودًا سنستدعي الدالة <code>save_settings</code> لإنشائه، ثم فتحنا الملف وضع القراءة واستدعينا البيانات المخزنة فيه واستخرجنا القيم المناسبة لضبط المتغيرات <code>api_key</code> <code>maxtokens</code> و <code>maxtokens</code> و<code>engine.</code>
</p>

<h2>
	الكود الكامل لإضافة جودو
</h2>

<p>
	فيما يلي الكود النهائي الكامل لأداتنا التي تضيف ميزات الذكاء الاصطناعي لمحرر ألعاب جودو:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4515_33" style=""><span class="lit">@tool</span><span class="pln">
extends </span><span class="typ">EditorPlugin</span><span class="pln">

const </span><span class="typ">MySettings</span><span class="pun">:</span><span class="typ">StringName</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">"res://addons/GPTIntegration/settings.json"</span><span class="pln">

enum modes </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Action</span><span class="pun">,</span><span class="pln"> 
    </span><span class="typ">Summarise</span><span class="pun">,</span><span class="pln">
    </span><span class="typ">Chat</span><span class="pun">,</span><span class="pln">
    </span><span class="typ">Help</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

var api_key </span><span class="pun">=</span><span class="pln"> </span><span class="str">""</span><span class="pln">
var max_tokens </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1024</span><span class="pln">
var temperature </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0.5</span><span class="pln">
var url </span><span class="pun">=</span><span class="pln"> </span><span class="str">"https://api.openai.com/v1/completions"</span><span class="pln">
var headers </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">"Content-Type: application/json"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Authorization: Bearer "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> api_key</span><span class="pun">]</span><span class="pln">
var engine </span><span class="pun">=</span><span class="pln"> </span><span class="str">"text-davinachi-003"</span><span class="pln">
var chat_dock
var http_request </span><span class="pun">:</span><span class="typ">HTTPRequest</span><span class="pln">
var current_mode
var cursor_pos
var code_editor
var settings_menu

func _enter_tree</span><span class="pun">():</span><span class="pln">
    printt</span><span class="pun">(</span><span class="str">'_enter_tree'</span><span class="pun">)</span><span class="pln">
    chat_dock </span><span class="pun">=</span><span class="pln"> preload</span><span class="pun">(</span><span class="str">"res://addons/GPTIntegration/Chat.tscn"</span><span class="pun">).</span><span class="pln">instantiate</span><span class="pun">()</span><span class="pln">
    add_control_to_dock</span><span class="pun">(</span><span class="typ">EditorPlugin</span><span class="pun">.</span><span class="pln">DOCK_SLOT_LEFT_UR</span><span class="pun">,</span><span class="pln"> chat_dock</span><span class="pun">)</span><span class="pln">

    chat_dock</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"Button"</span><span class="pun">).</span><span class="pln">connect</span><span class="pun">(</span><span class="str">"pressed"</span><span class="pun">,</span><span class="pln"> _on_chat_button_down</span><span class="pun">)</span><span class="pln">
    chat_dock</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"HBoxContainer/Action"</span><span class="pun">).</span><span class="pln">connect</span><span class="pun">(</span><span class="str">"pressed"</span><span class="pun">,</span><span class="pln"> _on_action_button_down</span><span class="pun">)</span><span class="pln">
    chat_dock</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"HBoxContainer/Help"</span><span class="pun">).</span><span class="pln">connect</span><span class="pun">(</span><span class="str">"pressed"</span><span class="pun">,</span><span class="pln"> _on_help_button_down</span><span class="pun">)</span><span class="pln">
    chat_dock</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"HBoxContainer/Summary"</span><span class="pun">).</span><span class="pln">connect</span><span class="pun">(</span><span class="str">"pressed"</span><span class="pun">,</span><span class="pln"> _on_summary_button_down</span><span class="pun">)</span><span class="pln">
    </span><span class="com"># Initialization of the plugin goes here.</span><span class="pln">
    add_tool_menu_item</span><span class="pun">(</span><span class="str">"GPT Chat"</span><span class="pun">,</span><span class="pln"> on_show_settings</span><span class="pun">)</span><span class="pln">
    load_settings</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">pass</span><span class="pln">

func on_show_settings</span><span class="pun">():</span><span class="pln">
    settings_menu </span><span class="pun">=</span><span class="pln"> preload</span><span class="pun">(</span><span class="str">"res://addons/GPTIntegration/SettingsWindow.tscn"</span><span class="pun">).</span><span class="pln">instantiate</span><span class="pun">()</span><span class="pln">
    settings_menu</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"Control/Button"</span><span class="pun">).</span><span class="pln">connect</span><span class="pun">(</span><span class="str">"pressed"</span><span class="pun">,</span><span class="pln"> on_settings_button_down</span><span class="pun">)</span><span class="pln">
    set_settings</span><span class="pun">(</span><span class="pln">api_key</span><span class="pun">,</span><span class="pln"> int</span><span class="pun">(</span><span class="pln">max_tokens</span><span class="pun">),</span><span class="pln"> float</span><span class="pun">(</span><span class="pln">temperature</span><span class="pun">),</span><span class="pln"> engine</span><span class="pun">)</span><span class="pln">
    add_child</span><span class="pun">(</span><span class="pln">settings_menu</span><span class="pun">)</span><span class="pln">
    settings_menu</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="str">"close_requested"</span><span class="pun">,</span><span class="pln"> settings_menu_close</span><span class="pun">)</span><span class="pln">
    settings_menu</span><span class="pun">.</span><span class="pln">popup</span><span class="pun">()</span><span class="pln">

func on_settings_button_down</span><span class="pun">():</span><span class="pln">
    api_key </span><span class="pun">=</span><span class="pln"> settings_menu</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"HBoxContainer/VBoxContainer2/APIKey"</span><span class="pun">).</span><span class="pln">text

    max_tokens </span><span class="pun">=</span><span class="pln"> int</span><span class="pun">(</span><span class="pln">settings_menu</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"HBoxContainer/VBoxContainer2/MaxTokens"</span><span class="pun">).</span><span class="pln">text</span><span class="pun">)</span><span class="pln">
    temperature </span><span class="pun">=</span><span class="pln"> float</span><span class="pun">(</span><span class="pln">settings_menu</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"HBoxContainer/VBoxContainer2/Temperature"</span><span class="pun">).</span><span class="pln">text</span><span class="pun">)</span><span class="pln">
    var index </span><span class="pun">=</span><span class="pln"> settings_menu</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"HBoxContainer/VBoxContainer2/OptionButton"</span><span class="pun">).</span><span class="pln">selected

    </span><span class="kwd">if</span><span class="pln"> engine </span><span class="pun">==</span><span class="pln"> </span><span class="str">"text-davinchi-003"</span><span class="pun">:</span><span class="pln">
        index </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
    </span><span class="kwd">elif</span><span class="pln"> index </span><span class="pun">==</span><span class="pln"> </span><span class="lit">1</span><span class="pun">:</span><span class="pln">
        engine </span><span class="pun">=</span><span class="pln"> </span><span class="str">"code-davinci-002"</span><span class="pln">
    </span><span class="kwd">elif</span><span class="pln"> index </span><span class="pun">==</span><span class="pln"> </span><span class="lit">2</span><span class="pun">:</span><span class="pln">
        engine </span><span class="pun">=</span><span class="pln"> </span><span class="str">"text-curie-001"</span><span class="pln">
    </span><span class="kwd">elif</span><span class="pln"> index </span><span class="pun">==</span><span class="pln"> </span><span class="lit">3</span><span class="pun">:</span><span class="pln"> 
        engine </span><span class="pun">=</span><span class="pln"> </span><span class="str">"text-babbage-001"</span><span class="pln">
    </span><span class="kwd">elif</span><span class="pln"> index </span><span class="pun">==</span><span class="pln"> </span><span class="lit">4</span><span class="pun">:</span><span class="pln">
        engine </span><span class="pun">=</span><span class="pln"> </span><span class="str">"text-ada-001"</span><span class="pln">
    </span><span class="kwd">elif</span><span class="pln"> index </span><span class="pun">==</span><span class="pln"> </span><span class="lit">5</span><span class="pun">:</span><span class="pln">
        engine </span><span class="pun">=</span><span class="pln"> </span><span class="str">"code-cushman-002"</span><span class="pln">

    settings_menu_close</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">pass</span><span class="pln">

func settings_menu_close</span><span class="pun">():</span><span class="pln">
    settings_menu</span><span class="pun">.</span><span class="pln">queue_free</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">pass</span><span class="pln">

</span><span class="com"># This GDScript code sets the settings in a settings</span><span class="pln">
</span><span class="com"># menu. It sets the API key, the maximum number of tokens,</span><span class="pln">
</span><span class="com"># the temperature, and the engine. The engine is set</span><span class="pln">
</span><span class="com"># by selecting the corresponding ID from a list of options.</span><span class="pln">
func set_settings</span><span class="pun">(</span><span class="pln">api_key</span><span class="pun">,</span><span class="pln"> maxtokens</span><span class="pun">,</span><span class="pln"> temp</span><span class="pun">,</span><span class="pln"> engine</span><span class="pun">):</span><span class="pln">
    settings_menu</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"HBoxContainer/VBoxContainer2/APIKey"</span><span class="pun">).</span><span class="pln">text </span><span class="pun">=</span><span class="pln"> api_key
    settings_menu</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"HBoxContainer/VBoxContainer2/MaxTokens"</span><span class="pun">).</span><span class="pln">text </span><span class="pun">=</span><span class="pln"> str</span><span class="pun">(</span><span class="pln">maxtokens</span><span class="pun">)</span><span class="pln">
    settings_menu</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"HBoxContainer/VBoxContainer2/Temperature"</span><span class="pun">).</span><span class="pln">text </span><span class="pun">=</span><span class="pln"> str</span><span class="pun">(</span><span class="pln">temp</span><span class="pun">)</span><span class="pln">
    var id </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> engine </span><span class="pun">==</span><span class="pln"> </span><span class="str">"text-davinchi-003"</span><span class="pun">:</span><span class="pln">
        id </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
    </span><span class="kwd">elif</span><span class="pln">  engine </span><span class="pun">==</span><span class="pln"> </span><span class="str">"code-davinci-002"</span><span class="pun">:</span><span class="pln">
        id </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
    </span><span class="kwd">elif</span><span class="pln"> engine </span><span class="pun">==</span><span class="pln"> </span><span class="str">"text-curie-001"</span><span class="pun">:</span><span class="pln">
        id </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2</span><span class="pln">
    </span><span class="kwd">elif</span><span class="pln"> engine </span><span class="pun">==</span><span class="pln"> </span><span class="str">"text-babbage-001"</span><span class="pun">:</span><span class="pln">
        id </span><span class="pun">=</span><span class="pln"> </span><span class="lit">3</span><span class="pln">
    </span><span class="kwd">elif</span><span class="pln"> engine </span><span class="pun">==</span><span class="pln"> </span><span class="str">"text-ada-001"</span><span class="pun">:</span><span class="pln">
        id </span><span class="pun">=</span><span class="pln"> </span><span class="lit">4</span><span class="pln">
    </span><span class="kwd">elif</span><span class="pln"> engine </span><span class="pun">==</span><span class="pln"> </span><span class="str">"code-cushman-002"</span><span class="pun">:</span><span class="pln">
        id </span><span class="pun">=</span><span class="pln"> </span><span class="lit">5</span><span class="pln">

    settings_menu</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"HBoxContainer/VBoxContainer2/OptionButton"</span><span class="pun">).</span><span class="pln">select</span><span class="pun">(</span><span class="pln">id</span><span class="pun">)</span><span class="pln">

func _exit_tree</span><span class="pun">():</span><span class="pln">
    </span><span class="com"># Clean-up of the plugin goes here.</span><span class="pln">
    remove_control_from_docks</span><span class="pun">(</span><span class="pln">chat_dock</span><span class="pun">)</span><span class="pln">
    chat_dock</span><span class="pun">.</span><span class="pln">queue_free</span><span class="pun">()</span><span class="pln">
    remove_tool_menu_item</span><span class="pun">(</span><span class="str">"GPT Chat"</span><span class="pun">)</span><span class="pln">
    save_settings</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">pass</span><span class="pln">

func _ready</span><span class="pun">():</span><span class="pln">
    http_request </span><span class="pun">=</span><span class="pln"> </span><span class="typ">HTTPRequest</span><span class="pun">.</span><span class="pln">new</span><span class="pun">()</span><span class="pln">
    add_child</span><span class="pun">(</span><span class="pln">http_request</span><span class="pun">)</span><span class="pln">
    http_request</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="str">"request_completed"</span><span class="pun">,</span><span class="pln"> _on_request_completed</span><span class="pun">)</span><span class="pln">

func _on_chat_button_down</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">if</span><span class="pun">(</span><span class="pln">chat_dock</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"TextEdit"</span><span class="pun">).</span><span class="pln">text </span><span class="pun">!=</span><span class="pln"> </span><span class="str">""</span><span class="pun">):</span><span class="pln">
        current_mode </span><span class="pun">=</span><span class="pln"> modes</span><span class="pun">.</span><span class="typ">Chat</span><span class="pln">
        var prompt </span><span class="pun">=</span><span class="pln"> chat_dock</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"TextEdit"</span><span class="pun">).</span><span class="pln">text
        chat_dock</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"TextEdit"</span><span class="pun">).</span><span class="pln">text </span><span class="pun">=</span><span class="pln"> </span><span class="str">""</span><span class="pln">
        add_to_chat</span><span class="pun">(</span><span class="str">"Me: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> prompt</span><span class="pun">)</span><span class="pln">
        call_GPT</span><span class="pun">(</span><span class="pln">prompt</span><span class="pun">)</span><span class="pln">

func call_GPT</span><span class="pun">(</span><span class="pln">prompt</span><span class="pun">):</span><span class="pln">
    var body </span><span class="pun">=</span><span class="pln"> JSON</span><span class="pun">.</span><span class="pln">new</span><span class="pun">().</span><span class="pln">stringify</span><span class="pun">({</span><span class="pln">
        </span><span class="str">"prompt"</span><span class="pun">:</span><span class="pln"> prompt</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"temperature"</span><span class="pun">:</span><span class="pln"> temperature</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"max_tokens"</span><span class="pun">:</span><span class="pln"> max_tokens</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"model"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"text-davinci-003"</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
    var error </span><span class="pun">=</span><span class="pln"> http_request</span><span class="pun">.</span><span class="pln">request</span><span class="pun">(</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> </span><span class="pun">[</span><span class="str">"Content-Type: application/json"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Authorization: Bearer "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> api_key</span><span class="pun">],</span><span class="pln"> </span><span class="typ">HTTPClient</span><span class="pun">.</span><span class="pln">METHOD_POST</span><span class="pun">,</span><span class="pln"> body</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> error </span><span class="pun">!=</span><span class="pln"> OK</span><span class="pun">:</span><span class="pln">
        push_error</span><span class="pun">(</span><span class="str">"Something Went Wrong!"</span><span class="pun">)</span><span class="pln">

func _on_action_button_down</span><span class="pun">():</span><span class="pln">
    current_mode </span><span class="pun">=</span><span class="pln"> modes</span><span class="pun">.</span><span class="typ">Action</span><span class="pln">
    call_GPT</span><span class="pun">(</span><span class="str">"Code this for Godot "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> get_selected_code</span><span class="pun">())</span><span class="pln">

func _on_help_button_down</span><span class="pun">():</span><span class="pln">
    current_mode </span><span class="pun">=</span><span class="pln"> modes</span><span class="pun">.</span><span class="typ">Help</span><span class="pln">
    var code </span><span class="pun">=</span><span class="pln"> get_selected_code</span><span class="pun">()</span><span class="pln">
    chat_dock</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"TextEdit"</span><span class="pun">).</span><span class="pln">text </span><span class="pun">=</span><span class="pln"> </span><span class="str">""</span><span class="pln">
    add_to_chat</span><span class="pun">(</span><span class="str">"Me: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"What is wrong with this GDScript code? "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> code</span><span class="pun">)</span><span class="pln">
    call_GPT</span><span class="pun">(</span><span class="str">"What is wrong with this GDScript code? "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> code</span><span class="pun">)</span><span class="pln">

func _on_summary_button_down</span><span class="pun">():</span><span class="pln">
    current_mode </span><span class="pun">=</span><span class="pln"> modes</span><span class="pun">.</span><span class="typ">Summarise</span><span class="pln">
    call_GPT</span><span class="pun">(</span><span class="str">"Summarize this GDScript Code "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> get_selected_code</span><span class="pun">())</span><span class="pln">



</span><span class="com"># This GDScript code is used to handle the response from</span><span class="pln">
</span><span class="com"># a request and either add it to a chat or summarise</span><span class="pln">
</span><span class="com"># it. If the mode is set to Chat, it will add the response</span><span class="pln">
</span><span class="com"># to the chat with the prefix "GPT". If the mode is set</span><span class="pln">
</span><span class="com"># to Summarise, it will loop through the response and</span><span class="pln">
</span><span class="com"># insert it into the code editor as a summarised version</span><span class="pln">
</span><span class="com"># with each line having a maximum length of 50 characters</span><span class="pln">
</span><span class="com"># and each line starting with a "#".</span><span class="pln">
func _on_request_completed</span><span class="pun">(</span><span class="pln">result</span><span class="pun">,</span><span class="pln"> responseCode</span><span class="pun">,</span><span class="pln"> headers</span><span class="pun">,</span><span class="pln"> body</span><span class="pun">):</span><span class="pln">
    printt</span><span class="pun">(</span><span class="pln">result</span><span class="pun">,</span><span class="pln"> responseCode</span><span class="pun">,</span><span class="pln"> headers</span><span class="pun">,</span><span class="pln"> body</span><span class="pun">)</span><span class="pln">
    var json </span><span class="pun">=</span><span class="pln"> JSON</span><span class="pun">.</span><span class="pln">new</span><span class="pun">()</span><span class="pln">
    var parse_result </span><span class="pun">=</span><span class="pln"> json</span><span class="pun">.</span><span class="pln">parse</span><span class="pun">(</span><span class="pln">body</span><span class="pun">.</span><span class="pln">get_string_from_utf8</span><span class="pun">())</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> parse_result</span><span class="pun">:</span><span class="pln">
        printerr</span><span class="pun">(</span><span class="pln">parse_result</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">
    var response </span><span class="pun">=</span><span class="pln"> json</span><span class="pun">.</span><span class="pln">get_data</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> response </span><span class="kwd">is</span><span class="pln"> </span><span class="typ">Dictionary</span><span class="pun">:</span><span class="pln">
        printt</span><span class="pun">(</span><span class="str">"Response"</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">has</span><span class="pun">(</span><span class="str">"error"</span><span class="pun">):</span><span class="pln">
            printt</span><span class="pun">(</span><span class="str">"Error"</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">[</span><span class="str">'error'</span><span class="pun">])</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln">
    </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
        printt</span><span class="pun">(</span><span class="str">"Response is not a Dictionary"</span><span class="pun">,</span><span class="pln"> headers</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">

    var newStr </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">choices</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">text
    </span><span class="kwd">if</span><span class="pln"> current_mode </span><span class="pun">==</span><span class="pln"> modes</span><span class="pun">.</span><span class="typ">Chat</span><span class="pun">:</span><span class="pln">
        add_to_chat</span><span class="pun">(</span><span class="str">"GPT: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> newStr</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">elif</span><span class="pln"> current_mode </span><span class="pun">==</span><span class="pln"> modes</span><span class="pun">.</span><span class="typ">Summarise</span><span class="pun">:</span><span class="pln">
        var str </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">choices</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">text</span><span class="pun">.</span><span class="pln">replace</span><span class="pun">(</span><span class="str">"\n"</span><span class="pln"> </span><span class="pun">,</span><span class="pln"> </span><span class="str">""</span><span class="pun">)</span><span class="pln">
        newStr </span><span class="pun">=</span><span class="pln"> </span><span class="str">"# "</span><span class="pln">
        var lineLength </span><span class="pun">=</span><span class="pln"> </span><span class="lit">50</span><span class="pln">
        var currentLineLength </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
        </span><span class="kwd">for</span><span class="pln"> i </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="pln">str</span><span class="pun">.</span><span class="pln">length</span><span class="pun">()):</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> currentLineLength </span><span class="pun">&gt;=</span><span class="pln"> lineLength </span><span class="kwd">and</span><span class="pln"> str</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="str">" "</span><span class="pun">:</span><span class="pln">
                newStr </span><span class="pun">+=</span><span class="pln"> </span><span class="str">"\n# "</span><span class="pln">
                currentLineLength </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
            </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
                newStr </span><span class="pun">+=</span><span class="pln"> str</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]</span><span class="pln">
                currentLineLength </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
        code_editor</span><span class="pun">.</span><span class="pln">insert_line_at</span><span class="pun">(</span><span class="pln">cursor_pos</span><span class="pun">,</span><span class="pln"> newStr</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">elif</span><span class="pln"> current_mode </span><span class="pun">==</span><span class="pln"> modes</span><span class="pun">.</span><span class="typ">Action</span><span class="pun">:</span><span class="pln">
        code_editor</span><span class="pun">.</span><span class="pln">insert_line_at</span><span class="pun">(</span><span class="pln">cursor_pos</span><span class="pun">,</span><span class="pln"> newStr</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">elif</span><span class="pln"> current_mode </span><span class="pun">==</span><span class="pln"> modes</span><span class="pun">.</span><span class="typ">Help</span><span class="pun">:</span><span class="pln">
        add_to_chat</span><span class="pun">(</span><span class="str">"GPT: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">choices</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">text</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">pass</span><span class="pln">

func get_selected_code</span><span class="pun">():</span><span class="pln">
    var currentScriptEditor </span><span class="pun">=</span><span class="pln"> get_editor_interface</span><span class="pun">().</span><span class="pln">get_script_editor</span><span class="pun">().</span><span class="pln">get_current_editor</span><span class="pun">()</span><span class="pln">

    code_editor </span><span class="pun">=</span><span class="pln"> currentScriptEditor</span><span class="pun">.</span><span class="pln">get_base_editor</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> current_mode </span><span class="pun">==</span><span class="pln"> modes</span><span class="pun">.</span><span class="typ">Summarise</span><span class="pun">:</span><span class="pln">
        cursor_pos </span><span class="pun">=</span><span class="pln"> code_editor</span><span class="pun">.</span><span class="pln">get_selection_from_line</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">elif</span><span class="pln"> current_mode </span><span class="pun">==</span><span class="pln"> modes</span><span class="pun">.</span><span class="typ">Action</span><span class="pun">:</span><span class="pln">
        cursor_pos </span><span class="pun">=</span><span class="pln"> code_editor</span><span class="pun">.</span><span class="pln">get_selection_to_line</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> code_editor</span><span class="pun">.</span><span class="pln">get_selected_text</span><span class="pun">()</span><span class="pln">

func add_to_chat</span><span class="pun">(</span><span class="pln">text</span><span class="pun">):</span><span class="pln">
    var chat_bubble </span><span class="pun">=</span><span class="pln"> preload</span><span class="pun">(</span><span class="str">"res://addons/GPTIntegration/ChatBubble.tscn"</span><span class="pun">).</span><span class="pln">instantiate</span><span class="pun">()</span><span class="pln">
    chat_bubble</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"RichTextLabel"</span><span class="pun">).</span><span class="pln">text </span><span class="pun">=</span><span class="pln"> </span><span class="str">"\n"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> text </span><span class="pun">+</span><span class="pln"> </span><span class="str">"\n"</span><span class="pln">
    chat_dock</span><span class="pun">.</span><span class="pln">get_node</span><span class="pun">(</span><span class="str">"ScrollContainer/VBoxContainer"</span><span class="pun">).</span><span class="pln">add_child</span><span class="pun">(</span><span class="pln">chat_bubble</span><span class="pun">)</span><span class="pln">
    </span><span class="com">#chat_dock.get_node("RichTextLabel").text += "\n" + text + "\n"</span><span class="pln">

</span><span class="com"># This GDScript code creates a JSON file containing the</span><span class="pln">
</span><span class="com"># values of the variables "api_key", "max_tokens", "temperature",</span><span class="pln">
</span><span class="com"># and "engine", and stores the file in the "res://addons/GPTIntegration/"</span><span class="pln">
</span><span class="com"># directory.</span><span class="pln">
func save_settings</span><span class="pun">():</span><span class="pln">

    var data </span><span class="pun">={</span><span class="pln">
        </span><span class="str">"api_key"</span><span class="pun">:</span><span class="pln">api_key</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"max_tokens"</span><span class="pun">:</span><span class="pln"> max_tokens</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"temperature"</span><span class="pun">:</span><span class="pln"> temperature</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"engine"</span><span class="pun">:</span><span class="pln"> engine
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
    var jsonStr </span><span class="pun">=</span><span class="pln"> JSON</span><span class="pun">.</span><span class="pln">stringify</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
    var file </span><span class="pun">=</span><span class="pln"> </span><span class="typ">FileAccess</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="typ">MySettings</span><span class="pun">,</span><span class="pln"> </span><span class="typ">FileAccess</span><span class="pun">.</span><span class="pln">WRITE</span><span class="pun">)</span><span class="pln">

    file</span><span class="pun">.</span><span class="pln">store_string</span><span class="pun">(</span><span class="pln">jsonStr</span><span class="pun">)</span><span class="pln">
    file</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">

</span><span class="com"># This GDScript code opens a JSON file, parses the data</span><span class="pln">
</span><span class="com"># from it, and assigns the values to variables. The variables</span><span class="pln">
</span><span class="com"># are api_key, max_tokens, temperature, and engine.</span><span class="pln">
func load_settings</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> </span><span class="typ">FileAccess</span><span class="pun">.</span><span class="pln">file_exists</span><span class="pun">(</span><span class="typ">MySettings</span><span class="pun">):</span><span class="pln">
        save_settings</span><span class="pun">()</span><span class="pln">

    var file </span><span class="pun">=</span><span class="pln"> </span><span class="typ">FileAccess</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="typ">MySettings</span><span class="pun">,</span><span class="pln"> </span><span class="typ">FileAccess</span><span class="pun">.</span><span class="pln">READ</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> file</span><span class="pun">:</span><span class="pln">
        printerr</span><span class="pun">(</span><span class="str">"Unable to create"</span><span class="pun">,</span><span class="pln"> </span><span class="typ">MySettings</span><span class="pun">,</span><span class="pln"> error_string</span><span class="pun">(</span><span class="pln">ERR_CANT_CREATE</span><span class="pun">))</span><span class="pln">
        print_stack</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">

    var jsonStr </span><span class="pun">=</span><span class="pln"> file</span><span class="pun">.</span><span class="pln">get_as_text</span><span class="pun">()</span><span class="pln">
    file</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
    var data </span><span class="pun">=</span><span class="pln"> JSON</span><span class="pun">.</span><span class="pln">parse_string</span><span class="pun">(</span><span class="pln">jsonStr</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
    api_key </span><span class="pun">=</span><span class="pln"> data</span><span class="pun">[</span><span class="str">"api_key"</span><span class="pun">]</span><span class="pln">
    max_tokens </span><span class="pun">=</span><span class="pln"> int</span><span class="pun">(</span><span class="pln">data</span><span class="pun">[</span><span class="str">"max_tokens"</span><span class="pun">])</span><span class="pln">
    temperature </span><span class="pun">=</span><span class="pln"> float</span><span class="pun">(</span><span class="pln">data</span><span class="pun">[</span><span class="str">"temperature"</span><span class="pun">])</span><span class="pln">
    engine </span><span class="pun">=</span><span class="pln"> data</span><span class="pun">[</span><span class="str">"engine"</span><span class="pun">]</span></pre>

<h2 id="-9">
	الخاتمة
</h2>

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

<p>
	ترجمة -وبتصرف- لمقال <a href="https://finepointcgi.io/2023/06/19/building-a-godot-engine-tool-script-for-interacting-with-openai/" rel="external nofollow">Building a Godot Engine Tool Script for Interacting with OpenAI</a>
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<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>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D8%B1%D8%A4%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B5%D9%85%D9%8A%D9%85%D9%8A%D8%A9-%D9%84%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%EF%BB%B7%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot-r2278/" rel="">الرؤية التصميمية لمحرك اﻷلعاب جودو Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D9%85%D8%AA%D8%A7%D8%AD%D8%A9-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2371/" rel="">لغات البرمجة المتاحة في جودو Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D9%83%D9%8A%D9%81-%D8%AA%D8%B5%D8%A8%D8%AD-%D9%85%D8%A8%D8%B1%D9%85%D8%AC-%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D9%81%D9%8A%D8%AF%D9%8A%D9%88-%D9%86%D8%A7%D8%AC%D8%AD-r2284/" rel="">كيف تصبح مبرمج ألعاب فيديو ناجح</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2494</guid><pubDate>Mon, 20 Jan 2025 15:08:01 +0000</pubDate></item><item><title>&#x62A;&#x639;&#x631;&#x641; &#x639;&#x644;&#x649; &#x627;&#x644;&#x639;&#x642;&#x62F; Nodes &#x641;&#x64A; &#x645;&#x62D;&#x631;&#x643; &#x623;&#x644;&#x639;&#x627;&#x628; &#x62C;&#x648;&#x62F;&#x648; Godot</title><link>https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%B9%D9%82%D8%AF-nodes-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot-r2490/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_01/Godot.png.33bed44c6de76ae61722f7c4de86000f.png" /></p>
<p>
	العقد Nodes هي البنى الأساسية لإنشاء الألعاب في محرك جودو Godot، فالعقدة هي كائن يمكنه تمثيل نوع معين من وظائف اللعبة، فقد تعرض بعض أنواع العقد رسومات graphics أو تشغّل رسومًا متحركة animation أو تمثّل نموذجًا ثلاثي الأبعاد 3D model لكائن.
</p>

<p>
	كما تحتوي العقدة أيضًا على مجموعة من الخاصيات properties التي تسمح بتخصيص سلوكها، ويعتمد اختيار أي عقدة على الوظيفة التي نحتاجها، ويمكن أن كل تكون عقدة كوحدة مستقلة تؤدي وظيفة معينة، كما يمكننا دمج العديد من هذه الوحدات أو العقد لتشكيل كائنات اللعبة بطريقة مرنة ومتكاملة.
</p>

<h2 id="">
	العمل مع العقد
</h2>

<p>
	برمجيًا، العقد عبارة عن كائنات objects فهي تغلّف البيانات data والسلوك behavior، ويمكنها أن ترث خاصيات properties من عقد أخرى. لننقر الآن على زر <strong>+</strong> أو زر إضافة/إنشاء عقدة جديدة Add/Create a New Node في تبويب <strong>المشهد Scene </strong>بدلًا من استخدام أحد الاقتراحات الافتراضية للعقد التي يقترحها علينا جودو وذلك كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2025_01/443411556_.png.44c6b19a23669428d62ba94734f2185a.png" data-fileid="165384" data-fileext="png" rel=""><img alt="إضافة عقدة جديدة" class="ipsImage ipsImage_thumbnailed" data-fileid="165384" data-ratio="113.00" data-unique="9belh9bqy" style="width: 300px; height: auto;" width="300" src="https://academy.hsoub.com/uploads/monthly_2025_01/443411556_.png.44c6b19a23669428d62ba94734f2185a.png"> </a>
</p>

<p>
	بعد النقر على زر إنشاء عقدة جديدة سنشاهد الآن تسلسل هرمي يتضمن كافة أنواع العقد المتاحة في <a href="https://academy.hsoub.com/programming/game-development/%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot/" rel="">محرك الألعاب جودو</a> كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2025_01/301488045_.png.360b135f463cb75caecd4ebd13b3cc65.png" data-fileid="165386" data-fileext="png" rel=""><img alt="إنشاء عقدة جديدة" class="ipsImage ipsImage_thumbnailed" data-fileid="165386" data-ratio="82.25" data-unique="5kto5j4qq" style="width: 400px; height: auto;" width="400" src="https://academy.hsoub.com/uploads/monthly_2025_01/.thumb.png.3aad3fd627d51a5fbca21e96a3ad394f.png"> </a>
</p>

<p>
	تندرج جميع العقد ذات الأيقونات الزرقاء على سبيل المثال ضمن الفئة Node2D، مما يعني أن هذه العقد سيكون لها خاصيات <a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D8%B9%D9%82%D8%AF-nodes-%D9%88%D8%A7%D9%84%D9%85%D8%B4%D8%A7%D9%87%D8%AF-scenes-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2357/" rel="">العقد</a> ثنائية الأبعاد Node2D التي سنتحدث عنها لاحقًا بمزيد من التفصيل.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2025_01/177939492_.png.608cc4b41b27f17fed7df0e301284481.png" data-fileid="165387" data-fileext="png" rel=""><img alt="أنواع العقد في محرك ألعاب جودو" class="ipsImage ipsImage_thumbnailed" data-fileid="165387" data-ratio="79.25" data-unique="lfj5ssdsg" style="width: 400px; height: auto;" width="400" src="https://academy.hsoub.com/uploads/monthly_2025_01/.thumb.png.9a1f17ddfd16dbdbc534b40c82ea7ec4.png"> </a>
</p>

<p>
	نلاحظ أن القائمة طويلة جدًا، وسيكون صعبًا التمرير عبرها للعثور على العقدة التي نحتاجها في كل مرة، لذا يمكننا استخدام وظيفة البحث للعثور على العقدة المطلوبة باستخدام عدد صغير من الأحرف. مثلًا يمكننا العثور على <a href="https://wiki.hsoub.com/Godot/2d/2d_sprite_animation" rel="external">عقدة Sprite2D</a> بسرعة بكتابة الحرفين sp فقط في حقل البحث وستنتقل إليها مباشرة، بعدها ننقر على زر <strong>أنشئ Create </strong>لإضافة العقدة للمشهد.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2025_01/Sprite2D.png.d57cd14e336d193b40d207c207c884d5.png" data-fileid="165385" data-fileext="png" rel=""><img alt="اليحث عن عقدة sprite2d" class="ipsImage ipsImage_thumbnailed" data-fileid="165385" data-unique="g4780q0cw" src="https://academy.hsoub.com/uploads/monthly_2025_01/Sprite2D.thumb.png.b42e1dc86962d8aec9c32b7987b8e9e4.png"> </a>
</p>

<p>
	أصبح لدينا الآن العقدة <code>Sprite2D</code> في تبويب المشهد Scene، لذا نتأكد من تحديدها، ثم ننظر إلى تبويب الفاحص Inspector على الجانب الأيسر من شاشة محرر جودو حيث سنرى فيها جميع خاصيات العقدة التي حدّدناها.
</p>

<p>
	نلاحظ أن الخاصيات الظاهرة للعقدة ليست فقط التي تخص عقدة Sprite2D نفسها، بل تشمل أيضًا الخصائص التي ورثتها من العقد الأخرى التي جاءت قبلها في سلسلة من العقد وهي منظَّمة حسب مصدرها، حيث ترث العقدة <code>Sprite2D</code> العقدة <code>Node2D</code> التي ترث بدورها العقدة <code>CanvasItem</code> التي ترث بدورها العقدة <code>Node</code> الأساسية البسيطة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2025_01/346925388_.png.1aae5ad8ce8373d1a86164a4bbde107f.png" data-fileid="165389" data-fileext="png" rel=""><img alt="جميع خاصيات العقدة المحددة " class="ipsImage ipsImage_thumbnailed" data-fileid="165389" data-unique="snszqxitp" src="https://academy.hsoub.com/uploads/monthly_2025_01/.thumb.png.954ba4288bc929d211611cbe42f88b8b.png"> </a>
</p>

<p>
	بعد إضافة هذه العقدة للعبتنا سنلاحظ أن الشخصية الرسومية Sprite لا تظهر كما هو متوقع في واجهة المستخدم. فالغرض من هذه العقدة هو عرض صورة أو خامة Texture. وبما أن الخاصية Texture في حاوية الفاحص Inspector فارغة حاليًا فلهذا السبب لم تظهر في نافذة العرض.
</p>

<p>
	لحسن الحظ يأتي كل مشروع جديد من جودو مع صورة باسم icon.svg يمكننا استخدامها حاليًا، وهذه الصورة هي أيقونة محرك الألعاب جودو، لذا سنسحبها من التبويب <strong>نظام الملفات Filesystem</strong> في الجانب الأيمن ونفلتها في الحقل الخاص بالخاصية <strong>Texture</strong>.
</p>

<p>
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="165391" data-ratio="93.84" data-unique="2fvzx0shb" width="276" alt="سحب أيقونة جودو.PNG" src="https://academy.hsoub.com/uploads/monthly_2025_01/618309922_.PNG.339a444f4f03f2ac15a1eb2fdeee9b36.PNG">
</p>

<p>
	ننقر لتوسيع قسم التحويل Transform في حاوية الفاحص Inspector، ونكتب ضمن خاصية الموضع Position القيمة 50 للمحور الأفقي x والقيمة 50 للمحور العمودي y لنحدد مكان ظهور عقدتنا داخل المشهد.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2025_01/Transform.png.c056e4bf46ca4a797fa0cdc92bdc4d63.png" data-fileid="165390" data-fileext="png" rel=""><img alt="قسم transform" class="ipsImage ipsImage_thumbnailed" data-fileid="165390" data-unique="ctag95qeh" src="https://academy.hsoub.com/uploads/monthly_2025_01/Transform.png.c056e4bf46ca4a797fa0cdc92bdc4d63.png"> </a>
</p>

<p>
	كما يمكننا أيضًا النقر على الشخصية الرسومية Sprite وسحبها ضمن نافذة العرض، وسنرى أن قيم الموضع Position تتغير أثناء تحريكنا لها.
</p>

<p>
	إحدى الخصائص المهمة للعقد هي إمكانية ترتيبها في تسلسل هرمي من العقدة الأم إلى العقدة الابن، مما يتيح لنا تنظيم الكائنات داخل المشهد، على سبيل المثال يمكننا إنشاء عقدة رئيسية للاعب Player تحتوي على عقد فرعية متعددة مثل عقدة Sprite2D لتمثيل الشكل المرئي للاعب وعقدة فرعية أخرى AnimationPlayer لتحريك اللاعب.
</p>

<p>
	لنجرب إنشاء تسلسل عقد هرمي دعونا نحدد أولاً عقدتنا <code>Sprite2D</code>، ثم نضغط على زر الإضافة مرة أخرى لإضافة عقدة Sprite2D جديدة. بعد ذلك، لنسحب الأيقونة نفسها لليسار قليلًا، ونعين خاصية الخامة Texture الخاصة بهذه العقدة الجديدة. نلاحظ أن قيم الموضع Position للعقدة الأب تتغير أثناء تحريكها. لكن في حال فحص قيم الموضع Position للعقدة الابن سنجد أنها ما تزال (50,50). فقيمة خاصية التحويل Transform لها نسبية وتعتمد على عقدة الكائن الأب.
</p>

<p>
	<img alt="تحريك عقدة الأب" class="ipsImage ipsImage_thumbnailed" data-fileid="165392" data-ratio="87.50" data-unique="mlpuzztd4" style="width: 352px; height: auto;" width="352" src="https://academy.hsoub.com/uploads/monthly_2025_01/388109066_.gif.3120bce0b1209dd57758b2db62e2b626.gif">
</p>

<ul>
	<li>
		إذا حركنا العقدة الأب، فإن جميع العناصر المرتبطة به أي كافة العقد الأبناء له ستتحرك معها تلقائيًا، لأنها مرتبطة به من حيث الموقع أو الدوران، أو أي تغييرات أخرى تجري عليه
	</li>
	<li>
		بينما إذا حركنا العقدة الابن فقط، فإن العقدة الأب لها لن تتأثر بحركتها
	</li>
</ul>

<h2 id="scenes">
	المشاهد Scenes
</h2>

<p>
	يمكن تشبيه المشهد Scenes في جودو على أنه حاوية تتضمن جميع كائنات أو عقد لعبتنا. يمكن أن تمثل هذه العقد شخصيات أو صور خلفية أو حتى الأكواد البرمجية التي تتحكم في كيفية تصرف الأشياء. ويمكن أن يكون المشهد بسيطًا ومكونًا كائن واحد أو معقدًا مثل مستوى كامل في اللعبة.
</p>

<p>
	فتجميع العقد مع بعضها البعض ضمن محرك ألعاب جودو يوفر لنا أداة قوية، ويمكّننا من إنشاء كائنات معقدة من وحدات البناء التي تمثلها العقد، فمثلًا قد تحتوي عقدة اللاعب Player في لعبتنا على العديد من العقد الأبناء المرتبطة بها مثل <code>Sprite2D</code> للعرض و <code>AnimationPlayer</code> لتحريكها و <code>Camera2D</code> لمتابعتها وغير ذلك.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2025_01/17805064_.png.9ee1382ee3bc9c4bea4e661b312e1e76.png" data-fileid="165388" data-fileext="png" rel=""><img alt="تجميع العقد في مشهد" class="ipsImage ipsImage_thumbnailed" data-fileid="165388" data-unique="cxedgzyit" src="https://academy.hsoub.com/uploads/monthly_2025_01/17805064_.png.9ee1382ee3bc9c4bea4e661b312e1e76.png"> </a>
</p>

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

<h2 id="-1">
	الخاتمة
</h2>

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

<p>
	ترجمة -وبتصرّف- للقسم <a href="https://kidscancode.org/godot_recipes/4.x/g101/start/101_03/index.html" rel="external nofollow">Nodes: Godot's building blocks</a> من توثيقات Kidscancode.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D8%B1%D8%A4%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B5%D9%85%D9%8A%D9%85%D9%8A%D8%A9-%D9%84%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%EF%BB%B7%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot-r2278/" rel="">الرؤية التصميمية لمحرك اﻷلعاب جودو Godot</a>
	</li>
	<li>
		<a href="https://wiki.hsoub.com/Godot/2d/canvas_layers" rel="external">طبقات الحاوية في جودو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%88%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%B4%D8%A7%D9%87%D8%AF-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%AC%D9%88%D8%AF%D9%88-r2281/" rel="">إنشاء وبرمجة مشاهد لعبة ثنائية الألعاب في محرك جودو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%86%D8%B3%D8%AE-%D9%85%D9%86-%D8%A7%D9%84%D9%85%D8%B4%D8%A7%D9%87%D8%AF-%D9%88%D8%A7%D9%84%D8%B9%D9%82%D8%AF-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-r2364/" rel="">إنشاء نسخ من المشاهد والعقد في جودو</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2490</guid><pubDate>Tue, 14 Jan 2025 15:06:01 +0000</pubDate></item><item><title>&#x62A;&#x639;&#x631;&#x641; &#x639;&#x644;&#x649; &#x648;&#x627;&#x62C;&#x647;&#x629; &#x645;&#x62D;&#x631;&#x643; &#x627;&#x644;&#x623;&#x644;&#x639;&#x627;&#x628; &#x62C;&#x648;&#x62F;&#x648;</title><link>https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-r2484/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_01/4.png.06e7b096ee4f88db3ed05b18492a2b28.png" /></p>
<p>
	سنتعرّف في هذه السلسلة من المقالات على محرك الألعاب جودو Godot بنسخته الرابعة والميزات التي يوفّرها، وسنبدأ مقال اليوم بشرح واجهة محرك جودو وأهم مكوناتها، ونوضّح التغييرات الرئيسية التي سنلاحظها عند الانتقال لهذا الإصدار من محرك الألعاب.
</p>

<h2 id="-1">
	ما هو محرك الألعاب جودو؟
</h2>

<p>
	سنتعرّف فيما يلي على محرك الألعاب جودو وسبب استخدامه والميزات التي يوفّرها.
</p>

<h3 id="gameengines">
	محركات الألعاب Game Engines
</h3>

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

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

<p>
	فمحرّك الألعاب هو مجموعة من الأدوات والتقنيات المُصمَّمة لمساعدة <a href="https://academy.hsoub.com/programming/game-development/%D9%85%D8%B7%D9%88%D8%B1-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8/" rel="">مطوري الألعاب</a>، مما يسمح لنا بالتركيز أكثر على بناء لعبتنا دون الحاجة لإعادة اختراع العجلة، حيث سنوضّح فيما يلي بعض الميزات التي يوفرها محرك الألعاب الجيد:
</p>

<ul>
	<li>
		<p>
			<strong>التصيير أو الإخراج Rendering ثنائي الأبعاد وثلاثي الأبعاد</strong>: هو عملية عرض اللعبة على شاشة اللاعب، ويجب أن يعمل محرك التصيير الجيد مع ميزات وحدة معالجة الرسوميات GPU الحديثة، ويدعم الشاشات عالية الدقة، ويحسن المؤثرات مثل الإضاءة ومنظور اللعب Perspective مع الحفاظ في الوقت نفسه على معدّل إطارات مرتفع لضمان تجربة لعب سلسة
		</p>
	</li>
	<li>
		<p>
			<strong>الفيزياء Physics</strong>: يُعَد إنشاء محرك فيزيائي دقيق وقابل للاستخدام مهمة ضخمة، إذ تتطلب معظم الألعاب اكتشاف التصادم والاستجابة له، وتحتاج العديد منها إلى محاكاة فيزيائية مثل الاحتكاك والعطالة أو القصور الذاتي Inertia وغير ذلك، ولن يرغب المطورون بتكبد عناء كتابة الشيفرة البرمجية لهذه المهمة
		</p>
	</li>
	<li>
		<p>
			<strong>دعم المنصات</strong>: قد تحتاج لإصدار لعبتك على منصات متعددة كالهاتف المحمول والويب والحاسوب الشخصي والطرفية Console، فمحرّك الألعاب الجيد يتيح لنا بناء لعبتنا مرة واحدة وتصديرها إلى منصة أو منصات أخرى
		</p>
	</li>
	<li>
		<p>
			<strong>بيئة التطوير</strong>: تُجمَع كل هذه الأدوات في تطبيق واحد، مما يؤدي إلى وجود كل شيء في بيئة واحدة حتى لا تضطر إلى تعلّم سير عمل جديد لكل مشروع تعمل عليه
		</p>
	</li>
</ul>

<p>
	هناك عشرات <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="">محركات الألعاب</a> الشهيرة التي يمكننا الاختيار من بينها مثل جودو GoDot ويونتي Unity وآن ريل Unreal وغيرها. وتجدر الإشارة لأن غالبية محرّكات الألعاب الشهيرة هي منتجات تجارية، فقد تكون مجانية للتنزيل وقد لا تكون كذلك، وأنها قد تتطلّب اتفاقية ترخيص أو حقوق ملكية إذا أردنا إصدار ألعابنا وخاصة إذا حقّقت أرباحًا، لذا يجب قراءة وفهم ما نوافق عليه وما يُسمح وما لا يُسمح بفعله عندما ننوي استخدام أي محرك.
</p>

<h3 id="-2">
	لماذا نستخدم محرك ألعاب جودو
</h3>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="165065" href="https://academy.hsoub.com/uploads/monthly_2025_01/01_godot_logo.png.6bfd306b02bda4579a948f330d2d9848.png" rel=""><img alt="01 godot logo" class="ipsImage ipsImage_thumbnailed" data-fileid="165065" data-ratio="35.33" data-unique="fny9p2d5y" style="width: 300px; height: auto;" width="300" src="https://academy.hsoub.com/uploads/monthly_2025_01/01_godot_logo.thumb.png.bc093f51264f958e9b538912a1b3f3a3.png"> </a>
</p>

<p>
	يُعَد محرّك ألعاب جودو مجانيًا ومفتوح المصدر، وهو يصدر وفق ترخيص MIT المرن، فلا توجد رسوم أو تكاليف مخفية أو حقوق ملكية يجب دفعها، ويُعَد جودو محرك ألعاب حديث ومتكامل الميزات. وهو يوفر الكثير من الفوائد لمطوري الألعاب، لأنه غير مثقل بالترخيص التجاري، ويوفر لنا سيطرة كاملة على كيفية ومكان توزيع لعبتنا. كما أن طبيعة جودو مفتوحة المصدر توفر مستوى شفافية أعلى من محركات الألعاب التجارية، فمثلًا إذا وجدنا أن ميزة معينة لا تلبي احتياجاتنا تمامًا، فيمكننا تعديل المحرّك دون الحاجة إلى إذن لذلك.
</p>

<p>
	<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" frameborder="0" height="360" id="ips_uid_3968_6" referrerpolicy="strict-origin-when-cross-origin" src="https://academy.hsoub.com/applications/core/interface/index.html" title="ما هو Godot - شرح محرك الألعاب جودو للمبتدئين" width="640" data-embed-src="https://www.youtube.com/embed/fF8oaHpFlAI"></iframe>
</p>

<h2 id="-3">
	اكتشاف واجهة محرر الألعاب جودو
</h2>

<p>
	سنكتشف فيما يلي واجهة محرك الألعاب جودو التي ستقضي فيها معظم وقتك عند بناء لعبتك.
</p>

<h3 id="projectmanager">
	مدير المشروع Project Manager
</h3>

<p>
	أول شيء سنراه عند فتح محرك ألعاب جودو هو نافذة مدير المشروع Project Manager،  والتي ستبدو كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="PNG" data-fileid="165066" href="https://academy.hsoub.com/uploads/monthly_2025_01/02___Project_Manager.PNG.bf204de5a4cd2f8700ba7bf1cc45c222.PNG" rel=""><img alt="02 مدير المشروع project manager" class="ipsImage ipsImage_thumbnailed" data-fileid="165066" data-unique="cq03hkats" style="width: 700px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_01/02___Project_Manager.thumb.PNG.f5229ed0994e6591e445beb2c72651f7.PNG"> </a>
</p>

<p>
	سنرى في هذه النافذة قائمة بمشاريع جودو، حيث يمكننا اختيار مشروع موجود مسبقًا والنقر على زر تشغيل Run لتشغيل اللعبة أو النقر على زر تحرير Edit للعمل على اللعبة في محرّر جودو. لنبدأ بالنقر على زر مشروع جديد New Project، في حال لم يكن لدينا أي مشاريع حتى الآن.
</p>

<p style="text-align: center;">
	<img alt="03 إنشاء مشروع جديد" class="ipsImage ipsImage_thumbnailed" data-fileid="165075" data-ratio="79.25" data-unique="ct13ivwhs" style="width: 400px; height: auto;" width="648" src="https://academy.hsoub.com/uploads/monthly_2025_01/03___.PNG.af01fb408688656c6c181aa12c66a212.PNG">
</p>

<p>
	يمكننا في النافذة السابقة إعطاء اسم للمشروع، وإنشاء مجلد لتخزينه ملفات لعبتنا فيه.
</p>

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

<p>
	يجب أن نحرص على اختيار اسم يصف عمل مشروع لعبتنا عند تسميته، فمثلًا لا يعد الاسم New Game Project23 جيدًا لكونه لا يساعدنا في تذكر ما يفعله هذا المشروع. يجب أيضًا أن نفكر في التوافق، فقد تكون بعض أنظمة التشغيل حساسة لحالة الأحرف وبعضها الآخر غير حساس لحالة الأحرف، مما يؤدي لحدوث مشكلات إذا نقلنا المشروع أو شاركناه من حاسوب لآخر، لذا يعتمد العديد من المبرمجين <a href="https://academy.hsoub.com/programming/general/%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%A8%D8%B3%D8%A7%D8%B7%D8%A9-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-r1870/" rel="">قواعد</a> تسمية موحّدة مثل عدم وجود مسافات بين الكلمات واستخدام شرطة سفلية _ بينها. سنسمّي المشروع الجديد getting_started، لذا نكتب هذا الاسم ونتأكد من تفعيل خيار أنشئ مجلد Create Folder، قبل النقر على زر إنشاء وتعديل Create &amp; Edit أسفل النافذة.
</p>

<p>
	يمثل الشكل التالي نافذة محرر محرك الألعاب جودو، وهو المكان الذي سنقضي فيه معظم وقتنا عند العمل في جودو، ويكون المحرر مقسمًا إلى أقسام كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="PNG" data-fileid="165068" href="https://academy.hsoub.com/uploads/monthly_2025_01/04----.PNG.a65ddae1f063de9bd15fd06bbbe53eb4.PNG" rel=""><img alt="04 محرر محرك ألعاب جودو" class="ipsImage ipsImage_thumbnailed" data-fileid="165068" data-unique="6p4ev3oft" style="width: 700px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_01/04----.thumb.PNG.a6b865bd14226253ee7aeac92ddd66bd.PNG"> </a>
</p>

<ul>
	<li>
		<strong>نافذة العرض Viewport</strong>: المكان الذي نرى فيه أجزاء لعبتنا أثناء العمل عليها
	</li>
	<li>
		<strong>مساحات العمل Workspaces</strong>: في الجزء العلوي الأوسط حيث يمكننا التبديل بين العمل في مساحات العمل ثنائية الأبعاد 2D أو ثلاثية الأبعاد 3D أو السكربت، ولكن تكون البداية من مساحة العمل ثلاثية الأبعاد
	</li>
	<li>
		<strong>أزرار اختبار اللعب</strong> <strong>Playtest Buttons</strong>: تتيح لنا هذه الأزرار تشغيل اللعبة والتحكم فيها أثناء الاختبار
	</li>
	<li>
		<strong>الحاويات Docks أو التبويبات Tabs</strong>: توجد على جانبي واجهة جودو عدد من الحاويات Docks والتبويبات حيث يمكننا من خلالها عرض عناصر اللعبة وضبط خاصياتها
	</li>
	<li>
		<strong>اللوحة السفلية Bottom Panel</strong>: تتضمن هذه اللوحة معلومات خاصة بالسياق لأدوات مختلفة، وأهمها لوحة الخرج Output، حيث سنرى رسائل الأخطاء أو المعلومات عند تشغيل لعبتنا
	</li>
</ul>

<h3 id="projectsettings">
	إعدادات المشروع Project Settings
</h3>

<p>
	تحدثنا عن الأجزاء الرئيسية لواجهة محرر جودو وكيفية عملها، ولنتحدّث الآن عن إعدادات المشروع، فإحدى المهام الأولى عند بدء مشروع جديد هي التأكد من صحة الإعدادات. ننقر على خيار مشروع Project من القائمة ثم نحدّد خيار إعدادات المشروع Project Settings.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="PNG" data-fileid="165070" href="https://academy.hsoub.com/uploads/monthly_2025_01/05-----.PNG.8e16f42e1f911bed7b67aea8b7d089d2.PNG" rel=""><img alt="05 إعدادات المشروع قسم التطبيق إعداد" class="ipsImage ipsImage_thumbnailed" data-fileid="165070" data-unique="42nf2kt3o" style="width: 700px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_01/05-----.thumb.PNG.5ca75e532bd12c212488552aaf1f23e7.PNG"> </a>
</p>

<p>
	النافذة السابقة هي نافذة إعدادات المشروع، وتوجد على الجهة اليمنى قائمة من الفئات. تكون الإعدادات الافتراضية مناسبة لمعظم المشاريع، فلا حاجة للقلق بشأن تغييرها إلّا إن كان لدينا خيار محدد يتطلب التغيير. سنلقي نظرة حاليًا على قسم التطبيق Application ثم إعداد Config حيث يمكننا من هنا ضبط عنوان اللعبة، واختيار المشهد الرئيسي الذي سنوضّحه لاحقًا، كما يمكننا من هنا تغيير أيقونة اللعبة.
</p>

<p>
	القسم الثاني هو قسم الإظهار Display ثم نافذة Window، وهو المكان الذي يمكننا من تحديد طريقة ظهور لعبتنا وعرضها. إذ يمكننا ضبط العرض <code>width</code> والارتفاع <code>height</code> لضبط حجم نافذة اللعبة، فمثلًا إذا أنشأنا لعبة لهاتف محمول، فيجب ضبطها على دقة وأبعاد الجهاز المستهدف. توجد أيضًا إعدادات لتغيير الحجم Scaling والتمدّد Stretching ووضع ملء الشاشة وغير ذلك. سنترك الحجم الافتراضي كما هو حاليًا، حيث سنتحدث لاحقًا عن كيفية ضبط هذه الإعدادات بدقة لتشغيل اللعبة على أجهزة مختلفة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="PNG" data-fileid="165071" href="https://academy.hsoub.com/uploads/monthly_2025_01/06-----.PNG.56b9ede61f73979fffb9a72af328866c.PNG" rel=""><img alt="06 إعدادات المشروع قسم عرض نافذة" class="ipsImage ipsImage_thumbnailed" data-fileid="165071" data-unique="wta5os1nw" style="width: 700px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_01/06-----.thumb.PNG.b1819471f475a2835b08259fa4501fa0.PNG"> </a>
</p>

<p>
	توجد أيضًا بعض التبويبات في الجزء العلوي من النافذة مثل التبويب عام General الذي تحدّثنا عنه. سنتحدث الآن بإيجاز عن تبويب خريطة الإدخال Input Map، وهو المكان الذي يمكننا فيه تحديد إجراءات إدخال مختلفة للتحكم في إجراءات الإدخال أي كيف نتعامل مع مدخلات لوحة المفاتيح والفأرة وغير ذلك. حيث نهتم في لعبتنا في تحديد المفتاح أو الزر الذي يضغط عليه اللاعب، وتحديد الإجراء الذي سيحدث عند الضغط عليه للتعامل مع <a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B9-%D9%84%D9%85%D8%AF%D8%AE%D9%84%D8%A7%D8%AA-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2384/" rel="">مدخلات اللاعب</a> بكفاءة.
</p>

<p>
	يوجد أيضًا تبويبات أخرى مثل تبويب التوطين Localization المخصص لدعم لغات متعددة، وتبويب إضافات Plugins التي أنشأ معظمها <a href="https://godotengine.org/community/" rel="external nofollow">مجتمع محرك ألعاب جودو</a>، والتي يمكن تنزيلها وإضافتها لتوفير مزيد من الميزات والأدوات المختلفة وغير ذلك وسنتحدث عنها لاحقًا.
</p>

<h2 id="godot3xgodot40">
	دليل الانتقال من الإصدار Godot 3.x للإصدار Godot 4.0
</h2>

<p>
	سنوضّح فيما يلي التغييرات الرئيسية والمشاكل التي يجب الانتباه إليها إذا أردتَ الانتقال إلى الإصدار 4.0 لمحرك ألعاب جودو.
</p>

<h3 id="-4">
	الأسماء الجديدة
</h3>

<p>
	أكبر تغيير في الإصدار Godot 4 هو توفر مجموعة كاملة من عمليات إعادة التسمية للعقد والدوال وأسماء الخاصيات، حيث تجري معظم هذه العمليات لجعل الأمور متناسقة أو واضحة. إليك فيما يلي بعض التغييرات الكبرى التي يجب الانتباه إليها:
</p>

<ul>
	<li>
		العقد ثنائية الأبعاد 2D وثلاثية الأبعاد 3D: حملت العقد ثنائية الأبعاد في الإصدار Godot 3.x اللاحقة 2D، ولكن لم يكن للعقد ثلاثية الأبعاد لاحقة، لذا أصبحت الآن جميع العقد تحمل إما اللاحقة 2D أو اللاحقة 3D لجعل الأمور متناسقة مثل <code>RigidBody2D</code> و <code>RigidBody3D</code>
	</li>
	<li>
		أعيدت تسمية العقدة <code>Spatial</code> إلى <code>Node3D</code> في الفئة ثلاثية الأبعاد لتتناسب معها
	</li>
	<li>
		أعيدت تسمية واحدة من أكثر العقد شهرة وهي <code>KinematicBody</code> إلى <code>CharacterBody2D</code> أو <code>CharacterBody3D</code>، وسنوضّح لاحقًا مزيدًا من تغييرات ا<a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel="">لواجهة البرمجية <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></a> لهذه العقدة.
	</li>
	<li>
		أعيدت تسمية الدالة <code>instance()‎</code> الخاصة بالعقدة <code>PackedScene</code> إلى <code>instantiate()‎</code>
	</li>
	<li>
		حلت الخاصيات <code>position</code> و <code>global_position</code> محل الخاصيات <code>translation</code> و <code>global_translation</code> للعقد ثلاثية الأبعاد، مما يجعلها متوافقة مع العقد ثنائية الأبعاد
	</li>
</ul>

<h3 id="signalscallables">
	الإشارات Signals والعناصر القابلة للاستدعاء Callables
</h3>

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

<p>
	فيما يلي مثال لتعريف إشارة وتوصيلها وإرسالها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7384_9" style=""><span class="pln">extends </span><span class="typ">Node</span><span class="pln">

signal my_signal

func _ready</span><span class="pun">():</span><span class="pln">
    my_signal</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="pln">signal_handler</span><span class="pun">)</span><span class="pln">

func _input</span><span class="pun">(</span><span class="pln">event</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">is_action_pressed</span><span class="pun">(</span><span class="str">"ui_select"</span><span class="pun">):</span><span class="pln">
        my_signal</span><span class="pun">.</span><span class="pln">emit</span><span class="pun">()</span><span class="pln">

func signal_handler</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"signal received"</span><span class="pun">)</span></pre>

<h3 id="tweens">
	عناصر الانتقال التدريجي Tweens
</h3>

<p>
	في حال استخدام <code>SceneTreeTween</code> في الإصدار Godot 3.5، فسنكون على دراية باستخدام عناصر الانتقال التدريجي <code>Tween</code> التي تُستخدَم مثلًا لتغيير اللون أو الموقع الخاص بالكائن تدريجيًا في الإصدار Godot 4.0.
</p>

<p>
	لم يَعُد <code>Tween</code> عقدة، لذا يمكننا إنشاء كائنات رسوم متحركة للانتقال التدريجي Tween لمرة واحدة كلما احتجنا إليها، وستصبح أكثر قوة وأسهل في الاستخدام من الطريقة القديمة بعد التعود عليها.
</p>

<h3 id="animatedsprite2d3d">
	العقدة AnimatedSprite[2D|3D]‎
</h3>

<p>
	يُعَد اختفاء الخاصية <code>playing</code> أكبر تغيير لمستخدمي الإصدار 3‎.x‎ لهذه العقدة، حيث أصبحت أكثر تناسقًا مع استخدام <code>AnimationPlayer</code>، إذ يمكن تبديل التشغيل التلقائي في لوحة SpriteFrames لتشغيل الرسوم المتحركة تلقائيًا. وعلينا استخدام الدالتين <code>play()‎</code> و <code>stop()‎</code> في الشيفرة البرمجية للتحكم في التشغيل.
</p>

<h3 id="characterbody2d3d">
	العقدة CharacterBody[2D|3D]‎
</h3>

<p>
	أكبر تغيير في هذه العقدة هو استخدام الدالة <code>move_and_slide()‎</code> التي لم تَعُد تستقبل معاملات، حيث فقد أصبحت جميع المعاملات خاصيات مُدمَجة، ويتضمن ذلك الخاصية <code>velocity</code> الأصيلة، لذا فلا حاجة للتصريح عن هذه الخاصيات.
</p>

<p>
	يمكن الاطلاع على <a href="https://kidscancode.org/godot_recipes/4.x/2d/platform_character/" rel="external nofollow">محارف المنصة</a> و<a href="https://kidscancode.org/godot_recipes/4.x/3d/basic_fps/" rel="external nofollow">محارف FPS الأساسية</a> للحصول على أمثلة تفصيلية لاستخدام هذه العقد.
</p>

<h3 id="tilemap">
	عقدة TileMap
</h3>

<p>
	جُدّدت العقدة <code>TileMap</code> بالكامل في الإصدار 4.0 ابتداءً من كيفية إنشاء موارد <code>TileSet</code> إلى كيفية رسم عناصر الرقعة Tiles والتفاعل معها.
</p>

<h3 id="rng">
	مولّد الأعداد العشوائية RNG
</h3>

<p>
	هناك بعض التغييرات على دوال توليد الأعداد العشوائية المُدمَجة مع لغة البرمجة GDScript، وهذه التغييرات هي:
</p>

<ul>
	<li>
		لم نعد بحاجة لاستدعاء الدالة <code>randomize()‎</code>، إذ سيكون الاستدعاء تلقائيًا. إذا أردنا الحصول على عشوائية قابلة للتكرار، نستخدم الدالة <code>seed()‎</code> لضبطها على قيمة محدَّدة مسبقًا.
	</li>
	<li>
		حلّت الدالة <code>randf_range()‎</code> للأعداد العشرية أو الدالة <code>randi_range()‎</code> للأعداد الصحيحة محل الدالة القديمة <code>rand_range()‎</code>.
	</li>
</ul>

<h3 id="raycasting">
	كشف تصادم الأشعة Raycasting
</h3>

<p>
	توجد واجهة برمجة تطبيقات جديدة عند كشف تصادم الأشعة لاكتشاف التصادم بين الكائنات في الشيفرة البرمجية، حيث تأخذ الدالة <code>PhysicsDirectSpaceState[2D|3D].intersect_ray()‎</code> كائنًا خاصًا كمعامل، ويحدّد هذا الكائن خاصيات الشعاع، فمثلًا نستخدم ما يلي لكشف تصادم الأشعة في فضاء ثلاثي الأبعاد:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7384_11" style=""><span class="pln">var space </span><span class="pun">=</span><span class="pln"> get_world_3d</span><span class="pun">().</span><span class="pln">direct_space_state
var ray </span><span class="pun">=</span><span class="pln"> </span><span class="typ">PhysicsRayQueryParameters3D</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">position</span><span class="pun">,</span><span class="pln"> destination</span><span class="pun">)</span><span class="pln">
var collision </span><span class="pun">=</span><span class="pln"> space</span><span class="pun">.</span><span class="pln">intersect_ray</span><span class="pun">(</span><span class="pln">ray</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> collision</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"ray collided"</span><span class="pun">)</span></pre>

<h2 id="-5">
	الخاتمة
</h2>

<p>
	تعرفنا في هذا المقال على محرك الألعاب جودو بنسخته الرابعة، واكتشفنا أهم ميزاته، كما استعرضنا واجهته الرئيسية مثل مدير المشروع ومحرر الألعاب وتعرفنا على كيفية استخدامه لإنشاء مشاريع جديدة وضبط الإعدادات. بالإضافة إلى ذلك، سطنا الضوء على أبرز التغييرات التي سنلاحظها عند الانتقال من الإصدار 3‎.‎x إلى الإصدار 4.0، مثل إعادة تسمية العقد والدوال وتعديل طريقة العمل مع الإشارات والعناصر القابلة للاستدعاء. سنعود لنافذة إعدادات المشروع لاحقًا، لذا لنغلقها الآن ونستعد للانتقال إلى مقالنا التالي من هذه السلسلة والذي سنشرح فيه مفهوم العقد Nodes وطريقة التعامل معها في محرك الألعاب جودو.
</p>

<p>
	ترجمة -وبتصرّف- للأقسام <a href="https://kidscancode.org/godot_recipes/4.x/g101/start/101_01/index.html" rel="external nofollow">What is Godot</a> و <a href="https://kidscancode.org/godot_recipes/4.x/g101/start/101_02/index.html" rel="external nofollow">The Godot Editor</a> و <a href="https://kidscancode.org/godot_recipes/4.x/basics/migrating/index.html" rel="external nofollow">Migrating from 3.x</a> من توثيقات Kidscancode.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D8%B1%D8%A4%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B5%D9%85%D9%8A%D9%85%D9%8A%D8%A9-%D9%84%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%EF%BB%B7%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot-r2278/" rel="">الرؤية التصميمية لمحرك اﻷلعاب جودو Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D8%AC%D9%83-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D9%88%D8%AF%D9%88-godot-r2382/" rel="">كتابة برنامجك الأول باستخدام جودو Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D8%B9%D9%82%D8%AF-nodes-%D9%88%D8%A7%D9%84%D9%85%D8%B4%D8%A7%D9%87%D8%AF-scenes-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2357/" rel="">العقد Nodes والمشاهد Scenes في جودو Godot</a>
	</li>
	<li>
		<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 Engines</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2484</guid><pubDate>Tue, 07 Jan 2025 15:04:04 +0000</pubDate></item><item><title>&#x62A;&#x62D;&#x631;&#x64A;&#x643; &#x627;&#x644;&#x634;&#x62E;&#x635;&#x64A;&#x629; &#x641;&#x64A; &#x644;&#x639;&#x628;&#x629; 3D &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x645;&#x62D;&#x631;&#x631; &#x627;&#x644;&#x62A;&#x62D;&#x631;&#x64A;&#x643; &#x641;&#x64A; &#x62C;&#x648;&#x62F;&#x648;</title><link>https://academy.hsoub.com/programming/game-development/%D8%AA%D8%AD%D8%B1%D9%8A%D9%83-%D8%A7%D9%84%D8%B4%D8%AE%D8%B5%D9%8A%D8%A9-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-3d-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AD%D8%B1%D8%B1-%D8%A7%D9%84%D8%AA%D8%AD%D8%B1%D9%8A%D9%83-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-r2477/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_12/1856752000_.png.908c7126a72265b0278905a9f11648b9.png" /></p>
<p>
	يعد تحريك الشخصيات عنصرًا أساسيًا في تطوير الألعاب، ويوفر لنا محرك جودو أدوات مدمجة تساعدنا على إنشاء حركات واقعية وجذابة للشخصيات، سنركز في هذا المقال على شرح استخدام محرر التحريك لجعل شخصيات لعبتنا ثلاثية الأبعاد تطفو وتلتف، ونوضح كيفية استخدام التعليمات البرمجية للتحكم في تحريك الشخصيات وتفاعلها.
</p>

<p>
	 
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="gif" data-fileid="164417" href="https://academy.hsoub.com/uploads/monthly_2024_12/01--.gif.c5439a268781daf9890a7f4a0490802f.gif" rel=""><img alt="01 شخصيات اللعبة" class="ipsImage ipsImage_thumbnailed" data-fileid="164417" data-unique="5i7d7cehn" style="width: 300px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/01--.gif.c5439a268781daf9890a7f4a0490802f.gif"> </a>
</p>

<p>
	لنبدأ بمقدمة حول استخدام محرر التحريك Animation Editor المدمج في <a href="https://academy.hsoub.com/programming/game-development/%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot/" rel="">محرك ألعاب جودو</a>.
</p>

<h2>
	استخدام محرر التحريك Animation Editor
</h2>

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

<h2>
	إنشاء حركة اللاعب
</h2>

<p>
	لنبدأ بإنشاء حركة اللاعب، سنفتح مشهد اللاعب ونحدد العقدة <code>Player</code> ثم نضيف لها العقدة <a href="https://docs.godotengine.org/en/stable/classes/class_animationplayer.html#class-animationplayer" rel="external nofollow"><code>AnimationPlayer</code></a> هذه العقدة هي المسؤولة عن تشغيل الحركات، بعد إضافة العقدة ستظهر لوحة خاصة بالتحريك باسم Animation في اللوحة السفلية.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="164400" href="https://academy.hsoub.com/uploads/monthly_2024_12/02--.png.ff285c22de2f189b05622a4c07fc80d4.png" rel=""><img alt="02 نافذة التحريك" class="ipsImage ipsImage_thumbnailed" data-fileid="164400" data-unique="rs944n039" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/02--.thumb.png.57deae7169569a63a50929e14a012fe8.png"> </a>
</p>

<p>
	كما تبين الصورة أعلاه، نجد في الأعلى شريط أدوات وقائمة منسدلة للتحريك تمكننا من الاختيار بين أنواع الحركات المختلفة، وفي المنتصف محرر مسار track editor dock وهو فارغ حاليًا وستُعرض فيه لاحقًا المسارات التي تتبعها العناصر المتحركة، وفي الأسفل خيارات للتصفية والالتقاط والتكبير و التصغير.
</p>

<p>
	لنبدأ بإنشاء حركة، ننقر على القائمة المنسدلة تحريك Animation في الأعلى، ثم نختار جديد New.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="164401" href="https://academy.hsoub.com/uploads/monthly_2024_12/03--.png.5fbe7980ceed2a33320599919f6eee3e.png" rel=""><img alt="03 حركة جديدة" class="ipsImage ipsImage_thumbnailed" data-fileid="164401" data-unique="eoispkfn1" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/03--.png.5fbe7980ceed2a33320599919f6eee3e.png"> </a>
</p>

<p>
	ينبغي علينا تسمية التحريك الجديد باسم مناسب وليكن float أي طفو أو عوم.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="164402" href="https://academy.hsoub.com/uploads/monthly_2024_12/04---float.png.0033006ccf58a280eb8aa1963ff30b2a.png" rel=""><img alt="04 حركة الطفو float" class="ipsImage ipsImage_thumbnailed" data-fileid="164402" data-unique="i2ryw71s3" src="https://academy.hsoub.com/uploads/monthly_2024_12/04---float.png.0033006ccf58a280eb8aa1963ff30b2a.png"> </a>
</p>

<p>
	بمجرد إنشاء التحريك، سيظهر لنا مخطط زمني timeline يعرض أرقامًا تمثل الوقت بالثواني ويسمح لنا بمراقبة تغير خصائص الشخصية عبر الزمن أثناء تشغيل التحريك.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="164419" href="https://academy.hsoub.com/uploads/monthly_2024_12/05--.png.24ab8eb9507e2d5d135e1bd4d14a0845.png" rel=""><img alt="05 مخطط زمني" class="ipsImage ipsImage_thumbnailed" data-fileid="164419" data-unique="x721j1946" style="width: 500px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/05--.png.24ab8eb9507e2d5d135e1bd4d14a0845.png"> </a>
</p>

<p>
	نريد أن يبدأ تشغيل الحركة تلقائيًا في بداية اللعب، وأن تتكرر عملية التحريك بدون توقف، ويمكننا تحقيق بذلك عن طريق النقر فوق الزر الذي يحمل أيقونة A+‎ في شريط أدوات التحريك والذي يعني التشغيل التلقائي Autoplay، ثم تفعيل الأسهم التي تدل على تكرار الحركة كما في الصورة التالية.
</p>

<p style="text-align: center;">
	<img alt="06 أسهم تكرار الحركة" class="ipsImage ipsImage_thumbnailed" data-fileid="164433" data-ratio="32.80" data-unique="g0ziy4zc7" style="width: 500px; height: auto;" width="843" src="https://academy.hsoub.com/uploads/monthly_2024_12/06---.png.2472cf01ec99765a254099ac58009492.png">
</p>

<p>
	يمكننا أيضًا تثبيت محرر التحريك من خلال النقر على أيقونة الدبوس الظاهرة أعلى يمين الصورة، وهذا يمنعها من الطي عند النقر فوق إطار العرض وإلغاء تحديد العقد.
</p>

<p style="text-align: center;">
	<img alt="07 تثبيت محرر التحريك" class="ipsImage ipsImage_thumbnailed" data-fileid="164434" data-ratio="20.31" data-unique="zv4gk7noz" style="width: 192px; height: auto;" width="192" src="https://academy.hsoub.com/uploads/monthly_2024_12/07---.png.fe7ffe3fa2c15e1ca2794099b63d634c.png">
</p>

<p>
	سنضبط مدة الحركة إلى <code>1.2</code> ثانية في الجزء العلوي الأيمن من الشريط، وهذا هذا يعني أن الحركة التي أنشأناها ستستغرق مدة ثانية و 200 ميلي ثانية لإتمامها بالكامل من بدايتها إلى نهايتها.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="164403" href="https://academy.hsoub.com/uploads/monthly_2024_12/08---.png.78ed56291a2240cda1b638428f0cb376.png" rel=""><img alt="08 ضبط مدة الحركة" class="ipsImage ipsImage_thumbnailed" data-fileid="164403" data-unique="bydk1hn7h" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/08---.png.78ed56291a2240cda1b638428f0cb376.png"> </a>
</p>

<p>
	ينبغي أن نرى الآن الشريط الرمادي يتسع قليلاً، ليُظهر لنا بداية ونهاية الحركة التي صنعناها ونلاحظ الخط الأزرق العمودي الذي يمثل مؤشر الوقت.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="164404" href="https://academy.hsoub.com/uploads/monthly_2024_12/09-.png.bf30ba4541fec2db419c7ff215abd760.png" rel=""><img alt="09 مؤشر الوقت" class="ipsImage ipsImage_thumbnailed" data-fileid="164404" data-unique="08g1ewiqn" src="https://academy.hsoub.com/uploads/monthly_2024_12/09-.png.bf30ba4541fec2db419c7ff215abd760.png"> </a>
</p>

<p>
	يمكننا النقر على شريط التمرير وسحبه في الجزء السفلي الأيسر لتكبير وتصغير المخطط الزمني.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="164405" href="https://academy.hsoub.com/uploads/monthly_2024_12/10----.png.d39bacb2d94c3759b86c00939f032fc9.png" rel=""><img alt="10 شريط تمرير المخطط الزمني" class="ipsImage ipsImage_thumbnailed" data-fileid="164405" data-unique="y5m87voqu" src="https://academy.hsoub.com/uploads/monthly_2024_12/10----.png.d39bacb2d94c3759b86c00939f032fc9.png"> </a>
</p>

<h2>
	إنشاء حركة الطفو Float Animation
</h2>

<p>
	سنعمل على إنشاء حركة الطفو التي تجعل الشخصيات تبدو وكأنها عائمة في الهواء بسلاسة دون تأثيرات مثل الجاذبية، حيث يمكننا تنفيذ الحركات من خلال تحريك معظم الخصائص وعلى أي عدد نريده من العقد باستخدام العقدة <a href="https://docs.godotengine.org/en/stable/classes/class_animationplayer.html#class-animationplayer" rel="external nofollow"><code>AnimationPlayer</code></a> التي أضفناها سابقًا.
</p>

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

<p>
	دعونا ندرج الإطار الأول، سنجري هنا تحريكًا لكل من الموضع Position والتدوير Rotation للعقدة <code>character</code>، لذا ينبغي علينا تحديد العقدة <code>character</code> ثم فتح قسم التحويل Transform في الفاحص Inspector، ثم النقر فوق رمز المفتاح بجوار خاصيتي الموضع والتدوير.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="164406" href="https://academy.hsoub.com/uploads/monthly_2024_12/11----.png.0f096a6ed7331b350ae2cbf4ab91090c.png" rel=""><img alt="11 رمز المفتاح للموضع والتدوير" class="ipsImage ipsImage_thumbnailed" data-fileid="164406" data-unique="lbowwa4t3" src="https://academy.hsoub.com/uploads/monthly_2024_12/11----.png.0f096a6ed7331b350ae2cbf4ab91090c.png"> </a>
</p>

<p>
	عند النقر على رمز المفتاح يطلب منا تحديد نوع المسار الجديد للخاصية.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="164407" href="https://academy.hsoub.com/uploads/monthly_2024_12/12-RESET.png.ac90efe9aa38ad5fde0a83a84564c6b1.png" rel=""><img alt="12 إنشاء مسارات reset" class="ipsImage ipsImage_thumbnailed" data-fileid="164407" data-unique="y2rejj682" style="width: 270px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/12-RESET.png.ac90efe9aa38ad5fde0a83a84564c6b1.png"> </a>
</p>

<p>
	بالنسبة لسلسلتنا، سنختار إنشاء مسارات RESET وهو الخيار الافتراضي حيث يظهر مساران في المحرر، ويمثل رمز المعين كل إطار رئيسي keyframe.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="164408" href="https://academy.hsoub.com/uploads/monthly_2024_12/13--.png.2101ff0ea37443d1ebfcf93d3b0cb371.png" rel=""><img alt="13 مساران للتحريك" class="ipsImage ipsImage_thumbnailed" data-fileid="164408" data-unique="m96tugi33" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/13--.png.2101ff0ea37443d1ebfcf93d3b0cb371.png"> </a>
</p>

<p>
	يمكننا النقر على المعين وسحبه لتحريكه للوقت المناسب، سنحركه في مسار الموضع إلى <code>0.3</code> ثانية وفي مسار التدوير إلى <code>0.1</code> ثانية.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="164409" href="https://academy.hsoub.com/uploads/monthly_2024_12/14--.png.cee4c5d44f1ffba791def7d220c40699.png" rel=""><img alt="14 معين التحريك" class="ipsImage ipsImage_thumbnailed" data-fileid="164409" data-unique="f6g7kpuqf" src="https://academy.hsoub.com/uploads/monthly_2024_12/14--.png.cee4c5d44f1ffba791def7d220c40699.png"> </a>
</p>

<p>
	كما سنحرك مؤشر الوقت إلى <code>0.5</code> ثانية عن طريق النقر والسحب على المخطط الزمني الرمادي.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="164410" href="https://academy.hsoub.com/uploads/monthly_2024_12/15--.png.e0049374df778f00693afbcecfdd6dab.png" rel=""><img alt="15 مؤشر الوقت" class="ipsImage ipsImage_thumbnailed" data-fileid="164410" data-unique="uob9smemm" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/15--.png.e0049374df778f00693afbcecfdd6dab.png"> </a>
</p>

<p>
	الآن نعود إلى الفاحص Inspector ونعيّن قيمة المحور Y لخاصية الموضع على <code>0.65</code> متر ، والمحور X لخاصية التدوير على <code>8</code> درجات.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="164411" href="https://academy.hsoub.com/uploads/monthly_2024_12/16----.png.a33d2c25356e23ae18376b0913b3ab0a.png" rel=""><img alt="16 تعديل خاصية الموضع والدوران" class="ipsImage ipsImage_thumbnailed" data-fileid="164411" data-unique="3twztkvsy" src="https://academy.hsoub.com/uploads/monthly_2024_12/16----.png.a33d2c25356e23ae18376b0913b3ab0a.png"> </a>
</p>

<p>
	بهذا نكون قد أنشأنا إطار رئيسي لكل من خاصيتي الموضع و التدوير.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="164412" href="https://academy.hsoub.com/uploads/monthly_2024_12/17----.png.8119a6c22630ec7cb108528c4ef8bc25.png" rel=""><img alt="17 إطار رئيسي للموضع والدوران" class="ipsImage ipsImage_thumbnailed" data-fileid="164412" data-unique="7t9yh3csv" src="https://academy.hsoub.com/uploads/monthly_2024_12/17----.png.8119a6c22630ec7cb108528c4ef8bc25.png"> </a>
</p>

<p>
	لنحرّك الآن الإطار الرئيسي للموضع إلى<code>0.7</code> ثانية عن طريق سحبه على المخطط الزمني.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="164413" href="https://academy.hsoub.com/uploads/monthly_2024_12/18----.png.754ad6b914755c4634e111b5f0d7ec76.png" rel=""><img alt="18 سحب الإطار الرئيسي للموضع" class="ipsImage ipsImage_thumbnailed" data-fileid="164413" data-unique="zoa2cv2k3" src="https://academy.hsoub.com/uploads/monthly_2024_12/18----.png.754ad6b914755c4634e111b5f0d7ec76.png"> </a>
</p>

<p>
	<strong>ملاحظة</strong>: ليس هدفنا في هذه السلسلة شرح مبادئ التحريك بالتفصيل، لكن يكفي أن نضع بعين الاعتبار عدم تحديد وقت ومسافة متساوية لكل شيء بالتساوي حيث يتلاعب مختصو التحريك عادة بالتوقيت والتباعد، وهما المبدآن الأساسيان للتحريك. لكوننا نريد الموازنة والتباين في حركة الشخصية ونجعلها أكثر حيوية.
</p>

<p>
	الآن لنحدد كيف ستنتهي الحركة ونحرك مؤشر الوقت إلى الموضع <code>1.2</code> ثانية. ثم نضبط المحور Y لخاصية الموضع على القيمة <code>0.35</code> والمحور X لخاصية التدوير على <code>9-</code> درجة، والآن ننشئ مرة أخرى مفتاح لكل خاصية.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="164414" href="https://academy.hsoub.com/uploads/monthly_2024_12/19----.png.5eb33f0f16d9e4f5cf0858190d1251bb.png" rel=""><img alt="19 إطار رئيسي للموضع والدوران" class="ipsImage ipsImage_thumbnailed" data-fileid="164414" data-unique="4zxf66frl" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/19----.thumb.png.d0c939e6af26c25b8eca4c4320ee0273.png"> </a>
</p>

<p>
	يمكننا معاينة النتيجة بالنقر فوق زر التشغيل أو الضغط على <code>Shift + D</code>. و لإيقاف التشغيل ننقر فوق زر الإيقاف أو نضغط على <code>S</code>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="164415" href="https://academy.hsoub.com/uploads/monthly_2024_12/20--.png.662711070d03a483b90e50b5e1a3fcdc.png" rel=""><img alt="20 تشغيل الحركة" class="ipsImage ipsImage_thumbnailed" data-fileid="164415" data-unique="lkqp1t5qe" src="https://academy.hsoub.com/uploads/monthly_2024_12/20--.png.662711070d03a483b90e50b5e1a3fcdc.png"> </a>
</p>

<p>
	سنلاحظ أن المحرك يجري عملية تداخل Interpolation بين الإطارات الرئيسية Keyframes التي أنشأناها لإنتاج حركة مستمرة. لكن تبدو الحركة في الوقت الحالي مصطنعة جدًا. وذلك لأن التداخل الافتراضي خطي، مما يسبب انتقالات بتسارع ثابت constant transitions، وهذا يخالف الكيفية التي تتحرك فيها الكائنات الحية في عالمنا الحقيقي.
</p>

<p>
	يمكننا التحكم في طريقة الانتقال بين الإطارات الرئيسية باستخدام ما يسمى بمنحنيات التخفيف easing curves. ننقر ونسحب بين المفتاحين الأوليين في المخطط الزمني لتحديدهما ضمن مربع.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="164416" href="https://academy.hsoub.com/uploads/monthly_2024_12/21-----.png.5843b374be52b1be5a57b4e836686734.png" rel=""><img alt="21 سحب المفاتيح في المخطط الزمني" class="ipsImage ipsImage_thumbnailed" data-fileid="164416" data-unique="cy2c8iyyj" src="https://academy.hsoub.com/uploads/monthly_2024_12/21-----.png.5843b374be52b1be5a57b4e836686734.png"> </a>
</p>

<p>
	يمكننا بعدها تحرير خصائص كلا المفتاحين في وقت واحد في الفاحص Inspector، حيث يمكنك رؤية خاصية التخفيف Easing.
</p>

<p style="text-align: center;">
	<img alt="22 خاصية التخفيف" class="ipsImage ipsImage_thumbnailed" data-fileid="164435" data-ratio="42.74" data-unique="rz4e9ovli" style="width: 372px; height: auto;" width="372" src="https://academy.hsoub.com/uploads/monthly_2024_12/22---Easing.png.470dd00155860e06b91eb3c59f475046.png">
</p>

<p>
	انقر على المنحني الظاهر في الصورة واسحبه نحو اليسار. سيؤدي ذلك إلى جعل الحركة تتباطئ أو تخف تدريجيًا Ease-out، أي تنتقل سريعًا في البداية وتتباطأ عندما يصل مؤشر الوقت إلى الإطار الرئيسي التالي.
</p>

<p style="text-align: center;">
	<img alt="23 تخفيف الحركة " class="ipsImage ipsImage_thumbnailed" data-fileid="164436" data-ratio="16.71" data-unique="b7d5shpbc" style="width: 359px; height: auto;" width="359" src="https://academy.hsoub.com/uploads/monthly_2024_12/23--.png.7d3828e6740726be7ebd251d0ddb085a.png">
</p>

<p>
	عندما نشغّل الحركة مرة أخرى سنرى الفرق، يجب أن يبدو النصف الأول من الحركة الآن أكثر مرونة.
</p>

<p>
	بعدها نطبّق التخفيف Easing على الإطار الرئيسي الرئيسي الثاني في مسار التدوير.
</p>

<p style="text-align: center;">
	<img alt="24 تخفيف المسار الثاني" class="ipsImage ipsImage_thumbnailed" data-fileid="164437" data-ratio="52.16" data-unique="i3jef7re1" style="width: 370px; height: auto;" width="370" src="https://academy.hsoub.com/uploads/monthly_2024_12/24---.png.c5446c305273c4b4ef6ea3878de7da6c.png">
</p>

<p>
	سنفعل العكس بالنسبة للإطار الرئيسي الثاني لخاصية الموضع ونسحبه إلى اليمين.
</p>

<p style="text-align: center;">
	<img alt="25 تخفيف الإطار الرئيسي الثاني للموضع" class="ipsImage ipsImage_thumbnailed" data-fileid="164438" data-ratio="52.88" data-unique="9hedjpgb5" style="width: 365px; height: auto;" width="365" src="https://academy.hsoub.com/uploads/monthly_2024_12/25----.png.11ae0a6fa660bf4f4daf819c64b58359.png">
</p>

<p>
	الآن سوف يطفو اللاعب عند تشغيل اللعبة وستبدو الحركة كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="gif" data-fileid="164418" href="https://academy.hsoub.com/uploads/monthly_2024_12/26-.gif.1b40e2a6725448b4ff78990a0cd90706.gif" rel=""><img alt="26 الحركة" class="ipsImage ipsImage_thumbnailed" data-fileid="164418" data-unique="o2ect4mm4" src="https://academy.hsoub.com/uploads/monthly_2024_12/26-.gif.1b40e2a6725448b4ff78990a0cd90706.gif"> </a>
</p>

<p>
	 
</p>

<p>
	<strong>ملاحظة</strong>: تؤثر الحركات التي أضفناها على خصائص العقد المتحركة في كل إطار، مما يعيد تعريف القيم الأولية التي حددناها. لذلك، إذا حركنا عقدة اللاعب مباشرة، فهذا سيمنعنا من تحريكها باستخدام التعليمات البرمجية. وهنا يأتي دور العقدة المحورية <code>Pivot</code> فعلى الرغم من أننا حركنا الشخصية، فلا يزال بإمكاننا تحريك المحور <code>Pivot</code> وتدويره لإضافة تغييرات إضافية على الحركة في السكربت البرمجي.
</p>

<p>
	يمكننا على سبيل المثال تحريك <code>Pivot</code> لأعلى في حال كان اللاعب الذي أنشأناه قريبًا جدًا من الأرض.
</p>

<h2>
	التحكم بالحركة من خلال الشيفرة البرمجية
</h2>

<p>
	يمكننا التحكم في تشغيل الحركة التي أنشأناها برمجيًا بناء على مدخلات اللاعب. لنجرب على سبيل المثال تغيير سرعة الحركة عندما تتحرك الشخصية.
</p>

<p>
	نفتح السكربت البرمجي للعقدة <code>Player</code> من خلال النقر على أيقونة السكربت المجاورة لها.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="164420" href="https://academy.hsoub.com/uploads/monthly_2024_12/27---.png.c89a0b83d6efaa91c596a2b899ad9555.png" rel=""><img alt="27 أيقونة السكربت البرمجي" class="ipsImage ipsImage_thumbnailed" data-fileid="164420" data-unique="89g0d5hwk" src="https://academy.hsoub.com/uploads/monthly_2024_12/27---.png.c89a0b83d6efaa91c596a2b899ad9555.png"> </a>
</p>

<p>
	نضيف الشيفرة التالية في الدالة <code>‎_physics_process()‎</code>، بعد السطر الذي نتحقق فيه من المتجه <code>direction</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6433_29" style=""><span class="pln">func _physics_process</span><span class="pun">(</span><span class="pln">delta</span><span class="pun">):</span><span class="pln">
    </span><span class="com">#...</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> direction </span><span class="pun">!=</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">ZERO</span><span class="pun">:</span><span class="pln">
        </span><span class="com">#...</span><span class="pln">
        $AnimationPlayer</span><span class="pun">.</span><span class="pln">speed_scale </span><span class="pun">=</span><span class="pln"> </span><span class="lit">4</span><span class="pln">
    </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
        $AnimationPlayer</span><span class="pun">.</span><span class="pln">speed_scale </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span></pre>

<p>
	تعمل هذه الشيفرة على تسريع الحركة عن طريق ضرب سرعة التشغيل في <code>4</code>، ثم إعادة ضبطها إلى الوضع الطبيعي بمجرد توقف اللاعب.
</p>

<p>
	وقد ذكرنا أن <code>pivot</code> يمكن أن يساعدنا في تطبيق حركة إضافية على الشخصية. ويمكننا استعماله هنا لجعل الشخصية تشكل قوس عندما تقفز بإضافة الكود التالي في نهاية الدالة <code>‎_physics_process()‎</code>.
</p>

<pre class="ipsCode">func _physics_process(delta):
    #...
    $Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse
</pre>

<h2>
	تحريك الأعداء
</h2>

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

<p>
	على سبيل المثال، يحتوي كل من مشهدي اللاعب <code>Player</code> والعدو <code>Mob </code>على عقدة محورية <code>pivot</code> وعقدة الشخصية <code>character</code>، لذا في هذه الحالة يمكننا إعادة استخدام الحركة بينهما.
</p>

<p>
	لنفتح مشهد اللاعب <code>player.tsc</code>، ونحدد العقدة <code>AnimationPlayer</code> ونفتح التحريك float. بعد ذلك، ننقر فوق التحريك Animation ومن ثم نحتار نسخ Copy.
</p>

<p>
	ثم نفتح مشهد العدو mob.tscn، وننشئ عقدة فرعية من نوع <code>AnimationPlayer</code> ونحددها، ثم نضغط على تحريك Animation ثم إدارة التحريك Manage Animations ونختار إضافة مكتبة Add Library، سنرى رسالة مفادها "Global library will be created" أي ستنشأ مكتبة عامة نترك الحقل النصي فارغًا ونضغط زر موافق.
</p>

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

<p>
	يمكننا تغيير سرعة التشغيل بناءً على السرعة العشوائية <code>random_speed</code> للعدو. لذا سنفتح سكربت العقدة <code>Mob</code> ونضيف في نهاية دالة <code>initialize()‎</code> السطر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6433_31" style=""><span class="pln">func initialize</span><span class="pun">(</span><span class="pln">start_position</span><span class="pun">,</span><span class="pln"> player_position</span><span class="pun">):</span><span class="pln">
    </span><span class="com">#...</span><span class="pln">
    $AnimationPlayer</span><span class="pun">.</span><span class="pln">speed_scale </span><span class="pun">=</span><span class="pln"> random_speed </span><span class="pun">/</span><span class="pln"> min_speed</span></pre>

<p>
	بإتمام هذه الخطوات، نكون قد أكملنا برمجة <a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%AD%D8%B1%D9%8A%D9%83-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D9%8B%D8%A7-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AD%D8%B1%D9%83-%D8%AC%D9%88%D8%AF%D9%88-r2450/" rel="">أول لعبة ثلاثية الأبعاد</a> بشكل كامل باستخدام محرك جودو، مما يفتح أمامنا الكثير من الإمكانيات لتطوير ألعاب احترافية أكثر إبداعًا.
</p>

<div class="banner-container ipsBox ipsPadding">
	<div class="inner-banner-container">
		<p class="banner-heading">
			دورة تطوير الألعاب
		</p>

		<p class="banner-subtitle">
			ابدأ رحلة صناعة الألعاب ثنائية وثلاثية الأبعاد وابتكر ألعاب ممتعة تفاعلية ومليئة بالتحديات.
		</p>

		<div>
			<a class="ipsButton ipsButton_large ipsButton_primary ipsButton_important" href="https://academy.hsoub.com/learn/game-development/" rel="">اشترك الآن</a>
		</div>
	</div>

	<div class="banner-img">
		<a href="https://academy.hsoub.com/learn/game-development/" rel=""><img alt="دورة تطوير الألعاب" src="https://academy.hsoub.com/learn/assets/images/courses/game-development.png"></a>
	</div>
</div>

<h2 id="">
	الكود الكامل للعبة ثلاثية الأبعاد بمحرك جودو
</h2>

<p>
	فيما يلي محتوى ملف السكربت <code>Player.gd </code>الخاص بعقدة اللاعب <code>Player</code>
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6433_34" style=""><span class="pln">extends </span><span class="typ">CharacterBody3D</span><span class="pln">

signal hit

</span><span class="com"># سرعة حركة اللاعب مقدرة بالمتر في الثانية</span><span class="pln">
</span><span class="lit">@export</span><span class="pln"> var speed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">14</span><span class="pln">
</span><span class="com"># التسارع نحو الأسفل في الهواء مقدرة بالمتر في الثانية مربع </span><span class="pln">
</span><span class="lit">@export</span><span class="pln"> var fall_acceleration </span><span class="pun">=</span><span class="pln"> </span><span class="lit">75</span><span class="pln">
</span><span class="com"># الدفعة العامودية المطبقة على الشخصية عند القفز مقدرة بالمتر في الثانية</span><span class="pln">
</span><span class="lit">@export</span><span class="pln"> var jump_impulse </span><span class="pun">=</span><span class="pln"> </span><span class="lit">20</span><span class="pln">
</span><span class="com"># الدفعة العامودية المطبقة على الشخصية عند القفز على عدو مقدرة بالمتر في الثانية</span><span class="pln">
</span><span class="lit">@export</span><span class="pln"> var bounce_impulse </span><span class="pun">=</span><span class="pln"> </span><span class="lit">16</span><span class="pln">

var target_velocity </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">ZERO


func _physics_process</span><span class="pun">(</span><span class="pln">delta</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># أنشأنا متغير محلي لتخزين دخل الاتجاه</span><span class="pln">
    var direction </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">ZERO

    </span><span class="com"># نتحقق من كل دخل حركة ونحدث الاتجاه حسبه</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">is_action_pressed</span><span class="pun">(</span><span class="str">"move_right"</span><span class="pun">):</span><span class="pln">
        direction</span><span class="pun">.</span><span class="pln">x </span><span class="pun">=</span><span class="pln"> direction</span><span class="pun">.</span><span class="pln">x </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">is_action_pressed</span><span class="pun">(</span><span class="str">"move_left"</span><span class="pun">):</span><span class="pln">
        direction</span><span class="pun">.</span><span class="pln">x </span><span class="pun">=</span><span class="pln"> direction</span><span class="pun">.</span><span class="pln">x </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">is_action_pressed</span><span class="pun">(</span><span class="str">"move_back"</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># ‫لاحظ أننا نعمل المحورين x و z الخاصين بالشعاع</span><span class="pln">
        </span><span class="com"># المستوي‫ XZ هو مستوي الأرض في ثلاثي الأبعاد</span><span class="pln">
        direction</span><span class="pun">.</span><span class="pln">z </span><span class="pun">=</span><span class="pln"> direction</span><span class="pun">.</span><span class="pln">z </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">is_action_pressed</span><span class="pun">(</span><span class="str">"move_forward"</span><span class="pun">):</span><span class="pln">
        direction</span><span class="pun">.</span><span class="pln">z </span><span class="pun">=</span><span class="pln"> direction</span><span class="pun">.</span><span class="pln">z </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pln">

    </span><span class="com"># يمنع الحركة القطرية السريعة</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> direction </span><span class="pun">!=</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">ZERO</span><span class="pun">:</span><span class="pln">
        direction </span><span class="pun">=</span><span class="pln"> direction</span><span class="pun">.</span><span class="pln">normalized</span><span class="pun">()</span><span class="pln">
        $Pivot</span><span class="pun">.</span><span class="pln">look_at</span><span class="pun">(</span><span class="pln">position </span><span class="pun">+</span><span class="pln"> direction</span><span class="pun">,</span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">UP</span><span class="pun">)</span><span class="pln">
        $AnimationPlayer</span><span class="pun">.</span><span class="pln">speed_scale </span><span class="pun">=</span><span class="pln"> </span><span class="lit">4</span><span class="pln">
    </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
        $AnimationPlayer</span><span class="pun">.</span><span class="pln">speed_scale </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">

    </span><span class="com"># السرعة الأرضية</span><span class="pln">
    target_velocity</span><span class="pun">.</span><span class="pln">x </span><span class="pun">=</span><span class="pln"> direction</span><span class="pun">.</span><span class="pln">x </span><span class="pun">*</span><span class="pln"> speed
    target_velocity</span><span class="pun">.</span><span class="pln">z </span><span class="pun">=</span><span class="pln"> direction</span><span class="pun">.</span><span class="pln">z </span><span class="pun">*</span><span class="pln"> speed

    </span><span class="com"># السرعة العمودية</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> is_on_floor</span><span class="pun">():</span><span class="pln"> </span><span class="com"># إذا كان في الهواء يسقط على الأرض أي الجاذبية</span><span class="pln">
        target_velocity</span><span class="pun">.</span><span class="pln">y </span><span class="pun">=</span><span class="pln"> target_velocity</span><span class="pun">.</span><span class="pln">y </span><span class="pun">-</span><span class="pln"> </span><span class="pun">(</span><span class="pln">fall_acceleration </span><span class="pun">*</span><span class="pln"> delta</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># القفز</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> is_on_floor</span><span class="pun">()</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">is_action_just_pressed</span><span class="pun">(</span><span class="str">"jump"</span><span class="pun">):</span><span class="pln">
        target_velocity</span><span class="pun">.</span><span class="pln">y </span><span class="pun">=</span><span class="pln"> jump_impulse

    </span><span class="com"># كرر خلال كل الاصطدامات التي تحصل في الإطار</span><span class="pln">
    </span><span class="com"># ‫يكون ذلك في C كالتالي</span><span class="pln">
</span><span class="com"># (int i = 0; i &lt; collisions.Count; i++)</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> index </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="pln">get_slide_collision_count</span><span class="pun">()):</span><span class="pln">
        </span><span class="com">#  نحصل على واحد من التصادمات مع اللاعب</span><span class="pln">
        var collision </span><span class="pun">=</span><span class="pln"> get_slide_collision</span><span class="pun">(</span><span class="pln">index</span><span class="pun">)</span><span class="pln">

        </span><span class="com"># إذا كان التصادم مع الأرض</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> collision</span><span class="pun">.</span><span class="pln">get_collider</span><span class="pun">()</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">:</span><span class="pln">
            </span><span class="kwd">continue</span><span class="pln">

        </span><span class="com"># إذا كان التصادم مع العدو</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> collision</span><span class="pun">.</span><span class="pln">get_collider</span><span class="pun">().</span><span class="pln">is_in_group</span><span class="pun">(</span><span class="str">"mob"</span><span class="pun">):</span><span class="pln">
            var mob </span><span class="pun">=</span><span class="pln"> collision</span><span class="pun">.</span><span class="pln">get_collider</span><span class="pun">()</span><span class="pln">
            </span><span class="com"># نتحقق أننا نصدمه من الأعلى</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">UP</span><span class="pun">.</span><span class="pln">dot</span><span class="pun">(</span><span class="pln">collision</span><span class="pun">.</span><span class="pln">get_normal</span><span class="pun">())</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">0.1</span><span class="pun">:</span><span class="pln">
                </span><span class="com"># إذا كان كذلك نسحقه</span><span class="pln">
                mob</span><span class="pun">.</span><span class="pln">squash</span><span class="pun">()</span><span class="pln">
                target_velocity</span><span class="pun">.</span><span class="pln">y </span><span class="pun">=</span><span class="pln"> bounce_impulse
                </span><span class="com">#  يمنع أي استدعاءات مكررة.</span><span class="pln">
                </span><span class="kwd">break</span><span class="pln">

    </span><span class="com"># تحريك الشخصية</span><span class="pln">
    velocity </span><span class="pun">=</span><span class="pln"> target_velocity
    move_and_slide</span><span class="pun">()</span><span class="pln">

    $Pivot</span><span class="pun">.</span><span class="pln">rotation</span><span class="pun">.</span><span class="pln">x </span><span class="pun">=</span><span class="pln"> PI </span><span class="pun">/</span><span class="pln"> </span><span class="lit">6</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> velocity</span><span class="pun">.</span><span class="pln">y </span><span class="pun">/</span><span class="pln"> jump_impulse

</span><span class="com"># أضف ذلك في الأسفل.</span><span class="pln">
func die</span><span class="pun">():</span><span class="pln">
    hit</span><span class="pun">.</span><span class="pln">emit</span><span class="pun">()</span><span class="pln">
    queue_free</span><span class="pun">()</span><span class="pln">

func _on_mob_detector_body_entered</span><span class="pun">(</span><span class="pln">body</span><span class="pun">):</span><span class="pln">
    die</span><span class="pun">()</span></pre>

<p>
	وفيما يلي ملف السكربت <code>mob.gd</code> الخاص بعقدة العدو <code>Mob</code>
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6433_36" style=""><span class="pln">extends </span><span class="typ">CharacterBody3D</span><span class="pln">

</span><span class="com"># السرعة الدنيا للعدو مقدرة بالمتر في الثانية</span><span class="pln">
</span><span class="lit">@export</span><span class="pln"> var min_speed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">10</span><span class="pln">
</span><span class="com"># السرعة القصوى للعدو مقدرة بالمتر في الثانية</span><span class="pln">
</span><span class="lit">@export</span><span class="pln"> var max_speed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">18</span><span class="pln">

</span><span class="com"># تنبثق عندما يقفز اللاعب على عدو</span><span class="pln">
signal squashed

func _physics_process</span><span class="pun">(</span><span class="pln">_delta</span><span class="pun">):</span><span class="pln">
    move_and_slide</span><span class="pun">()</span><span class="pln">

</span><span class="com"># يتم استدعاء هذه الدالة من المشهد الأساسي</span><span class="pln">
func initialize</span><span class="pun">(</span><span class="pln">start_position</span><span class="pun">,</span><span class="pln"> player_position</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># نحدد مكان العدو عن طريق وضعه في‫ start_position</span><span class="pln">
    </span><span class="com"># ‫وندوره نحو player_position لينظر إلى اللاعب</span><span class="pln">
    look_at_from_position</span><span class="pun">(</span><span class="pln">start_position</span><span class="pun">,</span><span class="pln"> player_position</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">UP</span><span class="pun">)</span><span class="pln">
    </span><span class="com">#تدوير العدو عشوائيًا ضمن مجال -90 و +90 درجة</span><span class="pln">
    </span><span class="com"># لكي لا تتحرك مباشرة نحو اللاعب</span><span class="pln">
    rotate_y</span><span class="pun">(</span><span class="pln">randf_range</span><span class="pun">(-</span><span class="pln">PI </span><span class="pun">/</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> PI </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">
    var random_speed </span><span class="pun">=</span><span class="pln"> randi_range</span><span class="pun">(</span><span class="pln">min_speed</span><span class="pun">,</span><span class="pln"> max_speed</span><span class="pun">)</span><span class="pln">
    </span><span class="com"># نحسب سرعة امامية تمثل السرعة</span><span class="pln">
    velocity </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">FORWARD </span><span class="pun">*</span><span class="pln"> random_speed
    </span><span class="com"># ‫ثم ندور شعاع السرعة اعتمادًا على دوران العدو حول Y</span><span class="pln">
    </span><span class="com"># للحركة في الاتجاه الذي ينظر إليه العدو</span><span class="pln">
    velocity </span><span class="pun">=</span><span class="pln"> velocity</span><span class="pun">.</span><span class="pln">rotated</span><span class="pun">(</span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">UP</span><span class="pun">,</span><span class="pln"> rotation</span><span class="pun">.</span><span class="pln">y</span><span class="pun">)</span><span class="pln">

func _on_visible_on_screen_notifier_3d_screen_exited</span><span class="pun">():</span><span class="pln">
    queue_free</span><span class="pun">()</span><span class="pln">

func squash</span><span class="pun">():</span><span class="pln">
    squashed</span><span class="pun">.</span><span class="pln">emit</span><span class="pun">()</span><span class="pln">
    queue_free</span><span class="pun">()</span><span class="pln"> </span><span class="com"># دمّر هذه العقدة</span></pre>

<h2 id="-1">
	الخلاصة
</h2>

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

<p>
	ترجمة - وبتصرف - لقسم <a href="https://docs.godotengine.org/en/stable/getting_started/first_3d_game/09.adding_animations.html" rel="external nofollow">Character animation</a> من توثيق جودو الرسمي.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/game-development/%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-%D8%A7%D9%84%D9%86%D9%82%D8%A7%D8%B7-%D9%88%D8%A7%D9%84%D9%84%D8%B9%D8%A8-%D9%85%D8%AC%D8%AF%D8%AF%D9%8B%D8%A7-%D9%88%D8%AA%D8%A3%D8%AB%D9%8A%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D8%B5%D9%88%D8%AA-%D9%84%D9%84%D8%B9%D8%A8%D8%A9-3d-%D8%B6%D9%85%D9%86-%D8%AC%D9%88%D8%AF%D9%88-r2472/" rel="">إضافة النقاط واللعب مجددًا وتأثيرات الصوت للعبة 3D ضمن جودو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D9%82%D8%AA%D9%84-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D8%B9%D9%86%D8%AF-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D8%AF%D8%A7%D9%85-%D8%A8%D8%A7%D9%84%D8%B9%D8%AF%D9%88-%D8%B6%D9%85%D9%86-%D9%84%D8%B9%D8%A8%D8%A9-3d-%D8%A8%D8%AC%D9%88%D8%AF%D9%88-r2471/" rel="">قتل اللاعب عند الاصطدام بالعدو ضمن لعبة 3D بجودو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D8%B9%D9%82%D8%AF-nodes-%D9%88%D8%A7%D9%84%D9%85%D8%B4%D8%A7%D9%87%D8%AF-scenes-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2357/" rel="">العقد Nodes والمشاهد Scenes في جودو Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A%D8%A9/" rel="">أشهر أنواع الألعاب الإلكترونية</a>
	</li>
</ul>

<p>
	 
</p>
]]></description><guid isPermaLink="false">2477</guid><pubDate>Tue, 24 Dec 2024 15:02:01 +0000</pubDate></item><item><title>&#x625;&#x636;&#x627;&#x641;&#x629; &#x627;&#x644;&#x646;&#x642;&#x627;&#x637; &#x648;&#x627;&#x644;&#x644;&#x639;&#x628; &#x645;&#x62C;&#x62F;&#x62F;&#x64B;&#x627; &#x648;&#x62A;&#x623;&#x62B;&#x64A;&#x631;&#x627;&#x62A; &#x627;&#x644;&#x635;&#x648;&#x62A; &#x644;&#x644;&#x639;&#x628;&#x629; 3D &#x636;&#x645;&#x646; &#x62C;&#x648;&#x62F;&#x648;</title><link>https://academy.hsoub.com/programming/game-development/%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-%D8%A7%D9%84%D9%86%D9%82%D8%A7%D8%B7-%D9%88%D8%A7%D9%84%D9%84%D8%B9%D8%A8-%D9%85%D8%AC%D8%AF%D8%AF%D9%8B%D8%A7-%D9%88%D8%AA%D8%A3%D8%AB%D9%8A%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D8%B5%D9%88%D8%AA-%D9%84%D9%84%D8%B9%D8%A8%D8%A9-3d-%D8%B6%D9%85%D9%86-%D8%AC%D9%88%D8%AF%D9%88-r2472/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_12/488125748_.png.9c32b0253eaf7f121319094aa4551a25.png" /></p>
<p>
	في هذا الدرس من سلسلة تطوير لعبة ثلاثية الأبعاد في جودو، سنضيف للعبتنا نظام النقاط وتشغيل المؤثرات الصوتية وإمكانية اللعب مجددًا. سنتعلم كيفية تتبّع النتيجة الحالية بواسطة متغير وعرضها على الشاشة باستخدام واجهة مستخدم بسيطة عن طريق كتابة نصية.
</p>

<h2 id="">
	إضافة عقدة واجهة المستخدم
</h2>

<p>
	سنضيف عقدة فرعية جديدة في المشهد الرئيسي من نوع <code>Control</code> إلى العقدة الرئيسية <code>Main</code> ونطلق عليها اسم <code>UserInterface</code> وهذا سينقلنا تلقائيًا إلى واجهة الفضاء ثنائي الأبعاد، حيث يمكننا تعديل واجهة المستخدم UI.
</p>

<p>
	نضيف عقدة <code>Label</code> ونسميها <code>ScoreLabel</code>
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/01--ScoreLabel.png.8d5b0ca91be2bd36f18373a63796eef2.png" data-fileid="164183" data-fileext="png" rel=""><img alt="01 عقدة scorelabel" class="ipsImage ipsImage_thumbnailed" data-fileid="164183" data-unique="pdek19nel" src="https://academy.hsoub.com/uploads/monthly_2024_12/01--ScoreLabel.png.8d5b0ca91be2bd36f18373a63796eef2.png"> </a>
</p>

<p>
	ثم نضبط الخاصية Text ضمن قائمة الفاحص Inspector للتسمية النصية بقيمة افتراضية مثل Score: 0
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/02--Text.png.bb2a11478aa1049738260bbbc011fbc6.png" data-fileid="164184" data-fileext="png" rel=""><img alt="02 الخاصية text" class="ipsImage ipsImage_thumbnailed" data-fileid="164184" data-unique="snrygb2mo" src="https://academy.hsoub.com/uploads/monthly_2024_12/02--Text.png.bb2a11478aa1049738260bbbc011fbc6.png"> </a>
</p>

<p>
	يكون لون النص أبيضًا بشكل افتراضي مثل خلفية لعبتنا، لذا نحتاج إلى تغيير لونه لرؤيته أثناء تشغيل اللعبة. ننتقل للأسفل إلى Theme Overrides ونفتح الألوان ونفعّل لون الخط Font Color لتلوين النص بالأسود لأنه يظهر بشكل جيد مع المشهد ثلاثي الأبعاد الأبيض.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/03---FontColor.png.029b401efe6f69438ce16549d85ed34e.png" data-fileid="164185" data-fileext="png" rel=""><img alt="03 لون الخط font color" class="ipsImage ipsImage_thumbnailed" data-fileid="164185" data-unique="iujyopl9c" src="https://academy.hsoub.com/uploads/monthly_2024_12/03---FontColor.png.029b401efe6f69438ce16549d85ed34e.png"> </a>
</p>

<p>
	أخيرًا، ننقر ونسحب النص في نافذة العرض لتحريكه بعيدًا عن الزاوية العلوية اليسرى.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/04--.png.1e654f115c293e1cff49240a50df7993.png" data-fileid="164186" data-fileext="png" rel=""><img alt="04 سحب النص" class="ipsImage ipsImage_thumbnailed" data-fileid="164186" data-unique="crz63f89x" src="https://academy.hsoub.com/uploads/monthly_2024_12/04--.png.1e654f115c293e1cff49240a50df7993.png"> </a>
</p>

<p>
	تتيح لنا عقدة <code>UserInterface</code> تجميع عناصر واجهة المستخدم الخاصة بنا في فرع من شجرة المشهد واستخدام مورد مظهر theme resource يتاح توارثه من قبل كل عناصرها الفرعية والذي سنستخدمه لتعيين خط لعبتنا.
</p>

<h2 id="-1">
	إنشاء مظهر واجهة المستخدم
</h2>

<p>
	نحدد عقدة <code>UserInterface</code> مرة أخرى، وننشئ في لوحة الفاحص Inspector مورد مظهر theme resource جديد عبر الذهاب إلى Theme ومن ثم الحقل Theme كالتالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/05---Theme.png.3ad2e38b6b8bf4eb2fcd1f82baa224cc.png" data-fileid="164187" data-fileext="png" rel=""><img alt="05 مظهر الواجهة theme" class="ipsImage ipsImage_thumbnailed" data-fileid="164187" data-unique="6vghh4pc8" src="https://academy.hsoub.com/uploads/monthly_2024_12/05---Theme.png.3ad2e38b6b8bf4eb2fcd1f82baa224cc.png"> </a>
</p>

<p>
	ننقر فوقه لفتح محرر المظهر في اللوحة السفلية لنستطيع معاينة كيفية ظهور جميع أدوات واجهة المستخدم المضمنة مع مورد المظهر الخاص بنا.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/06--.png.3091a3d3cace3096a251cb74ba64a6a4.png" data-fileid="164188" data-fileext="png" rel=""><img alt="06 محرر المظهر" class="ipsImage ipsImage_thumbnailed" data-fileid="164188" data-unique="1f1ze7zsl" src="https://academy.hsoub.com/uploads/monthly_2024_12/06--.thumb.png.ab8946695f81666acba6d294067cfbb3.png"> </a>
</p>

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

<p>
	يتوقّع حقل ملف الخط ملفات <a href="https://academy.hsoub.com/programming/css/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AE%D8%B7%D9%88%D8%B7-%D8%A7%D9%84%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D9%81%D9%8A-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%85%D8%B9-css-r2222/" rel="">كملفات خطوط الكتابة</a> الموجودة على حاسوبنا، فهناك صيغتان شائعتان لملفات الخطوط هما TTF و OTF
</p>

<p>
	من قائمة نظام الملفات FileSystem، نوسّع دليل الخطوط <code>fonts</code> وننقر ونسحب ملف <code>Montserrat-Medium.ttf</code> الذي أضفناه إلى المشروع على حقل الخط الافتراضي Default Font حتى يظهر النص مرة أخرى في معاينة المظهر.
</p>

<p>
	سنلاحظ أن النص صغير قليلاً، لذا نضبط حجم الخط الافتراضي Default Font Size على قيمة <code>22</code> بكسل لزيادة حجم النص.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/07---.png.36d9af69d3e4bf521e1235f7b1adfb97.png" data-fileid="164189" data-fileext="png" rel=""><img alt="07 حجم الخط الافتراضي" class="ipsImage ipsImage_thumbnailed" data-fileid="164189" data-unique="64tz5p00b" src="https://academy.hsoub.com/uploads/monthly_2024_12/07---.png.36d9af69d3e4bf521e1235f7b1adfb97.png"> </a>
</p>

<h2 id="-2">
	زيادة قيمة النتيجة
</h2>

<p>
	للعمل على نظام النقاط، نرفق سكريبت جديد بعقدة <code>ScoreLabel</code> ونعرّف فيه متغير النتيجة <code>score</code>
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1543_6" style=""><span class="pln">extends </span><span class="typ">Label</span><span class="pln">

var score </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span></pre>

<p>
	يجب أن تزداد قيمة النتيجة بمقدار <code>1</code> في كل مرة ندمّر عدو، ويمكننا الاستفادة من إشارة <code>squashed</code> الخاصة بالأعداء لمعرفة متى يحدث ذلك، ولكن بما أننا نستنسخ الأعداء من خلال الشيفرة البرمجية، فلا يمكننا توصيل إشارة العدو <code>ScroeLabel</code>عبر المحرر، ويتعين علينا بدلاً من ذلك إنشاء الاتصال من الشيفرة نفسها في كل مرة <a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D9%88%D9%84%D9%8A%D8%AF-%D8%A7%D9%84%D8%A3%D8%B9%D8%AF%D8%A7%D8%A1-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-r2459/" rel="">نولّد فيها عدو</a>.
</p>

<p>
	نفتح السكربت الرئيسي <code>main.gd</code>، ويمكن النقر فوق اسمه في العمود الأيسر لمحرر النصوص البرمجية إذا كان لا يزال مفتوحًا.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/08---main-gd.png.0c8ecdc9d502242fe69a64fcc68dded9.png" data-fileid="164190" data-fileext="png" rel=""><img alt="08 السكربيت الرئيسي main gd" class="ipsImage ipsImage_thumbnailed" data-fileid="164190" data-unique="yvoctco7v" src="https://academy.hsoub.com/uploads/monthly_2024_12/08---main-gd.png.0c8ecdc9d502242fe69a64fcc68dded9.png"> </a>
</p>

<p>
	أو بدلاً من ذلك يمكن النقر نقرًا مزدوجًا فوق ملف <code>main.gd</code> في نافذة نظام الملفات FileSystem، وإضافة السطر التالي أسفل دالة <code>‎</code><em>on</em>mob<em>timer</em>timeout()‎:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1543_8" style=""><span class="pln">func _on_mob_timer_timeout</span><span class="pun">():</span><span class="pln">
    </span><span class="com">#...</span><span class="pln">
    </span><span class="com"># نربط العدو مع قائمة النتيجة لتحديث النتيجة عند تدمير عدو</span><span class="pln">
    mob</span><span class="pun">.</span><span class="pln">squashed</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="pln">$UserInterface</span><span class="pun">/</span><span class="typ">ScoreLabel</span><span class="pun">.</span><span class="pln">_on_mob_squashed</span><span class="pun">.</span><span class="pln">bind</span><span class="pun">())</span></pre>

<p>
	يعني هذا السطر أنه عندما يصدر العدو إشارة <code>squashed</code>، ستستقبل عقدة <code>ScoreLabel</code> الإشارة وتستدعي الدالة <code>‎_on_mob_squashed ()‎</code>، ننتقل إلى السكربت <code>ScoreLabel.gd</code> لتعريف دالة رد النداء callback function المسماة <code>‎_on_mob_squashed()‎</code>، ونزيد هناك قيمة النتيجة ونحدث النص المعروض.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1543_10" style=""><span class="pln">func _on_mob_squashed</span><span class="pun">():</span><span class="pln">
    score </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
    text </span><span class="pun">=</span><span class="pln"> </span><span class="str">"Score: %s"</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> score</span></pre>

<p>
	يستخدم السطر الثاني قيمة متغير النتيجة <code>score</code> لاستبدال الموضع المؤقت <code>‎%s</code>، ويحوّل <a href="https://academy.hsoub.com/programming/game-development/%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot/" rel="">محرك جودو</a> القيم تلقائيًا إلى نص عند استخدام هذه الميزة، وهو أمر مفيد حين إخراج النص في التسميات النصية أو حين استخدام دالة <code>print()‎</code>.
</p>

<p>
	لمزيد من المعلومات حول التعامل مع النصوص يمكن مراجعة <a href="https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_format_string.html" rel="external nofollow">تنسيقات النصوص في GDScript</a> في توثيق جودو الرسمي.
</p>

<p>
	يمكننا الآن تشغيل اللعبة وتدمير بعض الأعداء لمشاهدة زيادة قيمة النتيجة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/9--.png.d37e3bbd30dddab3500e3fd49a8bf260.png" data-fileid="164191" data-fileext="png" rel=""><img alt="9 تشغيل اللعبة" class="ipsImage ipsImage_thumbnailed" data-fileid="164191" data-unique="zpt42nizs" src="https://academy.hsoub.com/uploads/monthly_2024_12/9--.png.d37e3bbd30dddab3500e3fd49a8bf260.png"> </a>
</p>

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

<h2 id="-3">
	إعادة اللعب
</h2>

<p>
	الآن سنضيف القدرة على إعادة اللعب بعد موت اللاعب، فعندما يموت اللاعب، سنعرض رسالة على الشاشة وننتظر إدخالًا منه.
</p>

<p>
	لتعد إلى المشهد الرئيسي <code>main.tscn</code> ونحدد عقدة <code>UserInterface</code> ثم نضيف عقدة فرعية جديدة من نوع <code>ColorRect</code> ونسميها <code>Retry</code> تُملأ هذه العقدة مستطيل بلون موحد وتُستخدم كطبقة تظليل لإعتام الشاشة.
</p>

<p>
	لاستخدامها على كامل نافذة العرض viewport، نستخدم قائمة Anchor Preset في شريط الأدوات.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/10--AnchorPreset.png.64062461566d8f8049f3164ed6855eaf.png" data-fileid="164192" data-fileext="png" rel=""><img alt="10 قائمة anchor preset" class="ipsImage ipsImage_thumbnailed" data-fileid="164192" data-unique="8zafvz3hk" src="https://academy.hsoub.com/uploads/monthly_2024_12/10--AnchorPreset.png.64062461566d8f8049f3164ed6855eaf.png"> </a>
</p>

<p>
	نفتحها ونطبق أمر مستطيل كامل Full Rect
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/11-.png.88974a86a64d3deb8ebbc4717e3391b4.png" data-fileid="164193" data-fileext="png" rel=""><img alt="11 مستطيل كامل" class="ipsImage ipsImage_thumbnailed" data-fileid="164193" data-unique="wnskkv4fj" src="https://academy.hsoub.com/uploads/monthly_2024_12/11-.png.88974a86a64d3deb8ebbc4717e3391b4.png"> </a>
</p>

<p>
	لم يحدث شيء لكن تتحرك المقابض الخضراء الأربعة فقط إلى زوايا مربع التحديد.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/12--.png.b18ac0a40543d1e0736e24a0f2e9cb94.png" data-fileid="164194" data-fileext="png" rel=""><img alt="12 مقابض الحركة" class="ipsImage ipsImage_thumbnailed" data-fileid="164194" data-unique="14orrr82k" src="https://academy.hsoub.com/uploads/monthly_2024_12/12--.png.b18ac0a40543d1e0736e24a0f2e9cb94.png"> </a>
</p>

<p>
	هذا لأن عقد واجهة المستخدم التي تحتوي على أيقونة خضراء تعمل مع نقاط الربط والهوامش بالنسبة إلى مربع إحاطة العنصر الأب، كما أن عقدة <code>UserInterface</code> هنا لها حجم صغير وعقدة <code>Retry</code> محدودة بها.
</p>

<p>
	نحدد <code>UserInterface</code> ونطبّق الأمر مستطيل كامل Full Rect عليها أيضًا من Anchor Preset، ويجب أن تغطي عقدة <code>Retry</code> الآن نافذة العرض بأكملها.
</p>

<p>
	دعنا نغيّر لونها لتعتيم منطقة اللعبة، سنحدد <code>Retry</code> وفي لوحة الفاحص Inspector، نضبط لون Color إلى لون غامق وشفاف في نفس الوقت. للقيام بذلك، نسحب مؤشر A إلى اليسار في مُحدّد اختيار اللون. يتحكم هذا المؤشر بقناة ألفا Alpha للون، أي معامل العتامة أو الشفافية.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/13--.png.d2231c9bac5559d12533c056f1d00ac3.png" data-fileid="164195" data-fileext="png" rel=""><img alt="13 لون العنصر" class="ipsImage ipsImage_thumbnailed" data-fileid="164195" data-unique="k2vbrfspg" src="https://academy.hsoub.com/uploads/monthly_2024_12/13--.png.d2231c9bac5559d12533c056f1d00ac3.png"> </a>
</p>

<p>
	بعدها نضيف عقدة <code>Label</code> كعقدة ابن لعقدة <code>Retry</code> ونكتب فيها نص مفاده اضغط على مفتاح Enter لإعادة المحاولة ومن ثم نطبق الأمر Center من Anchor Preset لنقلها وتثبيتها في مركز الشاشة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/14--.png.fc6a4cfeb218b209828b27b91f8d0253.png" data-fileid="164196" data-fileext="png" rel=""><img alt="14 مركز الشاشة" class="ipsImage ipsImage_thumbnailed" data-fileid="164196" data-unique="xbrz3casj" src="https://academy.hsoub.com/uploads/monthly_2024_12/14--.png.fc6a4cfeb218b209828b27b91f8d0253.png"> </a>
</p>

<h2 id="-4">
	برمجة خيار إعادة المحاولة
</h2>

<p>
	يمكننا الآن الانتقال إلى الشيفرة لإظهار وإخفاء عقدة <code>Retry</code> عند موت اللاعب وإعادة اللعب. نفتح السكربت الرئيسي <code>main.gd</code>.
</p>

<p>
	أولاً، نريد إخفاء التراكب في بداية اللعبة. لذا نضيف هذا السطر إلى الدالة <code>‎_ready()‎</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1543_12" style=""><span class="pln">func _ready</span><span class="pun">():</span><span class="pln">
    $UserInterface</span><span class="pun">/</span><span class="typ">Retry</span><span class="pun">.</span><span class="pln">hide</span><span class="pun">()</span></pre>

<p>
	بعدها عندما يتعرض اللاعب للاصطدام، نُظهر عنصر واجهة المستخدم Retry كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1543_14" style=""><span class="pln">func _on_player_hit</span><span class="pun">():</span><span class="pln">
    </span><span class="com">#...</span><span class="pln">
    $UserInterface</span><span class="pun">/</span><span class="typ">Retry</span><span class="pun">.</span><span class="pln">show</span><span class="pun">()</span></pre>

<p>
	أخيرًا، عندما تكون عقدة <code>Retry</code> مرئية، نحتاج إلى الاستماع إلى دخل اللاعب من أجل إعادة تشغيل اللعبة إذا ضغط على زر Enter. للقيام بذلك، نستخدم دالة رد النداء <code>‎_unhandled_input()‎</code>، والتي يجري تشغيلها عند أي إدخال.
</p>

<p>
	إذا ضغط اللاعب على زر الإدخال المحدد مسبقًا <code>ui_accept</code> وكانت عقدة <code>Retry</code> مرئية، فإننا نعيد تحميل المشهد الحالي.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1543_16" style=""><span class="pln">func _unhandled_input</span><span class="pun">(</span><span class="pln">event</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">is_action_pressed</span><span class="pun">(</span><span class="str">"ui_accept"</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> $UserInterface</span><span class="pun">/</span><span class="typ">Retry</span><span class="pun">.</span><span class="pln">visible</span><span class="pun">:</span><span class="pln">
        </span><span class="com"># هذا يعيد تشغيل المشهد الحالي</span><span class="pln">
        get_tree</span><span class="pun">().</span><span class="pln">reload_current_scene</span><span class="pun">()</span></pre>

<p>
	تمنحنا دالة <code>get_tree()‎</code> الوصول إلى كائن SceneTree العام الذي يسمح لنا بإعادة تحميل وتشغيل المشهد الحالي.
</p>

<h2 id="-5">
	إضافة المؤثرات الصوتية
</h2>

<p>
	سنستخدم الآن ميزة أخرى في جودو لإضافة مؤثرات صوتية تعمل بشكل مستمر في الخلفية ألا وهي التحميل التلقائي autoloads.
</p>

<p>
	لتشغيل الصوت، كل ما علينا فعله هو إضافة عقدة <code>AudioStreamPlayer</code> إلى المشهد الخاص بنا وإرفاق ملف صوت بها. عند بدء تشغيل المشهد، يمكن أن يعمل الصوت تلقائيًا. ومع ذلك، عند إعادة تحميل المشهد، كما نفعل لإعادة التشغيل نعيد تعيين عقد الصوت، وتبدأ المؤثرات صوتية من البداية.
</p>

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

<p>
	ننشئ مشهدًا جديدًا بالانتقال إلى قائمة مشهد Scene والنقر على مشهد جديد New Scene أو باستخدام الرمز + بجوار المشهد المفتوح حاليًا.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/15--.png.72ac48f15425f3d3ca71d9f3067d45ad.png" data-fileid="164197" data-fileext="png" rel=""><img alt="15 مشهد جديد" class="ipsImage ipsImage_thumbnailed" data-fileid="164197" data-unique="nlqfhlmd9" src="https://academy.hsoub.com/uploads/monthly_2024_12/15--.png.72ac48f15425f3d3ca71d9f3067d45ad.png"> </a>
</p>

<p>
	ننقر فوق الزر عقدة أخرى Other Node لإنشاء <code>AudioStreamPlayer</code> ونعيد تسميتها إلى <code>MusicPlayer</code>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/16---.png.5c65fdc0c0366b3282d5a75cdbe7f2cc.png" data-fileid="164198" data-fileext="png" rel=""><img alt="16 عقدة التأثيرات الصوتية" class="ipsImage ipsImage_thumbnailed" data-fileid="164198" data-unique="g8tty2phs" src="https://academy.hsoub.com/uploads/monthly_2024_12/16---.png.5c65fdc0c0366b3282d5a75cdbe7f2cc.png"> </a>
</p>

<p>
	أضفنا مقطع صوتي إلى المسار <code>art/‎</code> وهو <code>House In a Forest Loop.ogg</code>. لسحب هذا المقطع إلى اللعبة، نضغط عليه ثم نسحبه لخاصية Stream في لوحة الفاحص Inspector. بعد ذلك، نفعّل خيار التشغيل التلقائي Autoplay لتشغيل المؤثرات الصوتية تلقائيًا عند بدء اللعبة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/17---.png.34c26fc0c9015732c3a30769abb7b55d.png" data-fileid="164199" data-fileext="png" rel=""><img alt="17 خيار التشغيل التلقائي" class="ipsImage ipsImage_thumbnailed" data-fileid="164199" data-unique="lou2ykxac" src="https://academy.hsoub.com/uploads/monthly_2024_12/17---.thumb.png.a5622c4b5d728583192e73f113268717.png"> </a>
</p>

<p>
	نحفظ المشهد باسم <code>MusicPlayer.tscn</code>، بعدها علينا تسجيله كمشهد تحميل تلقائي، ولفعل ذلك نتوجه إلى قائمة مشروع Project ومن ثم إعدادات المشروع Project Settings وتنقر على تبويبة التحميل التلقائي Autoload.
</p>

<p>
	نحتاج إلى إدخال المسار إلى المشهد في حقل المسار Path، ولفعل ذلك ننقر فوق أيقونة المجلد لفتح مستعرض الملفات وننقر نقرًا مزدوجًا فوق <code>MusicPlayer.tscn</code>، ثم ننقر فوق الزر إضافة على اليمين لتسجيل العقدة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/18--.png.c49fd2690c7b439383ef497a2cb69daa.png" data-fileid="164200" data-fileext="png" rel=""><img alt="18 تسجيل العقدة" class="ipsImage ipsImage_thumbnailed" data-fileid="164200" data-unique="l88fdb453" src="https://academy.hsoub.com/uploads/monthly_2024_12/18--.png.c49fd2690c7b439383ef497a2cb69daa.png"> </a>
</p>

<p>
	يجري الآن تحميل <code>MusicPlayer.tscn</code> في أي مشهد نفتحه أو نشغله. لذا، إذا شغلنا اللعبة الآن، تعمل المؤثرات الصوتية تلقائيًا في أي مشهد.
</p>

<p>
	قبل أن ننهي هذا الدرس، لنلقي نظرة سريعة على كيفية عمل الميزة، عند تشغيل اللعبة، تتغير نافذة Scene وتمنحنا تبويبتين هما عن بعد Remote ومحلي Local.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/19--Remote.png.c2c26fd63c0bac37b2d6a5fc0f17ef86.png" data-fileid="164201" data-fileext="png" rel=""><img alt="19 تبويب remote" class="ipsImage ipsImage_thumbnailed" data-fileid="164201" data-unique="xvsw3wox9" src="https://academy.hsoub.com/uploads/monthly_2024_12/19--Remote.png.c2c26fd63c0bac37b2d6a5fc0f17ef86.png"> </a>
</p>

<p>
	يتيح لنا تبويب Remote تصوّر شجرة عقد اللعبة التي نعمل عليها. سنرى هناك العقدة الرئيسية Main وكل ما يشتمل عليه المشهد والأعداء التي استنسخناها في الأسفل.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/20---.png.03075a62e16b2eac7e2128b99e4cd3fb.png" data-fileid="164202" data-fileext="png" rel=""><img alt="20 شجرة عقد اللعبة" class="ipsImage ipsImage_thumbnailed" data-fileid="164202" data-unique="wj5xf0ltx" src="https://academy.hsoub.com/uploads/monthly_2024_12/20---.png.03075a62e16b2eac7e2128b99e4cd3fb.png"> </a>
</p>

<p>
	في الأعلى توجد العقدة<code>MusicPlayer</code> التي جرى تحميلها تلقائيًا، والعقدة الجذر root هي نافذة عرض لعبتنا.
</p>

<h2 id="-6">
	شيفرة المشهد الرئيسي
</h2>

<p>
	فيما يلي سكربت <code>main.gd</code> الكامل بلغة GDScript للرجوع إليه
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1543_18" style=""><span class="pln">extends </span><span class="typ">Node</span><span class="pln">

</span><span class="lit">@export</span><span class="pln"> var mob_scene</span><span class="pun">:</span><span class="pln"> </span><span class="typ">PackedScene</span><span class="pln">

func _ready</span><span class="pun">():</span><span class="pln">
    $UserInterface</span><span class="pun">/</span><span class="typ">Retry</span><span class="pun">.</span><span class="pln">hide</span><span class="pun">()</span><span class="pln">


func _on_mob_timer_timeout</span><span class="pun">():</span><span class="pln">
    </span><span class="com"># إنشاء نسخة جديدة من مشهد العدو</span><span class="pln">
    var mob </span><span class="pun">=</span><span class="pln"> mob_scene</span><span class="pun">.</span><span class="pln">instantiate</span><span class="pun">()</span><span class="pln">

    </span><span class="com"># ‎‫اختيار مكان عشوائي على SpawnPath</span><span class="pln">
    </span><span class="com"># ‫نخزن المرجع في عقدة SpawnLocation</span><span class="pln">
    var mob_spawn_location </span><span class="pun">=</span><span class="pln"> get_node</span><span class="pun">(</span><span class="str">"SpawnPath/SpawnLocation"</span><span class="pun">)</span><span class="pln">
    </span><span class="com"># ونعطيه انزياح عشوائي</span><span class="pln">
    mob_spawn_location</span><span class="pun">.</span><span class="pln">progress_ratio </span><span class="pun">=</span><span class="pln"> randf</span><span class="pun">()</span><span class="pln">

    var player_position </span><span class="pun">=</span><span class="pln"> $Player</span><span class="pun">.</span><span class="pln">position
    mob</span><span class="pun">.</span><span class="pln">initialize</span><span class="pun">(</span><span class="pln">mob_spawn_location</span><span class="pun">.</span><span class="pln">position</span><span class="pun">,</span><span class="pln"> player_position</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># توليد العدو عن طريق إضافته للمشهد الرئيسي</span><span class="pln">
    add_child</span><span class="pun">(</span><span class="pln">mob</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># نربط العدو مع قائمة النتيجة لتحديث النتيجة عند تدمير عدو </span><span class="pln">
    mob</span><span class="pun">.</span><span class="pln">squashed</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="pln">$UserInterface</span><span class="pun">/</span><span class="typ">ScoreLabel</span><span class="pun">.</span><span class="pln">_on_mob_squashed</span><span class="pun">.</span><span class="pln">bind</span><span class="pun">())</span><span class="pln">

func _on_player_hit</span><span class="pun">():</span><span class="pln">
    $MobTimer</span><span class="pun">.</span><span class="pln">stop</span><span class="pun">()</span><span class="pln">
    $UserInterface</span><span class="pun">/</span><span class="typ">Retry</span><span class="pun">.</span><span class="pln">show</span><span class="pun">()</span><span class="pln">

func _unhandled_input</span><span class="pun">(</span><span class="pln">event</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">is_action_pressed</span><span class="pun">(</span><span class="str">"ui_accept"</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> $UserInterface</span><span class="pun">/</span><span class="typ">Retry</span><span class="pun">.</span><span class="pln">visible</span><span class="pun">:</span><span class="pln">
        </span><span class="com"># هذا يعيد تشغيل المشهد</span><span class="pln">
        get_tree</span><span class="pun">().</span><span class="pln">reload_current_scene</span><span class="pun">()</span></pre>

<h2 id="-7">
	الخلاصة
</h2>

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

<p>
	ترجمة - وبتصرف - لقسم <a href="https://docs.godotengine.org/en/stable/getting_started/first_3d_game/08.score_and_replay.html" rel="external nofollow">Score and replay</a> من توثيق جودو الرسمي.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/game-development/%D9%82%D8%AA%D9%84-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D8%B9%D9%86%D8%AF-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D8%AF%D8%A7%D9%85-%D8%A8%D8%A7%D9%84%D8%B9%D8%AF%D9%88-%D8%B6%D9%85%D9%86-%D9%84%D8%B9%D8%A8%D8%A9-3d-%D8%A8%D8%AC%D9%88%D8%AF%D9%88-r2471/" rel="">قتل اللاعب عند الاصطدام بالعدو ضمن لعبة 3D بجودو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%85%D8%B4%D9%87%D8%AF-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D9%88%D8%B9%D9%85%D9%84%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A5%D8%AF%D8%AE%D8%A7%D9%84-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D9%88%D8%AF%D9%88-r2447/" rel="">إعداد مشهد اللاعب وعمليات الإدخال في لعبة ثلاثية الأبعاد باستخدام جودو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D9%82%D9%81%D8%B2-%D9%88%D8%AA%D8%AF%D9%85%D9%8A%D8%B1-%D8%A7%D9%84%D8%A3%D8%B9%D8%AF%D8%A7%D8%A1-%D8%B6%D9%85%D9%86-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-r2464/" rel="">القفز وتدمير الأعداء ضمن لعبة ثلاثية الأبعاد في جودو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D9%88%D9%84%D9%8A%D8%AF-%D8%A7%D9%84%D8%A3%D8%B9%D8%AF%D8%A7%D8%A1-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-r2459/" rel="">توليد الأعداء في لعبة ثلاثية الأبعاد في محرك الألعاب جودو</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2472</guid><pubDate>Sat, 21 Dec 2024 15:06:01 +0000</pubDate></item><item><title>&#x642;&#x62A;&#x644; &#x627;&#x644;&#x644;&#x627;&#x639;&#x628; &#x639;&#x646;&#x62F; &#x627;&#x644;&#x627;&#x635;&#x637;&#x62F;&#x627;&#x645; &#x628;&#x627;&#x644;&#x639;&#x62F;&#x648; &#x636;&#x645;&#x646; &#x644;&#x639;&#x628;&#x629; 3D &#x628;&#x62C;&#x648;&#x62F;&#x648;</title><link>https://academy.hsoub.com/programming/game-development/%D9%82%D8%AA%D9%84-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D8%B9%D9%86%D8%AF-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D8%AF%D8%A7%D9%85-%D8%A8%D8%A7%D9%84%D8%B9%D8%AF%D9%88-%D8%B6%D9%85%D9%86-%D9%84%D8%B9%D8%A8%D8%A9-3d-%D8%A8%D8%AC%D9%88%D8%AF%D9%88-r2471/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_12/--.png.396d6414853c314ea85bc17369ef6594.png" /></p>
<p>
	أضفنا في <a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D9%82%D9%81%D8%B2-%D9%88%D8%AA%D8%AF%D9%85%D9%8A%D8%B1-%D8%A7%D9%84%D8%A3%D8%B9%D8%AF%D8%A7%D8%A1-%D8%B6%D9%85%D9%86-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-r2464/" rel="">المقال السابق </a>خاصية القفز والهبوط فوق الأعداء وتدميرهم ضمن لعبة ثلاثية الأبعاد، لكننا لم نجعل اللاعب يموت بعد في حال اصطدم بعدو ما موجود على الأرض، لذا لنعمل على تحقيق هذا الأمر، ونشرح في فقراتنا التالية كيفية تحقيقه خطوة بخطوة عن طريق استخدام هياكل التصادم Hitboxes وعقد Area في محرك جودو.
</p>

<h2 id="-1">
	تحديد ما إذا كان اللاعب على الأرض
</h2>

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

<p>
	أو يمكننا بدلاً من ذلك، استخدام عقدة <code>Area3D</code> والتي تعمل بشكل جيد مع هياكل التصادم Hitboxes في <a href="https://academy.hsoub.com/programming/game-development/%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot/" rel="">محرك جودو</a>. حيث يمكن تعريف منطقة معينة حول اللاعب. فإذا دخل العدو في هذه المنطقة أثناء وجود اللاعب على الأرض، سنفعّل حدث الاصطدام وننفذ منطق موت اللاعب وهو الأسلوب الذي سنعتمده.
</p>

<h2>
	هيكل تصادم Hitbox مع عقدة Area
</h2>

<p>
	لنعد إلى مشهد <code>player.tscn</code> ونضف عقدة فرعية جديدة من النوع <code>Area3D</code>، سنطلق عليها اسم <code>MobDetector</code> أي كاشف الأعداء، ومن ثم سنضيف العقدة <code>CollisionShape3D</code> كعقدة فرعية لها.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/01--CollisionShape.png.8f5b427c0c740ec7f39576c09fecbada.png" data-fileid="164177" data-fileext="png" rel=""><img alt="01 العقدة collisionshape" class="ipsImage ipsImage_thumbnailed" data-fileid="164177" data-unique="ibcn3up0b" src="https://academy.hsoub.com/uploads/monthly_2024_12/01--CollisionShape.png.8f5b427c0c740ec7f39576c09fecbada.png"> </a>
</p>

<p>
	بعدها سنعين شكله ليكون أسطوانيًا وذلك باختيار NewClindershape في قائمة الفاحص Inspector.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/02-New-CylinderShape.jpg.1b146d4aa6ba0342948a65bad93b7b94.jpg" data-fileid="164178" data-fileext="jpg" rel=""><img alt="02 new cylindershape" class="ipsImage ipsImage_thumbnailed" data-fileid="164178" data-unique="dnvqsuama" src="https://academy.hsoub.com/uploads/monthly_2024_12/02-New-CylinderShape.jpg.1b146d4aa6ba0342948a65bad93b7b94.jpg"> </a>
</p>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/03---.jpg.0c0a286ca4198252933dd9d9e5d7299a.jpg" data-fileid="164179" data-fileext="jpg" rel=""><img alt="03 تقليل ارتفاع الأسطوانة" class="ipsImage ipsImage_thumbnailed" data-fileid="164179" data-unique="aa8mitm9q" src="https://academy.hsoub.com/uploads/monthly_2024_12/03---.jpg.0c0a286ca4198252933dd9d9e5d7299a.jpg"> </a>
</p>

<p>
	نريد أيضًا أن تكون الأسطوانة أوسع من الكرة، فبهذه الطريقة، يُضرب اللاعب قبل الاصطدام ويجري دفعه فوق صندوق الاصطدام الخاص بالعدو، فكلما كانت الأسطوانة أوسع، كلما كان من الأسهل قتل اللاعب.
</p>

<p>
	بعد ذلك سنحدد عقدة <code>MobDetector</code> مرة أخرى، ونعطّل خاصية قابلية المراقبة Monitorable في الفاحص Inspector، وهذا سيجعل من العقد الفيزيائية الأخرى غير قادرة على اكتشاف المنطقة، إذ تسمح خاصية قابلية المراقبة Monitorable باكتشاف التصادمات، بعدها سنريل طبقة الاصطدام من الطبقة Layer في قسم التصادم Collision ونضبط القناع على طبقة الأعداء.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/04--Monitorable.jpg.d3f17ae14c07bf0b62bf119ee6d757ef.jpg" data-fileid="164180" data-fileext="jpg" rel=""><img alt="04 تعطيل monitorable" class="ipsImage ipsImage_thumbnailed" data-fileid="164180" data-unique="vw81tboos" src="https://academy.hsoub.com/uploads/monthly_2024_12/04--Monitorable.jpg.d3f17ae14c07bf0b62bf119ee6d757ef.jpg"> </a>
</p>

<p>
	عندما تكتشف المناطق تصادمًا ستصدر إشارة، وكي نوصّل هذه الإشارة إلى عقدة اللاعب <code>Player</code> نحدد العقدة <code>MobDetector</code> وننتقل إلى التبويب Node في نافذة الفاحص Inspector، وننقر مرتين فوق إشارة <code>body_entered</code> التي جرى وصلها بعقدة <code>Player</code>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/05--body_entered.jpg.35742056c51706aee0dc17c4a9f2245a.jpg" data-fileid="164181" data-fileext="jpg" rel=""><img alt="05 إشارة body entered" class="ipsImage ipsImage_thumbnailed" data-fileid="164181" data-unique="9ewhf6jkr" src="https://academy.hsoub.com/uploads/monthly_2024_12/05--body_entered.jpg.35742056c51706aee0dc17c4a9f2245a.jpg"> </a>
</p>

<p>
	تطلق العقدة <code>MobDetctor</code> إشارة <code>body_entered</code> عندما تدخلها عقدة <code>CharacterBody3D</code> أو <code>RigidBody3D</code>، لكن نظرًا لأن العقدة <code>MobDetctor </code>مهيأة لتعمل فقط مع الطبقات الفيزيائية الخاصة بالأعداء، فإنها ستكتشف فقط الأجسام التي تنتمي إلى هذه الطبقات، أي ستكتشف فقط عقد العدو.
</p>

<p>
	من الناحية البرمجية، يتوجب علينا القيام بأمرين الأول إصدار إشارة سنستخدمها لاحقًا لإنهاء اللعبة والثاني تدمير اللاعب، ويمكننا تغليف هذه العمليات في دالة <code>die()‎</code> التي تساعدنا بوضع دلالة مفيدة إلى شيفرتنا كالتالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5706_6" style=""><span class="com"># تنشر عندما يصطدم اللاعب بعدو</span><span class="pln">
</span><span class="com"># ضع هذه في أعلى السكريبت</span><span class="pln">
signal hit

</span><span class="com"># وأضف هذه الدالة في الأسفل</span><span class="pln">
func die</span><span class="pun">():</span><span class="pln">
    hit</span><span class="pun">.</span><span class="pln">emit</span><span class="pun">()</span><span class="pln">
    queue_free</span><span class="pun">()</span><span class="pln">


func _on_mob_detector_body_entered</span><span class="pun">(</span><span class="pln">body</span><span class="pun">):</span><span class="pln">
    die</span><span class="pun">()</span></pre>

<p>
	جرب اللعبة مرة أخرى بالضغط على <code>F5</code>. إذا أعددنا كل شيء بشكل صحيح، فيجب أن تموت الشخصية عندما يصطدم العدو بهيكل التصادم، لاحظ أن السطر التالي سيعطي خطأ في حال عدم وجود عقدة لاعب <code>Player</code> ففي هذه الحالة لن يكون هناك مرجع إلى <code>‎</code>$Player!
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5706_8" style=""><span class="pln">var player_position </span><span class="pun">=</span><span class="pln"> $Player</span><span class="pun">.</span><span class="pln">position</span></pre>

<p>
	لاحظ أيضًا أن اصطدام العدو باللاعب وموته يعتمد على حجم وموضع اللاعب<code>Player</code> وأشكال تصادم العدو <code>mob</code>، قد تحتاج إلى نقلها وتغيير حجمها للحصول على تجربة واقعية في اللعبة.
</p>

<h2>
	إنهاء اللعبة
</h2>

<p>
	يمكننا استخدام إشارة ضرب <code>hit</code> اللاعب <code>player</code> لإنهاء اللعبة، فكل ما يتعين علينا فعله هو توصيله بالعقدة الرئيسية <code>Main</code> وإيقاف <code>MobTimer</code> كرد فعل.
</p>

<p>
	لنفتح المشهد الرئيسي للعبة <code>main.tscn</code>، ونحدد عقدة اللاعب <code>Player</code>، وفي نافذة العقدة Node نوصّل إشارة <code>hit</code> الخاصة بها بالعقدة الرئيسية <code>Main</code>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_12/06--hit.jpg.c5e5a317ce5a61877cc201f0ae78658b.jpg" data-fileid="164182" data-fileext="jpg" rel=""><img alt="06 إشارة hit" class="ipsImage ipsImage_thumbnailed" data-fileid="164182" data-unique="8lf6a8457" src="https://academy.hsoub.com/uploads/monthly_2024_12/06--hit.jpg.c5e5a317ce5a61877cc201f0ae78658b.jpg"> </a>
</p>

<p>
	ثم نحصل على المؤقت ونوقفه في الدالة <code>‎_on_player_hit()‎</code>.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5706_11" style=""><span class="pln">func _on_player_hit</span><span class="pun">():</span><span class="pln">
    $MobTimer</span><span class="pun">.</span><span class="pln">stop</span><span class="pun">()</span></pre>

<p>
	إذا جربنا اللعبة الآن، فستتوقف الأعداء عن الظهور عندما يموت اللاعب، وستغادر الأعداء المتبقية الشاشة.
</p>

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

<h2>
	الشيفرة النهائية للعبة ثلاثية الأبعاد في جودو
</h2>

<p>
	فيما يلي الشيفرات الكاملة للعقد الرئيسية <code>Main</code> و<code>Mob</code> و<code>Player</code> للرجوع إليها.
</p>

<h3>
	شيفرة المشهد الرئيسي
</h3>

<p>
	لنبدأ بسكربت <code>main.gd</code> الذي يعمل على إنشاء الأعداء بشكل عشوائي في مواقع مختلفة على المسار المحدد عند مرور الوقت بناءً على مؤقت العدو، ويحدد هدف العدو وهو اللاعب كما أنه يوقف ظهور الأعداء عندما يتعرض اللاعب للإصابة.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5706_13" style=""><span class="pln">extends </span><span class="typ">Node</span><span class="pln">

</span><span class="lit">@export</span><span class="pln"> var mob_scene</span><span class="pun">:</span><span class="pln"> </span><span class="typ">PackedScene</span><span class="pln">


func _on_mob_timer_timeout</span><span class="pun">():</span><span class="pln">
    </span><span class="com"># إنشاء نسخة جديدة من مشهد العدو</span><span class="pln">
    var mob </span><span class="pun">=</span><span class="pln"> mob_scene</span><span class="pun">.</span><span class="pln">instantiate</span><span class="pun">()</span><span class="pln">

    </span><span class="com"># اختيار مكان عشوائي على‫ SpawnPath</span><span class="pln">
    </span><span class="com"># ‫نخزن المرجع على عقدة SpawnLocation</span><span class="pln">
    var mob_spawn_location </span><span class="pun">=</span><span class="pln"> get_node</span><span class="pun">(</span><span class="str">"SpawnPath/SpawnLocation"</span><span class="pun">)</span><span class="pln">
    </span><span class="com"># ونعطيه انزياح عشوائي</span><span class="pln">
    mob_spawn_location</span><span class="pun">.</span><span class="pln">progress_ratio </span><span class="pun">=</span><span class="pln"> randf</span><span class="pun">()</span><span class="pln">

    var player_position </span><span class="pun">=</span><span class="pln"> $Player</span><span class="pun">.</span><span class="pln">position
    mob</span><span class="pun">.</span><span class="pln">initialize</span><span class="pun">(</span><span class="pln">mob_spawn_location</span><span class="pun">.</span><span class="pln">position</span><span class="pun">,</span><span class="pln"> player_position</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># خلق العدو عن طريق إضافته إلى المشهد الرئيسي</span><span class="pln">
    add_child</span><span class="pun">(</span><span class="pln">mob</span><span class="pun">)</span><span class="pln">

func _on_player_hit</span><span class="pun">():</span><span class="pln">
    $MobTimer</span><span class="pun">.</span><span class="pln">stop</span><span class="pun">()</span></pre>

<h3>
	شيفرة العدو
</h3>

<p>
	السكربت التالي هو سكربت العدو <code>Mob.gd</code> الذي يتعامل مع حركة العدو في اللعبة، حيث يوجه العدو عشوائيًا نحو اللاعب، ويحدد سرعته بشكل عشوائي، ويتأكد من تدمير العدو عند خروجه من الشاشة أو عندما يقفز اللاعب عليه.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5706_17" style=""><span class="pln">extends </span><span class="typ">CharacterBody3D</span><span class="pln">

</span><span class="com"># السرعة الدنيا للعدو مقدرة بالمتر في الثانية</span><span class="pln">
</span><span class="lit">@export</span><span class="pln"> var min_speed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">10</span><span class="pln">
</span><span class="com"># السرعة القصوى للعدو مقدرة بالمتر في الثانية</span><span class="pln">
</span><span class="lit">@export</span><span class="pln"> var max_speed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">18</span><span class="pln">

</span><span class="com"># تنبثق عندما يقفز اللاعب على عدو</span><span class="pln">
signal squashed

func _physics_process</span><span class="pun">(</span><span class="pln">_delta</span><span class="pun">):</span><span class="pln">
    move_and_slide</span><span class="pun">()</span><span class="pln">

</span><span class="com"># يتم استدعاء هذه الدالة من المشهد الأساسي</span><span class="pln">
func initialize</span><span class="pun">(</span><span class="pln">start_position</span><span class="pun">,</span><span class="pln"> player_position</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># نحدد مكان العدو عن طريق وضعه في‫ start_position</span><span class="pln">
    </span><span class="com"># وندوره نحو‫ player_position لينظر إلى اللاعب</span><span class="pln">
    look_at_from_position</span><span class="pun">(</span><span class="pln">start_position</span><span class="pun">,</span><span class="pln"> player_position</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">UP</span><span class="pun">)</span><span class="pln">
    </span><span class="com">#تدوير العدو عشوائيًا ضمن مجال -90 و +90 درجة  </span><span class="pln">
    </span><span class="com"># لكي لا تتحرك مباشرة نحو اللاعب</span><span class="pln">
    rotate_y</span><span class="pun">(</span><span class="pln">randf_range</span><span class="pun">(-</span><span class="pln">PI </span><span class="pun">/</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> PI </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">
    var random_speed </span><span class="pun">=</span><span class="pln"> randi_range</span><span class="pun">(</span><span class="pln">min_speed</span><span class="pun">,</span><span class="pln"> max_speed</span><span class="pun">)</span><span class="pln">
    </span><span class="com"># نحسب سرعة امامية تمثل السرعة</span><span class="pln">
    velocity </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">FORWARD </span><span class="pun">*</span><span class="pln"> random_speed
    </span><span class="com"># ‫ثم ندور شعاع السرعة اعتمادًا على دوران العدو حول Y</span><span class="pln">
    </span><span class="com"># للحركة في الاتجاه الذي ينظر إليه العدو</span><span class="pln">
    velocity </span><span class="pun">=</span><span class="pln"> velocity</span><span class="pun">.</span><span class="pln">rotated</span><span class="pun">(</span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">UP</span><span class="pun">,</span><span class="pln"> rotation</span><span class="pun">.</span><span class="pln">y</span><span class="pun">)</span><span class="pln">

func _on_visible_on_screen_notifier_3d_screen_exited</span><span class="pun">():</span><span class="pln">
    queue_free</span><span class="pun">()</span><span class="pln">

func squash</span><span class="pun">():</span><span class="pln">
    squashed</span><span class="pun">.</span><span class="pln">emit</span><span class="pun">()</span><span class="pln">
    queue_free</span><span class="pun">()</span><span class="pln"> </span><span class="com"># تدمير العقدة</span></pre>

<p>
	 
</p>

<h3>
	شيفرة اللاعب
</h3>

<p>
	وأخيرًا، نوضح سكربت <code>Player.gd</code> الذي يتحكم في حركة اللاعب كالقفز والتحرك على الأرض والسقوط في الهواء، فعندما يصطدم اللاعب بالعدو من الأعلى، سيدمره باستخدام الدالة <code>squash()‎</code> ويعطى اللاعب دفعة قفز عمودية، وعند اصطدامه مع العدو  يموت باستخدام الدالة <code>‎die()‎</code>.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5706_19" style=""><span class="pln">extends </span><span class="typ">CharacterBody3D</span><span class="pln">

signal hit

</span><span class="com"># سرعة حركة اللاعب مقدرة بالمتر في الثانية</span><span class="pln">
</span><span class="lit">@export</span><span class="pln"> var speed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">14</span><span class="pln">
</span><span class="com"># التسارع نحو الأسفل في الهواء مقدرة بالمتر في الثانية مربع </span><span class="pln">
</span><span class="lit">@export</span><span class="pln"> var fall_acceleration </span><span class="pun">=</span><span class="pln"> </span><span class="lit">75</span><span class="pln">
</span><span class="com"># الدفعة العامودية المطبقة على الشخصية عند القفز مقدرة بالمتر في الثانية</span><span class="pln">
</span><span class="lit">@export</span><span class="pln"> var jump_impulse </span><span class="pun">=</span><span class="pln"> </span><span class="lit">20</span><span class="pln">
</span><span class="com"># الدفعة العامودية المطبقة على الشخصية عند القفز على عدو مقدرة بالمتر في الثانية</span><span class="pln">
</span><span class="lit">@export</span><span class="pln"> var bounce_impulse </span><span class="pun">=</span><span class="pln"> </span><span class="lit">16</span><span class="pln">

var target_velocity </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">ZERO


func _physics_process</span><span class="pun">(</span><span class="pln">delta</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># أنشأنا متغير محلي لتخزين دخل الاتجاه</span><span class="pln">
    var direction </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">ZERO

    </span><span class="com"># نتحقق من كل دخل حركة ونحدث الاتجاه حسبه</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">is_action_pressed</span><span class="pun">(</span><span class="str">"move_right"</span><span class="pun">):</span><span class="pln">
        direction</span><span class="pun">.</span><span class="pln">x </span><span class="pun">=</span><span class="pln"> direction</span><span class="pun">.</span><span class="pln">x </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">is_action_pressed</span><span class="pun">(</span><span class="str">"move_left"</span><span class="pun">):</span><span class="pln">
        direction</span><span class="pun">.</span><span class="pln">x </span><span class="pun">=</span><span class="pln"> direction</span><span class="pun">.</span><span class="pln">x </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">is_action_pressed</span><span class="pun">(</span><span class="str">"move_back"</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># ‫لاحظ أننا نعمل المحورين x و z الخاصين بالشعاع</span><span class="pln">
        </span><span class="com"># المستوي‫ XZ هو مستوي الأرض في ثلاثي الأبعاد</span><span class="pln">
        direction</span><span class="pun">.</span><span class="pln">z </span><span class="pun">=</span><span class="pln"> direction</span><span class="pun">.</span><span class="pln">z </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">is_action_pressed</span><span class="pun">(</span><span class="str">"move_forward"</span><span class="pun">):</span><span class="pln">
        direction</span><span class="pun">.</span><span class="pln">z </span><span class="pun">=</span><span class="pln"> direction</span><span class="pun">.</span><span class="pln">z </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pln">

    </span><span class="com"># منع الحركة القطرية السريعة</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> direction </span><span class="pun">!=</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">ZERO</span><span class="pun">:</span><span class="pln">
        direction </span><span class="pun">=</span><span class="pln"> direction</span><span class="pun">.</span><span class="pln">normalized</span><span class="pun">()</span><span class="pln">
        $Pivot</span><span class="pun">.</span><span class="pln">look_at</span><span class="pun">(</span><span class="pln">position </span><span class="pun">+</span><span class="pln"> direction</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">UP</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># السرعة الأرضية</span><span class="pln">
    target_velocity</span><span class="pun">.</span><span class="pln">x </span><span class="pun">=</span><span class="pln"> direction</span><span class="pun">.</span><span class="pln">x </span><span class="pun">*</span><span class="pln"> speed
    target_velocity</span><span class="pun">.</span><span class="pln">z </span><span class="pun">=</span><span class="pln"> direction</span><span class="pun">.</span><span class="pln">z </span><span class="pun">*</span><span class="pln"> speed

    </span><span class="com"># السرعة العمودية</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> is_on_floor</span><span class="pun">():</span><span class="pln"> </span><span class="com"># إذا كان في الهواء يسقط على الأرض أي الجاذبية</span><span class="pln">
        target_velocity</span><span class="pun">.</span><span class="pln">y </span><span class="pun">=</span><span class="pln"> target_velocity</span><span class="pun">.</span><span class="pln">y </span><span class="pun">-</span><span class="pln"> </span><span class="pun">(</span><span class="pln">fall_acceleration </span><span class="pun">*</span><span class="pln"> delta</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># القفز</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> is_on_floor</span><span class="pun">()</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">is_action_just_pressed</span><span class="pun">(</span><span class="str">"jump"</span><span class="pun">):</span><span class="pln">
        target_velocity</span><span class="pun">.</span><span class="pln">y </span><span class="pun">=</span><span class="pln"> jump_impulse

    </span><span class="com"># كرر خلال كل الاصطدامات التي تحصل في الإطار</span><span class="pln">
    </span><span class="com"># ‫يكون ذلك بلغة C كالتالي</span><span class="pln">
</span><span class="com"># (int i = 0; i &lt; collisions.Count; i++)</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> index </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="pln">get_slide_collision_count</span><span class="pun">()):</span><span class="pln">
        </span><span class="com"># نحصل على واحد من التصادمات مع اللاعب</span><span class="pln">
        var collision </span><span class="pun">=</span><span class="pln"> get_slide_collision</span><span class="pun">(</span><span class="pln">index</span><span class="pun">)</span><span class="pln">

        </span><span class="com"># إذا كان التصادم مع الأرض</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> collision</span><span class="pun">.</span><span class="pln">get_collider</span><span class="pun">()</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">:</span><span class="pln">
            </span><span class="kwd">continue</span><span class="pln">

        </span><span class="com"># إذا كان التصادم مع العدو</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> collision</span><span class="pun">.</span><span class="pln">get_collider</span><span class="pun">().</span><span class="pln">is_in_group</span><span class="pun">(</span><span class="str">"mob"</span><span class="pun">):</span><span class="pln">
            var mob </span><span class="pun">=</span><span class="pln"> collision</span><span class="pun">.</span><span class="pln">get_collider</span><span class="pun">()</span><span class="pln">
            </span><span class="com"># نتحقق أننا نصدمه من الأعلى</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">UP</span><span class="pun">.</span><span class="pln">dot</span><span class="pun">(</span><span class="pln">collision</span><span class="pun">.</span><span class="pln">get_normal</span><span class="pun">())</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">0.1</span><span class="pun">:</span><span class="pln">
                </span><span class="com"># إذا كان كذلك نسحقه</span><span class="pln">
                mob</span><span class="pun">.</span><span class="pln">squash</span><span class="pun">()</span><span class="pln">
                target_velocity</span><span class="pun">.</span><span class="pln">y </span><span class="pun">=</span><span class="pln"> bounce_impulse
                </span><span class="com"># يمنع أي استدعاءات مكررة</span><span class="pln">
                </span><span class="kwd">break</span><span class="pln">

    </span><span class="com"># تحريك الشخصية</span><span class="pln">
    velocity </span><span class="pun">=</span><span class="pln"> target_velocity
    move_and_slide</span><span class="pun">()</span><span class="pln">

</span><span class="com"># أضف ذلك في الأسفل</span><span class="pln">
func die</span><span class="pun">():</span><span class="pln">
    hit</span><span class="pun">.</span><span class="pln">emit</span><span class="pun">()</span><span class="pln">
    queue_free</span><span class="pun">()</span><span class="pln">

func _on_mob_detector_body_entered</span><span class="pun">(</span><span class="pln">body</span><span class="pun">):</span><span class="pln">
    die</span><span class="pun">()</span></pre>

<h2 id="-2">
	الخاتمة
</h2>

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

<p>
	ترجمة - وبتصرف - لقسم <a href="https://docs.godotengine.org/en/stable/getting_started/first_3d_game/07.killing_player.html" rel="external nofollow">Killing the player</a> من توثيق جودو الرسمي.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D9%82%D9%81%D8%B2-%D9%88%D8%AA%D8%AF%D9%85%D9%8A%D8%B1-%D8%A7%D9%84%D8%A3%D8%B9%D8%AF%D8%A7%D8%A1-%D8%B6%D9%85%D9%86-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-r2464/" rel="">القفز وتدمير الأعداء ضمن لعبة ثلاثية الأبعاد في جودو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B5%D9%85%D9%8A%D9%85-%D9%85%D8%B4%D9%87%D8%AF-%D8%A7%D9%84%D8%A3%D8%B9%D8%AF%D8%A7%D8%A1-%D9%84%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-r2455/" rel="">تصميم مشهد الأعداء للعبة ثلاثية الأبعاد في جودو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D8%B9%D9%82%D8%AF-nodes-%D9%88%D8%A7%D9%84%D9%85%D8%B4%D8%A7%D9%87%D8%AF-scenes-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2357/" rel="">العقد Nodes والمشاهد Scenes في جودو Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B9-%D9%84%D9%85%D8%AF%D8%AE%D9%84%D8%A7%D8%AA-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2384/" rel="">الاستماع لمدخلات اللاعب في جودو Godot</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2471</guid><pubDate>Mon, 16 Dec 2024 15:03:01 +0000</pubDate></item><item><title>&#x627;&#x644;&#x642;&#x641;&#x632; &#x648;&#x62A;&#x62F;&#x645;&#x64A;&#x631; &#x627;&#x644;&#x623;&#x639;&#x62F;&#x627;&#x621; &#x636;&#x645;&#x646; &#x644;&#x639;&#x628;&#x629; &#x62B;&#x644;&#x627;&#x62B;&#x64A;&#x629; &#x627;&#x644;&#x623;&#x628;&#x639;&#x627;&#x62F; &#x641;&#x64A; &#x62C;&#x648;&#x62F;&#x648;</title><link>https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D9%82%D9%81%D8%B2-%D9%88%D8%AA%D8%AF%D9%85%D9%8A%D8%B1-%D8%A7%D9%84%D8%A3%D8%B9%D8%AF%D8%A7%D8%A1-%D8%B6%D9%85%D9%86-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-r2464/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_12/2009289661_.png.ba727e5f4ffcc0a11333b5c0321dad08.png" /></p>
<p>
	نركز في هذا المقال على شرح طريقة تحسين التفاعل بين اللاعب والأعداء في لعبتنا ثلاثية الأبعاد التي بدأنا تطويرها في <a href="https://academy.hsoub.com/programming/game-development/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%85%D9%86%D8%B7%D9%82%D8%A9-%D8%A7%D9%84%D9%84%D8%B9%D8%A8-%D9%84%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D9%88%D8%AF%D9%88-r2444/" rel="">مقال سابق</a>، ونشرح كيف نجعل اللاعب قادرًا على تدمير العدو إذا هبط عليه من الأعلى. بالمقابل، سنجعل اللاعب يموت إذا اصطدم العدو به أثناء وجوده على الأرض.
</p>

<h2 id="-1">
	التحكم في التفاعلات الفيزيائية
</h2>

<p>
	سنتحكم في التفاعلات الفيزيائية ونضبطها لتحديد كيفية تفاعل الكائنات المختلفة مع بعضها البعض ضمن اللعبة، لتحقيق ذلك يجب أن تكون على دراية بمفهوم <a href="https://docs.godotengine.org/en/stable/tutorials/physics/physics_introduction.html#doc-physics-introduction-collision-layers-and-masks" rel="external nofollow">طبقات الفيزياء في جودو</a>. حيث تتمتع الأجسام الفيزيائية بميزات تسهّل التحكم في التفاعل بينها، وهي الطبقات Layers والأقنعة Masks.
</p>

<ul>
	<li>
		تُحدد <strong>الطبقات Layers </strong>الطبقة الفيزيائية التي ينتمي إليها الكائن، وتستخدم لتنظيم الكائنات الفيزيائية في اللعبة
	</li>
	<li>
		تُحدد <strong>الأقنعة Masks</strong> ما هي الطبقات التي يمكن للجسم رصدها أو اكتشافها والتفاعل معها
	</li>
</ul>

<p>
	يؤثر هذا التفاعل بين الطبقات والأقنعة بشكل مباشر على <a href="https://academy.hsoub.com/programming/game-development/%D9%83%D8%B4%D9%81-%D8%A7%D9%84%D8%AA%D8%B5%D8%A7%D8%AF%D9%85%D8%A7%D8%AA-r770/" rel="">كشف الاصطدام Collision Detection</a> فعندما نرغب أن يتفاعل جسمان معًا، يجب أن يتوافق قناع أحدهما مع طبقة الآخر على الأقل. فإذا كنا نريد أن يتفاعل جسمان مثل اللاعب والعدو، يجب التأكد من إعداد الطبقات والأقنعة بحيث تكون طبقة أحدهما مدرجة ضمن قناع الآخر، والعكس، وإذا كنا نريد تجنب التفاعل بينهما، نجعل القناع الخاص بأحدهما لا يراقب الطبقة التي ينتمي إليها الكائن الآخر. ستتضح الفكرة لنا أكثر عند التطبيق العملي.
</p>

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

<p>
	عند إنشاء الأجسام والمناطق في محرك الألعاب ستُعيّن افتراضيًا إلى الطبقة والقناع رقم <code>1</code>. هذا يعني أن جميع الأجسام التي عينت بهذا الرقم ستتفاعل وتتصادم مع بعضها تلقائيًا دون الحاجة إلى ضبط إعدادات إضافية.
</p>

<h2>
	تحديد أسماء الطبقات
</h2>

<p>
	دعونا الآن نعطي طبقات الفيزياء أسماء مميزة لتسهيل إدارة التفاعلات داخل لعبتنا، للقيام بذلك سنفتح <a href="https://academy.hsoub.com/programming/game-development/%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot/" rel="">محرك ألعاب جودو</a> وننتقل إلى القائمة العلوية <strong>مشروع Project</strong> ومن ثم نختار <strong>إعدادات المشروع Project Settings</strong>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163662" href="https://academy.hsoub.com/uploads/monthly_2024_12/1.png.f7c76838aba7d8cd1c3d4e1e0429ccf1.png" rel=""><img alt="إعدادات المشروع" class="ipsImage ipsImage_thumbnailed" data-fileid="163662" data-unique="fcr6k4dsw" style="width: 510px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/1.png.f7c76838aba7d8cd1c3d4e1e0429ccf1.png"> </a>
</p>

<p>
	ننتقل للقائمة اليسرى ونذهب للأسفل حتى نصل لقسم أسماء الطبقات، ثم نختار <strong>فيزياء 3D أو 3D Physics‎</strong>. ستظهر لنا قائمة بالطبقات وحقل نصي على يمين كل طبقة. يمكن من خلال هذا الحقل النصي تغيير اسم الطبقة، سنسمي الطبقات الثلاث الأولى بأسماء مناسبة وهي <code>player</code> لتمثيل طبقة اللاعب، و <code>enemies</code> لتمثيل طبقة الأعداء، و <code>world</code> لتمثيل عالم اللعبة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163663" href="https://academy.hsoub.com/uploads/monthly_2024_12/2.png.dfa4a7dc0ffae19153aafa8ea887568d.png" rel=""><img alt="فيزياء 3D" class="ipsImage ipsImage_thumbnailed" data-fileid="163663" data-unique="aaxrk8gf2" style="width: 669px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/2.png.dfa4a7dc0ffae19153aafa8ea887568d.png"> </a>
</p>

<p>
	بعد تسمية الطبقات، دعونا نخصص كل كائن في اللعبة ليكون جزءًا من إحدى هذه الطبقات.
</p>

<h2 id="-2">
	تعيين الطبقات والأقنعة
</h2>

<p>
	حان دور تعيين الطبقات والأقنعة للكائنات الموجودة في مشهد اللعبة، بداية سنحدد عقدة الأرضية <strong><code>Ground</code> </strong>في المشهد الرئيسي <strong>Main</strong>. ونوسّع قسم الاصطدام <strong>Collision </strong>في الفاحص <strong>Inspector</strong>. سنرى عندها طبقات العقدة وأقنعتها على شكل شبكة من الأزرار كما في الصورة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163664" href="https://academy.hsoub.com/uploads/monthly_2024_12/3.png.0e070053ea6234207c8d7e37b3bb4f6e.png" rel=""><img alt=" قسم الاصطدام Collision" class="ipsImage ipsImage_thumbnailed" data-fileid="163664" data-unique="0upwteg5e" style="width: 242px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/3.png.0e070053ea6234207c8d7e37b3bb4f6e.png"> </a>
</p>

<p>
	الأرضية جزء من عالم اللعبة، ونريدها أن تكون جزءًا من الطبقة الثالثة في مشهد لعبتنا. للقيام بذلك ليس علينا سوى تعطيل الزر المفعل رقم 1 الذي يمثل الطبقة الأولى، وتفعيل الزر رقم 3 الذي يمثل الطبقة الثالثة بالنقر فوقه، بعدها سنعطّل الزر رقم 1 للقناع بالنقر عليه أيضًا.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163665" href="https://academy.hsoub.com/uploads/monthly_2024_12/4.png.a05b9524dbf1d4de6e1524a3ef6179a5.png" rel=""><img alt="أرقام الطبقات" class="ipsImage ipsImage_thumbnailed" data-fileid="163665" data-unique="zg8pfn8rp" style="width: 235px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/4.png.a05b9524dbf1d4de6e1524a3ef6179a5.png"> </a>
</p>

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

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163666" href="https://academy.hsoub.com/uploads/monthly_2024_12/5.png.852d13de4130056b14ff965af5de925e.png" rel=""><img alt="خصائص الطبقات" class="ipsImage ipsImage_thumbnailed" data-fileid="163666" data-unique="ei2bynerw" style="width: 306px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/5.png.852d13de4130056b14ff965af5de925e.png"> </a>
</p>

<p>
	سنعالج الآن عقدتي <code>Player</code> و <code>Mob</code>، وللقيام ذلك افتح مشهد اللاعب <code>player.tscn</code> بالنقر المزدوج فوق ملفه الموجود أسفل نافذة <a href="https://wiki.hsoub.com/Godot/best_practices/project_organization" rel="external">نظام الملفات في جودو</a>. وللتحكم في تفاعلات اللاعب مع الأعداء والعالم، حدد عقدة اللاعب وعيّن القناع <strong>Mask </strong>من قسم الاصطدام <strong>Collision </strong>لكل من الأعداء <strong>enemies </strong>والعالم <strong>world</strong>. يمكنك ترك خاصية الطبقة الافتراضية كما هي لأن الطبقة الأولى هي طبقة اللاعب <strong>player</strong>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163667" href="https://academy.hsoub.com/uploads/monthly_2024_12/6.png.035d3df2f9954811de811cdb29dd39fc.png" rel=""><img alt="تعيين قناع Player" class="ipsImage ipsImage_thumbnailed" data-fileid="163667" data-unique="z78ckvu8d" style="width: 242px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/6.png.035d3df2f9954811de811cdb29dd39fc.png"> </a>
</p>

<p>
	بعدها، لنفتح مشهد الأعداء Mob بالنقر المزدوج على الملف <code>mob.tscn</code> ونحدد عقدة <code>Mob</code> ونضبط طبقة تصادمه من قسم التصادم <strong>Collision</strong> الموجود داخل قسم الطبقة <strong>Layer </strong>ونجعل قيمته <strong>enemies </strong>لنحدد أنه سيصطدم فقط مع الأجسام التي تنتمي إلى طبقة الأعداء، ونلغي ضبط قناع التصادم من القسم تصادم <strong>Collision </strong>الموجود داخل قسم القناع <strong>Mask </strong>ونتركه فارغًا، لنمنعه من التفاعل مع الطبقات الأخرى كاللاعب أو البيئة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163668" href="https://academy.hsoub.com/uploads/monthly_2024_12/7.png.9589d3966f0fdd70d00fe2e29725643d.png" rel=""><img alt="تعيين قناع Mob " class="ipsImage ipsImage_thumbnailed" data-fileid="163668" data-unique="afgtj0peq" style="width: 218px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/7.png.9589d3966f0fdd70d00fe2e29725643d.png"> </a>
</p>

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

<p>
	<strong>ملاحظة</strong>: لا يحتاج الأعداء إلى استخدام قناع mask لطبقة العالم world لأن حركتهم محصورة على المستوى XZ الذي يحدد الاتجاهين العرضي والطولي، ونحن لا نطبق عليهم أي جاذبية بشكل افتراضي.
</p>

<h2>
	تنفيذ القفز
</h2>

<p>
	في هذه الخطوة، سنكتب كود بسيط لتنفيذ القفز في اللعبة، تتطلب عملية القفز وحدها سطرين فقط من التعليمات البرمجية، ولكتابتها افتح السكربت الخاص بالعقدة <code>Player</code>. سنحتاج إلى قيمة للتحكم في قوة القفزة وتحديث <code>()‎_physics_process</code> لبرمجة القفزة. لنضف <code>jump_impulse</code> بعد السطر الذي يحدد <code>fall_acceleration</code> في الجزء العلوي من السكربت.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5606_9" style=""><span class="com">#...</span><span class="pln">
</span><span class="com"># تطبيق الدفعة العمودية للشخصية عند القفر مقدرة بواحدة المتر</span><span class="pln">
</span><span class="lit">@export</span><span class="pln"> var jump_impulse </span><span class="pun">=</span><span class="pln"> </span><span class="lit">20</span></pre>

<p>
	ثم أضف الشيفرة البرمجية التالية قبل كتلة التعليمات البرمجية <code>move_and_slide()‎</code> داخل <code>‎_physics_process()‎</code>.
</p>

<pre class="ipsCode prettyprint lang-ruby prettyprinted" id="ips_uid_5606_11" style=""><span class="pln">func _physics_process</span><span class="pun">(</span><span class="pln">delta</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">if</span><span class="pln"> is_on_floor</span><span class="pun">()</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">is_action_just_pressed</span><span class="pun">(</span><span class="str">"jump"</span><span class="pun">):</span><span class="pln">
        target_velocity</span><span class="pun">.</span><span class="pln">y </span><span class="pun">=</span><span class="pln"> jump_impulse

    </span><span class="com">#...</span></pre>

<p>
	هذا كل ما تحتاجه للقفز!
</p>

<p>
	يعد التابع <code>is_on_floor()‎</code> أداة من الصنف <code>CharacterBody3D</code>، فهو يعيد <code>true</code> إذا اصطدم الجسم بالأرضية في هذا الإطار، ولهذا السبب نطبق الجاذبية على شخصية اللاعب فنجعله يصطدم بالأرض بدلاً من أن يطفو فوقها مثل شخصيات الأعداء.
</p>

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

<h2>
	تدمير الأعداء
</h2>

<p>
	دعنا نضف ميزة تدمير الأعداء للعبتنا، سنجعل من الشخصية تثب فوق الأعداء وتقتلهم في نفس الوقت. سنكون بحاجة إلى اكتشاف الاصطدامات مع العدو وتمييزها عن الاصطدامات بالأرضية. للقيام بذلك، يمكننا استخدام ميزة <a href="http://xn--%20https-vf0cah/docs.godotengine.org/en/stable/tutorials/scripting/groups.html#doc-groups" rel="external nofollow">التصنيف بالوسوم</a> الخاصة بـجودو.
</p>

<p>
	افتح المشهد <code>mob.tscn</code> مرة أخرى وحدد عقدة <strong><code>Mob</code></strong>. انتقل إلى قائمة العقدة <strong>Node </strong>على اليمين لرؤية قائمة الإشارات. تحتوي قائمة العقدة <strong>Node </strong>تبوبين هما الإشارات <strong>Signals </strong>التي استخدمناها سابقًا، والمجموعات <strong>Groups </strong>التي تسمح لنا بإسناد وسوم للعُقد. انقر عليها لتكشف عن حقل مخصص لكتابة اسم الوسم، اكتب mob في هذا الحقل وانقر فوق زر إضافة Add.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163669" href="https://academy.hsoub.com/uploads/monthly_2024_12/8.png.f0081d5317fc1eeaa0171d97fe63b0f1.png" rel=""><img alt="8" class="ipsImage ipsImage_thumbnailed" data-fileid="163669" data-unique="go6ei3ses" src="https://academy.hsoub.com/uploads/monthly_2024_12/8.png.f0081d5317fc1eeaa0171d97fe63b0f1.png"> </a>
</p>

<p>
	سيظهر رمز في قائمة المشهد Scene للإشارة إلى أن العقدة جزء من مجموعة واحدة على الأقل.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163670" href="https://academy.hsoub.com/uploads/monthly_2024_12/9.png.3ca419c6435af6b5c76365d3b216b7ba.png" rel=""><img alt="9" class="ipsImage ipsImage_thumbnailed" data-fileid="163670" data-unique="mmryf3424" src="https://academy.hsoub.com/uploads/monthly_2024_12/9.png.3ca419c6435af6b5c76365d3b216b7ba.png"> </a>
</p>

<p>
	يمكننا الآن استخدام هذه المجموعة في شيفرتنا البرمجية لتمييز الاصطدامات بالأعداء عن الاصطدامات بالأرض.
</p>

<h2>
	برمجة عملية تدمير اللاعب
</h2>

<p>
	لبرمجة عملية التدمير والارتداد في السكربت الخاص باللاعب، سنحتاج إلى إضافة خاصية جديدة تُسمى <code>bounce_impulse</code> في الجزء العلوي من السكربت. لا نريد بالضرورة عند تدمير عدو أن ترتفع الشخصية إلى أعلى مستوى كما هو الحال عند القفز، هنا ستساعدنا هذه الخاصية في ضبط مقدار الارتداد بما يناسب الموقف.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5606_14" style=""><span class="com"># تطبيق الدفعة العمودية للشخصية عند القفر مقدرة بواحدة المتر</span><span class="pln">
</span><span class="lit">@export</span><span class="pln"> var bounce_impulse </span><span class="pun">=</span><span class="pln"> </span><span class="lit">16</span></pre>

<p>
	والآن وبعد كتلة شيفرة القفز التي أضفناها أعلاه في <code>‎ _physics_process()‎</code> نضيف الحلقة التالية، إذ يجعل جودو الجسم يتحرك أحيانًا عدة مرات متتالية لتسهيل حركة الشخصية باستخدام <code>move_and_slide()‎</code>، لذلك علينا أن نراجع جميع الاصطدامات التي قد تكون حدثت.
</p>

<p>
	سنتحقق في في كل تكرار للحلقة فيما إذا كنا لامسنا عدو، وإذا كان الأمر كذلك، فإننا نقتله ونرتد عنه. لن تعمل الحلقة إذا لم يحدث أي تصادم في إطار معين. لاحظ الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5606_16" style=""><span class="pln">func _physics_process</span><span class="pun">(</span><span class="pln">delta</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">for</span><span class="pln"> index </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="pln">get_slide_collision_count</span><span class="pun">()):</span><span class="pln">
        </span><span class="com"># نحصل على واحد من الاصطدامات مع اللاعب</span><span class="pln">
        var collision </span><span class="pun">=</span><span class="pln"> get_slide_collision</span><span class="pun">(</span><span class="pln">index</span><span class="pun">)</span><span class="pln">

        </span><span class="com"># إذا كان الاصطدام مع الأرض</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> collision</span><span class="pun">.</span><span class="pln">get_collider</span><span class="pun">()</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">:</span><span class="pln">
            </span><span class="kwd">continue</span><span class="pln">

        </span><span class="com"># إذا كان الاصطدام مع العدو</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> collision</span><span class="pun">.</span><span class="pln">get_collider</span><span class="pun">().</span><span class="pln">is_in_group</span><span class="pun">(</span><span class="str">"mob"</span><span class="pun">):</span><span class="pln">
            var mob </span><span class="pun">=</span><span class="pln"> collision</span><span class="pun">.</span><span class="pln">get_collider</span><span class="pun">()</span><span class="pln">
            </span><span class="com"># نتأكد من الضربة أنها من الأعلى</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">UP</span><span class="pun">.</span><span class="pln">dot</span><span class="pun">(</span><span class="pln">collision</span><span class="pun">.</span><span class="pln">get_normal</span><span class="pun">())</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">0.1</span><span class="pun">:</span><span class="pln">
                </span><span class="com"># إذا كان كذلك ندمره ونقفز</span><span class="pln">
                mob</span><span class="pun">.</span><span class="pln">squash</span><span class="pun">()</span><span class="pln">
                target_velocity</span><span class="pun">.</span><span class="pln">y </span><span class="pun">=</span><span class="pln"> bounce_impulse
                </span><span class="com"># تمنع أي استدعاءات مكررة</span><span class="pln">
                </span><span class="kwd">break</span></pre>

<p>
	تأتي الدالتان <code>get_slide_collision_count()‎</code> و<code>get_slide_collision()‎</code> كلاهما من الصنف <code>CharacterBody3D</code> وهما مرتبطتان بالدالة <code>move_and_slide()‎</code>.
</p>

<p>
	تُعيد الدالة <code>get_slide_collision()‎</code> كائن <code>KinematicCollision3D</code> الذي يحتوي على معلومات حول مكان وكيفية حدوث التصادم، على سبيل المثال نستخدم الخاصية <code>get_collider</code> الخاصة بها للتحقق مما إذا كنا قد اصطدمنا بـعدو mob عن طريق استدعاء <code>is_in_group()‎</code> عليه بهذا الشكل:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5606_18" style=""><span class="pln">collision</span><span class="pun">.</span><span class="pln">get_collider</span><span class="pun">().</span><span class="pln">is_in_group</span><span class="pun">(</span><span class="str">"mob"</span><span class="pun">)</span></pre>

<p>
	<strong>ملاحظة</strong>: التابع <code>is_in_group()‎</code> متاح في كل صنف عقدة <code>Node</code>.
</p>

<p>
	بعدها نستخدم جداء الأشعة النقطي <a href="https://academy.hsoub.com/design/3d/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%A7%D9%84%D8%AC%D8%AF%D8%A7%D8%A1-%D8%A7%D9%84%D9%86%D9%82%D8%B7%D9%8A-dot-product-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%A3%D8%B4%D8%B9%D8%A9-%D9%81%D9%8A-%D8%A7%D9%84%D8%AA%D8%B5%D8%A7%D9%85%D9%8A%D9%85-3d-r862/" rel="">vector dot product</a> للتأكد من أننا هبطنا على العدو:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5606_20" style=""><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">UP</span><span class="pun">.</span><span class="pln">dot</span><span class="pun">(</span><span class="pln">collision</span><span class="pun">.</span><span class="pln">get_normal</span><span class="pun">())</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">0.1</span></pre>

<p>
	التصادم الافتراضي هو شعاع ثلاثي الأبعاد متعامد مع المستوي الذي حدث فيه الاصطدام، يتيح لنا الجداء النقطي مقارنته بالاتجاه الصاعد. في حالة الجداء النقطي؛ عندما تكون النتيجة أكبر من <code>0</code> يكون المتجهان بزاوية أقل من 90 درجة، والقيمة الأعلى من <code>0.1</code> تخبرنا أننا فوق العدو تقريبًا.
</p>

<p>
	بعد التعامل مع منطق التدمير والارتداد، نخرج من الحلقة عبر عبارة <code>Break</code> لمنع المزيد من الاستدعاءات المكررة إلى <code>mob.squash()‎</code>، التي قد تؤدي بخلاف ذلك إلى أخطاء غير مقصودة مثل حساب النتيجة عدة مرات لقتل عدو واحد.
</p>

<p>
	نستدعي هنا دالة واحدة غير معرّفة ألا وهي <code>mob.squash()‎</code>، لذلك يتعين علينا إضافتها إلى صنف العدو Mob.
</p>

<p>
	افتح السكربت <code>Mob.gd</code> بالنقر المزدوج عليه في نافذة نظام الملفات. نريد تحديد إشارة جديدة في الجزء العلوي من السكربت تسمى <code>squashed</code>. وفي الأسفل، يمكنك إضافة دالة التدمير، حيث نصدر الإشارة وندمر العدو.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5606_22" style=""><span class="com"># تنبثق عندما يقفز اللاعب على العدو</span><span class="pln">
signal squashed

</span><span class="com"># ...</span><span class="pln">


func squash</span><span class="pun">():</span><span class="pln">
    squashed</span><span class="pun">.</span><span class="pln">emit</span><span class="pun">()</span><span class="pln">
    queue_free</span><span class="pun">()</span></pre>

<p>
	لتجربة اللعبة، يمكن الضغط على <code>F5</code> وتعيين <code>main.tscn</code> كمشهد رئيسي للمشروع.
</p>

<h2 id="-3">
	الخلاصة
</h2>

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

<p>
	ترجمة - وبتصرف - لقسم <a href="https://docs.godotengine.org/en/stable/getting_started/first_3d_game/06.jump_and_squash.html" rel="external nofollow">Jumping and squashing monsters</a> من توثيق جودو الرسمي.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D9%88%D9%84%D9%8A%D8%AF-%D8%A7%D9%84%D8%A3%D8%B9%D8%AF%D8%A7%D8%A1-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-r2459/" rel="">توليد الأعداء في لعبة ثلاثية الأبعاد في محرك الألعاب جودو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%85%D8%B4%D9%87%D8%AF-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D9%88%D8%B9%D9%85%D9%84%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A5%D8%AF%D8%AE%D8%A7%D9%84-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D9%88%D8%AF%D9%88-r2447/" rel="">إعداد مشهد اللاعب وعمليات الإدخال في لعبة ثلاثية الأبعاد باستخدام جودو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D8%B9%D9%82%D8%AF-nodes-%D9%88%D8%A7%D9%84%D9%85%D8%B4%D8%A7%D9%87%D8%AF-scenes-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2357/" rel="">العقد Nodes والمشاهد Scenes في جودو Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D8%AC%D9%83-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D9%88%D8%AF%D9%88-godot-r2382/" rel="">كتابة برنامجك الأول باستخدام جودو Godot</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2464</guid><pubDate>Mon, 09 Dec 2024 15:02:00 +0000</pubDate></item><item><title>&#x62A;&#x648;&#x644;&#x64A;&#x62F; &#x627;&#x644;&#x623;&#x639;&#x62F;&#x627;&#x621; &#x641;&#x64A; &#x644;&#x639;&#x628;&#x629; &#x62B;&#x644;&#x627;&#x62B;&#x64A;&#x629; &#x627;&#x644;&#x623;&#x628;&#x639;&#x627;&#x62F; &#x641;&#x64A; &#x645;&#x62D;&#x631;&#x643; &#x627;&#x644;&#x623;&#x644;&#x639;&#x627;&#x628; &#x62C;&#x648;&#x62F;&#x648;</title><link>https://academy.hsoub.com/programming/game-development/%D8%AA%D9%88%D9%84%D9%8A%D8%AF-%D8%A7%D9%84%D8%A3%D8%B9%D8%AF%D8%A7%D8%A1-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-r2459/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_12/1443664388_.png.d837cd207cb153002395ddf7547eef9e.png" /></p>
<p>
	سنتطرق في هذا الدرس إلى شرح كيفية توليد الأعداء في لعبة ثلاثية الأبعاد وذلك ضمن مسار عشوائي نختاره، بحيث يصبح لدينا بضعة أعداء ضمن مساحة اللعب.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163430" href="https://academy.hsoub.com/uploads/monthly_2024_12/1.png.8756a1b87d3bc0a07dfa9aa49b0099a8.png" rel=""><img alt="1" class="ipsImage ipsImage_thumbnailed" data-fileid="163430" data-ratio="78.75" data-unique="tm1or2h2f" style="width: 400px; height: auto;" width="400" src="https://academy.hsoub.com/uploads/monthly_2024_12/1.png.8756a1b87d3bc0a07dfa9aa49b0099a8.png"> </a>
</p>

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

<h2>
	تغيير دقة اللعبة
</h2>

<p>
	بداية سنغيّر دقة اللعبة قبل إنشاء المسار، حيث يبلغ حجم النافذة الافتراضية للعبة 1152x648 وسنعدّلها إلى 720x540 لتصبح أصغر حجمًا وأكثر توافقًا مع التصميم المطلوب.
</p>

<p>
	نفتح المشهد الرئيسي <code>Main</code> للعبة بالنقر نقرًا مزدوجًا على <code>main.tscn</code> في قائمة نظام الملفات Filesystem dock الموجود على الشريط الجانبي الذي يعرض ملفات المشروع.
</p>

<p>
	ننتقل بعدها إلى تبويب المشروع Project ثم نختار إعدادات المشروع Project Settings.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163431" href="https://academy.hsoub.com/uploads/monthly_2024_12/2.png.32b4c8a7ac8c9953a0dd28b22d100896.png" rel=""><img alt="2" class="ipsImage ipsImage_thumbnailed" data-fileid="163431" data-unique="kdu9pe8tm" style="width: 300px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/2.png.32b4c8a7ac8c9953a0dd28b22d100896.png"> </a>
</p>

<p>
	ننتقل بعد ذلك إلى القسم Display في القائمة الموجودة يسار نافذة إعدادات المشروع، ثم ونمرر إلى الأسفل حتى نجد قسم Window. ونغير قيمة العرض Width إلى 720، وقيمة الارتفاع Height إلى 540 كما في الصورة أدناه.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163432" href="https://academy.hsoub.com/uploads/monthly_2024_12/3.png.0ae0ce874cbadeb92035b00009f09e2a.png" rel=""><img alt="3" class="ipsImage ipsImage_thumbnailed" data-fileid="163432" data-unique="e8ip9rfoj" style="width: 450px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/3.png.0ae0ce874cbadeb92035b00009f09e2a.png"> </a>
</p>

<h2 id="spawnpath">
	إنشاء مسار توليد الأعداء Spawn Path
</h2>

<p>
	نحتاج الآن إلى تصميم مسار ثلاثي الأبعاد وهنا نحتاج لاستخدام العقدة <strong><code>PathFollow3D</code></strong> لتوليد مواقع عشوائية على هذا المسار، الأمر هنا مشابه لما شرحناه عند <a href="https://academy.hsoub.com/programming/game-development/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A7%D9%84%D9%85%D8%B4%D9%87%D8%AF-%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A-%D9%84%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot-r2291/" rel="">إنشاء المشهد الرئيسي وتوليد الأعداد في اللعبة ثنائية الأبعاد في جودو</a>، لكن رسم المسار في المجال ثلاثي الأبعاد أكثر تعقيدًا، حيث نريد أن يحيط المسار بنافذة عرض اللعبة، كي يظهر الأعداء مباشرة من خارج نطاق رؤية اللاعب.
</p>

<p>
	مع ذلك، إذا رسمنا المسار ضمن هذا المجال، فقد لا يكون مرئيًا في معاينة الكاميرا مما يجعل تحديد موضعه بدقة أمرًا صعبًا. لذلك، سنستخدم طريقة تساعدنا في تصور حدود نافذة العرض وتنسيق المسار بشكل صحيح.
</p>

<p>
	يمكننا استخدام بعض الشبكات المؤقتة placeholder meshes للعثور على حدود العرض، والشبكات المؤقتة هي مجسمات ثلاثية الأبعاد بسيطة مثل مربعات أو كرات توفر أدوات مساعدة أثناء التصميم وتساعدنا على تحديد حدود نافذة العرض في مشهد ثلاثي الأبعاد وتصور أين يمكن أن يبدأ المسار وأين تكون العناصر خارج نطاق الكاميرا.
</p>

<p>
	يجب أن تكون نافذة العرض مقسمة إلى قسمين مع معاينة الكاميرا في الأسفل. إذا لم تكن كذلك فاضغط على‎‎ <code>ctrl + 2</code>‎‎ ‎‎أو <code>cmd + 2</code> في حال كنت تستخدم نظام تشغيل macOS لقسمها إلى قسمين، ثم حدد عقدة <strong><code>Camera3D</code> </strong>وانقر فوق مربع الاختيار Preview في أسفل نافذة العرض.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163433" href="https://academy.hsoub.com/uploads/monthly_2024_12/4.png.a14bbadc75ead32bce3e0cb3c97ccf1b.png" rel=""><img alt="4" class="ipsImage ipsImage_thumbnailed" data-fileid="163433" data-unique="xlbcywv6w" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/4.thumb.png.ccbee11360e873208da7c903c2870838.png"> </a>
</p>

<h2 id="cylindermesh">
	إضافة أسطوانات مؤقتة CylinderMesh
</h2>

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

<p>
	لإضافة شبكة اسطوانات مؤقتة، أضف عقدة <strong><code>Node3D</code> </strong>جديدة كعقدة فرعية للعقدة الرئيسية وسمّها <code>Cylinders</code>، ثم حددها وأضف عقدة فرعية <strong><code>MeshInstance3D</code> </strong>لنستخدمها في تجميع الأسطوانات. حدّد <code>Cylinders</code> لإضافة عقدة <code>MeshInstance3D</code> فرعية.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163434" href="https://academy.hsoub.com/uploads/monthly_2024_12/5.png.ba057d7ec314577f43a02378680eef44.png" rel=""><img alt="5" class="ipsImage ipsImage_thumbnailed" data-fileid="163434" data-unique="4m0whtinf" src="https://academy.hsoub.com/uploads/monthly_2024_12/5.png.ba057d7ec314577f43a02378680eef44.png"> </a>
</p>

<p>
	أسند CylinderMesh إلى الخاصية Mesh من قائمة الفاحص Inspector كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163435" href="https://academy.hsoub.com/uploads/monthly_2024_12/6.png.d89d55cbbc19649722a6c605beb620c8.png" rel=""><img alt="6" class="ipsImage ipsImage_thumbnailed" data-fileid="163435" data-unique="k0kb83bwd" src="https://academy.hsoub.com/uploads/monthly_2024_12/6.png.d89d55cbbc19649722a6c605beb620c8.png"> </a>
</p>

<p>
	عند العمل في جودو سيساعدنا التبديل بين طرق عرض مختلفة في نافذة المشهد Viewport على رؤية المشهد من زوايا مختلفة. وأحد هذه الزوايا هي Top View التي تعني المنظور العلوي، حيث سترى من خلالها المشهد كما لو كنت تنظر إليه من أعلى، مما يساعدك على ترتيب الأشياء بشكل دقيق.
</p>

<p>
	اضبط نافذة العرض على خيار العرض العمودي العُلوي Top View باستخدام القائمة الظاهرة أعلى يسار نافذة العرض.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163436" href="https://academy.hsoub.com/uploads/monthly_2024_12/7.png.c2b5b4e3f792211b8b6e6068ee854b0d.png" rel=""><img alt="7" class="ipsImage ipsImage_thumbnailed" data-fileid="163436" data-unique="cs2m7uqoj" style="width: 300px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/7.png.c2b5b4e3f792211b8b6e6068ee854b0d.png"> </a>
</p>

<p>
	إذا كانت الشبكة الظاهرة تُشتت تركيزك، فيمكنك إيقاف عرضها أو تعديلها بالانتقال إلى قائمة العرض في شريط الأدوات والنقر فوق عرض الشبكة View Grid، عند النقر فوق هذا الخيار، سيتوقف عرض الشبكة على شاشة المحرر إذا كانت مُفعلة مسبقًا وإذا كنت تريد إظهار الشبكة مرة أخرى، يمكنك تفعيل الخيار من جديد.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163437" href="https://academy.hsoub.com/uploads/monthly_2024_12/8.png.15df9337cc41a4a8cc8bd3dbde3f9d3f.png" rel=""><img alt="8" class="ipsImage ipsImage_thumbnailed" data-fileid="163437" data-unique="3t47uxqn8" style="width: 300px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/8.png.15df9337cc41a4a8cc8bd3dbde3f9d3f.png"> </a>
</p>

<p>
	يساعدك تحريك الأسطوانة في المشهد على مراقبة كيفية ظهورها من زاوية الكاميرا، ويسهل عليك ترتيب العناصر بشكل دقيق واحترافي في المستوى الأرضي، لذا ابدأ يتحريك الأسطوانة على طول المستوى الأرضي وانظر لمعاينة الكاميرا أسفل نافذة العرض. يُفضل أن تستخدم الالتقاط الشبكي Grid Sanp لفعل ذلك، حيث يمكنك تفعيله عن طريق النقر على أيقونة المغناطيس في شريط الأدوات كما في الصورة التالية أو الضغط على مفتاح Y.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163438" href="https://academy.hsoub.com/uploads/monthly_2024_12/9.png.a5fd9e84fa6146814ce24fea89e8811a.png" rel=""><img alt="9" class="ipsImage ipsImage_thumbnailed" data-fileid="163438" data-unique="dfkzs5ic9" src="https://academy.hsoub.com/uploads/monthly_2024_12/9.png.a5fd9e84fa6146814ce24fea89e8811a.png"> </a>
</p>

<p>
	حرك الأسطوانة بحيث تكون خارج رؤية الكاميرا مباشرة في الزاوية العلوية اليسرى كما في الصورة التالية.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163439" href="https://academy.hsoub.com/uploads/monthly_2024_12/10.png.5c2802581fa7bb2e0385e51a7b12ebdf.png" rel=""><img alt="10" class="ipsImage ipsImage_thumbnailed" data-fileid="163439" data-unique="x6bjjsry1" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/10.thumb.png.73731345427a0dd441d58265a2570442.png"> </a>
</p>

<p>
	الآن علينا تكرار الشبكة أو الأسطوانة التي استخدمناها كشبكة مؤقتة ووضعها في مناطق مختلفة حول منطقة اللعب بحيث تتوفر مجموعة نسخ ضمن المشهد، ونسحبها إلى خارج معاينة الكاميرا مباشرة، فهذا سيساعد هذا على تصور حدود اللعبة بشكل جيد.
</p>

<p>
	لإنشاء نسخ من الشبكة ووضعها حول منطقة اللعب. نضغط على <code>Ctrl + D</code>‏ أو <code>Cmd + D</code> على نظام تشغيل macOS لتكرار العقدة، ويمكن أيضًا النقر بالزر الأيمن على العقدة في قائمة المشهد وتحديد خيار مضاعفة Duplicate، ثم تحريك النسخة لأسفل على طول المحور Z الأزرق حتى تصل لخارج معاينة الكاميرا مباشرة.
</p>

<p>
	نحدد كلا الأسطوانتين بالضغط على مفتاح Shift والنقر على الأسطوانة التي لم يتم تحديدها ومضاعفتها.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163440" href="https://academy.hsoub.com/uploads/monthly_2024_12/11.png.87e6e11441f71723786ad114022f9e84.png" rel=""><img alt="11" class="ipsImage ipsImage_thumbnailed" data-fileid="163440" data-unique="0jz7clqss" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/11.png.87e6e11441f71723786ad114022f9e84.png"> </a>
</p>

<p>
	نحركهم إلى اليمين عن طريق سحب المحور X باللون الأحمر.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163441" href="https://academy.hsoub.com/uploads/monthly_2024_12/12.png.f16e6c4ec2d26910455e9aafa94546d7.png" rel=""><img alt="12" class="ipsImage ipsImage_thumbnailed" data-fileid="163441" data-unique="8s9evr4dh" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/12.png.f16e6c4ec2d26910455e9aafa94546d7.png"> </a>
</p>

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

<p>
	يمكننا تحديث جميع الأسطوانات الأربعة مرة واحدة من خلال تحديد جميع نسخ الشبكة في قائمة المشهد عن طريق النقر فوق أول واحدة ثم الضغط مفتاح Shift والنقر على آخر واحدة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163442" href="https://academy.hsoub.com/uploads/monthly_2024_12/13.png.c2ff41ea3325dfb65316bdf8447c065b.png" rel=""><img alt="13" class="ipsImage ipsImage_thumbnailed" data-fileid="163442" data-unique="35epec1wf" src="https://academy.hsoub.com/uploads/monthly_2024_12/13.png.c2ff41ea3325dfb65316bdf8447c065b.png"> </a>
</p>

<p>
	من قائمة الفاحص Inspector، نوسع قسم المادة Material لتظهر الخيارات الموجودة بداخله، سنضيف الخيار <strong><code>StandardMaterial3D</code> </strong>إلى الحيز الأول في القائمة -وهو الحيز رقم 0- وبذلك، ستطبق المادة على الاسطوانات التي حددتها في المشهد.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163443" href="https://academy.hsoub.com/uploads/monthly_2024_12/14.png.6079ae9372d1e632918031890024c9c6.png" rel=""><img alt="14" class="ipsImage ipsImage_thumbnailed" data-fileid="163443" data-unique="rdduzq96z" src="https://academy.hsoub.com/uploads/monthly_2024_12/14.thumb.png.42752250f0543014aa94bc4e83378ce8.png"> </a>
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163444" href="https://academy.hsoub.com/uploads/monthly_2024_12/15.png.7bb86e8ef1364c4069f1879d2a4e3a8d.png" rel=""><img alt="15" class="ipsImage ipsImage_thumbnailed" data-fileid="163444" data-unique="jsd5auh9z" src="https://academy.hsoub.com/uploads/monthly_2024_12/15.png.7bb86e8ef1364c4069f1879d2a4e3a8d.png"> </a>
</p>

<p>
	انقر على أيقونة الجسم الكروي لفتح مورد المادة Material resource حيث ستحصل هنا على معاينة للمادة وقائمة طويلة من الخصائص لاستخدامها في إنشاء جميع أنواع الأسطح، من المعدن إلى الصخور أو الماء.
</p>

<p>
	لتغيير اللون افتح قسم Albedo داخل إعدادات المادة:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163445" href="https://academy.hsoub.com/uploads/monthly_2024_12/16.png.48958da6a4e977fe771c280d46039dc8.png" rel=""><img alt="16" class="ipsImage ipsImage_thumbnailed" data-fileid="163445" data-unique="wyar4za5m" src="https://academy.hsoub.com/uploads/monthly_2024_12/16.png.48958da6a4e977fe771c280d46039dc8.png"> </a>
</p>

<p>
	اضبط اللون على خيار يتناقض مع الخلفية، مثل اللون البرتقالي الساطع.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163446" href="https://academy.hsoub.com/uploads/monthly_2024_12/17.png.2dfe20c9f12db22232837821efb74b17.png" rel=""><img alt="17" class="ipsImage ipsImage_thumbnailed" data-fileid="163446" data-unique="4rrh83ez4" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_12/17.png.2dfe20c9f12db22232837821efb74b17.png"> </a>
</p>

<p>
	يمكنك الآن استخدام الأسطوانات كدليل. ضعها في قائمة المشهد بالنقر فوق السهم الرمادي المجاور لها، ويمكنك التبديل بين عرضها وإخفائها من خلال النقر على أيقونة العين بجوار الأسطوانات.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163447" href="https://academy.hsoub.com/uploads/monthly_2024_12/18.png.b129b46d50d764b3754d24f1e1e019da.png" rel=""><img alt="18" class="ipsImage ipsImage_thumbnailed" data-fileid="163447" data-unique="ny48ho4gx" src="https://academy.hsoub.com/uploads/monthly_2024_12/18.png.b129b46d50d764b3754d24f1e1e019da.png"> </a>
</p>

<p>
	الآن أضف عقدة فرعية <code>Path3D</code> إلى العقدة الرئيسية<code>Main</code>. ستظهر في شريط الأدوات أربع أيقونات، انقر على أداة Add Point التي تحمل علامة "+" الخضراء كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163448" href="https://academy.hsoub.com/uploads/monthly_2024_12/19.png.00ebbf787c9fdd23ed604d5673993439.png" rel=""><img alt="19" class="ipsImage ipsImage_thumbnailed" data-fileid="163448" data-unique="8e9wyfuu4" src="https://academy.hsoub.com/uploads/monthly_2024_12/19.png.00ebbf787c9fdd23ed604d5673993439.png"> </a>
</p>

<p>
	<strong>ملاحظة</strong>: يمكنك تمرير المؤشر فوق أي رمز لرؤية تلميح توضيحي يصف الأداة.
</p>

<p>
	انقر وسط كل أسطوانة لإنشاء نقطة، ثم انقر فوق رمز إغلاق المنحنى Close Curve في شريط الأدوات لإغلاق المسار. في حال كانت أي من النقاط بعيدة قليلاً، يمكنك النقر فوقها وسحبها لإعادة تموضعها.
</p>

<p style="text-align: center;">
	<img alt="21.png" class="ipsImage ipsImage_thumbnailed" data-fileid="163449" data-ratio="86.54" data-unique="2io8y5zxe" width="468" src="https://academy.hsoub.com/uploads/monthly_2024_12/21.png.8c88566057307647a62167a85470816e.png">
</p>

<p>
	يجب أن يبدو مسارك كالتالي.
</p>

<p style="text-align: center;">
	<img alt="22.png" class="ipsImage ipsImage_thumbnailed" data-fileid="163450" data-ratio="27.69" data-unique="ujnfs5pyg" width="242" src="https://academy.hsoub.com/uploads/monthly_2024_12/22.png.c4f99f0075588954333e4d42043a89e1.png">
</p>

<p>
	نحتاج إلى عقدة <code>PathFollow3D</code> لاختيار مواضع عشوائية عليها، لذا أضف <code>PathFollow3D</code> كعنصر فرعي لعقدة <code>Path3D</code> ثم أعد تسمية العقدتين إلى <code>SpawnLocation</code> و <code>SpawnPath</code> على التوالي فذلك سيوضّح حالة استخدامنا هنا.
</p>

<p>
	نحن جاهزون الآن لبرمجة آلية توليد الأعداء في اللعبة.
</p>

<h2 id="-1">
	توليد الأعداء عشوائيًا
</h2>

<p>
	انقر بالزر الأيمن على العقدة الرئيسية وأرفق سكريبت جديد لها من أجل توليد الأعداء بشكل دوري، أي على فترات زمنية منتظمة.
</p>

<p>
	نضيف أولاً متغير إلى قائمة الفاحص Inspector بحيث يمكننا إسناد <code>mob.tscn</code> أو أي عدو آخر إليه كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9992_7" style=""><span class="pln">extends </span><span class="typ">Node</span><span class="pln">

</span><span class="lit">@export</span><span class="pln"> var mob_scene</span><span class="pun">:</span><span class="pln"> </span><span class="typ">PackedScene</span><span class="pln"> </span></pre>

<p>
	وبما أننا نريد توليد العدو على فترات زمنية منتظمة، سنحتاج إلى العودة إلى المشهد وإضافة مؤقت، لكن قبل ذلك نحتاج إلى إسناد ملف <code>mob.tscn</code> إلى خاصية <code>mob_scene</code> أعلاه بخلاف ذلك سيكون فارغ.
</p>

<p>
	عد إلى الشاشة ثلاثية الأبعاد وحدد العقدة الرئيسية، ثم اسحب <code>mob.tscn</code> من قائمة نظام الملفات إلى حيز مشهد العدو في قائمة الفاحص Inspector.
</p>

<p style="text-align: center;">
	<img alt="23.png" class="ipsImage ipsImage_thumbnailed" data-fileid="163451" data-ratio="52.50" data-unique="pgmcs5mju" width="320" src="https://academy.hsoub.com/uploads/monthly_2024_12/23.png.a251bd140ff6215a77a86d5b13ca856c.png">
</p>

<p>
	أضف عقدة <code>Timer</code> جديدة كعنصر فرعي للعقدة الرئيسية، وسمها <code>MobTimer</code>
</p>

<p style="text-align: center;">
	<img alt="24.png" class="ipsImage ipsImage_thumbnailed" data-fileid="163452" data-ratio="19.82" data-unique="ptvkcusnx" width="222" src="https://academy.hsoub.com/uploads/monthly_2024_12/24.png.561400032cb93f88d6c0533cf87bf5b0.png">
</p>

<p>
	اضبط من قائمة الفاحص Inspector وقت الانتظار Wait Time على القيمة <code>0.5</code> ثانية وفعّل التشغيل التلقائي Autostart حتى يبدأ توليد الأعداء تلقائيًا عند تشغيل اللعبة.
</p>

<p style="text-align: center;">
	<img alt="25.png" class="ipsImage ipsImage_thumbnailed" data-fileid="163453" data-ratio="62.37" data-unique="sjuhec1dl" style="width: 300px; height: auto;" width="396" src="https://academy.hsoub.com/uploads/monthly_2024_12/25.png.42b95f4e2f5175ea5f7b0c8a57504483.png">
</p>

<p>
	تطلق المؤقتات إشارة <code>timeout</code> في كل مرة تصل فيها إلى نهاية وقت انتظارها، وبشكل افتراضي يُعاد تشغيلها تلقائيًا، مما يؤدي إلى إصدار الإشارة بشكل متواصل، ويمكن الاتصال بها من العقدة الرئيسية لتوليد عدو كل 0.5 ثانية.
</p>

<p>
	توجه إلى قائمة العقدة على اليمين أثناء تحديد MobTimer وانقر نقرًا مزدوجًا على إشارة <code>timeout</code>
</p>

<p style="text-align: center;">
	<img alt="26.png" class="ipsImage ipsImage_thumbnailed" data-fileid="163454" data-ratio="46.73" data-unique="pj1syknvf" width="306" src="https://academy.hsoub.com/uploads/monthly_2024_12/26.png.da1f6cfe480c66800e1b4d7a67bbf22c.png">
</p>

<p>
	صِلها بالعقدة الرئيسية
</p>

<p style="text-align: center;">
	<img alt="27.png" class="ipsImage ipsImage_thumbnailed" data-fileid="163455" data-ratio="83.89" data-unique="0npldg1th" style="width: 400px; height: auto;" width="447" src="https://academy.hsoub.com/uploads/monthly_2024_12/27.png.79c4faebac05360fb04cbe62f0a13b47.png">
</p>

<p>
	سيعيدك هذا إلى النص البرمجي مع دالة جديدة <code>‎_on_mob_timer_timeout()‎</code> هذه الدالة فارغة ولا تحتوي الدالة أي منطق برمجي لذا نحتاج لبرمجة منطق توليد العدو فيها عن طريق الخطوات التالية:
</p>

<ol>
	<li>
		إنشاء نسخة من مشهد العدو باستخدام <code>mob_scene.instantiate()‎</code>
	</li>
	<li>
		أخذ عينة عشوائية لموقع العدو باستخدام <code>randf_range()‎</code>
	</li>
	<li>
		الحصول على موضع اللاعب عبر الوصول إلى العقدة Player في المشهد
	</li>
	<li>
		استدعاء دالة <code>initialize()‎</code> لإعداد العدو باستخدام الموقع العشوائي وموقع اللاعب 5 إضافة العدو كعنصر فرعي للعقدة الرئيسية في المشهد باستخدام<code>add_child()</code><code>‎</code>
	</li>
</ol>

<p>
	بهذا يمكن توليد الأعداء بشكل دوري في نقاط عشوائية على المسار، مع الأخذ بعين الاعتبار مكان اللاعب ليتفاعل العدو معه كما توضح الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3027_19" style=""><span class="pln">func _on_mob_timer_timeout</span><span class="pun">():</span><span class="pln">
    </span><span class="com"># إنشاء نسخة جديدة من مشهد العدو</span><span class="pln">
    var mob </span><span class="pun">=</span><span class="pln"> mob_scene</span><span class="pun">.</span><span class="pln">instantiate</span><span class="pun">()</span><span class="pln">

    </span><span class="com"># اختر مكان عشوائي على‫ SpawnPath</span><span class="pln">
    </span><span class="com"># ‫نخزن المرجع إلى عقدة SpawnLocation</span><span class="pln">
    var mob_spawn_location </span><span class="pun">=</span><span class="pln"> get_node</span><span class="pun">(</span><span class="str">"SpawnPath/SpawnLocation"</span><span class="pun">)</span><span class="pln">
    </span><span class="com"># ونعطيها انزياح عشوائي</span><span class="pln">
    mob_spawn_location</span><span class="pun">.</span><span class="pln">progress_ratio </span><span class="pun">=</span><span class="pln"> randf</span><span class="pun">()</span><span class="pln">

    var player_position </span><span class="pun">=</span><span class="pln"> $Player</span><span class="pun">.</span><span class="pln">position
    mob</span><span class="pun">.</span><span class="pln">initialize</span><span class="pun">(</span><span class="pln">mob_spawn_location</span><span class="pun">.</span><span class="pln">position</span><span class="pun">,</span><span class="pln"> player_position</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># توليد الأعداء عن طريق إضافتها إلى المشهد الأساسي</span><span class="pln">
    add_child</span><span class="pun">(</span><span class="pln">mob</span><span class="pun">)</span><span class="pln"> </span></pre>

<p>
	تُنتج دالة <code>randf()‎</code> قيمة عشوائية بين <code>0</code> و<code>1</code>، وهي تمثل القيمة المتوقعة للمتغير <code>progress_ratio</code> في عقدة PathFollow. حيث تشير <code>0</code> إلى بداية المسار، وتشير <code>1</code> إلى نهايته. وبما أن المسار الذي حددناه يحيط بنافذة عرض الكاميرا، لذا فإن أي قيمة عشوائية بين <code>0</code> و<code>1</code> ستحدد موقعًا عشوائيًا على طول حواف نافذة العرض.
</p>

<p>
	فيما يلي الكود البرمجي الكامل للملف <code>main.gd</code> للرجوع إليه عند الحاجة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3027_21" style=""><span class="pln">extends </span><span class="typ">Node</span><span class="pln">

</span><span class="lit">@export</span><span class="pln"> var mob_scene</span><span class="pun">:</span><span class="pln"> </span><span class="typ">PackedScene</span><span class="pln">


func _on_mob_timer_timeout</span><span class="pun">():</span><span class="pln">
    </span><span class="com"># إنشاء نسخة جديدة من مشهد العدو</span><span class="pln">
    var mob </span><span class="pun">=</span><span class="pln"> mob_scene</span><span class="pun">.</span><span class="pln">instantiate</span><span class="pun">()</span><span class="pln">

    </span><span class="com"># اختر مكان عشوائي على‫ SpawnPath</span><span class="pln">
    </span><span class="com"># ‫نخزن المرجع إلى عقدة SpawnLocation</span><span class="pln">
    var mob_spawn_location </span><span class="pun">=</span><span class="pln"> get_node</span><span class="pun">(</span><span class="str">"SpawnPath/SpawnLocation"</span><span class="pun">)</span><span class="pln">
    </span><span class="com">#  ونعطيها انزياح عشوائي</span><span class="pln">
    mob_spawn_location</span><span class="pun">.</span><span class="pln">progress_ratio </span><span class="pun">=</span><span class="pln"> randf</span><span class="pun">()</span><span class="pln">

    var player_position </span><span class="pun">=</span><span class="pln"> $Player</span><span class="pun">.</span><span class="pln">position
    mob</span><span class="pun">.</span><span class="pln">initialize</span><span class="pun">(</span><span class="pln">mob_spawn_location</span><span class="pun">.</span><span class="pln">position</span><span class="pun">,</span><span class="pln"> player_position</span><span class="pun">)</span><span class="pln">

    </span><span class="com">#  توليد الأعداء عن طريق إضافتها إلى المشهد الأساسي</span><span class="pln">
    add_child</span><span class="pun">(</span><span class="pln">mob</span><span class="pun">)</span></pre>

<p>
	يمكنك الآن اختبار المشهد بالضغط على F6 حيث يجب أن ترى الأعداء تظهر وتتحرك بخط مستقيم.
</p>

<p style="text-align: center;">
	<img alt="28.png" class="ipsImage ipsImage_thumbnailed" data-fileid="163456" data-ratio="80.34" data-unique="mnysrh4jh" width="468" src="https://academy.hsoub.com/uploads/monthly_2024_12/28.png.fd133b199acce43dbf8e0dbd470e762c.png">
</p>

<p>
	تتصادم شخصيات الأعداء حاليًا وتنزلق بمواجهة بعضها عندما تلتقي مساراتها، وسنتحدث عن هذه المسألة في الدرس التالي ونوضح طريقة ضبط التصادمات.
</p>

<h2 id="-2">
	الخلاصة
</h2>

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

<p>
	ترجمة -وبتصرف- لقسم <a href="https://docs.godotengine.org/en/stable/getting_started/first_3d_game/05.spawning_mobs.html" rel="external nofollow">Spawning monsters</a> من توثيق جودو الرسمي.
</p>

<h2 id="-3">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B5%D9%85%D9%8A%D9%85-%D9%85%D8%B4%D9%87%D8%AF-%D8%A7%D9%84%D8%A3%D8%B9%D8%AF%D8%A7%D8%A1-%D9%84%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-r2455/" rel="">تصميم مشهد الأعداء للعبة ثلاثية الأبعاد في جودو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%A5%D8%B4%D8%A7%D8%B1%D8%A7%D8%AA-signals-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2385/" rel="">استخدام الإشارات Signals في جودو Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-r2304/" rel="">دليلك الشامل إلى برمجة الألعاب</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A%D8%A9/" rel="">أشهر أنواع الألعاب الإلكترونية</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2459</guid><pubDate>Mon, 02 Dec 2024 15:00:00 +0000</pubDate></item><item><title>&#x62A;&#x635;&#x645;&#x64A;&#x645; &#x645;&#x634;&#x647;&#x62F; &#x627;&#x644;&#x623;&#x639;&#x62F;&#x627;&#x621; &#x644;&#x644;&#x639;&#x628;&#x629; &#x62B;&#x644;&#x627;&#x62B;&#x64A;&#x629; &#x627;&#x644;&#x623;&#x628;&#x639;&#x627;&#x62F; &#x641;&#x64A; &#x62C;&#x648;&#x62F;&#x648;</title><link>https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B5%D9%85%D9%8A%D9%85-%D9%85%D8%B4%D9%87%D8%AF-%D8%A7%D9%84%D8%A3%D8%B9%D8%AF%D8%A7%D8%A1-%D9%84%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-r2455/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_11/732888669_.png.2fcff71ffca319be85b50b76c24b52a4.png" /></p>
<p>
	ستتعلم في هذا المقال كيفية برمجة الأعداء في لعبة ثلاثية الأبعاد في محرك الألعاب جودو، حيث سنطلق على كل عدو اسم mob. ستتعرف أيضًا على كيفية توليدهم عشوائيًا في مواقع مختلفة حول منطقة اللعب.
</p>

<h2 id="-1">
	إنشاء مشهد الأعداء
</h2>

<p>
	لنصمم الأعداء في لعبتنا نحتاج لإنشاء مشهد جديد وسيكون هيكل العقدة مشابهًا <a href="https://academy.hsoub.com/programming/game-development/%D8%A3%D9%86%D8%B4%D8%A6-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AD%D8%B1%D9%83-%D8%AC%D9%88%D8%AF%D9%88-%D9%85%D8%B4%D9%87%D8%AF-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D9%88%D8%B9%D9%85%D9%84%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A5%D8%AF%D8%AE%D8%A7%D9%84-r2447/" rel="">لمشهد اللاعب</a> الذي أطلقنا عليه اسم <code>player.tscn</code>.
</p>

<p>
	أنشئ مشهدًا يحتوي على عقدة <code>CharacterBody3D</code> لتكون بمثابة جذر المشهد فهذه العقدة توفر وظائف فيزياء جاهزة وتوفر حركات واقعية وأطلق عليها اسم <code>Mob</code>، وأضف عقدة فرعية <code>Node3D</code> وأطلق عليها اسم <code>Pivot</code> لتكون كنقطة ارتكاز لتحريك الشخصية، ثم اسحب وأفلت الملف <code>mob.glb</code> من قائمة نظام الملفات إلى <code>Pivot</code> لإضافة نموذج العدو ثلاثي الأبعاد إلى المشهد.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163062" href="https://academy.hsoub.com/uploads/monthly_2024_11/1.png.406a68a19ba08fd6a909748889f00b24.png" rel=""><img alt="1" class="ipsImage ipsImage_thumbnailed" data-fileid="163062" data-unique="w40iaini9" src="https://academy.hsoub.com/uploads/monthly_2024_11/1.png.406a68a19ba08fd6a909748889f00b24.png"> </a>
</p>

<p>
	بعد سحب الملف ستنشأ عقدة جديدة تمثل الكائن الرسومي داخل اللعب، يمكنك إعادة تسميتها إلى <code>Character</code>
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163063" href="https://academy.hsoub.com/uploads/monthly_2024_11/2.png.5594e4b1a5b32272960ae2cc086a9555.png" rel=""><img alt="2" class="ipsImage ipsImage_thumbnailed" data-fileid="163063" data-unique="9bhdt5lug" style="width: 200px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/2.png.5594e4b1a5b32272960ae2cc086a9555.png"> </a>
</p>

<p>
	نحتاج إلى هيكل تصادم collision shape ليعمل الجسم بالشكل المتوقع ويتمكن من التفاعل مع الفيزياء أو الاصطدامات داخل اللعبة بطريقة صحيحة، لذا انقر بزر الفأرة الأيمن على عقدة جذر المشهد <code>Mob</code> ثم انقر فوق إضافة عقدة فرعية Add Child Node.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163064" href="https://academy.hsoub.com/uploads/monthly_2024_11/3.png.36a1ccf58d4be932f71e8c5743330084.png" rel=""><img alt="3" class="ipsImage ipsImage_thumbnailed" data-fileid="163064" data-unique="xs554hn8o" style="width: 200px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/3.png.36a1ccf58d4be932f71e8c5743330084.png"> </a>
</p>

<p>
	أضف العقدة <code>CollisionShape3D</code>
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163065" href="https://academy.hsoub.com/uploads/monthly_2024_11/4.png.a514cb5badb6eba483114f6fe16411eb.png" rel=""><img alt="4" class="ipsImage ipsImage_thumbnailed" data-fileid="163065" data-unique="sqxiyw9i6" style="width: 200px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/4.png.a514cb5badb6eba483114f6fe16411eb.png"> </a>
</p>

<p>
	تحتوي العقدة <code>CollisionShape3D </code>على خاصية الشكل Shape.  اربط بهذه الخاصية عقدة <code>BoxShape3D</code> من قائمة الفاحص Inspector لإضافة شكل صندوق ثلاثي الأبعاد لتحديد حدود تصادم الكائنات كما في الصورة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="163066" href="https://academy.hsoub.com/uploads/monthly_2024_11/5.jpg.d667a004cfd057a60758c04d2e28d7f6.jpg" rel=""><img alt="5" class="ipsImage ipsImage_thumbnailed" data-fileid="163066" data-unique="hvqkpyumy" src="https://academy.hsoub.com/uploads/monthly_2024_11/5.jpg.d667a004cfd057a60758c04d2e28d7f6.jpg"> </a>
</p>

<p>
	لتحسين تجربة اللعبة، عليك تعديل حجم الصندوق <code>BoxShape3D</code> بحيث يتناسب بشكل أفضل مع النموذج ثلاثي الأبعاد، يمكنك القيام بذلك بسهولة عن طريق النقر على النقاط البرتقالية الظاهرة على الصندوق وسحبها لتغيير حجمه.
</p>

<p>
	يجب أن يلمس الصندوق الأرض ويكون أقل ثخانة من النموذج بقليل، حيث تعمل محركات الفيزياء بطريقة تجعل الاصطدام يحدث إذا لامس مجسّم اللاعب الكروي حتى زاوية الصندوق، وقد تموت على مسافة من العدو إذا كان الصندوق كبيرًا جدًا مقارنة بالنموذج ثلاثي الأبعاد، مما يجعل اللعبة غير منصفة للاعبين ولا تحقق تجربة لعب صحيحة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163067" href="https://academy.hsoub.com/uploads/monthly_2024_11/6.png.53a93ec5a0fcfea615fc7f4e53208e00.png" rel=""><img alt="6" class="ipsImage ipsImage_thumbnailed" data-fileid="163067" data-unique="srriffaqn" style="width: 400px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/6.png.53a93ec5a0fcfea615fc7f4e53208e00.png"> </a>
</p>

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

<div class="banner-container ipsBox ipsPadding">
	<div class="inner-banner-container">
		<p class="banner-heading">
			دورة تطوير الألعاب
		</p>

		<p class="banner-subtitle">
			ابدأ رحلتك في برمجة وتطوير الألعاب ثنائية وثلاثية الأبعاد وصمم ألعاب تفاعلية ممتعة<br>
			ومليئة بالتحديات.
		</p>

		<div>
			<a class="ipsButton ipsButton_large ipsButton_primary ipsButton_important" href="https://academy.hsoub.com/learn/game-development/" rel="">اشترك الآن</a>
		</div>
	</div>

	<div class="banner-img">
		<a href="https://academy.hsoub.com/learn/game-development/" rel=""><img alt="دورة تطوير الألعاب" src="https://academy.hsoub.com/learn/assets/images/courses/game-development.png"></a>
	</div>
</div>

<h2 id="-2">
	إزالة شخصيات الأعداء الموجودين خارج الشاشة
</h2>

<p>
	سنعمل على توليد شخصيات الأعداء في اللعبة على فترات زمنية منتظمة في مرحلة اللعبة. لكن انتبه فقد يزداد عدد الأعداء في هذه الحالة إلى ما لا نهاية إذا لم نكن حذرين، وليس هذا ما نريده لأن كل نسخة من العدو لها تكلفة ذاكرة ومعالجة ولا حاجة لتحمل تكلفتها عندما يكون العدو خارج الشاشة.
</p>

<p>
	فبمجرد أن يخرج عدو ما من نطاق الشاشة فإننا لم نعد بحاجته بعد ذلك ويجب حذفه. لحسن الحظ يوفر <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="">محرك الألعاب</a> جودو عقدة تكتشف خروج الكائنات من حدود الشاشة تسمى <code>VisibleOnScreenNotifier3D</code> وسنستخدمها لتدمير الأعداء.
</p>

<p>
	<strong>ملاحظة</strong>: عندما تستمر في استنساخ كائن ما ينصح باستخدام تقنية تسمى pooling وتعني تجميع الكائنات وإعادة استخدامها لتجنب تكلفة إنشاء وتدمير النسخ في كل مرة، فهي تعتمد على إنشاء مسبق لمصفوفة من الكائنات وحفظها في الذاكرة وإعادة استخدامها مرارًا وتكرارًا لتحسين الأداء. لكن لا داعي للقلق بشأن هذا الأمر عند العمل بلغة GDScript، فالسبب الرئيسي لاستخدام تقنية pooling هو تجنب التجمد في اللغات التي تعتمد على كنس المهملات garbage-collected مثل لغة C#‎، أما لغة GDScript فتستخدم تقنية عد المراجع مختلفة في ‎<a href="https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_basics.html#doc-gdscript-basics-memory-management" rel="external nofollow">إدارة الذاكرة</a>.
</p>

<p>
	لمراقبة ما إذا كان الكائن قد خرج من الشاشة حدد عقدة <code>Mob</code> وأضف عقدة فرعية <code>VisibleOnScreenNotifier3D</code>، سيظهر صندوق آخر وردي اللون هذه المرة وستصدر العقدة إشارة عندما يخرج هذا المربع تمامًا عن الشاشة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163068" href="https://academy.hsoub.com/uploads/monthly_2024_11/7.png.fd4bce497a0d5dd7ed1dc32baf5970d3.png" rel=""><img alt="7" class="ipsImage ipsImage_thumbnailed" data-fileid="163068" data-unique="2dwvqzy6b" style="width: 450px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/7.png.fd4bce497a0d5dd7ed1dc32baf5970d3.png"> </a>
</p>

<p>
	غيّر حجمه باستخدام النقاط البرتقالية حتى يغطي النموذج ثلاثي الأبعاد بالكامل.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163069" href="https://academy.hsoub.com/uploads/monthly_2024_11/8.png.afcfd28c38c61ab1fa39bc254e0ca8f4.png" rel=""><img alt="8" class="ipsImage ipsImage_thumbnailed" data-fileid="163069" data-unique="5c1kymom1" style="width: 450px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/8.png.afcfd28c38c61ab1fa39bc254e0ca8f4.png"> </a>
</p>

<h2 id="-3">
	برمجة حركة العدو
</h2>

<p>
	دعنا الآن نعمل على حركة الأعداء، وسننجز ذلك على مرحلتين، الأولى سنكتب سكريبت على <code>Mob</code> يعرّف دالة لتهيئة العدو، ثم نبرمج آلية الظهور العشوائي في مشهد <code>main.tscn</code> ونستدعي الدالة من هناك.
</p>

<p>
	أرفق سكريبت بالعقدة <code>Mob</code>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163070" href="https://academy.hsoub.com/uploads/monthly_2024_11/9.png.a3631f0ea147c7cf5ef59ddab291cd3e.png" rel=""><img alt="9" class="ipsImage ipsImage_thumbnailed" data-fileid="163070" data-unique="zrvkx5m7w" src="https://academy.hsoub.com/uploads/monthly_2024_11/9.png.a3631f0ea147c7cf5ef59ddab291cd3e.png"> </a>
</p>

<p>
	فيما يلي الشيفرة البرمجية التي ستنجز الحركة بلغة GDScript، كما تلاحظ عرفنا خاصيتين <code>min_speed</code> و <code>max_speed</code> لتحديد نطاق سرعة عشوائي والذي سنستخدمه لاحقًا  لضبط قيمة سرعة الحركة <code>CharacterBody3D.velocity</code>.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5932_20" style=""><span class="pln">extends </span><span class="typ">CharacterBody3D</span><span class="pln">

</span><span class="com"># سرعة العدو الدنيا مقدرة بالمتر في الثانية.</span><span class="pln">
</span><span class="lit">@export</span><span class="pln"> var min_speed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">10</span><span class="pln">
</span><span class="com"># سرعة العدو القصوى مقدرة بالمتر في الثانية.</span><span class="pln">
</span><span class="lit">@export</span><span class="pln"> var max_speed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">18</span><span class="pln">


func _physics_process</span><span class="pun">(</span><span class="pln">_delta</span><span class="pun">):</span><span class="pln">
    move_and_slide</span><span class="pun">()</span></pre>

<p>
	سنحرك العدو على غرار <a href="https://academy.hsoub.com/programming/game-development/%D8%A3%D9%86%D8%B4%D8%A6-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AD%D8%B1%D9%83-%D8%AC%D9%88%D8%AF%D9%88-%D8%AA%D8%AD%D8%B1%D9%8A%D9%83-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D9%8B%D8%A7-r2450/" rel="">تحريك اللاعب</a> في كل إطار عن طريق استدعاء الدالة <code>CharacterBody3D.move_and_slide()‎</code>. لكن لا نعمل هذه المرة على تحديث السرعة في كل إطار بل نريد أن يتحرك العدو بسرعة ثابتة ويخرج من الشاشة حتى لو اصطدم بعائق.
</p>

<p>
	نحتاج إلى تعريف دالة أخرى لحساب <code>CharacterBody3D.velocity</code> حيث تحوّل هذه الدالة العدو باتجاه اللاعب وتضفي طابعًا عشوائيًا على كل من زاوية حركته وسرعته.
</p>

<p>
	ستأخذ الدالة موقع ظهور العدو أول مرة <code>start_position</code>، وموقع اللاعب <code>player_position </code>كوسيط. نضع العدو في الموقع <code>start_position</code> ونوجهه نحو اللاعب باستخدام الدالة<code>look_at_from_position()‎</code>، ولجعل الحركة أكثر طبيعية نعطي العدو زاوية عشوائية عن طريق الدوران بمقدار عشوائي حول المحور Y بحيث لا يكون دائمًا موجهًا بشكل مباشر نحو اللاعب. تعطينا الدالة <code>randf_range()‎</code> في الكود التالي قيمة عشوائية بين <code>‎-PI/4</code> راديان و <code>PI/4</code> راديان وسنستخدمها لتدوير العدو.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5932_22" style=""><span class="com"># يتم استدعاء هذه الدالة من المشهد  الأساسي</span><span class="pln">
func initialize</span><span class="pun">(</span><span class="pln">start_position</span><span class="pun">,</span><span class="pln"> player_position</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># نحدد واحد من الأعداء بوضعه في‫ start_position</span><span class="pln">
    </span><span class="com"># ‫ونديره باتجاه player_position ليواجه اللاعب</span><span class="pln">
    look_at_from_position</span><span class="pun">(</span><span class="pln">start_position</span><span class="pun">,</span><span class="pln"> player_position</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">UP</span><span class="pun">)</span><span class="pln">
    </span><span class="com"># دوّر هذا العدو تلقائيًا بين  -45 و +45 درجة</span><span class="pln">
    </span><span class="com"># لكي لا تتحرك نحو اللاعب بشكل مباشر.</span><span class="pln">
    rotate_y</span><span class="pun">(</span><span class="pln">randf_range</span><span class="pun">(-</span><span class="pln">PI </span><span class="pun">/</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> PI </span><span class="pun">/</span><span class="pln"> </span><span class="lit">4</span><span class="pun">))</span></pre>

<p>
	لقد حصلنا على موقع عشوائي، والآن نحتاج إلى تحديد سرعة عشوائية باستخدام <code>random_speed</code>. سنستخدم الدالة <code>randi_range()‎</code> للحصول على قيمة عدد صحيح عشوائي حيث سنحدد الحد الأدنى <code>min_speed</code> والحد الأقصى <code>max_speed</code> للسرعة.أما <code>random_speed</code> فهو مجرد عدد صحيح نستخدمه لمضاعفة سرعة الحركة <code>CharacterBody3D.velocity</code>. بعد ذلك، سنوجه  شعاع <code>CharacterBody3D.velocity</code>  نحو اللاعب مع تطبيق <code>random_speed</code> لتحديد السرعة.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5932_24" style=""><span class="pln">func initialize</span><span class="pun">(</span><span class="pln">start_position</span><span class="pun">,</span><span class="pln"> player_position</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># ...</span><span class="pln">

    </span><span class="com"># ‫‎نحسب السرعة العشوائية (عدد صحيح)‎‎‏</span><span class="pln">
    var random_speed </span><span class="pun">=</span><span class="pln"> randi_range</span><span class="pun">(</span><span class="pln">min_speed</span><span class="pun">,</span><span class="pln"> max_speed</span><span class="pun">)</span><span class="pln">
    </span><span class="com"># نحسب سرعة أمامية تمثّل السرعة</span><span class="pln">
    velocity </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">FORWARD </span><span class="pun">*</span><span class="pln"> random_speed
    </span><span class="com"># ‫ثم ندوّر شعاع السرعة على اتجاه دوران العدو حول Y</span><span class="pln">
    </span><span class="com"># لكي يتحرك في اتجاه نظر العدو</span><span class="pln">
    velocity </span><span class="pun">=</span><span class="pln"> velocity</span><span class="pun">.</span><span class="pln">rotated</span><span class="pun">(</span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">UP</span><span class="pun">,</span><span class="pln"> rotation</span><span class="pun">.</span><span class="pln">y</span><span class="pun">)</span></pre>

<h2 id="-4">
	مغادرة الشاشة
</h2>

<p>
	ما زلنا بحاجة إلى تدمير الأعداء عندما يخرجون من الشاشة، لذلك سنربط إشارة <code>screen_exited</code> الخاصة بعقدة <code>VisibleOnScreenNotifier3D</code> بالعقدة <code>Mob</code>.
</p>

<p>
	عد إلى نافذة العرض لاثي الأبعاد، اضغط على التسمية ثلاثي الأبعاد 3D أعلى المحرر أو يمكنك الضغط على <code>Ctrl+F2</code> أو <code>Alt+2</code> على نظام التشغيل macOS.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163071" href="https://academy.hsoub.com/uploads/monthly_2024_11/10.png.5e0a89379baccbdda5bb6f261f60581c.png" rel=""><img alt="10" class="ipsImage ipsImage_thumbnailed" data-fileid="163071" data-unique="kndcjo4n5" src="https://academy.hsoub.com/uploads/monthly_2024_11/10.png.5e0a89379baccbdda5bb6f261f60581c.png"> </a>
</p>

<p>
	حدد العقدة <code>VisibleOnScreenNotifier3D</code> وانتقل إلى القائمة التي تعرض معلومات  العقدة في الجانب الأيمن من واجهة المحرر، ثم انقر نقرًا مزدوجًا فوق الإشارة <code>screen_exited()</code>التي تُرسل عندما يخرج الكائن من الشاشة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163072" href="https://academy.hsoub.com/uploads/monthly_2024_11/11.png.a79f08f8253b25e508d4db9bdc0a1151.png" rel=""><img alt="11" class="ipsImage ipsImage_thumbnailed" data-fileid="163072" data-unique="lwe118tn1" src="https://academy.hsoub.com/uploads/monthly_2024_11/11.png.a79f08f8253b25e508d4db9bdc0a1151.png"> </a>
</p>

<p>
	صِل هذه الإشارة بالعقدة <code>Mob</code>
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163073" href="https://academy.hsoub.com/uploads/monthly_2024_11/12.png.784ebc9e2c3b61652b15ff81713ffa84.png" rel=""><img alt="12" class="ipsImage ipsImage_thumbnailed" data-fileid="163073" data-unique="m145dcbw8" style="width: 300px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/12.png.784ebc9e2c3b61652b15ff81713ffa84.png"> </a>
</p>

<p>
	سيؤدي هذا إلى إعادتك إلى محرر النصوص وإضافة دالة جديدة <code>‎_on_visible_on_screen_notifier_3d_screen_exited()‎</code> ثم استدعِ من خلالها تابع <code>queue_free()‎</code> حيث تعمل هذه الدالة على تدمير النسخة التي يتم استدعاؤها عليها.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5932_30" style=""><span class="pln">func _on_visible_on_screen_notifier_3d_screen_exited</span><span class="pun">():</span><span class="pln">
    queue_free</span><span class="pun">()</span><span class="pln"> </span></pre>

<p>
	بهذا أصبح العدو جاهزًا للدخول إلى منطقة اللعب وسنشرح في الدرس التالي كيفية توليد الأعداء داخل مستوى اللعبة.
</p>

<p>
	إليك الشيفرة البرمجية الكاملة للملف <code>Mob.gd</code> للرجوع إليها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5932_28" style=""><span class="pln">extends </span><span class="typ">CharacterBody3D</span><span class="pln">

</span><span class="com"># سرعة العدو الدنيا مقدرة بالمتر في الثانية</span><span class="pln">
</span><span class="lit">@export</span><span class="pln"> var min_speed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">10</span><span class="pln">
</span><span class="com"># سرعة العدو القصوى مقدرة بالمتر في الثانية</span><span class="pln">
</span><span class="lit">@export</span><span class="pln"> var max_speed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">18</span><span class="pln">

func _physics_process</span><span class="pun">(</span><span class="pln">_delta</span><span class="pun">):</span><span class="pln">
    move_and_slide</span><span class="pun">()</span><span class="pln">

</span><span class="com"># يتم استدعاء هذه الدالة من المشهد  الأساسي</span><span class="pln">
func initialize</span><span class="pun">(</span><span class="pln">start_position</span><span class="pun">,</span><span class="pln"> player_position</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># ‫نحدد واحد من العدو بوضعه في start_position</span><span class="pln">
    </span><span class="com"># ‫ونديره باتجاه player_position, ليواجه اللاعب..</span><span class="pln">
    look_at_from_position</span><span class="pun">(</span><span class="pln">start_position</span><span class="pun">,</span><span class="pln"> player_position</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">UP</span><span class="pun">)</span><span class="pln">
    </span><span class="com"># دوّر هذا العدو تلقائيًا بين  -45 و +45 درجة,</span><span class="pln">
    </span><span class="com"># لكي لا تتحرك نحو اللاعب بشكل مباشر</span><span class="pln">
    rotate_y</span><span class="pun">(</span><span class="pln">randf_range</span><span class="pun">(-</span><span class="pln">PI </span><span class="pun">/</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> PI </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">
    var random_speed </span><span class="pun">=</span><span class="pln"> randi_range</span><span class="pun">(</span><span class="pln">min_speed</span><span class="pun">,</span><span class="pln"> max_speed</span><span class="pun">)</span><span class="pln">
    </span><span class="com"># نحسب سرعة أمامية تمثّل السرعة</span><span class="pln">
    velocity </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">FORWARD </span><span class="pun">*</span><span class="pln"> random_speed
    </span><span class="com"># ثم ندوّر شعاع السرعة على اتجاه دوران العدو حول‫ Y</span><span class="pln">
    </span><span class="com"># لكي يتحرك في اتجاه نظر العدو</span><span class="pln">
    velocity </span><span class="pun">=</span><span class="pln"> velocity</span><span class="pun">.</span><span class="pln">rotated</span><span class="pun">(</span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">UP</span><span class="pun">,</span><span class="pln"> rotation</span><span class="pun">.</span><span class="pln">y</span><span class="pun">)</span><span class="pln">

func _on_visible_on_screen_notifier_3d_screen_exited</span><span class="pun">():</span><span class="pln">
    queue_free</span><span class="pun">()</span><span class="pln"> </span></pre>

<h2 id="-5">
	الخلاصة
</h2>

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

<p>
	ترجمة -وبتصرف- لقسم <a href="https://docs.godotengine.org/en/stable/getting_started/first_3d_game/04.mob_scene.html" rel="external nofollow">Designing the mob scene</a> من توثيق جودو الرسمي.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%AD%D8%B1%D9%8A%D9%83-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D9%8B%D8%A7-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AD%D8%B1%D9%83-%D8%AC%D9%88%D8%AF%D9%88-r2450/" rel="">المقال السابق: تحريك اللاعب برمجيًا في لعبة ثلاثية الأبعاد باستخدام محرك جودو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B9-%D9%84%D9%85%D8%AF%D8%AE%D9%84%D8%A7%D8%AA-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2384/" rel="">الاستماع لمدخلات اللاعب في جودو Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D8%B1%D8%A4%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B5%D9%85%D9%8A%D9%85%D9%8A%D8%A9-%D9%84%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%EF%BB%B7%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot-r2278/" rel="">الرؤية التصميمية لمحرك اﻷلعاب جودو Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot-%D9%84%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r2241/" rel="">إعداد محرك الألعاب جودو Godot للعمل مع قاعدة البيانات SQLite</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2455</guid><pubDate>Tue, 26 Nov 2024 15:00:00 +0000</pubDate></item><item><title>&#x62A;&#x62D;&#x631;&#x64A;&#x643; &#x627;&#x644;&#x644;&#x627;&#x639;&#x628; &#x628;&#x631;&#x645;&#x62C;&#x64A;&#x64B;&#x627; &#x641;&#x64A; &#x644;&#x639;&#x628;&#x629; &#x62B;&#x644;&#x627;&#x62B;&#x64A;&#x629; &#x627;&#x644;&#x623;&#x628;&#x639;&#x627;&#x62F; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x645;&#x62D;&#x631;&#x643; &#x62C;&#x648;&#x62F;&#x648;</title><link>https://academy.hsoub.com/programming/game-development/%D8%AA%D8%AD%D8%B1%D9%8A%D9%83-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D9%8B%D8%A7-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AD%D8%B1%D9%83-%D8%AC%D9%88%D8%AF%D9%88-r2450/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_11/1253772842_.png.9da33a8cc74bd1d4f1ca62359ce65647.png" /></p>
<p>
	حان وقت البرمجة في سلسلتنا التي تشرح <a href="https://academy.hsoub.com/tags/3d_game/" rel="">تطوير الألعاب ثلاثية الأبعاد</a> باستخدام محرك الألعاب جودو، إذ سنستخدم إجراءات الإدخال التي أنشأناها في <a href="https://academy.hsoub.com/programming/game-development/%D8%A3%D9%86%D8%B4%D8%A6-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AD%D8%B1%D9%83-%D8%AC%D9%88%D8%AF%D9%88-%D9%85%D8%B4%D9%87%D8%AF-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D9%88%D8%B9%D9%85%D9%84%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A5%D8%AF%D8%AE%D8%A7%D9%84-r2447/" rel="">الدرس الماضي</a> لتحريك الشخصية في هذا الدرس باستخدام الشيفرة البرمجية، إذ ستساعدنا الشيفرة بضبط تسارع حركة اللاعب وسرعته القصوى بالإضافة لسرعة الجاذبية التي تحدد مقدار تأثير الجاذبية الافتراضية على حركة الشخصية في اللعبة وغيرها من المفاهيم المفيدة.
</p>

<p>
	انقر بزر الفأرة الأيمن على عقدة <code>Player</code> وحدد خيار إضافة سكريبت Attach Script لإضافة سكربت جديد إليها، ثم اضبط القالب Template في النافذة المنبثقة على Empty قبل الضغط على زر إنشاء Create.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162466" href="https://academy.hsoub.com/uploads/monthly_2024_11/1.png.20af146f970de7316875e043c4dd5ea8.png" rel=""><img alt="1" class="ipsImage ipsImage_thumbnailed" data-fileid="162466" data-unique="f989028r6" src="https://academy.hsoub.com/uploads/monthly_2024_11/1.png.20af146f970de7316875e043c4dd5ea8.png"> </a>
</p>

<p>
	سنعرّف خصائص الصنف class مثل سرعة الحركة movement speed وتسارع السقوط fall acceleration الذي يمثل الجاذبية، والسرعة التي سنستخدمها لتحريك الشخصية.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7169_6" style=""><span class="pln">extends </span><span class="typ">CharacterBody3D</span><span class="pln">

</span><span class="com"># سرعة اللاعب بواحدة المتر في الثانية</span><span class="pln">
</span><span class="lit">@export</span><span class="pln"> var speed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">14</span><span class="pln">
</span><span class="com"># التسارع نحو الأسفل في الهواء بوحدة متر في الثانية للتربيع</span><span class="pln">
</span><span class="lit">@export</span><span class="pln"> var fall_acceleration </span><span class="pun">=</span><span class="pln"> </span><span class="lit">75</span><span class="pln">

var target_velocity </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">ZERO</span></pre>

<p>
	هذه الخصائص مشتركة لجسم متحرك حيث أن <code>target_velocity</code> هو شعاع ثلاثي الأبعاد <code>3D vector</code> يجمع بين السرعة والاتجاه. وقد عرفناه هنا كخاصية لأننا نريد تحديث قيمته وإعادة استخدامه عبر الإطارات.
</p>

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

<p>
	لتحديد اتجاه الحركة نبدأ بحساب شعاع اتجاه الإدخال باستخدام الكائن العام <code>input</code> في <code>‎_physics_process()‎</code> لبرمجة الحركة كما في الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7169_8" style=""><span class="pln">func _physics_process</span><span class="pun">(</span><span class="pln">delta</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># أنشأنا متغير محلي لتخزين اتجاه الإدخال</span><span class="pln">
    var direction </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">ZERO

    </span><span class="com"># نتحقق من كل خطوة ونحدّث الاتجاه حسب ذلك</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">is_action_pressed</span><span class="pun">(</span><span class="str">"move_right"</span><span class="pun">):</span><span class="pln">
        direction</span><span class="pun">.</span><span class="pln">x </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">is_action_pressed</span><span class="pun">(</span><span class="str">"move_left"</span><span class="pun">):</span><span class="pln">
        direction</span><span class="pun">.</span><span class="pln">x </span><span class="pun">-=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">is_action_pressed</span><span class="pun">(</span><span class="str">"move_back"</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># لاحظ أننا نعمل مع محاور أشعة x و z</span><span class="pln">
        </span><span class="com"># إن المسطح xz في ثلاثي الأبعاد هو مستوي الأرض</span><span class="pln">
        direction</span><span class="pun">.</span><span class="pln">z </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">is_action_pressed</span><span class="pun">(</span><span class="str">"move_forward"</span><span class="pun">):</span><span class="pln">
        direction</span><span class="pun">.</span><span class="pln">z </span><span class="pun">-=</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span></pre>

<p>
	سنجري هنا جميع الحسابات باستخدام الدالة الافتراضية <code>‎_physics_process()‎</code> وهي على غرار الدالة <code>‎_process()‎</code> التي تتيح لنا تحديث العقدة في كل إطار ولكنها مصممة خصيصًا للشيفرة المتعلقة بالفيزياء مثل تحريك جسم حركي kinematic يتفاعل مع محيطه من خلال الكود البرمجي، أو جسم صلب rigid يعتمد على محرك الفيزياء في جودو للتحرك والتفاعل بشكل واقعي مع البيئة المحيطة به بناءً على القوى المؤثرة عليه مثل الجاذبية أو التصادمات.
</p>

<p>
	ولتعلّم المزيد حول الفرق بين الدالتين ‎ <code>_process()</code>‎ و ‎ ‎ <code>‎_physics_process()‎</code> راجع توثيق جودو حول <a href="https://docs.godotengine.org/en/stable/tutorials/scripting/idle_and_physics_processing.html#doc-idle-and-physics-processing" rel="external nofollow">معالجة الفيزياء والسكون</a> ومعرفة كيفية استخدام هذه الدوال لضبط الحركات الفيزيائية بطريقة مستقرة وواقعية.
</p>

<p>
	نبدأ بتهيئة قيمة المتغير <code>direction</code> إلى <code>Vector3.ZERO</code> ليكون متجهًا فارغًا (أي أن قيمته تكون صفرًا في المحاور الثلاثة x و y و z) ثم نتحقق مما إذا كان اللاعب يضغط على إدخال <code>move_*‎</code> واحد أو أكثر ونغيّر مكوني <code>x</code> و<code>z</code> للشعاع وفقًا لذلك بحيث تتوافق هذه مع محاور مستوى الأرض.
</p>

<p>
	توفر لنا هذه الحالات الأربعة ثمانية احتمالات، وثمانية اتجاهات ممكنة.
</p>

<p>
	سيكون طول الشعاع في حالة ضغط اللاعب مثلًا على كل من W و D في وقت واحد حوالي <code>1.4</code>، ولكن إذا ضغط على مفتاح واحد سيكون طوله <code>1</code>. نستدعي تابع <code>normalized()‎</code> ليكون طول الشعاع ثابتًا ولا يتحرك بشكل أسرع قطريًا.
</p>

<p>
	وفيما يلي شيفرة تحريك أو تدوير الكائن في اتجاه حركة اللاعب بناءً على المدخلات.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7169_10" style=""><span class="com">#func _physics_process(delta):</span><span class="pln">
    </span><span class="com">#...</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> direction </span><span class="pun">!=</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">ZERO</span><span class="pun">:</span><span class="pln">
        direction </span><span class="pun">=</span><span class="pln"> direction</span><span class="pun">.</span><span class="pln">normalized</span><span class="pun">()</span><span class="pln">
        </span><span class="com"># تهيئة خاصية‫ basis سيؤثر على دوارن العقدة.</span><span class="pln">
        $Pivot</span><span class="pun">.</span><span class="pln">basis </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Basis</span><span class="pun">.</span><span class="pln">looking_at</span><span class="pun">(</span><span class="pln">direction</span><span class="pun">)</span></pre>

<p>
	لاحظ أننا نعالج الشعاع فقط إذا كان طوله أكبر من الصفر، مما يعني أن اللاعب يضغط على مفتاح اتجاه، مما يؤدي إلى تحديث <code>direction</code>،بحيث يعكس الاتجاه الذي يريد اللاعب التحرك فيه.
</p>

<p>
	الآن سنحسب الاتجاه الذي ينظر إليه <code>‎$‎Pivot</code> عن طريق إنشاء <code>Basis</code> ينظر في اتجاه <code>direction</code>، ومن ثم نحدّث قيمة السرعة حيث يتعين علينا حساب سرعة الأرض أو السرعة الأفقية وسرعة السقوط أو السرعة العمودية بشكل منفصل، حيث تُدمج السرعات معًا وتُحرّك الشخصية باستخدام الدالة <code>move_and_slide()‎</code> لتطبيق الحركة الفعلية مع الفيزياء.
</p>

<p>
	تأكد من الضغط على مفتاح Tab مرة واحدة بحيث تكون الأسطر داخل دالة <code>‎_physics_process()‎</code> ولكن خارج الشرط الذي كتبناه للتو أعلاه.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7169_12" style=""><span class="pln">func _physics_process</span><span class="pun">(</span><span class="pln">delta</span><span class="pun">):</span><span class="pln">
    </span><span class="com">#...</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> direction </span><span class="pun">!=</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">ZERO</span><span class="pun">:</span><span class="pln">
        </span><span class="com">#...</span><span class="pln">

    </span><span class="com"># السرعة الأرضية</span><span class="pln">
    target_velocity</span><span class="pun">.</span><span class="pln">x </span><span class="pun">=</span><span class="pln"> direction</span><span class="pun">.</span><span class="pln">x </span><span class="pun">*</span><span class="pln"> speed
    target_velocity</span><span class="pun">.</span><span class="pln">z </span><span class="pun">=</span><span class="pln"> direction</span><span class="pun">.</span><span class="pln">z </span><span class="pun">*</span><span class="pln"> speed

    </span><span class="com"># السرعة العمودية</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> is_on_floor</span><span class="pun">():</span><span class="pln"> </span><span class="com"># إذا كان في الهواء، فسيسقط على الأرض </span><span class="pln">
        target_velocity</span><span class="pun">.</span><span class="pln">y </span><span class="pun">=</span><span class="pln"> target_velocity</span><span class="pun">.</span><span class="pln">y </span><span class="pun">-</span><span class="pln"> </span><span class="pun">(</span><span class="pln">fall_acceleration </span><span class="pun">*</span><span class="pln"> delta</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># تحريك الشخصية</span><span class="pln">
    velocity </span><span class="pun">=</span><span class="pln"> target_velocity
    move_and_slide</span><span class="pun">()</span></pre>

<p>
	تعيد دالة <code>‏‎CharacterBody3D.is_on_floor()‎‏</code> قيمة <code>true</code> إذا تصادم الجسم مع الأرض في هذا الإطار، لهذا السبب نطبق الجاذبية على اللاعب <code>Player</code> فقط عندما يكون في الهواء.
</p>

<p>
	بالنسبة للسرعة العمودية، نطرح تسارع السقوط مضروبًا في وقت دلتا في كل إطار. سيؤدي هذا السطر من الشيفرة إلى سقوط شخصيتنا في كل إطار طالما أنها ليست على الأرض أو لم تصطدم بها.
</p>

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

<p>
	نستدعي في السطر الأخير التابع <code>CharacterBody3D.move_and_slide()‎</code> وهو تابع ذو قدرات عظيمة لصنف <code>CharacterBody3D</code> إذ يسمح لك بتحريك الشخصية بسلاسة، حيث سيحاول محرك جودو إصلاح الحركة إذا اصطدمت بحائط في منتصفها باستخدام قيمة السرعة الأصلية <code>CharacterBody3D</code>.
</p>

<p>
	هذه هي كل الشيفرة التي تحتاجها لتحريك الشخصية على الأرض. فيما يلي شيفرة <code>Player.gd</code> الكاملة لاستخدامها كمرجع:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7169_14" style=""><span class="pln">extends </span><span class="typ">CharacterBody3D</span><span class="pln">

</span><span class="com"># سرعة اللاعب بواحدة المتر في الثانية</span><span class="pln">
</span><span class="lit">@export</span><span class="pln"> var speed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">14</span><span class="pln">
</span><span class="com"># التسارع نحو الأسفل في الهواء بوحدة متر في الثانية للتربيع.</span><span class="pln">
</span><span class="lit">@export</span><span class="pln"> var fall_acceleration </span><span class="pun">=</span><span class="pln"> </span><span class="lit">75</span><span class="pln">

var target_velocity </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">ZERO


func _physics_process</span><span class="pun">(</span><span class="pln">delta</span><span class="pun">):</span><span class="pln">
    var direction </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">ZERO

    </span><span class="kwd">if</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">is_action_pressed</span><span class="pun">(</span><span class="str">"move_right"</span><span class="pun">):</span><span class="pln">
        direction</span><span class="pun">.</span><span class="pln">x </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">is_action_pressed</span><span class="pun">(</span><span class="str">"move_left"</span><span class="pun">):</span><span class="pln">
        direction</span><span class="pun">.</span><span class="pln">x </span><span class="pun">-=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">is_action_pressed</span><span class="pun">(</span><span class="str">"move_back"</span><span class="pun">):</span><span class="pln">
        direction</span><span class="pun">.</span><span class="pln">z </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="typ">Input</span><span class="pun">.</span><span class="pln">is_action_pressed</span><span class="pun">(</span><span class="str">"move_forward"</span><span class="pun">):</span><span class="pln">
        direction</span><span class="pun">.</span><span class="pln">z </span><span class="pun">-=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> direction </span><span class="pun">!=</span><span class="pln"> </span><span class="typ">Vector3</span><span class="pun">.</span><span class="pln">ZERO</span><span class="pun">:</span><span class="pln">
        direction </span><span class="pun">=</span><span class="pln"> direction</span><span class="pun">.</span><span class="pln">normalized</span><span class="pun">()</span><span class="pln">
        $Pivot</span><span class="pun">.</span><span class="pln">basis </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Basis</span><span class="pun">.</span><span class="pln">looking_at</span><span class="pun">(</span><span class="pln">direction</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># السرعة الأرضية</span><span class="pln">
    target_velocity</span><span class="pun">.</span><span class="pln">x </span><span class="pun">=</span><span class="pln"> direction</span><span class="pun">.</span><span class="pln">x </span><span class="pun">*</span><span class="pln"> speed
    target_velocity</span><span class="pun">.</span><span class="pln">z </span><span class="pun">=</span><span class="pln"> direction</span><span class="pun">.</span><span class="pln">z </span><span class="pun">*</span><span class="pln"> speed

    </span><span class="com"># السرعة العمودية</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> is_on_floor</span><span class="pun">():</span><span class="pln"> </span><span class="com"># إذا كان في الهواء، فيسقط على الأرض </span><span class="pln">
        target_velocity</span><span class="pun">.</span><span class="pln">y </span><span class="pun">=</span><span class="pln"> target_velocity</span><span class="pun">.</span><span class="pln">y </span><span class="pun">-</span><span class="pln"> </span><span class="pun">(</span><span class="pln">fall_acceleration </span><span class="pun">*</span><span class="pln"> delta</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># تحريك الشخصية</span><span class="pln">
    velocity </span><span class="pun">=</span><span class="pln"> target_velocity
    move_and_slide</span><span class="pun">()</span></pre>

<div class="banner-container ipsBox ipsPadding">
	<div class="inner-banner-container">
		<p class="banner-heading">
			دورة تطوير الألعاب
		</p>

		<p class="banner-subtitle">
			ابدأ رحلتك في برمجة وتطوير الألعاب ثنائية وثلاثية الأبعاد وصمم ألعاب تفاعلية ممتعة ومليئة بالتحديات
		</p>

		<div>
			<a class="ipsButton ipsButton_large ipsButton_primary ipsButton_important" href="https://academy.hsoub.com/learn/game-development/" rel="">اشترك الآن</a>
		</div>
	</div>

	<div class="banner-img">
		<a href="https://academy.hsoub.com/learn/game-development/" rel=""><img alt="دورة تطوير الألعاب" src="https://academy.hsoub.com/learn/assets/images/courses/game-development.png"></a>
	</div>
</div>

<h2 id="-1">
	اختبار حركة اللاعب
</h2>

<p>
	نحتاج إلى استنساخ اللاعب ثم إضافة كاميرا من أجل وضع اللاعب في المشهد <code>Main</code> واختباره. لن ترى أي شيء في الفضاء ثلاثي الأبعاد إذا لم تحوي نافذة العرض كاميرا موجهة نحو شيء ما، على عكس الفضاء ثنائي الأبعاد.
</p>

<p>
	احفظ المشهد <code>Player</code> وافتح المشهد <code>Main</code> من خلال النقر على تبويب Main  أعلى المحرر.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162467" href="https://academy.hsoub.com/uploads/monthly_2024_11/2.png.8ddbc4c4e9b34142cbf90b55a019dd78.png" rel=""><img alt="2" class="ipsImage ipsImage_thumbnailed" data-fileid="162467" data-unique="ts335wcu5" src="https://academy.hsoub.com/uploads/monthly_2024_11/2.png.8ddbc4c4e9b34142cbf90b55a019dd78.png"> </a>
</p>

<p>
	إذا أغلقت المشهد من قبل، فتوجه إلى نافذة نظام الملفات FileSystem Dock وانقر نقرًا مزدوجًا فوق <code>main.tscn</code> لإعادة فتحه.
</p>

<p>
	انقر بزر الفأرة الأيمن على العقدة الرئيسية <code>Main</code> وحدد Instantiate Child Scene لاستنساخ المشهد <code>Player</code> .
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162468" href="https://academy.hsoub.com/uploads/monthly_2024_11/3.png.d7288818a20854155d398e7e4c353bb5.png" rel=""><img alt="3" class="ipsImage ipsImage_thumbnailed" data-fileid="162468" data-unique="azl8hwc8x" src="https://academy.hsoub.com/uploads/monthly_2024_11/3.png.d7288818a20854155d398e7e4c353bb5.png"> </a>
</p>

<p>
	الآن انقر نقرًا مزدوجًا فوق <code>player.tscn</code> في النافذة المنبثقة لتظهر الشخصية في وسط نافذة العرض.
</p>

<h3 id="-2">
	إضافة كاميرا
</h3>

<p>
	سننشئ إعدادًا أساسيًا تمامًا كما فعلنا مع <code>`Pivot. انقر بزر الفأرة الأيمن على عقدة المشهد الرئيسي</code>Main<code>مرة أخرى وحدد خيار "إضافة عقدة فرعية Add Child Node"، ثم أنشئ عقدة</code>Marker3D<code>جديدة وسمها</code>CameraPivot<code>، ومن ثم حدد</code>CameraPivot<code>وأضف إليها عقدة فرعية</code>Camera3D` لتبدو شجرة المشهد على هذا النحو
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162469" href="https://academy.hsoub.com/uploads/monthly_2024_11/4.png.a37ca0d4eb9ff2e7b59d5b3d509dc778.png" rel=""><img alt="4" class="ipsImage ipsImage_thumbnailed" data-fileid="162469" data-unique="spzrykw6t" src="https://academy.hsoub.com/uploads/monthly_2024_11/4.png.a37ca0d4eb9ff2e7b59d5b3d509dc778.png"> </a>
</p>

<p>
	ستلاحظ مربع اختيار معاينة Preview يظهر في الزاوية العلوية اليسرى عند تحديد الكاميرا حيث يمكنك النقر فوقه لمعاينة إسقاط الكاميرا داخل اللعبة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162470" href="https://academy.hsoub.com/uploads/monthly_2024_11/5.png.fbc973d373feaf48adce661209158262.png" rel=""><img alt="5" class="ipsImage ipsImage_thumbnailed" data-fileid="162470" data-unique="9ktsfuxn7" src="https://academy.hsoub.com/uploads/monthly_2024_11/5.png.fbc973d373feaf48adce661209158262.png"> </a>
</p>

<p>
	سنستخدم المحور Pivot لتدوير الكاميرا كما لو كانت على رافعة، لذا دعنا أولاً نقسم نافذة العرض ثلاثي الأبعاد 3D view لنتمكن من التنقل بحرية في المشهد ورؤية ما تراه الكاميرا. في شريط الأدوات أعلى النافذة مباشرةً، انقر فوق View ثم 2‎ ‎Viewports. يمكنك أيضًا الضغط على <code>Ctrl + 2</code> (أو <code>Cmd + 2</code>على نظام التشغيل macOS).
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162471" href="https://academy.hsoub.com/uploads/monthly_2024_11/6.png.88004ba42a89f59d66d64add8f152856.png" rel=""><img alt="6" class="ipsImage ipsImage_thumbnailed" data-fileid="162471" data-unique="rduzdvb06" src="https://academy.hsoub.com/uploads/monthly_2024_11/6.png.88004ba42a89f59d66d64add8f152856.png"> </a>
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162472" href="https://academy.hsoub.com/uploads/monthly_2024_11/7.png.0a3c7b3fec5e7938af170522fc0d764a.png" rel=""><img alt="7" class="ipsImage ipsImage_thumbnailed" data-fileid="162472" data-unique="icjax8t1t" src="https://academy.hsoub.com/uploads/monthly_2024_11/7.png.0a3c7b3fec5e7938af170522fc0d764a.png"> </a>
</p>

<p>
	حدد <code>Camera3D</code> في النافذة السفلية، وشغّل معاينة الكاميرا بالنقر فوق مربع الاختيار Preview.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162473" href="https://academy.hsoub.com/uploads/monthly_2024_11/8.png.90ff16abb96939734c1ef352012d9785.png" rel=""><img alt="8" class="ipsImage ipsImage_thumbnailed" data-fileid="162473" data-unique="4xpp24y18" src="https://academy.hsoub.com/uploads/monthly_2024_11/8.png.90ff16abb96939734c1ef352012d9785.png"> </a>
</p>

<p>
	حرك الكاميرا في النافذة العلوية حوالي <code>19</code> وحدة على المحور Z ذي اللون الأزرق.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162474" href="https://academy.hsoub.com/uploads/monthly_2024_11/9.png.425029aa64de22553b76884c1a357bfc.png" rel=""><img alt="9" class="ipsImage ipsImage_thumbnailed" data-fileid="162474" data-unique="mkxcml1q2" src="https://academy.hsoub.com/uploads/monthly_2024_11/9.png.425029aa64de22553b76884c1a357bfc.png"> </a>
</p>

<p>
	هنا نرى ثمرة عملنا، حدد CameraPivot ودوره بمقدار <code>‎-‎45</code> درجة حول محور X باستخدام الدائرة الحمراء لترى الكاميرا تتحرك كما لو كانت متصلة برافعة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162475" href="https://academy.hsoub.com/uploads/monthly_2024_11/10.png.bef720f01a4940e5110401fa038bc59e.png" rel=""><img alt="10" class="ipsImage ipsImage_thumbnailed" data-fileid="162475" data-unique="1pd9xnwt5" src="https://academy.hsoub.com/uploads/monthly_2024_11/10.png.bef720f01a4940e5110401fa038bc59e.png"> </a>
</p>

<p>
	يمكنك تشغيل المشهد بالضغط على <code>F6</code> ثم الضغط على مفاتيح الأسهم لتحريك الشخصية.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162476" href="https://academy.hsoub.com/uploads/monthly_2024_11/11.png.ef327bbbf2fe4661a3a5a2451c99693f.png" rel=""><img alt="11" class="ipsImage ipsImage_thumbnailed" data-fileid="162476" data-unique="o5bvlc3on" style="width: 516px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/11.png.ef327bbbf2fe4661a3a5a2451c99693f.png"> </a>
</p>

<p>
	يمكننا رؤية مساحة فارغة حول الشخصية بسبب الإسقاط المنظوري perspective projection، لذلك سنستخدم بدلاً منه في هذه اللعبة إسقاطًا متعامدًا orthographic projection لتأطير منطقة اللعب بشكل أفضل وتسهيل قراءة المسافات على اللاعب.
</p>

<p>
	حدد الكاميرا مرة أخرى ومن قائمة الفاحص Inspector اضبط الإسقاط على القيمة "متعامد Orthogonal" والحجم على <code>19</code>. يجب أن تبدو الشخصية مسطحة أكثر ومنسجمة مع الأرضية.
</p>

<p>
	<strong>ملاحظة</strong>: تعتمد جودة الظل الاتجاهي directional shadow على قيمة Far للكاميرا عند استخدام كاميرا متعامدة فإعداد Far يحدد المسافة الأبعد التي يمكن للكاميرا رؤيته، وكلما زادت قيمة البعد زادت المسافة التي ستتمكن الكاميرا من الرؤية فيها. بالرغم من ذلك، فإن قيم البعد الأعلى ستقلل أيضًا من جودة الظل حيث يجب أن يغطي عرض الظل مسافة أكبر.
</p>

<p>
	قلل خاصية البعد للكاميرا إلى قيمة أقل مثل <code>100</code> إذا بدت الظلال الاتجاهية ضبابية جدًا بعد التبديل إلى كاميرا متعامدة فلا تقلل من خاصية البعد هذه كثيرًا وإلا ستبدأ الكائنات في البعد بالاختفاء.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162477" href="https://academy.hsoub.com/uploads/monthly_2024_11/12.png.27612b6a6a78b4b4e1af937fd85a1880.png" rel=""><img alt="12" class="ipsImage ipsImage_thumbnailed" data-fileid="162477" data-unique="s49glscly" src="https://academy.hsoub.com/uploads/monthly_2024_11/12.png.27612b6a6a78b4b4e1af937fd85a1880.png"> </a>
</p>

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

<h2 id="-3">
	الخلاصة
</h2>

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

<p>
	ترجمة - وبتصرف - لقسم <a href="https://docs.godotengine.org/en/stable/gettingstarted/first3dgame/03.playermovement_code.html" rel="external nofollow">Moving the player with code</a> من توثيق جودو الرسمي.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/game-development/%D8%A3%D9%86%D8%B4%D8%A6-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AD%D8%B1%D9%83-%D8%AC%D9%88%D8%AF%D9%88-%D9%85%D8%B4%D9%87%D8%AF-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D9%88%D8%B9%D9%85%D9%84%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A5%D8%AF%D8%AE%D8%A7%D9%84-r2447/" rel="">أنشئ لعبة ثلاثية الأبعاد باستخدام محرك جودو: مشهد اللاعب وعمليات الإدخال</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D8%B9%D9%82%D8%AF-nodes-%D9%88%D8%A7%D9%84%D9%85%D8%B4%D8%A7%D9%87%D8%AF-scenes-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2357/" rel="">العقد Nodes والمشاهد Scenes في جودو Godot</a>
	</li>
	<li>
		<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 Engines</a>
	</li>
	<li>
		<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>
	</li>
</ul>
]]></description><guid isPermaLink="false">2450</guid><pubDate>Tue, 19 Nov 2024 15:00:00 +0000</pubDate></item><item><title>&#x625;&#x639;&#x62F;&#x627;&#x62F; &#x645;&#x634;&#x647;&#x62F; &#x627;&#x644;&#x644;&#x627;&#x639;&#x628; &#x648;&#x639;&#x645;&#x644;&#x64A;&#x627;&#x62A; &#x627;&#x644;&#x625;&#x62F;&#x62E;&#x627;&#x644; &#x641;&#x64A; &#x644;&#x639;&#x628;&#x629; &#x62B;&#x644;&#x627;&#x62B;&#x64A;&#x629; &#x627;&#x644;&#x623;&#x628;&#x639;&#x627;&#x62F; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x62C;&#x648;&#x62F;&#x648;</title><link>https://academy.hsoub.com/programming/game-development/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%85%D8%B4%D9%87%D8%AF-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D9%88%D8%B9%D9%85%D9%84%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A5%D8%AF%D8%AE%D8%A7%D9%84-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D9%88%D8%AF%D9%88-r2447/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_11/32191667_.png.6af64cda1530cafb39959ecdb7635e69.png" /></p>
<p>
	سنكتمل في مقال اليوم إنشاء لعبتنا ثلاثية الأبعاد باستخدام محرك جودو التي بدأنا العمل عليها في <a href="https://academy.hsoub.com/programming/game-development/%D8%A3%D9%86%D8%B4%D8%A6-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AD%D8%B1%D9%83-%D8%AC%D9%88%D8%AF%D9%88-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%85%D9%86%D8%B7%D9%82%D8%A9-%D8%A7%D9%84%D9%84%D8%B9%D8%A8-r2444/" rel="">المقال السابق</a> وأعددنا فيها منطقة اللعب، وسنصمم في هذا الدرس مشهد اللاعب ونحقق عمليات إدخال مخصصة ونبرمج حركة اللاعب، وفي النهاية سيكون لديك شخصية لعبة تتحرك في ثمانية اتجاهات.
</p>

<h2 id="-1">
	إنشاء مشهد اللاعب
</h2>

<p>
	أنشئ مشهدًا جديدًا بالانتقال إلى قائمة "مشهد Scene" في أعلى اليسار وانقر فوق "مشهد جديد New Scene"
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162263" href="https://academy.hsoub.com/uploads/monthly_2024_11/1.png.17e204264b33a97169c6545bc1f1dc4d.png" rel=""><img alt="1" class="ipsImage ipsImage_thumbnailed" data-fileid="162263" data-unique="nz58qnla5" style="width: 300px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/1.png.17e204264b33a97169c6545bc1f1dc4d.png"> </a>
</p>

<p>
	أنشئ عقدة <code>CharacterBody3D</code> كعقدة جذر حيث تستخدم هذه العقدة للتحكم في شخصيات الألعاب ثلاثية الأبعاد، مثل اللاعبين أو الأعداء. وتوفر أدوات للتحكم في الحركة والتصادم.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162264" href="https://academy.hsoub.com/uploads/monthly_2024_11/2.png.9cde82b5c4f5e52edde58502a1228580.png" rel=""><img alt="2" class="ipsImage ipsImage_thumbnailed" data-fileid="162264" data-unique="qh6hr3nik" style="width: 500px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/2.png.9cde82b5c4f5e52edde58502a1228580.png"> </a>
</p>

<p>
	غيّر اسم العقدة <code>CharacterBody3D</code> إلى <code>Player</code> لتحديد أنها تمثل شخصية اللاعب في اللعبة. تشبه هذه العقدة كل من المناطق area والأجسام الصلبة rigid bodies التي استخدمناها في <a href="https://academy.hsoub.com/programming/game-development/%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-%D8%A7%D9%84%D8%A8%D8%B9%D8%AF-%D8%B9%D8%A8%D8%B1-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-godot-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%AA%D8%AC%D9%87%D9%8A%D8%B2-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D9%88%D8%B6%D8%A8%D8%B7-%D8%A7%D9%84%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF%D8%A7%D8%AA-r2280/" rel="">برمجة لعبة ثنائية الأبعاد</a>، إذ يمكنها التحرك والاصطدام بالبيئة مثل الأجسام الصلبة، لكن بدلاً من التحكم بها بواسطة محرك الفيزياء الذي يحدد سلوك الحركة، مثل الجاذبية أو الارتداد، بشكل تلقائ.، فإنك هنا تحدد حركتها بنفسك. وسترى كيف نستخدم ميزات العقدة الفريدة عند برمجة قفزة اللاعب وآلية القتال. وللتعرف على أنواع العقد المختلفة، راجع <a href="https://docs.godotengine.org/en/stable/tutorials/physics/physics_introduction.html#doc-physics-introduction" rel="external nofollow">مقدمة إلى الفيزياء</a> في توثيق جودو.
</p>

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

<p>
	أضف عقدة <code>Node3D</code> كعقدة فرعية للعقدة <code>Player</code> وسمّها <code>Pivot</code> لتكون عقدة وسيطة يمكننا التحكم من خلالها في زاوية دوران النموذج.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162265" href="https://academy.hsoub.com/uploads/monthly_2024_11/3.png.1fcf228bee80163b91d9b17771a6984e.png" rel=""><img alt="3" class="ipsImage ipsImage_thumbnailed" data-fileid="162265" data-unique="amlkrkrrz" style="width: 500px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/3.png.1fcf228bee80163b91d9b17771a6984e.png"> </a>
</p>

<p>
	بعد ذلك، في لوحة "نظام الملفات FileSystem"، افتح مجلد الرسومات <code>art/‎</code> بالنقر المزدوج عليه، ثم اسحب وأفلت الملف <code>player.glb</code> على العقدة <code>Pivot</code>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162266" href="https://academy.hsoub.com/uploads/monthly_2024_11/4.png.5d535765fdaad086fb1ad42db11d7cfe.png" rel=""><img alt="4" class="ipsImage ipsImage_thumbnailed" data-fileid="162266" data-unique="402podm4h" src="https://academy.hsoub.com/uploads/monthly_2024_11/4.png.5d535765fdaad086fb1ad42db11d7cfe.png"> </a>
</p>

<p>
	يجب أن يؤدي هذا إلى إنشاء النموذج كعقدة فرعية للعقدة <code>Pivot</code>، كما يمكنك إعادة تسميتها إلى <code>Character</code>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162267" href="https://academy.hsoub.com/uploads/monthly_2024_11/5.png.090e07426c4008d2ff1a79157c30ce58.png" rel=""><img alt="5" class="ipsImage ipsImage_thumbnailed" data-fileid="162267" data-unique="066btq5pe" src="https://academy.hsoub.com/uploads/monthly_2024_11/5.png.090e07426c4008d2ff1a79157c30ce58.png"> </a>
</p>

<p>
	ملاحظة: تحتوي ملفات <code>‎.glb</code> على بيانات مشهد ثلاثي الأبعاد بناءً على مواصفات GLTF 2.0 مفتوحة المصدر، وهي بديل حديث وقوي لنوع الملفات الاحتكارية FBX، والذي يدعمه جودو أيضًا. وقد صممنا النموذج في Blender 3D لإنتاج هذه الملفات، ثم صدّرناه إلى GLTF.
</p>

<p>
	كما هو الحال مع جميع أنواع العقد، نحتاج إلى شكل تصادم لشخصيتنا كي تتصادم به مع البيئة. حدد عقدة <code>Player</code> مرة أخرى وأضف عقدة فرعية <code>CollisionShape3D</code> في قائمة "الفاحص Inspector" ومن خاصية الشكل "Shape" أضف شكل <code>SphereShape3D</code> جديد.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162268" href="https://academy.hsoub.com/uploads/monthly_2024_11/6.png.23d6b6845a4e5d4710b80bbca041b6f9.png" rel=""><img alt="6" class="ipsImage ipsImage_thumbnailed" data-fileid="162268" data-unique="jf6k4b7h2" src="https://academy.hsoub.com/uploads/monthly_2024_11/6.png.23d6b6845a4e5d4710b80bbca041b6f9.png"> </a>
</p>

<p>
	سيظهر الإطار السلكي للكرة أسفل الشخصية كما في الصورة التالية.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162269" href="https://academy.hsoub.com/uploads/monthly_2024_11/7.png.a94d0bf67d31665feb5bb602c50ac88e.png" rel=""><img alt="7" class="ipsImage ipsImage_thumbnailed" data-fileid="162269" data-unique="ioltlz4e9" style="width: 400px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/7.png.a94d0bf67d31665feb5bb602c50ac88e.png"> </a>
</p>

<p>
	سيكون هذا هو الشكل الذي يستخدمه المحرك الفيزيائي للاصطدام بالبيئة، لذلك نريد أن يتناسب بشكل أفضل مع النموذج ثلاثي الأبعاد. صغّره قليلاً عن طريق سحب النقطة البرتقالية في نافذة العرض viewport، حيث يبلغ نصف قطر الكرة حوالي <code>0.8</code> متر، ثم حرك الشكل لأعلى بحيث يتماشى قعره تقريبًا مع مستوى الشبكة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162270" href="https://academy.hsoub.com/uploads/monthly_2024_11/8.png.b771e39dea8a4db5d91eeb766820ea31.png" rel=""><img alt="8" class="ipsImage ipsImage_thumbnailed" data-fileid="162270" data-unique="g17ujikvx" style="width: 400px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/8.png.b771e39dea8a4db5d91eeb766820ea31.png"> </a>
</p>

<p>
	يمكنك إخفاء وإظهار النموذج عن طريق النقر فوق أيقونة العين بجوار عقدة <code>Character</code> أو عقدة <code>Pivot</code>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162271" href="https://academy.hsoub.com/uploads/monthly_2024_11/9.png.e163194bd4328670236707b3583b4fff.png" rel=""><img alt="9" class="ipsImage ipsImage_thumbnailed" data-fileid="162271" data-unique="pbgkp1gsd" src="https://academy.hsoub.com/uploads/monthly_2024_11/9.png.e163194bd4328670236707b3583b4fff.png"> </a>
</p>

<p>
	احفظ المشهد باسم <code>player.tscn</code>
</p>

<p>
	نحتاج الآن إلى تعريف بعض إجراءات الإدخال قبل البدء بالبرمجة عندما تصبح العقد جاهزة.
</p>

<h2 id="-2">
	إنشاء إجراءات الإدخال
</h2>

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

<p>
	هذا النظام هو "خريطة الإدخال Input Map"، وللوصول إلى المحرر الخاص بها، توجه إلى قائمة "المشروع Project" وحدد "إعدادات المشروع Project Settings".
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162272" href="https://academy.hsoub.com/uploads/monthly_2024_11/10.png.4ff6efbb609f04f452d8474df9394c50.png" rel=""><img alt="10" class="ipsImage ipsImage_thumbnailed" data-fileid="162272" data-unique="aejwlxmpd" style="width: 300px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/10.png.4ff6efbb609f04f452d8474df9394c50.png"> </a>
</p>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162273" href="https://academy.hsoub.com/uploads/monthly_2024_11/11.png.dcdb71b3f70a75ba7f66ee8148825e8f.png" rel=""><img alt="11" class="ipsImage ipsImage_thumbnailed" data-fileid="162273" data-unique="9vekuhm1p" style="width: 400px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/11.thumb.png.9af3e40068ad09fe9a38c645727acbd0.png"> </a>
</p>

<p>
	تأتي مشاريع جودو مع بعض الإجراءات المحددة مسبقًا predefined actions والموجهة لتصميم واجهة المستخدم التي يمكننا استخدامها هنا، لكننا سنحدد إجراءاتنا الخاصة لدعم وحدات التحكم gamepads.
</p>

<p>
	سنسمي إجراءاتنا <code>move_left</code> للتحرك يسارًا، و <code>move_right</code> للتحرك يمينًا و <code>move_forward</code> للتحرك للأمام، و <code>move_back</code> للتحرك للخلف، و <code>jump</code> للقفز.
</p>

<p>
	اكتب اسم الإجراء في الشريط العلوي واضغط على Enter لإضافته.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162274" href="https://academy.hsoub.com/uploads/monthly_2024_11/12.png.57eb028d4b0c694afb6185278597333e.png" rel=""><img alt="12" class="ipsImage ipsImage_thumbnailed" data-fileid="162274" data-unique="jhaom9uli" style="width: 400px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/12.png.57eb028d4b0c694afb6185278597333e.png"> </a>
</p>

<p>
	أنشئ الإجراءات الخمسة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162275" href="https://academy.hsoub.com/uploads/monthly_2024_11/13.png.e07c7341fff96fe4086e35edec769d0f.png" rel=""><img alt="13" class="ipsImage ipsImage_thumbnailed" data-fileid="162275" data-unique="gijk18r81" src="https://academy.hsoub.com/uploads/monthly_2024_11/13.png.e07c7341fff96fe4086e35edec769d0f.png"> </a>
</p>

<p>
	لربط مفتاح أو زر بإجراء، انقر فوق زر إشارة "+" على يمينه. افعل ذلك من أجل <code>move_left</code> للتحرك يسارًا، ثم اضغط على مفتاح السهم الأيسر وانقر فوق موافق OK.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162276" href="https://academy.hsoub.com/uploads/monthly_2024_11/14.png.6b62d2f2dd819641e2c5b75c27df6e5e.png" rel=""><img alt="14" class="ipsImage ipsImage_thumbnailed" data-fileid="162276" data-unique="cl1umkqdb" style="width: 400px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/14.png.6b62d2f2dd819641e2c5b75c27df6e5e.png"> </a>
</p>

<p>
	اربط أيضًا مفتاح <code>A</code> على إجراء <code>move_left</code>
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162277" href="https://academy.hsoub.com/uploads/monthly_2024_11/15.png.391143c086d6f481ccd4413a554c0119.png" rel=""><img alt="15" class="ipsImage ipsImage_thumbnailed" data-fileid="162277" data-unique="b5zovr5zq" style="width: 500px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/15.png.391143c086d6f481ccd4413a554c0119.png"> </a>
</p>

<p>
	لنضيف الآن دعمًا لعصا التحكم اليسرى في وحدة التحكم Gamepad من خلال النقر فوق الزر "+" مرة أخرى ولكن هذه المرة سنحدد خيار Manual Selection ومن ثم‏ محاور عصا التحكم Joypad Axes ‏
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162278" href="https://academy.hsoub.com/uploads/monthly_2024_11/16.png.3ade4abbfbabebdbde33c95645db9f6f.png" rel=""><img alt="16" class="ipsImage ipsImage_thumbnailed" data-fileid="162278" data-unique="vvxwei59a" style="width: 400px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/16.png.3ade4abbfbabebdbde33c95645db9f6f.png"> </a>
</p>

<p>
	حدد المحور السالب X لعصا التحكم اليسرى.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162279" href="https://academy.hsoub.com/uploads/monthly_2024_11/17.png.83091e615ebedcb63ac899215248bdd2.png" rel=""><img alt="17" class="ipsImage ipsImage_thumbnailed" data-fileid="162279" data-unique="56hqm9bxc" style="width: 400px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/17.png.83091e615ebedcb63ac899215248bdd2.png"> </a>
</p>

<p>
	اترك القيم الأخرى كقيمة افتراضية واضغط على موافق OK.
</p>

<p>
	<strong>ملاحظة</strong>: إذا كنت تريد أن تحتوي وحدات التحكم على إجراءات إدخال مختلفة، فيجب عليك استخدام خيار أجهزة Devices في قائمة الخيارات الإضافية Additional Options. يقابل الجهاز 0 أول وحدة تحكم موصولة، ويقابل الجهاز 1 ثاني وحدة تحكم ...إلخ.
</p>

<p>
	افعل الشيء نفسه لإجراءات الإدخال الأخرى. على سبيل المثال، اربط السهم الأيمن و المفتاح D، والمحور الموجب لعصا التحكم اليسرى بالإجراء <code>move_right</code>، وبعد ربط جميع المفاتيح، يجب أن تبدو واجهتك على هذا النحو.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162280" href="https://academy.hsoub.com/uploads/monthly_2024_11/18.png.76aa8b2ed33ae574ea7b54f89d840e39.png" rel=""><img alt="18" class="ipsImage ipsImage_thumbnailed" data-fileid="162280" data-unique="e91oabjob" style="width: 400px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/18.png.76aa8b2ed33ae574ea7b54f89d840e39.png"> </a>
</p>

<p>
	آخر إجراء مطلوب إعداده هو إجراء القفز <code>jump</code>، اربط مفتاح المسافة Space وزر A في وحدة التحكم Gamepad من أجل تحقيقه في لعبتك.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162281" href="https://academy.hsoub.com/uploads/monthly_2024_11/19.png.f211179ba5c58fa04d3c2261e33d7b82.png" rel=""><img alt="19" class="ipsImage ipsImage_thumbnailed" data-fileid="162281" data-unique="8icj3gb5r" style="width: 400px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/19.png.f211179ba5c58fa04d3c2261e33d7b82.png"> </a>
</p>

<p>
	يجب أن يبدو إجراء إدخال القفز الخاص بك على هذا النحو.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162282" href="https://academy.hsoub.com/uploads/monthly_2024_11/20.png.1153a18e2121dece75aec2260c970b72.png" rel=""><img alt="20" class="ipsImage ipsImage_thumbnailed" data-fileid="162282" data-unique="lr5emj483" src="https://academy.hsoub.com/uploads/monthly_2024_11/20.png.1153a18e2121dece75aec2260c970b72.png"> </a>
</p>

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

<h2 id="-3">
	الخلاصة
</h2>

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

<p>
	ترجمة -وبتصرف- لقسم <a href="https://docs.godotengine.org/en/stable/gettingstarted/first3dgame/02.playerinput.html" rel="external nofollow">Player scene and input actions</a> من توثيق جودو الرسمي.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/game-development/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%85%D9%86%D8%B7%D9%82%D8%A9-%D8%A7%D9%84%D9%84%D8%B9%D8%A8-%D9%84%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D9%88%D8%AF%D9%88-r2444/" rel="">إعداد منطقة اللعب للعبة ثلاثية الأبعاد باستخدام جودو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B9-%D9%84%D9%85%D8%AF%D8%AE%D9%84%D8%A7%D8%AA-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2384/" rel="">الاستماع لمدخلات اللاعب في جودو Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%A7%D9%84%D9%81%D9%8A%D8%AF%D9%8A%D9%88-%D8%AA%D8%B7%D9%88%D8%B1%D9%87%D8%A7-%D9%88%D8%A3%D9%87%D9%85%D9%8A%D8%AA%D9%87%D8%A7-%D9%88%D8%AE%D8%B7%D9%88%D8%A7%D8%AA-%D8%A8%D8%B1%D9%85%D8%AC%D8%AA%D9%87%D8%A7-r2290/" rel="">ألعاب الفيديو: تطورها وأهميتها وخطوات برمجتها</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A3%D9%81%D9%83%D8%A7%D8%B1-%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D9%81%D9%8A%D8%AF%D9%8A%D9%88/" rel="">كيف تحصل على أفكار ألعاب فيديو ناجحة</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2447</guid><pubDate>Fri, 15 Nov 2024 15:05:00 +0000</pubDate></item><item><title>&#x625;&#x639;&#x62F;&#x627;&#x62F; &#x645;&#x646;&#x637;&#x642;&#x629; &#x627;&#x644;&#x644;&#x639;&#x628; &#x644;&#x644;&#x639;&#x628;&#x629; &#x62B;&#x644;&#x627;&#x62B;&#x64A;&#x629; &#x627;&#x644;&#x623;&#x628;&#x639;&#x627;&#x62F; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x62C;&#x648;&#x62F;&#x648;</title><link>https://academy.hsoub.com/programming/game-development/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%85%D9%86%D8%B7%D9%82%D8%A9-%D8%A7%D9%84%D9%84%D8%B9%D8%A8-%D9%84%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D9%88%D8%AF%D9%88-r2444/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_11/507099753_.png.c46c4315d4d0b99a7d0c3ac631c5db45.png" /></p>
<p>
	ستُنشئ في هذا المقال والمقالات اللاحقة لعبة كاملة ثلاثية الأبعاد باستخدام محرك الألعاب جودو Godot، وسيكون لديك في نهاية السلسلة مشروع بسيط ومتكامل من تصميمك الخاص، مثل الصورة المتحركة أدناه.
</p>

<p style="text-align: center;">
	<img alt="Copy of squash-the-creeps-final.gif" class="ipsImage ipsImage_thumbnailed" data-fileid="161747" data-ratio="75.31" data-unique="q7w6rcw62" style="width: 350px; height: auto;" width="478" src="https://academy.hsoub.com/uploads/monthly_2024_11/Copyofsquash-the-creeps-final.gif.2fd29d568a22f91215083327066f4a70.gif">
</p>

<p>
	ستكون اللعبة التي سنبرمجها هنا مشابهة <a href="https://academy.hsoub.com/programming/game-development/%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-%D8%A7%D9%84%D8%A8%D8%B9%D8%AF-%D8%B9%D8%A8%D8%B1-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-godot-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%AA%D8%AC%D9%87%D9%8A%D8%B2-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D9%88%D8%B6%D8%A8%D8%B7-%D8%A7%D9%84%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF%D8%A7%D8%AA-r2280/" rel="">للعبة ثنائية الأبعاد</a> التي شرحناها في مقالاتنا السابقة، ولكن مع لمسة إضافية وهي القفز بهدف القضاء على الأعداء، فتكون بهذه الطريقة قد تعرفت على أنماط تعلمتها في الدروس السابقة واستفدت منها في بناء شيفرات وميزات جديدة.
</p>

<p>
	إليك ما ستتعلمه خلال تطوير لعبة ثلاثية الأبعاد في جودو:
</p>

<ul>
	<li>
		العمل مع إحداثيات ثلاثية الأبعاد في حركة القفز.
	</li>
	<li>
		استخدام الأجسام الحركية kinematic bodies لتحريك شخصيات ثلاثية الأبعاد واكتشاف كيف ومتى يحدث التصادم.
	</li>
	<li>
		استخدام طبقات الفيزياء physics layers والتصنيف في مجموعات للكشف عن تفاعلات كيانات محددة.
	</li>
	<li>
		كتابة التعليمات البرمجية الأساسية للعبة عن طريق إنشاء الأعداء على فترات زمنية منتظمة.
	</li>
	<li>
		تصميم الحركة وتغيير سرعتها في وقت التنفيذ.
	</li>
	<li>
		رسم واجهة مستخدم في لعبة ثلاثية الأبعاد، وغيره الكثير.
	</li>
</ul>

<p>
	سنبدأ مع تعليمات مشروحة بالتفصيل ونختصرها كلما مررنا بخطوات مشابهة، وإذا كنت من المبرمجين المتمرسين يمكنك النظر إلى الشيفرة البرمجية النهائية على الرابط التالي: <a href="https://github.com/godotengine/godot-3d-dodge-the-creeps" rel="external nofollow">Squash the Creep source code</a>
</p>

<p>
	لقد أعددنا بعض موارد assets اللعبة حتى نتمكن من البدء مباشرة بكتابة الشيفرة البرمجية. ويمكنك تنزيلها من هنا: <a href="https://github.com/godotengine/godot-3d-dodge-the-creeps/releases/tag/1.1.0" rel="external nofollow">Squash the Creeps assets</a> حيث سنعمل أولاً على نموذج أولي أساسي لحركة اللاعب، ثم سنضيف الوحوش التي ستوزع عشوائيًا حول الشاشة، بعد ذلك، سننفذ حركة القفز وآلية القتال قبل تحسين اللعبة ببعض الرسوم المتحركة، وسنختم بالنتيجة مع عبارة إعادة المحاولة.
</p>

<h2 id="-1">
	البدء بإعداد منطقة اللعب
</h2>

<p>
	سنتعلم في البداية كيفية إعداد منطقة اللعب، إذ ستكون أحداث اللعبة تقع بكاملها في هذه المنطقة. لنبدأ بإعداد منطقة اللعب عن طريق استيراد موارد البدء start assets وإعداد مشهد اللعبة. حضّرنا مشروع جودو مع النماذج ثلاثية الأبعاد والأصوات التي سنستخدمها في هذه السلسلة، إذا لم تحمّل الملف المضغوط حتى الآن، يمكنك تنزيله من <a href="https://github.com/godotengine/godot-3d-dodge-the-creeps/releases/tag/1.1.0" rel="external nofollow">هنا</a>، بعد ذلك، استخرج ملف ‎.‎zip على حاسوبك، وافتح مدير مشروع جودو وانقر على زر "استيراد Import".
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="161727" href="https://academy.hsoub.com/uploads/monthly_2024_11/1.png.52b28b7d3da4620ba84214503806b979.png" rel=""><img alt="1" class="ipsImage ipsImage_thumbnailed" data-fileid="161727" data-unique="dlrx1mgpw" style="width: 500px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/1.png.52b28b7d3da4620ba84214503806b979.png"> </a>
</p>

<p>
	أدخل المسار الكامل للمجلد الذي أُنشئ حديثًا <code>squash_the_creeps_start/‎</code> في نافذة الاستيراد المنبثقة، ثم انقر على زر "تصفح Browse" على اليمين لفتح مستعرض الملفات والانتقال إلى ملف <code>project.godot</code> الموجود داخل المجلد.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="161728" href="https://academy.hsoub.com/uploads/monthly_2024_11/2.png.0df23d6e53a5608a36faa06cf5c79da3.png" rel=""><img alt="2" class="ipsImage ipsImage_thumbnailed" data-fileid="161728" data-unique="o98b0lo1l" style="width: 500px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/2.png.0df23d6e53a5608a36faa06cf5c79da3.png"> </a>
</p>

<p>
	الآن انقر فوق "استيراد وتعديل Import &amp; Edit" لفتح المشروع في المحرر
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="161729" href="https://academy.hsoub.com/uploads/monthly_2024_11/3.png.cea0d0908132b93d64dfa7a97cface58.png" rel=""><img alt="3" class="ipsImage ipsImage_thumbnailed" data-fileid="161729" data-unique="xja6ck3ev" style="width: 400px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/3.png.cea0d0908132b93d64dfa7a97cface58.png"> </a>
</p>

<p>
	يحتوي مشروع البداية على أيقونة ومجلدين هما<code> art</code> و <code>fonts</code>، حيث ستجد هناك الموارد الفنية كالصور والأيقونات والصوتية التي سنستخدمها في اللعبة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="161730" href="https://academy.hsoub.com/uploads/monthly_2024_11/4.png.79ce77bf2af1ec01f1e331a16d07d9c5.png" rel=""><img alt="4" class="ipsImage ipsImage_thumbnailed" data-fileid="161730" data-unique="30yewl1cx" style="width: 264px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/4.png.79ce77bf2af1ec01f1e331a16d07d9c5.png"> </a>
</p>

<p>
	كما هو ملاحظ من الصورة السابقة التي توضّح محتويات نظام الملفات في مشروع جودو؛ هناك نموذجان ثلاثيا الأبعاد هما <code>player.glb</code> الخاص بالشخصية الرئيسية للاعب و<code>mob.glb</code> الخاص بشخصية العدو في مجلد <code>art</code> وبعض المواد التي تنتمي إلى هذه النماذج مع مقطوعة موسيقية.
</p>

<h2 id="-2">
	خطوات إنشاء منطقة اللعب
</h2>

<p>
	سنُنشئ الآن المشهد الرئيسي في اللعبة باستخدام <a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D8%B9%D9%82%D8%AF-nodes-%D9%88%D8%A7%D9%84%D9%85%D8%B4%D8%A7%D9%87%D8%AF-scenes-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2357/" rel="">عقدة Node</a> اعتيادية. انقر في نافذة "المشهد Scene" على زر "إضافة عقدة فرعية Add Child Node" المتمثل برمز إشارة الجمع في أعلى اليسار وانقر نقرًا مزدوجًا فوق Node ثم سمّي العقدة الرئيسية باسم <code>Main</code>.
</p>

<p>
	هناك طريقة بديلة لإعادة تسمية العقدة وهي النقر بالزر الأيمن فوق العقدة واختيار "إعادة التسمية Rename" أو <code>F2</code> يمكنك الضغط أيضًا على <code>Ctrl + A</code> (أو <code>Cmd + A</code> على نظام التشغيل macOS) لإضافة عقدة إلى المشهد.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="161731" href="https://academy.hsoub.com/uploads/monthly_2024_11/5.png.0b3cf721f11a934590706cf5a7caa288.png" rel=""><img alt="5" class="ipsImage ipsImage_thumbnailed" data-fileid="161731" data-unique="df2cornkm" src="https://academy.hsoub.com/uploads/monthly_2024_11/5.png.0b3cf721f11a934590706cf5a7caa288.png"> </a>
</p>

<p>
	احفظ المشهد باسم <code>main.tscn</code> بالضغط على <code>Ctrl + S</code>‏ (Cmd + S<code>‎</code> ‏على نظام التشغيل macOS).
</p>

<p>
	سنبدأ بإضافة أرضية تمنع الشخصيات من السقوط، ويمكنك استخدام عقد <code>StaticBody3D</code> لإنشاء تصادمات مع أجسام ثابتة static colliders مثل الأرضية أو الجدران أو الأسقف بحيث تصطدم بها الشخصيات أو الأجسام الأخرى دون أن تتأثر هي، لكنها تتطلب عقدًا فرعية <code>CollisionShape3D</code> لتحديد منطقة التصادم. أضف عقدة <code>StaticBody3D</code> بعد تحديد العقدة الرئيسية <code>Main</code>، ثم عقدة <code>CollisionShape3D</code> وأعد تسمية العقدة <code>StaticBody3D</code> إلى <code>Ground</code>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="161732" href="https://academy.hsoub.com/uploads/monthly_2024_11/6.png.052ffe3d1451710a9fb580fb7f727915.png" rel=""><img alt="6" class="ipsImage ipsImage_thumbnailed" data-fileid="161732" data-unique="l7umbog62" style="width: 500px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/6.png.052ffe3d1451710a9fb580fb7f727915.png"> </a>
</p>

<p>
	يجب أن يظهر تسلسل العقد في المشهد الخاص بك على هذا النحو:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="161733" href="https://academy.hsoub.com/uploads/monthly_2024_11/7.png.d2466891e2b02d4cc363d1176d1a20da.png" rel=""><img alt="7" class="ipsImage ipsImage_thumbnailed" data-fileid="161733" data-unique="56f4u56pc" src="https://academy.hsoub.com/uploads/monthly_2024_11/7.png.d2466891e2b02d4cc363d1176d1a20da.png"> </a>
</p>

<p>
	سترى علامة تحذير بجوار عقدة <code>CollisionShape3D</code> لأننا لم نحدد شكلها، وإذا نقرت على الأيقونة فستظهر نافذة منبثقة لتزويدك بمزيد من المعلومات.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="161734" href="https://academy.hsoub.com/uploads/monthly_2024_11/8.png.983324585dae628ce5f5b01f991a9cad.png" rel=""><img alt="8" class="ipsImage ipsImage_thumbnailed" data-fileid="161734" data-unique="lfh06cqpn" style="width: 500px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/8.png.983324585dae628ce5f5b01f991a9cad.png"> </a>
</p>

<p>
	حدد العقدة <code>CollisionShape3D</code> لإنشاء شكل التصادم، ثم توجه إلى قائمة "الفاحص Inspector" وانقر على الحقل <empty> بجوار خاصية الشكل "Shape" لإنشاء "BoxShape3D" وهو شكل صندوقي ثلاثي الأبعاد.</empty>
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="161735" href="https://academy.hsoub.com/uploads/monthly_2024_11/9.jpg.14dfe5bcb94f9ebdc2a520d9b49efb43.jpg" rel=""><img alt="9" class="ipsImage ipsImage_thumbnailed" data-fileid="161735" data-unique="bgnfc6a8q" src="https://academy.hsoub.com/uploads/monthly_2024_11/9.jpg.14dfe5bcb94f9ebdc2a520d9b49efb43.jpg"> </a>
</p>

<p>
	سنختار شكل الصندوق لكونه مثالي للأرضية المستوية والجدران، ويعتبر موثوقًا من ناحية اعتراض وصد الأجسام سريعة الحركة بفضل سماكته التي تجعل التصادم أكثر استقرارًا.
</p>

<p>
	يظهر إطار سلكي للصندوق wireframe في نافذة العرض viewport بثلاث نقاط برتقالية، حيث يمكنك نقر وسحب هذه النقاط لتعديل أبعاد الشكل بشكل تفاعلي. فوظيفة الإطار السلكي هي مساعدة <a href="https://academy.hsoub.com/programming/game-development/%D9%85%D8%B7%D9%88%D8%B1-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8/" rel="">مطور اللعبة</a> على رؤية حجم الشكل وأبعاده وتعديله بسهولة أثناء التصميم.
</p>

<p>
	كما يمكنك تحديد الحجم بدقة من قائمة "الفاحص Inspector" من خلال النقر فوق <code>BoxShape3D</code> لتوسيع المورد، ثم ضبط قيمة الحجم إلى <code>60</code> على محور X، و <code>2</code> على محور Y، و <code>60</code> على محور Z.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="161736" href="https://academy.hsoub.com/uploads/monthly_2024_11/10.png.e9425aa26b74855c1f3acf0b4723b5de.png" rel=""><img alt="10" class="ipsImage ipsImage_thumbnailed" data-fileid="161736" data-unique="4q2zi5c0v" style="width: 264px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/10.png.e9425aa26b74855c1f3acf0b4723b5de.png"> </a>
</p>

<p>
	ونظرًا لكون أشكال التصادم غير مرئية، فستحتاج إلى إضافة أرضية مرئية معها من خلال تحديد العقدة <code>Ground</code> وإضافة <code>MeshInstance3D</code> كعقدة فرعية لها.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="161737" href="https://academy.hsoub.com/uploads/monthly_2024_11/11.png.2089676e6e27e848a5011da57ea48e36.png" rel=""><img alt="11" class="ipsImage ipsImage_thumbnailed" data-fileid="161737" data-unique="0uainq8cj" src="https://academy.hsoub.com/uploads/monthly_2024_11/11.png.2089676e6e27e848a5011da57ea48e36.png"> </a>
</p>

<p>
	انقر على الحقل بجوار الشبكة "Mesh" في قائمة "الفاحص Inspector"، ثم أنشئ مورد <code>BoxMesh</code> لإنشاء صندوق مرئي.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="161738" href="https://academy.hsoub.com/uploads/monthly_2024_11/12.png.73120cc412ce52cf0efde59be19cc40a.png" rel=""><img alt="12" class="ipsImage ipsImage_thumbnailed" data-fileid="161738" data-unique="tv27rtlmp" src="https://academy.hsoub.com/uploads/monthly_2024_11/12.png.73120cc412ce52cf0efde59be19cc40a.png"> </a>
</p>

<p>
	كما تلاحظ؛ يكون الصندوق بشكل اقتراضي صغيرًا جدًا ولذا نحتاج إلى زيادة حجمه عن طريق النقر فوق أيقونة الصندوق لتوسيع المورد واضبط قيمته على <code>60</code> و <code>2</code> و <code>60</code>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="161739" href="https://academy.hsoub.com/uploads/monthly_2024_11/13.png.633329bf7fc69a1aa9276d0d91412e61.png" rel=""><img alt="13" class="ipsImage ipsImage_thumbnailed" data-fileid="161739" data-unique="nq93dm2qz" src="https://academy.hsoub.com/uploads/monthly_2024_11/13.png.633329bf7fc69a1aa9276d0d91412e61.png"> </a>
</p>

<p>
	يجب أن ترى الآن مسطحًا رماديًا عريضًا يغطي الشبكة والمحاور الزرقاء والحمراء في نافذة العرض.
</p>

<p>
	سنحرّك الأرضية إلى أسفل حتى نتمكن من رؤية شبكة الأرضية floor grid. حدد العقدة <code>Ground</code> واضغط باستمرار على مفتاح Ctrl لتشغيل التقاط الشبكة grid snapping، ثم انقر واسحب لأسفل على محور Y الذي هو السهم الأخضر في أداة التحريك move gizmo.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="161740" href="https://academy.hsoub.com/uploads/monthly_2024_11/14.png.691e9645c7bdd10be05eb40d12316bbf.png" rel=""><img alt="14" class="ipsImage ipsImage_thumbnailed" data-fileid="161740" data-unique="fvkkkthb3" style="width: 500px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/14.thumb.png.b87e86e417861b7fb2fde819029b7fcd.png"> </a>
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p data-gramm="false">
		ملاحظة: إذا لم تتمكن من رؤية محرك Manipulator كائن ثلاثي الأبعاد كما هو موضح في الصورة أعلاه، فتأكد من تنشيط وضع التحديد Select Mode في شريط الأدوات أعلى نافذة العرض.
	</p>
</blockquote>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="161741" href="https://academy.hsoub.com/uploads/monthly_2024_11/15.png.9928df10be587e6a7153bc83f1d11998.png" rel=""><img alt="15" class="ipsImage ipsImage_thumbnailed" data-fileid="161741" data-unique="8id8sr02p" src="https://academy.hsoub.com/uploads/monthly_2024_11/15.png.9928df10be587e6a7153bc83f1d11998.png"> </a>
</p>

<p>
	حرك الأرضية إلى أسفل بمقدار<code>1</code> متر حتى تصبح شبكة المحرر مرئية، وستخبرك إشارة في الركن الأيسر السفلي من نافذة العرض بمدى انتقال translating العقدة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="161742" href="https://academy.hsoub.com/uploads/monthly_2024_11/16.png.1effc3b1f92aa065e910ca2bdf8a2e65.png" rel=""><img alt="16" class="ipsImage ipsImage_thumbnailed" data-fileid="161742" data-unique="azoqlda77" src="https://academy.hsoub.com/uploads/monthly_2024_11/16.png.1effc3b1f92aa065e910ca2bdf8a2e65.png"> </a>
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p data-gramm="false">
		ملاحظة: يؤدي تحريك العقدة <code>Ground</code> إلى تحريك العقدتين الفرعيتين معها. تأكد من تحريك العقدة <code>Ground</code>، وليس العقدة <code>MeshInstance3D</code> أو <code>CollisionShape3D</code>
	</p>
</blockquote>

<p>
	في النهاية، يجب أن تكون قيمة <code>‎‎transform.position.y</code> للعقدة <code>Ground</code> هي ‎-‎1.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="161743" href="https://academy.hsoub.com/uploads/monthly_2024_11/17.png.5dd3a7f76328ca064de9c0568fa96d2a.png" rel=""><img alt="17" class="ipsImage ipsImage_thumbnailed" data-fileid="161743" data-unique="gd17y4b7d" style="width: 500px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/17.thumb.png.c7b17d85413b58afbf974bb68f63110b.png"> </a>
</p>

<p>
	لنضف ضوءًا اتجاهيًا directional light حتى لا يكون مشهدنا بالكامل باللون الرمادي ولتوفير إضاءة طبيعية، لذا حدد العقدة الرئيسية <code>Main</code> وأضف عقدة فرعية <code>DirectionalLight3D</code> لها فهذه العقدة هي المسؤولة عن توفير الإضاءة في المشهد.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="161744" href="https://academy.hsoub.com/uploads/monthly_2024_11/18.png.321f76cfe54bb14e8d2bd3c5e5c1e959.png" rel=""><img alt="18" class="ipsImage ipsImage_thumbnailed" data-fileid="161744" data-unique="xg3tzo6n0" style="width: 500px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/18.png.321f76cfe54bb14e8d2bd3c5e5c1e959.png"> </a>
</p>

<p>
	نحن بحاجة إلى تحريك وتدوير العقدة <code>DirectionalLight3D</code> حيث يمكنك تحريكها لأعلى بالنقر والسحب على السهم الأخضر لأداة التحريك، وانقر واسحب على القوس الأحمر لتدويرها حول محور X حتى تضاء الأرضية.
</p>

<p>
	شغل الظل من قائمة "الفاحص Inspector"، حيث يمكن تفعيله بالنقر فوق مربع الاختيار جانب "فعّال On".
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="161745" href="https://academy.hsoub.com/uploads/monthly_2024_11/19.png.b2fdba08a46db3371b60596e72599152.png" rel=""><img alt="19" class="ipsImage ipsImage_thumbnailed" data-fileid="161745" data-unique="qrsxuagjq" src="https://academy.hsoub.com/uploads/monthly_2024_11/19.png.b2fdba08a46db3371b60596e72599152.png"> </a>
</p>

<p>
	في هذه المرحلة، يجب أن يبدو مشروعك كما يلي.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="161746" href="https://academy.hsoub.com/uploads/monthly_2024_11/20.png.2fb44ebbf8b10f78234ca5b8836faabe.png" rel=""><img alt="20" class="ipsImage ipsImage_thumbnailed" data-fileid="161746" data-unique="9akcbscqx" style="width: 500px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/20.thumb.png.4ccb29cdd18c8f61618c3aae0fe58b70.png"> </a>
</p>

<h2 id="-3">
	الخلاصة
</h2>

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

<p>
	ترجمة - وبتصرف - لقسم <a href="https://docs.godotengine.org/en/stable/getting_started/first_3d_game/index.html" rel="external nofollow">Your first 3D game</a> وقسم <a href="https://docs.godotengine.org/en/stable/gettingstarted/first3dgame/01.gamesetup.html" rel="external nofollow">Setting up the game area</a> من توثيق جودو الرسمي.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/game-development/%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-%D8%A7%D9%84%D8%A8%D8%B9%D8%AF-%D8%B9%D8%A8%D8%B1-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-godot-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D8%AE%D9%8A%D8%B1-%D8%B1%D8%A8%D8%B7-%D9%85%D8%B4%D8%A7%D9%87%D8%AF-%D8%A7%D9%84%D9%84%D8%B9%D8%A8%D8%A9-%D9%88%D9%88%D8%B6%D8%B9-%D8%A7%D9%84%D9%84%D9%85%D8%B3%D8%A7%D8%AA-%D8%A7%EF%BB%B7%D8%AE%D9%8A%D8%B1%D8%A9-r2295/" rel="">بناء لعبة ثنائية البعد عبر محرك الألعاب Godot - الجزء الأخير: ربط مشاهد اللعبة ووضع اللمسات اﻷخيرة</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot/" rel="">مدخل إلى محرك الألعاب جودو Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-r2304/" rel="">دليلك الشامل إلى برمجة الألعاب</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A%D8%A9/" rel="">أشهر أنواع الألعاب الإلكترونية</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2444</guid><pubDate>Sun, 10 Nov 2024 15:00:00 +0000</pubDate></item><item><title>&#x62F;&#x644;&#x64A;&#x644;&#x643; &#x644;&#x644;&#x63A;&#x629; &#x628;&#x631;&#x645;&#x62C;&#x629; &#x628;&#x631;&#x648;&#x633;&#x64A;&#x633;&#x64A;&#x646;&#x62C; Processing | &#x627;&#x644;&#x62C;&#x632;&#x621; &#x627;&#x644;&#x62B;&#x627;&#x644;&#x62B;: &#x628;&#x646;&#x627;&#x621; &#x644;&#x639;&#x628;&#x629; &#x628;&#x633;&#x64A;&#x637;&#x629;</title><link>https://academy.hsoub.com/programming/game-development/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D9%84%D9%84%D8%BA%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%B1%D9%88%D8%B3%D9%8A%D8%B3%D9%8A%D9%86%D8%AC-processing-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%B9%D8%A8%D8%A9-%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9-r2437/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_10/----Processing-----.png.7dbc655f827480ccce7bc86b9c68f34f.png" /></p>
<p>
	تسهّل لغة بروسيسنج Processing من عملية إنشاء النماذج الأولية للتطبيقات المرئية. إذ يصبح بناء لعبة بسيطة أسهل مما قد تتصور بفضل تركيباتها البرمجية سهلة الاستخدام وبعض الحسابات.
</p>

<p>
	هذا هو الجزء الثالث من سلسلة <a href="https://academy.hsoub.com/tags/%D9%84%D8%BA%D8%A9%20%D8%A8%D8%B1%D9%85%D8%AC%D8%A9%20processing/" rel="">لغة بروسيسنج Processing</a>. قدمنا في الجزأين الأول والثاني شرحًا تفصيليًا أساسيًا للغة بروسيسنج Processing. والخطوة التي سنشرحها اليوم والتي ستعزز لك تعلم بروسيسنج Processing هي بكل بساطة التعرف على مزيد من الأمثلة البرمجية العملية.
</p>

<p>
	سنوضح في هذا المقال كيفية استخدام بروسيسنج Processing لتنفيذ لعبتك الخاصة، خطوة بخطوة. سيتم شرح كل خطوة بالتفصيل. وبعد ذلك، سننقل اللعبة إلى الويب.
</p>

<p>
	قبل أن نبدأ التطبيق العملي، إليك شيفرة الشعار الوارد في تمرين <a href="https://gist.github.com/oguzgelal/a3b8fba696cc4e662158" rel="external nofollow">شاشة توقف DVD</a><span ipsnoautolink="true">‎‎‎</span> الذي شرحناه في <a href="https://academy.hsoub.com/programming/game-development/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D9%84%D9%84%D8%BA%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-processing-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D9%84%D8%B1%D8%B3%D9%85-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%A7%D8%B9%D9%84-%D9%85%D8%B9-%D8%AF%D8%AE%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-r2432/" rel="">المقال السابق</a>.
</p>

<h2 id="processing-1">
	الدرس التعليمي لبروسيسنج Processing: لعبة بسيطة
</h2>

<p>
	<a href="https://codepen.io/anon/pen/BjyzoP/left" rel="external nofollow">اللعبة</a> التي سنبنيها في هذا الدرس التعليمي باستخدام Processing هي مزيج بين كل من <a href="https://academy.hsoub.com/programming/game-development/%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A%D8%A9/" rel="">الألعاب الإلكترونية</a> التالية "Flappy Bird" و "Pong" و "Brick Breaker". وسبب اختيار هذه اللعبة هو احتواؤها على معظم المفاهيم التي يصادفها المبتدئون عند تعلم <a href="https://academy.hsoub.com/programming/game-development/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-r2304/" rel="">برمجة الألعاب</a>. وتشمل هذه المفاهيم الجاذبية، والتصادمات، والحفاظ على النقاط، والتعامل مع شاشات مختلفة وتفاعل لوحة المفاتيح/الفأرة. ولعبة "Flappy Pong" تحتوي على كل هذه المفاهيم.
</p>

<p>
	<a href="https://codepen.io/HsoubAcademy/pen/zYgPBob" rel="external nofollow">العب الآن!</a>
</p>

<p>
	<iframe allowfullscreen="true" allowtransparency="true" frameborder="no" height="450" loading="lazy" scrolling="no" src="https://codepen.io/HsoubAcademy/embed/zYgPBob?default-tab=result" style="width: 100%;" title="Flappy Pong">See the Pen Flappy Pong by Hsoub Academy (@HsoubAcademy) on CodePen.</iframe>
</p>

<p>
	ليس من السهل بناء ألعابٍ معقدة دون استخدام مفاهيم <a href="https://academy.hsoub.com/programming/general/%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-oop/" rel="">البرمجة بالكائنات <abbr title="Object-Oriented Programming | البرمجة كائنية التوجه"><abbr title="Object-Oriented Programming | البرمجة كائنية التوجه">OOP</abbr></abbr></a>،  فألعاب المنصات ذات المستويات المتعددة، ومتعددة اللاعبين تجعل الشيفرة معقدة جدًا والبرمجة كائينة التوجه تساعدك على تنظيمها. وقد بذلنا قصارى جهدنا لجعل هذا الدرس التعليمي منظمًا وبسيطًا.
</p>

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

<h2 id="flappypong">
	بناء لعبة Flappy Pong
</h2>

<h3 id="1processing">
	الخطوة 1: تهيئة ومعالجة الشاشات المختلفة
</h3>

<p>
	الخطوة الأولى هي تهيئة مشروعنا. سنكتب كتل الإعداد والرسم كالمعتاد، وما من شيء جديد في هذه الخطوة، ثم سنتعامل مع الشاشات المختلفة (شاشة البداية، شاشة اللعب، شاشة الخروج إلخ). فكيف نجعل بروسيسنج Processing تعرض الصفحة الصحيحة في الوقت الصحيح؟
</p>

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

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4424_20" style=""><span class="com">/********* VARIABLES *********/</span><span class="pln">

</span><span class="com">// نتحكم بالشاشة التي نريد تنشيطها من خلال تحديث وضبط متغيّر‫ gameScreen</span><span class="pln">
</span><span class="com">// ‫ نعرض الشاشة الصحيحة بحسب قيمة هذا المتغير</span><span class="pln">
</span><span class="com">//</span><span class="pln">
</span><span class="com">// 0: الشاشة الأساسية</span><span class="pln">
</span><span class="com">// 1: شاشة اللعبة</span><span class="pln">
</span><span class="com">// 2: شاشة نهاية اللعبة</span><span class="pln">

</span><span class="typ">int</span><span class="pln"> gameScreen </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">/********* SETUP BLOCK *********/</span><span class="pln">

</span><span class="kwd">void</span><span class="pln"> setup</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  size</span><span class="pun">(</span><span class="lit">500</span><span class="pun">,</span><span class="pln"> </span><span class="lit">500</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">


</span><span class="com">/********* DRAW BLOCK *********/</span><span class="pln">

</span><span class="kwd">void</span><span class="pln"> draw</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="pln"> </span><span class="pun">(</span><span class="pln">gameScreen </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    initScreen</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">gameScreen </span><span class="pun">==</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    gameScreen</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">gameScreen </span><span class="pun">==</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    gameOverScreen</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">/********* SCREEN CONTENTS *********/</span><span class="pln">

</span><span class="kwd">void</span><span class="pln"> initScreen</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="pun">}</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> gameScreen</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="pun">}</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> gameOverScreen</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="pun">}</span><span class="pln">


</span><span class="com">/********* INPUTS *********/</span><span class="pln">

</span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> mousePressed</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="pln"> </span><span class="pun">(</span><span class="pln">gameScreen</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">
    startGame</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">/********* OTHER FUNCTIONS *********/</span><span class="pln">

</span><span class="com">// هذا التابع يحدد المتغيرات الضرورية لبدء اللعبة </span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> startGame</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  gameScreen</span><span class="pun">=</span><span class="lit">1</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	قد تبدو الشيفرة معقدة في البداية، ولكن كل ما فعلناه هو بناء الهيكل الأساسي وفصل الأجزاء المختلفة باستخدام كتل التعليقات.
</p>

<p>
	كما ترى، نحدد تابعًا مختلفًا لعرض كل شاشة. ونتحقق ببساطة من قيمة متغير <code>gameScreen</code> في كتلة الرسم الخاصة بنا، ونستدعي التابع المقابل.
</p>

<p>
	في الجزء <code>void mousePressed(){...}‎</code>، نستمع إلى نقرات الفأرة وإذا كانت الشاشة النشطة هي 0، الشاشة الأولية، نستدعي تابع <code>startGame()‎</code> الذي يبدأ اللعبة. ويغيّر السطر الأول من هذا التابع متغير <code>gameScreen</code> إلى 1 شاشة اللعبة.
</p>

<p>
	إذا فهمت ذلك، فالخطوة التالية هي تنفيذ شاشتنا الأولية. وللقيام بذلك، سنحرر تابع <code>initScreen()‎</code>. الذي يبدأ من هنا :
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4424_22" style=""><span class="kwd">void</span><span class="pln"> initScreen</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  background</span><span class="pun">(</span><span class="lit">0</span><span class="pun">);</span><span class="pln">
  textAlign</span><span class="pun">(</span><span class="pln">CENTER</span><span class="pun">);</span><span class="pln">
  text</span><span class="pun">(</span><span class="str">"Click to start"</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">/</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> width</span><span class="pun">/</span><span class="lit">2</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	و
</p>

<p>
	الآن، في شاشة البداية، أضفتا خلفية سوداء ونص بسيط يظهر في منتصف الشاشة مكتوب عليه "انقر للبدء Click to start". ومع ذلك، عند النقر على الشاشة، لا يحدث أي شيء، وهذا متوقع لأننا لم نكتب بعد أي كود لتنفيذ محتوى اللعبة بعد الضغط على الزر. حالياً، لا تحتوي  الدالة ()gameScreen على أية تعليمات، ولذلك عند الانتقال لشاشة اللعبة، فإن المحتوى السابق (أي النص) لا يزال يظهر لأننا لم نضع تعليمة ()background لإعادة رسم الخلفية وإخفاء المحتويات السابقة. لهذا السبب، يستمر النص في الظهور حتى بعد الانتقال، تمامًا كما كان يحدث في مثال الكرة المتحركة التي كانت تترك أثراً خلفها.لذلك دعونا نمضي قدمًا ونبدأ في تنفيذ شاشة اللعبة.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4424_24" style=""><span class="kwd">void</span><span class="pln"> gameScreen</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  background</span><span class="pun">(</span><span class="lit">255</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	بعد هذا التغيير، ستلاحظ أن الخلفية تتحول إلى بيضاء ويختفي النص.
</p>

<h3 id="2processing">
	الخطوة 2: إنشاء الكرة وتطبيق الجاذبية
</h3>

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

<p>
	أولًا نضيف ما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4424_26" style=""><span class="pun">...</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> ballX</span><span class="pun">,</span><span class="pln"> ballY</span><span class="pun">;</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> ballSize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">20</span><span class="pun">;</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> ballColor </span><span class="pun">=</span><span class="pln"> color</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="kwd">void</span><span class="pln"> setup</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="pun">...</span><span class="pln">
  ballX</span><span class="pun">=</span><span class="pln">width</span><span class="pun">/</span><span class="lit">4</span><span class="pun">;</span><span class="pln">
  ballY</span><span class="pun">=</span><span class="pln">height</span><span class="pun">/</span><span class="lit">5</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="kwd">void</span><span class="pln"> gameScreen</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="pun">...</span><span class="pln">
  drawBall</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="kwd">void</span><span class="pln"> drawBall</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  fill</span><span class="pun">(</span><span class="pln">ballColor</span><span class="pun">);</span><span class="pln">
  ellipse</span><span class="pun">(</span><span class="pln">ballX</span><span class="pun">,</span><span class="pln"> ballY</span><span class="pun">,</span><span class="pln"> ballSize</span><span class="pun">,</span><span class="pln"> ballSize</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	عرّفنا الإحداثيات كمتغيرات عامّة، وأنشأنا تابعًا لرسم الكرة التي استدعيناها من التابع <code>gameScreen</code>، الشيء الوحيد الذي يجب الانتباه إليه هنا هو أننا هيّئنا الإحداثيات، ولكننا عرفناها في <code>setup()‎</code>. السبب وراء قيامنا بذلك هو أننا أردنا أن تبدأ الكرة من مسافة ربع من اليسار وخمس من الأعلى. ليس هناك سبب محدد وراء رغبتنا في ذلك، إلا أنّ هذه نقطة جيدة لبدء الكرة. لذلك نحن بحاجة للحصول على <code>width</code> و<code>height</code> للرسم ديناميكيًا. يتم تعريف حجم الرسم في <code>setup()‎</code> بعد السطر الأول. لم يتم ضبط <code>width</code> و<code>height</code> قبل تنفيذ <code>setup()‎</code>، ولهذا السبب لم نتمكن من تحقيق ذلك إذا حدّدنا المتغيرات في الأعلى.
</p>

<h4 id="">
	الجاذبية
</h4>

<p>
	الآن تطبيق الجاذبية هو الجزء السهل في الواقع. إذ لا يوجد سوى عدد قليل من الحيل. وهذا التطبيق أولًا:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4424_28" style=""><span class="pun">...</span><span class="pln">
</span><span class="typ">float</span><span class="pln"> gravity </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">
</span><span class="typ">float</span><span class="pln"> ballSpeedVert </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> gameScreen</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="pun">...</span><span class="pln">
  applyGravity</span><span class="pun">();</span><span class="pln">
  keepInScreen</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="kwd">void</span><span class="pln"> applyGravity</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  ballSpeedVert </span><span class="pun">+=</span><span class="pln"> gravity</span><span class="pun">;</span><span class="pln">
  ballY </span><span class="pun">+=</span><span class="pln"> ballSpeedVert</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> makeBounceBottom</span><span class="pun">(</span><span class="typ">float</span><span class="pln"> surface</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  ballY </span><span class="pun">=</span><span class="pln"> surface</span><span class="pun">-(</span><span class="pln">ballSize</span><span class="pun">/</span><span class="lit">2</span><span class="pun">);</span><span class="pln">
  ballSpeedVert</span><span class="pun">*=-</span><span class="lit">1</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> makeBounceTop</span><span class="pun">(</span><span class="typ">float</span><span class="pln"> surface</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  ballY </span><span class="pun">=</span><span class="pln"> surface</span><span class="pun">+(</span><span class="pln">ballSize</span><span class="pun">/</span><span class="lit">2</span><span class="pun">);</span><span class="pln">
  ballSpeedVert</span><span class="pun">*=-</span><span class="lit">1</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"> keepInScreen</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="pln"> </span><span class="pun">(</span><span class="pln">ballY</span><span class="pun">+(</span><span class="pln">ballSize</span><span class="pun">/</span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> height</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> 
    makeBounceBottom</span><span class="pun">(</span><span class="pln">height</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="pln"> </span><span class="pun">(</span><span class="pln">ballY</span><span class="pun">-(</span><span class="pln">ballSize</span><span class="pun">/</span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    makeBounceTop</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="pun">}</span></pre>

<p>
	والنتيجة هي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="gif" data-fileid="160693" href="https://academy.hsoub.com/uploads/monthly_2024_10/game1.gif.45dc465f53a35da33e8bb1c20d71504a.gif" rel=""><img alt="game1" class="ipsImage ipsImage_thumbnailed" data-fileid="160693" data-unique="qwq8n8s9g" src="https://academy.hsoub.com/uploads/monthly_2024_10/game1.thumb.gif.fdf35b64b25ff097028069acfed215d1.gif"> </a>
</p>

<p>
	تمهّل يا عالم الفيزياء! أعلم أن هذه ليست الطريقة التي تعمل بها الجاذبية في الحياة الواقعية. بدلًا من ذلك، هي عملية رسوم متحركة أكثر من أي شيء آخر. المتغير الذي عرفناه <code>gravity</code> هو مجرد قيمة رقمية <code>float</code> حتى نتمكن من استخدام القيم العشرية، وليس فقط الأعداد الصحيحة - نضيفها إلى <code>ballSpeedVert</code> في كل حلقة. و <code>ballSpeedVert</code> هي السرعة الرأسية للكرة، التي تضاف إلى محور Y للكرة (<code>ballY</code>) في كل حلقة. نشاهد إحداثيات الكرة ونتأكد من بقائها في الشاشة. إن لم نفعل ذلك، ستسقط إلى ما لا نهاية. في الوقت الحالي، تتحرك الكرة رأسيًا فقط. لذلك نشاهد حدود الأرضية والسقف للشاشة.
</p>

<p>
	نتحقق مما إذا كانت<code>ballY</code> (+ نصف القطر) أقل من الارتفاع باستخدام التابع <code>keepInScreen()‎</code>، وبالمثل <code>ballY</code> (- نصف القطر) أكبر من<code>0</code>. إذا لم تتحقق الشروط، نجعل الكرة ترتد (من الأسفل أو الأعلى) باستخدام تابعيّ <code>makeBounceBottom()‎</code> و<code>makeBounceTop()‎</code>. لجعل الكرة ترتد، نحرك الكرة إلى الموقع المحدد الذي يجب أن ترتد فيه ونضرب السرعة العمودية (<code>ballSpeedVert</code>) في<code>-1</code> (الضرب في -1 يغير الإشارة). عندما تحتوي قيمة السرعة على علامة سالب، فإن إضافة الإحداثي Y تجعل السرعة <code>(ballY + (-ballSpeedVert</code>، وهي <code>ballY - ballSpeedVert</code>. لذا تغيّر اتجاه الكرة على الفور بنفس السرعة. بعد ذلك، عندما نضيف الجاذبية إلى <code>ballSpeedVert</code> وتكون قيمة <code>ballSpeedVert</code> سالبة، فإنها تبدأ في الاقتراب من 0، وتصبح في النهاية 0، وتبدأ في الزيادة مرة أخرى. وهذا يجعل الكرة ترتفع، وترتفع بشكل أبطأ، إلى أن تتوقف وتبدأ بالسقوط.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="PNG" data-fileid="160694" href="https://academy.hsoub.com/uploads/monthly_2024_10/game2.PNG.bc6bc5f8e588b0f86d54ad9c5428f26f.PNG" rel=""><img alt="game2" class="ipsImage ipsImage_thumbnailed" data-fileid="160694" data-unique="mqh8no9tq" src="https://academy.hsoub.com/uploads/monthly_2024_10/game2.thumb.PNG.79fc2c5133e5e317370092a42752cd36.PNG"> </a>
</p>

<p>
	هناك مشكلة في عملية الرسوم المتحركة لدينا، رغم ذلك، إذ إنّ الكرة تستمر في الارتداد. إذا كان هذا سيناريو حقيقيًا، لكانت الكرة ستواجه مقاومة الهواء والاحتكاك في كل مرة تلمس فيها سطحًا. هذا هو السلوك الذي نريده لعملية الرسوم المتحركة في لعبتنا، لذا فإن تنفيذ ذلك أمر سهل. ونضيف ما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4424_30" style=""><span class="pun">...</span><span class="pln">
</span><span class="typ">float</span><span class="pln"> airfriction </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0.0001</span><span class="pun">;</span><span class="pln">
</span><span class="typ">float</span><span class="pln"> friction </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0.1</span><span class="pun">;</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> applyGravity</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="pun">...</span><span class="pln">
  ballSpeedVert </span><span class="pun">-=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">ballSpeedVert </span><span class="pun">*</span><span class="pln"> airfriction</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> makeBounceBottom</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> surface</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="pun">...</span><span class="pln">
  ballSpeedVert </span><span class="pun">-=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">ballSpeedVert </span><span class="pun">*</span><span class="pln"> friction</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> makeBounceTop</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> surface</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="pun">...</span><span class="pln">
  ballSpeedVert </span><span class="pun">-=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">ballSpeedVert </span><span class="pun">*</span><span class="pln"> friction</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	والآن ستبدو عملية التحريك الخاصة بنا بهذا الشكل:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="gif" data-fileid="160695" href="https://academy.hsoub.com/uploads/monthly_2024_10/game3.gif.8ab57a1acc2221f5e2d25db54b22fe4c.gif" rel=""><img alt="game3" class="ipsImage ipsImage_thumbnailed" data-fileid="160695" data-unique="h10dccael" src="https://academy.hsoub.com/uploads/monthly_2024_10/game3.thumb.gif.876b568a48711cd73c2af964ba45f597.gif"> </a>
</p>

<p>
	كما يوحي الاسم، <code>friction</code> هو الاحتكاك السطحي و <code>airfriction</code> هو احتكاك الهواء. لذا من الواضح أن <code>friction</code> يجب أن يحدث في كل مرة تلمس فيها الكرة أي سطح. ومع ذلك، يجب أن نطبّق <code>airfriction</code> باستمرار. وهذا ما فعلناه. والآن ننفّذ تابع <code>applyGravity()‎</code> على كل حلقة، لذلك نحذف <code>0.0001</code> بالمئة من قيمتها الحالية من <code>ballSpeedVert</code> في كل حلقة. ثمّ ننفذ تابعي <code>makeBounceBottom()‎</code> و <code>makeBounceTop()‎</code> عندما تلمس الكرة أي سطح. لذا، في تلك الطرق، فعلنا نفس الشيء، ولكن هذه المرة باستخدام <code>friction</code>.
</p>

<h3 id="3processing">
	الخطوة 3: إنشاء مضرب تنس
</h3>

<p>
	الآن نحتاج إلى مضرب تنس للكرة لكي ترتدّ عليه. ويجب أن نتحكم بالمضرب. والآن لنتحكم به من خلال الفأرة. هذه هي الشيفرة البرمجية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4424_32" style=""><span class="pun">...</span><span class="pln">
color racketColor </span><span class="pun">=</span><span class="pln"> color</span><span class="pun">(</span><span class="lit">0</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">float</span><span class="pln"> racketWidth </span><span class="pun">=</span><span class="pln"> </span><span class="lit">100</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">float</span><span class="pln"> racketHeight </span><span class="pun">=</span><span class="pln"> </span><span class="lit">10</span><span class="pun">;</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> gameScreen</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="pun">...</span><span class="pln">
  drawRacket</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><span class="kwd">void</span><span class="pln"> drawRacket</span><span class="pun">(){</span><span class="pln">
  fill</span><span class="pun">(</span><span class="pln">racketColor</span><span class="pun">);</span><span class="pln">
  rectMode</span><span class="pun">(</span><span class="pln">CENTER</span><span class="pun">);</span><span class="pln">
  rect</span><span class="pun">(</span><span class="pln">mouseX</span><span class="pun">,</span><span class="pln"> mouseY</span><span class="pun">,</span><span class="pln"> racketWidth</span><span class="pun">,</span><span class="pln"> racketHeight</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لقد عرّفنا اللون، الطول والعرض الخاصين بالمضرب كمتغير عام، لأننا قد نريدهم أن يتغيروا أثناء اللعب. طبّقنا التابع <code>drawRacket()‎</code> الذي ينفّذ ما يوضحه اسمه (يرسم المضرب). ضبطنا وضع <code>rectMode</code> على المركز، بحيث يكون مضربنا محاذيًا لمركز المؤشر.
</p>

<p>
	الآن بعد أن أنشأنا المضرب، علينا أن نجعل الكرة ترتد عليه.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4424_34" style=""><span class="pun">...</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> racketBounceRate </span><span class="pun">=</span><span class="pln"> </span><span class="lit">20</span><span class="pun">;</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> gameScreen</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="pun">...</span><span class="pln">
  watchRacketBounce</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><span class="kwd">void</span><span class="pln"> watchRacketBounce</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">float</span><span class="pln"> overhead </span><span class="pun">=</span><span class="pln"> mouseY </span><span class="pun">-</span><span class="pln"> pmouseY</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">((</span><span class="pln">ballX</span><span class="pun">+(</span><span class="pln">ballSize</span><span class="pun">/</span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> mouseX</span><span class="pun">-(</span><span class="pln">racketWidth</span><span class="pun">/</span><span class="lit">2</span><span class="pun">))</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> </span><span class="pun">(</span><span class="pln">ballX</span><span class="pun">-(</span><span class="pln">ballSize</span><span class="pun">/</span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln"> mouseX</span><span class="pun">+(</span><span class="pln">racketWidth</span><span class="pun">/</span><span class="lit">2</span><span class="pun">)))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">dist</span><span class="pun">(</span><span class="pln">ballX</span><span class="pun">,</span><span class="pln"> ballY</span><span class="pun">,</span><span class="pln"> ballX</span><span class="pun">,</span><span class="pln"> mouseY</span><span class="pun">)&lt;=(</span><span class="pln">ballSize</span><span class="pun">/</span><span class="lit">2</span><span class="pun">)+</span><span class="pln">abs</span><span class="pun">(</span><span class="pln">overhead</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      makeBounceBottom</span><span class="pun">(</span><span class="pln">mouseY</span><span class="pun">);</span><span class="pln">
      </span><span class="com">// يتحرك المضرب إلى الأعلى</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">overhead</span><span class="pun">&lt;</span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        ballY</span><span class="pun">+=</span><span class="pln">overhead</span><span class="pun">;</span><span class="pln">
        ballSpeedVert</span><span class="pun">+=</span><span class="pln">overhead</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><span class="pun">}</span></pre>

<p>
	وهذه هي النتيجة:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="gif" data-fileid="160696" href="https://academy.hsoub.com/uploads/monthly_2024_10/game4.gif.04fb969d3dcfd8e1f18a63934a17e6cb.gif" rel=""><img alt="game4" class="ipsImage ipsImage_thumbnailed" data-fileid="160696" data-unique="niji5hn7g" src="https://academy.hsoub.com/uploads/monthly_2024_10/game4.thumb.gif.07be87653d5290ee32309786ec4d7e86.gif"> </a>
</p>

<p>
	إذن ما يفعله <code>watchRacketBounce()‎</code> هو التأكد من اصطدام المضرب والكرة. هناك شيئان يجب التحقق منهما هنا، وهما إذا ما كانت الكرة والمضرب مصطفين رأسيًا وأفقيًا. تتحقق عبارة if الأولى مما إذا كان إحداثي X للجانب الأيمن من الكرة أكبر من إحداثي X للجانب الأيسر من المضرب (والعكس صحيح). إذا كان الأمر كذلك، فإن العبارة الثانية تتحقق مما إذا كانت المسافة بين الكرة والمضرب أصغر من أو تساوي نصف قطر الكرة (مما يعني أنهما يتصادمان). لذا، إذا استوفيت هذه الشروط، فسيتم استدعاء تابع <code>makeBounceBottom()‎</code> وترتد الكرة على مضربنا (عند <code>mouseY</code>، حيث يوجد المضرب).
</p>

<p>
	هل لاحظت مقدار المتغير <code>overhead</code> الذي يتم حسابه بواسطة <code>mouseY - pmouseY</code>؟ يخزّن المتغيّران <code>pmouseX</code> و <code>pmouseY</code> إحداثيات الفأرة في الإطار السابق. نظرًا لأن الفأرة يمكن أن تتحرك بسرعة كبيرة، فهناك احتمال كبير أننا قد لا نكتشف المسافة بين الكرة والمضرب بشكل صحيح بين الإطارات إذا كان الفأر يتحرك نحو الكرة بسرعة كافية. لذا، سنأخذ اختلاف إحداثيات الماوس بين الإطارات ونأخذ ذلك في الاعتبار أثناء اكتشاف المسافة. كلما تحركت الفأرة بشكل أسرع، كانت المسافة الأكبر مقبولة.
</p>

<p>
	نستخدم أيضًا <code>overhead</code> لسبب آخر. نكتشف الاتجاه الذي تتحرك به الفأرة من خلال التحقق من علامة <code>overhead</code>. إذا كان <code>overhead</code> سالبًا، فهذا يعني أن الفأرة كانت في مكان ما أسفل الإطار السابق، لذا فإن الفأرة (المضرب) يتحرك نحو الأعلى. في هذه الحالة، نريد إضافة سرعة إضافية للكرة وتحريكها أبعد قليلًا من الارتداد العادي لمحاكاة تأثير ضرب الكرة بالمضرب. إذا كان <code>overhead</code> أقل من 0، فإننا نضيفه إلى <code>ballY</code> و <code>ballSpeedVert</code> لجعل الكرة ترتفع إلى أعلى وأسرع. لذا، كلما أسرع المضرب في ضرب الكرة، كلما تحركت إلى أعلى وأسرع.
</p>

<h3 id="4processing">
	الخطوة 4: الحركة الأفقية والسيطرة على الكرة
</h3>

<p>
	في هذا القسم، سنضيف الحركة الأفقية إلى الكرة. ثمّ، سنحقّق التحكم بالكرة أفقياً بدون مضرب. لنبدأ:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4424_36" style=""><span class="pun">...</span><span class="pln">
</span><span class="com">// سنبدأ بـ 0، لكننا نعطي 10 للاختبار فقط</span><span class="pln">
</span><span class="pun">تعويم</span><span class="pln"> </span><span class="pun">الكرة</span><span class="pln"> </span><span class="typ">SpeedHorizon</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">10</span><span class="pun">؛</span><span class="pln">

</span><span class="typ">float</span><span class="pln"> ballSpeedHorizon </span><span class="pun">=</span><span class="pln"> </span><span class="lit">10</span><span class="pun">;</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> gameScreen</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="pun">...</span><span class="pln">
  applyHorizontalSpeed</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><span class="kwd">void</span><span class="pln"> applyHorizontalSpeed</span><span class="pun">(){</span><span class="pln">
  ballX </span><span class="pun">+=</span><span class="pln"> ballSpeedHorizon</span><span class="pun">;</span><span class="pln">
  ballSpeedHorizon </span><span class="pun">-=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">ballSpeedHorizon </span><span class="pun">*</span><span class="pln"> airfriction</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> makeBounceLeft</span><span class="pun">(</span><span class="typ">float</span><span class="pln"> surface</span><span class="pun">){</span><span class="pln">
  ballX </span><span class="pun">=</span><span class="pln"> surface</span><span class="pun">+(</span><span class="pln">ballSize</span><span class="pun">/</span><span class="lit">2</span><span class="pun">);</span><span class="pln">
  ballSpeedHorizon</span><span class="pun">*=-</span><span class="lit">1</span><span class="pun">;</span><span class="pln">
  ballSpeedHorizon </span><span class="pun">-=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">ballSpeedHorizon </span><span class="pun">*</span><span class="pln"> friction</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> makeBounceRight</span><span class="pun">(</span><span class="typ">float</span><span class="pln"> surface</span><span class="pun">){</span><span class="pln">
  ballX </span><span class="pun">=</span><span class="pln"> surface</span><span class="pun">-(</span><span class="pln">ballSize</span><span class="pun">/</span><span class="lit">2</span><span class="pun">);</span><span class="pln">
  ballSpeedHorizon</span><span class="pun">*=-</span><span class="lit">1</span><span class="pun">;</span><span class="pln">
  ballSpeedHorizon </span><span class="pun">-=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">ballSpeedHorizon </span><span class="pun">*</span><span class="pln"> friction</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="kwd">void</span><span class="pln"> keepInScreen</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="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">ballX</span><span class="pun">-(</span><span class="pln">ballSize</span><span class="pun">/</span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">){</span><span class="pln">
    makeBounceLeft</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="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">ballX</span><span class="pun">+(</span><span class="pln">ballSize</span><span class="pun">/</span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> width</span><span class="pun">){</span><span class="pln">
    makeBounceRight</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></pre>

<p>
	وستكون النتيجة كالتالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="gif" data-fileid="160697" href="https://academy.hsoub.com/uploads/monthly_2024_10/game5.gif.7f9a8565cc08cce36c30b5bd6b0ef9b9.gif" rel=""><img alt="game5" class="ipsImage ipsImage_thumbnailed" data-fileid="160697" data-unique="hs6stmi02" src="https://academy.hsoub.com/uploads/monthly_2024_10/game5.thumb.gif.556c286227c89ab65a36953981a45832.gif"> </a>
</p>

<p>
	الفكرة هنا هي نفسها التي نفذناها للحركة الرأسية. أنشأنا متغير السرعة الأفقي، <code>ballSpeedHorizon</code>. لقد أنشأنا تابعاً لتطبيق السرعة الأفقية على <code>ballX</code> وإزالة احتكاك الهواء. أضفنا عبارتي if إضافيتين إلى تابع <code>keepInScreen()‎‎</code> والتي ستراقب الكرة وهي تضرب الحواف اليسرى واليمنى للشاشة. وأخيرًا، أنشأنا تابعي <code>makeBounceLeft()‎</code> و<code>makeBounceRight()‎</code> للتعامل مع الارتدادات من اليسار واليمين.
</p>

<p>
	الآن بعد أن أضفنا السرعة الأفقية إلى اللعبة، نريد التحكم في الكرة بالمضرب. كما هو الحال في لعبة أتاري الشهيرة <a href="%E2%80%8Ehttps://www.youtube.com/watch?v=MTnhlAlzb6Y%E2%80%8E%E2%80%8E" rel="">Breakout‎</a> وفي جميع ألعاب كسر الطوب الأخرى، يجب أن تتحرك الكرة يسارًا أو يمينًا وفقًا للنقطة التي تضربها على المضرب. يجب أن تمنح حواف المضرب الكرة سرعة أفقية أكبر بينما لا ينبغي أن يكون للوسط أي تأثير. الشيفرة البرمجية أولا:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4424_38" style=""><span class="kwd">void</span><span class="pln"> watchRacketBounce</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="kwd">if</span><span class="pln"> </span><span class="pun">((</span><span class="pln">ballX</span><span class="pun">+(</span><span class="pln">ballSize</span><span class="pun">/</span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> mouseX</span><span class="pun">-(</span><span class="pln">racketWidth</span><span class="pun">/</span><span class="lit">2</span><span class="pun">))</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> </span><span class="pun">(</span><span class="pln">ballX</span><span class="pun">-(</span><span class="pln">ballSize</span><span class="pun">/</span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln"> mouseX</span><span class="pun">+(</span><span class="pln">racketWidth</span><span class="pun">/</span><span class="lit">2</span><span class="pun">)))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">dist</span><span class="pun">(</span><span class="pln">ballX</span><span class="pun">,</span><span class="pln"> ballY</span><span class="pun">,</span><span class="pln"> ballX</span><span class="pun">,</span><span class="pln"> mouseY</span><span class="pun">)&lt;=(</span><span class="pln">ballSize</span><span class="pun">/</span><span class="lit">2</span><span class="pun">)+</span><span class="pln">abs</span><span class="pun">(</span><span class="pln">overhead</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="pun">...</span><span class="pln">
      ballSpeedHorizon </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">ballX </span><span class="pun">-</span><span class="pln"> mouseX</span><span class="pun">)/</span><span class="lit">5</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><span class="pun">}</span></pre>

<p>
	النتيجة هي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="gif" data-fileid="160698" href="https://academy.hsoub.com/uploads/monthly_2024_10/game6.gif.95f3920dea000836b9647f1dd1a12173.gif" rel=""><img alt="game6" class="ipsImage ipsImage_thumbnailed" data-fileid="160698" data-unique="5s746xx3y" src="https://academy.hsoub.com/uploads/monthly_2024_10/game6.thumb.gif.8f7e39c726342782e0ae57268e3c5904.gif"> </a>
</p>

<p>
	إن إضافة هذا السطر البسيط إلى <code>watchRacketBounce()‎</code> أدى المهمة. ما فعلناه هو أننا حددنا مسافة النقطة التي تضربها الكرة من مركز المضرب باستخدام <code>ballX - mouseX</code>. ثم نجعلها بالسرعة الأفقية. كان الفرق الفعلي كبيرًا جدًا، لذا أجرينا بعض المحاولات واكتشفنا أن عُشر القيمة تبدو طبيعية أكثر.
</p>

<h3 id="5processing">
	الخطوة 5: إنشاء الجدران
</h3>

<p>
	بدأ رسمنا يبدو وكأنه لعبة مع كل خطوة. في هذه الخطوة، سنضيف جدران تتحرك نحو اليسار، تمامًا كما في Flappy Bird:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4424_40" style=""><span class="pun">...</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> wallSpeed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">5</span><span class="pun">;</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> wallInterval </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1000</span><span class="pun">;</span><span class="pln">
</span><span class="typ">float</span><span class="pln"> lastAddTime </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> minGapHeight </span><span class="pun">=</span><span class="pln"> </span><span class="lit">200</span><span class="pun">;</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> maxGapHeight </span><span class="pun">=</span><span class="pln"> </span><span class="lit">300</span><span class="pun">;</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> wallWidth </span><span class="pun">=</span><span class="pln"> </span><span class="lit">80</span><span class="pun">;</span><span class="pln">
color wallColors </span><span class="pun">=</span><span class="pln"> color</span><span class="pun">(</span><span class="lit">0</span><span class="pun">);</span><span class="pln">
</span><span class="com">// تقوم قائمة المصفوفات هذه بتخزين بيانات الفجوات بين الجدران. يتم رسم الجدران الفعلية وفقًا لذلك</span><span class="pln">
</span><span class="com">// [gapWallX, gapWallY, gapWallWidth, gapWallHeight]</span><span class="pln">
</span><span class="typ">ArrayList</span><span class="pun">&lt;</span><span class="typ">int</span><span class="pun">[]&gt;</span><span class="pln"> walls </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ArrayList</span><span class="pun">&lt;</span><span class="typ">int</span><span class="pun">[]&gt;();</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> gameScreen</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="pun">...</span><span class="pln">
  wallAdder</span><span class="pun">();</span><span class="pln">
  wallHandler</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="kwd">void</span><span class="pln"> wallAdder</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">millis</span><span class="pun">()-</span><span class="pln">lastAddTime </span><span class="pun">&gt;</span><span class="pln"> wallInterval</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">int</span><span class="pln"> randHeight </span><span class="pun">=</span><span class="pln"> round</span><span class="pun">(</span><span class="pln">random</span><span class="pun">(</span><span class="pln">minGapHeight</span><span class="pun">,</span><span class="pln"> maxGapHeight</span><span class="pun">));</span><span class="pln">
    </span><span class="typ">int</span><span class="pln"> randY </span><span class="pun">=</span><span class="pln"> round</span><span class="pun">(</span><span class="pln">random</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">-</span><span class="pln">randHeight</span><span class="pun">));</span><span class="pln">
    </span><span class="com">// {gapWallX, gapWallY, gapWallWidth, gapWallHeight}</span><span class="pln">
    </span><span class="typ">int</span><span class="pun">[]</span><span class="pln"> randWall </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"> randY</span><span class="pun">,</span><span class="pln"> wallWidth</span><span class="pun">,</span><span class="pln"> randHeight</span><span class="pun">};</span><span class="pln"> 
    walls</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">randWall</span><span class="pun">);</span><span class="pln">
    lastAddTime </span><span class="pun">=</span><span class="pln"> millis</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="kwd">void</span><span class="pln"> wallHandler</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">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"> walls</span><span class="pun">.</span><span class="pln">size</span><span class="pun">();</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    wallRemover</span><span class="pun">(</span><span class="pln">i</span><span class="pun">);</span><span class="pln">
    wallMover</span><span class="pun">(</span><span class="pln">i</span><span class="pun">);</span><span class="pln">
    wallDrawer</span><span class="pun">(</span><span class="pln">i</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="kwd">void</span><span class="pln"> wallDrawer</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> index</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">int</span><span class="pun">[]</span><span class="pln"> wall </span><span class="pun">=</span><span class="pln"> walls</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">index</span><span class="pun">);</span><span class="pln">
  </span><span class="com">// للحصول على إعدادات جدار الفجوة </span><span class="pln">
  </span><span class="typ">int</span><span class="pln"> gapWallX </span><span class="pun">=</span><span class="pln"> wall</span><span class="pun">[</span><span class="lit">0</span><span class="pun">];</span><span class="pln">
  </span><span class="typ">int</span><span class="pln"> gapWallY </span><span class="pun">=</span><span class="pln"> wall</span><span class="pun">[</span><span class="lit">1</span><span class="pun">];</span><span class="pln">
  </span><span class="typ">int</span><span class="pln"> gapWallWidth </span><span class="pun">=</span><span class="pln"> wall</span><span class="pun">[</span><span class="lit">2</span><span class="pun">];</span><span class="pln">
  </span><span class="typ">int</span><span class="pln"> gapWallHeight </span><span class="pun">=</span><span class="pln"> wall</span><span class="pun">[</span><span class="lit">3</span><span class="pun">];</span><span class="pln">
  </span><span class="com">// رسم الجدران الفعلية</span><span class="pln">
  rectMode</span><span class="pun">(</span><span class="pln">CORNER</span><span class="pun">);</span><span class="pln">
  fill</span><span class="pun">(</span><span class="pln">wallColors</span><span class="pun">);</span><span class="pln">
  rect</span><span class="pun">(</span><span class="pln">gapWallX</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> gapWallWidth</span><span class="pun">,</span><span class="pln"> gapWallY</span><span class="pun">);</span><span class="pln">
  rect</span><span class="pun">(</span><span class="pln">gapWallX</span><span class="pun">,</span><span class="pln"> gapWallY</span><span class="pun">+</span><span class="pln">gapWallHeight</span><span class="pun">,</span><span class="pln"> gapWallWidth</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">-(</span><span class="pln">gapWallY</span><span class="pun">+</span><span class="pln">gapWallHeight</span><span class="pun">));</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> wallMover</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> index</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">int</span><span class="pun">[]</span><span class="pln"> wall </span><span class="pun">=</span><span class="pln"> walls</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">index</span><span class="pun">);</span><span class="pln">
  wall</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"> wallSpeed</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> wallRemover</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> index</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">int</span><span class="pun">[]</span><span class="pln"> wall </span><span class="pun">=</span><span class="pln"> walls</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">index</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">wall</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]+</span><span class="pln">wall</span><span class="pun">[</span><span class="lit">2</span><span class="pun">]</span><span class="pln"> </span><span class="pun">&lt;=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    walls</span><span class="pun">.</span><span class="pln">remove</span><span class="pun">(</span><span class="pln">index</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	وهذه نتيجة ما نفّذنا:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="gif" data-fileid="160699" href="https://academy.hsoub.com/uploads/monthly_2024_10/game7.gif.575cec4be728b1cc9f25b21ef8136444.gif" rel=""><img alt="game7" class="ipsImage ipsImage_thumbnailed" data-fileid="160699" data-unique="lcjgpbubj" src="https://academy.hsoub.com/uploads/monthly_2024_10/game7.thumb.gif.3437164d8746a6a17e6931c019881b33.gif"> </a>
</p>

<p>
	قد تبدو الشيفرة البرمجية طويلةً ومخيفةً، إلا أننا نعدك أنّه ما من شيءٍ صعب. الشيء الأول الذي يجب ملاحظته هو <code>ArrayList</code>. بالنسبة لأولئك الذين لا يعرفون ما هي <code>ArrayList</code>، فهي مجرد تطبيق للقائمة التي تعمل كالمصفوفة، ولكنها تملك بعض الميزات الإضافية. إذ إنها قابلة لتغيير الحجم، وتحتوي على توابع مفيدة مثل <code>(list.add (index</code>، و<code>(list.get(index</code> و<code>(list.remove(index</code>. نحتفظ ببيانات الجدار كمصفوفات صحيحة داخل قائمة المصفوفات. البيانات التي نحتفظ بها في المصفوفات مخصصة للفجوة بين جدارين. تحتوي المصفوفات على القيم التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4424_42" style=""><span class="pun">[</span><span class="pln">gap wall X</span><span class="pun">,</span><span class="pln"> gap wall Y</span><span class="pun">,</span><span class="pln"> gap wall width</span><span class="pun">,</span><span class="pln"> gap wall height</span><span class="pun">]</span></pre>

<p>
	ترسم الجدران الفعلية بناءً على قيم جدار الفجوة. لاحظ أنه يمكننا التعامل مع كل هذه الأمور بشكل أفضل باستخدام الفئات، ولكن نظرًا لأن استخدام البرمجة كائنية التوجه <abbr title="Object-Oriented Programming | البرمجة كائنية التوجه"><abbr title="Object-Oriented Programming | البرمجة كائنية التوجه">OOP</abbr></abbr> ليس ضمن نطاق البرنامج التعليمي للمعالجة، فهذه هي الطريقة التي سنتعامل بها. لدينا طريقتان أساسيتان لإدارة الجدران هما <code>wallAdder()‎</code> و<code>wallHandler</code>.
</p>

<p>
	يضيف تابع <code>wallAdder()‎</code> ببساطة جدرانًا جديدة في كل مللي ثانية من <code>wallInterval</code> إلى قائمة المصفوفات. لدينا متغير عام <code>lastAddTime</code> الذي يخزن الوقت الذي تمت فيه إضافة الجدار الأخير (بالمللي ثانية). إذا كانت قيمة المللي ثانية الحالية <code>millis()‎</code> ناقص آخر مللي ثانية تمت إضافتها <code>lastAddTime</code> أكبر من قيمة الفاصل الزمني الخاصة بنا <code>wallInterval</code>، فهذا يعني أن الوقت قد حان لإضافة جدار جديد. يتم بعد ذلك إنشاء متغيرات الفجوة العشوائية بناءً على المتغيرات العامة المحددة في الأعلى. ثم تتم إضافة جدار جديد (مصفوفة عدد صحيح تخزن بيانات جدار الفجوة) إلى قائمة المصفوفات ويتم تعيين <code>lastAddTime</code> على المللي ثانية الحالية <code>millis()‎</code>.
</p>

<p>
	تنفذ حلقات <code>wallHandler()‎</code> عبر الجدران الحالية الموجودة في قائمة المصفوفات. ولكل عنصر في كل حلقة، يستدعي كلاً من <code>(wallRemover(i</code> و <code>(wallMover(i</code> و <code>(wallDrawer(i</code> حسب قيمة فهرس قائمة المصفوفات. يقوم هذا التابع بما يوحي اسمه. يرسم التابع <code>wallDrawer()‎</code> الجدران الفعلية بناءً على بيانات فجوة الجدار. فهو يلتقط مصفوفة بيانات الجدار من قائمة المصفوفات، ويستدعي التابع <code>rect()‎</code> لرسم الجدران إلى المكان الذي ينبغي أن تكون فيه بالفعل. يلتقط تابع <code>wallMover()‎</code> العنصر من قائمة المصفوفات، وتغير موقعه X بناءً على المتغير العام <code>wallSpeed</code>. أخيرًا، يزيل التابع <code>wallRemover()‎</code> الجدران من قائمة المصفوفات الموجودة خارج الشاشة. لأننا إن لم نفعل ذلك، لكانت بروسيسنج Processing قد تعاملت معهم كما لو كانوا لا يزالون في الشاشة. وكان من الممكن أن يكون ذلك خسارة فادحة في الأداء. لذا، عند إزالة جدار من قائمة المصفوفات، لا يتم رسمه في الحلقات اللاحقة.
</p>

<p>
	التحدي الأخير المتبقي هو اكتشاف الاصطدامات بين الكرة والجدران.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4424_44" style=""><span class="kwd">void</span><span class="pln"> wallHandler</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">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"> walls</span><span class="pun">.</span><span class="pln">size</span><span class="pun">();</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
    watchWallCollision</span><span class="pun">(</span><span class="pln">i</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><span class="kwd">void</span><span class="pln"> watchWallCollision</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> index</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">int</span><span class="pun">[]</span><span class="pln"> wall </span><span class="pun">=</span><span class="pln"> walls</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">index</span><span class="pun">);</span><span class="pln">
  </span><span class="com">// الحصول على إعدادات جدار الفجوة </span><span class="pln">
  </span><span class="typ">int</span><span class="pln"> gapWallX </span><span class="pun">=</span><span class="pln"> wall</span><span class="pun">[</span><span class="lit">0</span><span class="pun">];</span><span class="pln">
  </span><span class="typ">int</span><span class="pln"> gapWallY </span><span class="pun">=</span><span class="pln"> wall</span><span class="pun">[</span><span class="lit">1</span><span class="pun">];</span><span class="pln">
  </span><span class="typ">int</span><span class="pln"> gapWallWidth </span><span class="pun">=</span><span class="pln"> wall</span><span class="pun">[</span><span class="lit">2</span><span class="pun">];</span><span class="pln">
  </span><span class="typ">int</span><span class="pln"> gapWallHeight </span><span class="pun">=</span><span class="pln"> wall</span><span class="pun">[</span><span class="lit">3</span><span class="pun">];</span><span class="pln">
  </span><span class="typ">int</span><span class="pln"> wallTopX </span><span class="pun">=</span><span class="pln"> gapWallX</span><span class="pun">;</span><span class="pln">
  </span><span class="typ">int</span><span class="pln"> wallTopY </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
  </span><span class="typ">int</span><span class="pln"> wallTopWidth </span><span class="pun">=</span><span class="pln"> gapWallWidth</span><span class="pun">;</span><span class="pln">
  </span><span class="typ">int</span><span class="pln"> wallTopHeight </span><span class="pun">=</span><span class="pln"> gapWallY</span><span class="pun">;</span><span class="pln">
  </span><span class="typ">int</span><span class="pln"> wallBottomX </span><span class="pun">=</span><span class="pln"> gapWallX</span><span class="pun">;</span><span class="pln">
  </span><span class="typ">int</span><span class="pln"> wallBottomY </span><span class="pun">=</span><span class="pln"> gapWallY</span><span class="pun">+</span><span class="pln">gapWallHeight</span><span class="pun">;</span><span class="pln">
  </span><span class="typ">int</span><span class="pln"> wallBottomWidth </span><span class="pun">=</span><span class="pln"> gapWallWidth</span><span class="pun">;</span><span class="pln">
  </span><span class="typ">int</span><span class="pln"> wallBottomHeight </span><span class="pun">=</span><span class="pln"> height</span><span class="pun">-(</span><span class="pln">gapWallY</span><span class="pun">+</span><span class="pln">gapWallHeight</span><span class="pun">);</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="pun">(</span><span class="pln">ballX</span><span class="pun">+(</span><span class="pln">ballSize</span><span class="pun">/</span><span class="lit">2</span><span class="pun">)&gt;</span><span class="pln">wallTopX</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln">
    </span><span class="pun">(</span><span class="pln">ballX</span><span class="pun">-(</span><span class="pln">ballSize</span><span class="pun">/</span><span class="lit">2</span><span class="pun">)&lt;</span><span class="pln">wallTopX</span><span class="pun">+</span><span class="pln">wallTopWidth</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln">
    </span><span class="pun">(</span><span class="pln">ballY</span><span class="pun">+(</span><span class="pln">ballSize</span><span class="pun">/</span><span class="lit">2</span><span class="pun">)&gt;</span><span class="pln">wallTopY</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln">
    </span><span class="pun">(</span><span class="pln">ballY</span><span class="pun">-(</span><span class="pln">ballSize</span><span class="pun">/</span><span class="lit">2</span><span class="pun">)&lt;</span><span class="pln">wallTopY</span><span class="pun">+</span><span class="pln">wallTopHeight</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="pun">}</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="pun">(</span><span class="pln">ballX</span><span class="pun">+(</span><span class="pln">ballSize</span><span class="pun">/</span><span class="lit">2</span><span class="pun">)&gt;</span><span class="pln">wallBottomX</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln">
    </span><span class="pun">(</span><span class="pln">ballX</span><span class="pun">-(</span><span class="pln">ballSize</span><span class="pun">/</span><span class="lit">2</span><span class="pun">)&lt;</span><span class="pln">wallBottomX</span><span class="pun">+</span><span class="pln">wallBottomWidth</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln">
    </span><span class="pun">(</span><span class="pln">ballY</span><span class="pun">+(</span><span class="pln">ballSize</span><span class="pun">/</span><span class="lit">2</span><span class="pun">)&gt;</span><span class="pln">wallBottomY</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln">
    </span><span class="pun">(</span><span class="pln">ballY</span><span class="pun">-(</span><span class="pln">ballSize</span><span class="pun">/</span><span class="lit">2</span><span class="pun">)&lt;</span><span class="pln">wallBottomY</span><span class="pun">+</span><span class="pln">wallBottomHeight</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="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يستدعى التابع <code>watchwallcollision</code> لكل جدار في كلّ حلقة. ثم نلتقط إحداثيات جدار الفجوة، ونحسب إحداثيات الجدران الفعلية (العلوية والسفلى) ونتحقق مما إذا كانت إحداثيات الكرة تصطدم بالجدران.
</p>

<h3 id="6processing">
	الخطوة 6: تنفيذ شريط الصحة Health Bar
</h3>

<p>
	الآن بعد أن أصبح بإمكاننا اكتشاف اصطدامات الكرة بالجدران، يمكننا تحديد آليات اللعبة. بعد ضبط للعبة، تمكنت من جعل اللعبة قابلة للعب إلى حد ما. ولكن مع ذلك، كان الأمر صعبًا للغاية. وكان أول ما فكرت به في اللعبة أن أجعلها مثل Flappy Bird، عندما تلمس الكرة الجدران، تنتهي اللعبة. ولكن بعد ذلك أدركت أنه سيكون من المستحيل اللعب. لذا هذا ما فكرت به:
</p>

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

<p>
	لننفذ شريط صحة مرتبط بالكرة، حيث سيقل تدريجيًا عند كل اصطدام بالجدران. وعندما يصل إلى الصفر، ستنتهي اللعبة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4424_46" style=""><span class="typ">int</span><span class="pln"> maxHealth </span><span class="pun">=</span><span class="pln"> </span><span class="lit">100</span><span class="pun">;</span><span class="pln">
</span><span class="typ">float</span><span class="pln"> health </span><span class="pun">=</span><span class="pln"> </span><span class="lit">100</span><span class="pun">;</span><span class="pln">
</span><span class="typ">float</span><span class="pln"> healthDecrease </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> healthBarWidth </span><span class="pun">=</span><span class="pln"> </span><span class="lit">60</span><span class="pun">;</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> gameScreen</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="pun">...</span><span class="pln">
  drawHealthBar</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><span class="kwd">void</span><span class="pln"> drawHealthBar</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// جعلها بلا حدود:</span><span class="pln">
  noStroke</span><span class="pun">();</span><span class="pln">
  fill</span><span class="pun">(</span><span class="lit">236</span><span class="pun">,</span><span class="pln"> </span><span class="lit">240</span><span class="pun">,</span><span class="pln"> </span><span class="lit">241</span><span class="pun">);</span><span class="pln">
  rectMode</span><span class="pun">(</span><span class="pln">CORNER</span><span class="pun">);</span><span class="pln">
  rect</span><span class="pun">(</span><span class="pln">ballX</span><span class="pun">-(</span><span class="pln">healthBarWidth</span><span class="pun">/</span><span class="lit">2</span><span class="pun">),</span><span class="pln"> ballY </span><span class="pun">-</span><span class="pln"> </span><span class="lit">30</span><span class="pun">,</span><span class="pln"> healthBarWidth</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">health </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">60</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fill</span><span class="pun">(</span><span class="lit">46</span><span class="pun">,</span><span class="pln"> </span><span class="lit">204</span><span class="pun">,</span><span class="pln"> </span><span class="lit">113</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">health </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">30</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fill</span><span class="pun">(</span><span class="lit">230</span><span class="pun">,</span><span class="pln"> </span><span class="lit">126</span><span class="pun">,</span><span class="pln"> </span><span class="lit">34</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fill</span><span class="pun">(</span><span class="lit">231</span><span class="pun">,</span><span class="pln"> </span><span class="lit">76</span><span class="pun">,</span><span class="pln"> </span><span class="lit">60</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  rectMode</span><span class="pun">(</span><span class="pln">CORNER</span><span class="pun">);</span><span class="pln">
  rect</span><span class="pun">(</span><span class="pln">ballX</span><span class="pun">-(</span><span class="pln">healthBarWidth</span><span class="pun">/</span><span class="lit">2</span><span class="pun">),</span><span class="pln"> ballY </span><span class="pun">-</span><span class="pln"> </span><span class="lit">30</span><span class="pun">,</span><span class="pln"> healthBarWidth</span><span class="pun">*(</span><span class="pln">health</span><span class="pun">/</span><span class="pln">maxHealth</span><span class="pun">),</span><span class="pln"> </span><span class="lit">5</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> decreaseHealth</span><span class="pun">(){</span><span class="pln">
  health </span><span class="pun">-=</span><span class="pln"> healthDecrease</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">health </span><span class="pun">&lt;=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">){</span><span class="pln">
    gameOver</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	وهذا تنفيذٌ سريع لما قمنا به:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="gif" data-fileid="160700" href="https://academy.hsoub.com/uploads/monthly_2024_10/game8.gif.af535717cabc13f6d4707f246d98c243.gif" rel=""><img alt="game8" class="ipsImage ipsImage_thumbnailed" data-fileid="160700" data-unique="lpjdfuta1" src="https://academy.hsoub.com/uploads/monthly_2024_10/game8.thumb.gif.3174ce3b5b589d9d5d47ed5df19d5764.gif"> </a>
</p>

<p>
	لقد أنشأنا متغيرًا عامًا <code>health</code> يحفظ قيمة صحة الكرة. ثم أنشأنا تابع <code>drawHealthBar()‎</code> كي يرسم مستطيلين أعلى الكرة. الأول هو شريط الصحة الأساسي الكامل، والآخر هو الشريط النشط الذي يُظهر الصحة الحالية حيث يتغير حجمه بناءً على مقدار الصحة المتبقية، ويحسب باستخدام <code>(healthBarWidth*(health/maxHealth</code>، وهي نسبة الصحة الحالية مقارنة بشريط الصحة الكامل. ثمّ، يتم تعيين ألوان التعبئة لهذا الشريط وفقًا لقيمة الصحة ويعرض أخيرًا الدرجة Score التي حصلت عليها:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4424_50" style=""><span class="pun">...</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> gameOverScreen</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  background</span><span class="pun">(</span><span class="lit">0</span><span class="pun">);</span><span class="pln">
  textAlign</span><span class="pun">(</span><span class="pln">CENTER</span><span class="pun">);</span><span class="pln">
  fill</span><span class="pun">(</span><span class="lit">255</span><span class="pun">);</span><span class="pln">
  textSize</span><span class="pun">(</span><span class="lit">30</span><span class="pun">);</span><span class="pln">
  text</span><span class="pun">(</span><span class="str">"Game Over"</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">/</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> width</span><span class="pun">/</span><span class="lit">2</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">20</span><span class="pun">);</span><span class="pln">
  textSize</span><span class="pun">(</span><span class="lit">15</span><span class="pun">);</span><span class="pln">
  text</span><span class="pun">(</span><span class="str">"Click to Restart"</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">/</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> width</span><span class="pun">/</span><span class="lit">2</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">10</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="kwd">void</span><span class="pln"> wallAdder</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">millis</span><span class="pun">()-</span><span class="pln">lastAddTime </span><span class="pun">&gt;</span><span class="pln"> wallInterval</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="typ">int</span><span class="pun">[]</span><span class="pln"> randWall </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"> randY</span><span class="pun">,</span><span class="pln"> wallWidth</span><span class="pun">,</span><span class="pln"> randHeight</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</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><span class="kwd">void</span><span class="pln"> watchWallCollision</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> index</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="typ">int</span><span class="pln"> wallScored </span><span class="pun">=</span><span class="pln"> wall</span><span class="pun">[</span><span class="lit">4</span><span class="pun">];</span><span class="pln">
  </span><span class="pun">...</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">ballX </span><span class="pun">&gt;</span><span class="pln"> gapWallX</span><span class="pun">+(</span><span class="pln">gapWallWidth</span><span class="pun">/</span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> wallScored</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">
    wallScored</span><span class="pun">=</span><span class="lit">1</span><span class="pun">;</span><span class="pln">
    wall</span><span class="pun">[</span><span class="lit">4</span><span class="pun">]=</span><span class="lit">1</span><span class="pun">;</span><span class="pln">
    score</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="kwd">void</span><span class="pln"> score</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  score</span><span class="pun">++;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> printScore</span><span class="pun">(){</span><span class="pln">
  textAlign</span><span class="pun">(</span><span class="pln">CENTER</span><span class="pun">);</span><span class="pln">
  fill</span><span class="pun">(</span><span class="lit">0</span><span class="pun">);</span><span class="pln">
  textSize</span><span class="pun">(</span><span class="lit">30</span><span class="pun">);</span><span class="pln"> 
  text</span><span class="pun">(</span><span class="pln">score</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">/</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	كنا بحاجة للتسجيل عندما تمر الكرة بالحائط. لكننا بحاجة إلى إضافة درجة واحدة كحد أقصى لكل جدار. بمعنى، إذا مرت الكرة بالحائط ثم عادت ومرت مرةً أخرى، فلا ينبغي إضافة نتيجة أخرى. ولتحقيق ذلك، أضفنا متغيرًا آخر إلى مصفوفة جدار الفجوة داخل قائمة المصفوفات. يخزن المتغير الجديد <code>0</code> إذا لم تتجاوز الكرة هذا الجدار بعد و<code>1</code> إذا تجاوزت ذلك الجدار. بعد ذلك، عدلنا تابع <code>watchWallCollision()‎</code>. أضفنا شرطًا لإطلاق تابع <code>Score()‎</code> ووضع علامة على الجدار كمرور عندما تمر الكرة بجدار لم تتجاوزه من قبل.
</p>

<p>
	نحن الآن قريبون جدًا من نهاية <a href="https://academy.hsoub.com/programming/game-development/%D9%85%D8%B7%D9%88%D8%B1-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8/" rel="">تطوير لعبتنا</a>. آخر ما عليك فعله هو برمجة النقر على نص <code>click to restart</code> الظاهر على الشاشة. إلا أنّنا بحاجة إلى تعيين كافة المتغيرات التي استخدمناها إلى قيمتها الأولية، وإعادة تشغيل اللعبة. ها هو.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4424_53" style=""><span class="pun">...</span><span class="pln">
</span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> mousePressed</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="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">gameScreen</span><span class="pun">==</span><span class="lit">2</span><span class="pun">){</span><span class="pln">
    restart</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><span class="kwd">void</span><span class="pln"> restart</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  score </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
  health </span><span class="pun">=</span><span class="pln"> maxHealth</span><span class="pun">;</span><span class="pln">
  ballX</span><span class="pun">=</span><span class="pln">width</span><span class="pun">/</span><span class="lit">4</span><span class="pun">;</span><span class="pln">
  ballY</span><span class="pun">=</span><span class="pln">height</span><span class="pun">/</span><span class="lit">5</span><span class="pun">;</span><span class="pln">
  lastAddTime </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
  walls</span><span class="pun">.</span><span class="pln">clear</span><span class="pun">();</span><span class="pln">
  gameScreen </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لنضف الآن بعض الألوان.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="gif" data-fileid="160701" href="https://academy.hsoub.com/uploads/monthly_2024_10/game9.gif.703ba3acb4202a394d8d1f967757f958.gif" rel=""><img alt="game9" class="ipsImage ipsImage_thumbnailed" data-fileid="160701" data-unique="gz894xnnz" src="https://academy.hsoub.com/uploads/monthly_2024_10/game9.thumb.gif.0bcb5c64c98c65bed6dfcd2e303d2463.gif"> </a>
</p>

<p>
	تهانينا لديك الآن لعبة Flappy Pong متكاملة، جرب تنفيذها واللعب بها، وفي حال واجهت أي أي مشكلة يمكنك إيجاد الشيفرة البرمجية الكاملة لمعالجة اللعبة <a href="https://gist.github.com/oguzgelal/a2a8db8b2da0e864d1d0#file-flappy_pong-pde" rel="external nofollow">هنا</a>
</p>

<h2 id="p5js">
	نقل شيفرة معالجة اللعبة إلى الويب باستخدام p5.js
</h2>

<p>
	تعرّف <a href="https://p5js.org/" rel="external nofollow">p5.js</a> على أنها مكتبة للغة جافاسكريبت p5.js ذات بنية <a href="https://github.com/processing/p5.js/wiki/Processing-transition" rel="external nofollow">مشابهة للغة برمجة بروسيسنج Processing</a><span style="display: none;"> </span>. وهي ليست مكتبة قادرة على تنفيذ شيفرة بروسيسنج Processing الموجودة ببساطة فبدلًا من ذلك، تتطلب p5.js كتابة تعليمات <a href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-%D9%85%D9%86-%D8%A7%D9%84%D8%B5%D9%81%D8%B1-%D8%AD%D8%AA%D9%89-%D8%A7%D9%84%D8%A7%D8%AD%D8%AA%D8%B1%D8%A7%D9%81-r2046/" rel="">جافا سكريبت</a> فعلية، على غرار منفذ جافا سكريبت لبروسيسنج Processing المعروف باسم <a href="http://processingjs.org/" rel="external nofollow">Processing.js</a>. مهمتنا هي تحويل شيفرة بروسيسنج Processing إلى جافا سكريبت باستخدام p5.js <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>. تحتوي المكتبة على مجموعة من الوظائف وبناء جملة مشابهة للمعالجة، وعلينا إجراء تغييرات معينة على التعليمات البرمجية الخاصة بنا لجعلها تعمل في جافا سكريبت - ولكن نظرًا لأن كلًّا من بروسيسنج Processing وجافا سكريبت يشتركان في أوجه التشابه مع جافا، فهي أقل انتقالًا مفاجئًا مما يبدو. حتى لو لم تكن <a href="https://www.toptal.com/javascript" rel="external nofollow">مطور جافا سكريبت</a>، فإن التغييرات بسيطة جدًا ويجب أن تكون قادرًا متابعتها بسهولة.
</p>

<p>
	أولًا، نحتاج إلى إنشاء ملف <code>Index.html</code> بسيط وإضافة <code>p5.min.js</code> إلى الترويسة. نحتاج أيضًا إلى إنشاء ملف آخر يسمى <code>flappy_pong.js</code> الذي سيحتوي على الشيفرة المحولة الخاصة بنا.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4424_60" style=""><span class="pun">&lt;</span><span class="pln">html</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">head</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">title</span><span class="pun">&gt;</span><span class="typ">Flappy</span><span class="pln"> </span><span class="typ">Pong</span><span class="pun">&lt;/</span><span class="pln">title</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">script tyle</span><span class="pun">=</span><span class="str">"text/javascript"</span><span class="pln"> src</span><span class="pun">=</span><span class="str">"https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.19/p5.min.js"</span><span class="pun">&gt;&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">script tyle</span><span class="pun">=</span><span class="str">"text/javascript"</span><span class="pln"> src</span><span class="pun">=</span><span class="str">"flappy_pong.js"</span><span class="pun">&gt;&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">style</span><span class="pun">&gt;</span><span class="pln">
            canvas </span><span class="pun">{</span><span class="pln">
                box</span><span class="pun">-</span><span class="pln">shadow</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="lit">20px</span><span class="pln"> lightgray</span><span class="pun">;</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">style</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">head</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">body</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">body</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">html</span><span class="pun">&gt;</span></pre>

<p>
	يجب أن تكون استراتيجيتنا أثناء تحويل الشيفرة بنسخ كل الشيفرات ولصقها في <code>flappy_pong.js</code> ثم تنفيذ جميع التغييرات. وهذا ما فعلنا، إليك الخطوات التي اتخذناها لتحديث الشفيرة:
</p>

<ul>
	<li>
		جافا سكريبت هي لغة لغة ديناميكية الأنواع (إذ لا توجد تصريحات عن نوع المتغير مثل <code>int</code> و <code>float</code>) لذلك نحتاج إلى تغيير جميع <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%85%D8%AA%D8%BA%D9%8A%D8%B1%D8%A7%D8%AA-%D9%88%D8%AA%D8%AE%D8%B2%D9%8A%D9%86-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r2287/" rel="">تصريحات المتغيرات</a> إلى var.
	</li>
	<li>
		لا يوجد <code>void</code> في جافا سكريبت. لذا يجب علينا تغيير جميع هذه الكلمات إلى الكلمة المفتاحية <code>function</code>.
	</li>
	<li>
		نحتاج إلى إزالة تصريحات النوع من الوسائط في توقيعات الدوال (على سبيل المثال نغير<br>
		<code>void wallMover(var index { </code>لتكون <code>function wallMover(index {</code> .
	</li>
	<li>
		لا يوجد <code>ArrayList</code> في جافا سكريبت. لكن يمكننا تحقيق الشيء ذاته باستخدام مصفوفات جافا سكريبت. نُنشئ التغييرات التالية:
	</li>
	<li>
		غيّر <code>ArrayList&lt;int[]&gt; walls = new ArrayList&lt;int[]&gt;();‎</code> إلى <code>var walls = [];‎</code>
	</li>
	<li>
		غيّر <code>walls.clear();‎</code> إلى <code>walls = [];‎</code>
	</li>
	<li>
		غيّر <code>walls.add(randWall);‎</code> إلى <code>walls.push(randWall);‎</code>
	</li>
	<li>
		غيّر <code>walls.remove(index);‎</code> إلى <code>walls.splice(index,1);‎</code>
	</li>
	<li>
		غيّر <code>walls.get(index);‎</code> إلى <code>walls[index]‎</code>
	</li>
	<li>
		غيّر <code>walls.size()‎</code> إلى <code>walls.length</code>
	</li>
	<li>
		غيّر تصريح المصفوفة <code>var randWall = {width, randY, wallWidth, randHeight, 0};‎</code> إلى <code>var randWall = [width, randY, wallWidth, randHeight, 0];‎</code>
	</li>
	<li>
		أزل كل كلمات <code>public</code>.
	</li>
	<li>
		انقل كل تصريحات <code>color(0)‎</code> إلى داخل الدالة <code>setup()‎</code> لأن <code>color()‎</code> لن تُعرَّف قبل استدعاء <code>setup()‎</code>.
	</li>
	<li>
		تغيير <code>size(500, 500);‎</code> إلى <code>createCanvas(500, 500);‎</code>
	</li>
	<li>
		أعد تسمية الدالة <code>gameScreen(){‎</code> إلى شيء آخر مثل <code>function gamePlayScreen(){‎</code> لأن لدينا بالفعل متغير عام يسمى <code>gameScreen</code>. عندما كنا نعمل مع لغة بروسيسنج، كان أحدها دالة والأخرى متغير <code>int</code>. ولكن جافاسكريبت تخلط بينها لأنها غير مكتوبة النوع.
	</li>
	<li>
		يحدث الشيء نفسه للدالة <code>score()‎</code>. غيّرنا اسمها إلى <code>addScore()‎</code>.
	</li>
	<li>
		يمكن العثور على شيفرة جافا سكريبت الكاملة التي تغطي كل شيء في هذا الدرس التعليمي باستخدام Processing <a href="https://gist.github.com/oguzgelal/a2a8db8b2da0e864d1d0#file-flappy_pong-js" rel="external nofollow">من هنا</a>.
	</li>
</ul>

<h2 id="-1">
	الخلاصة
</h2>

<p>
	إلى هنا نكون قد انتهينا من <a href="https://academy.hsoub.com/tags/%D9%84%D8%BA%D8%A9%20%D8%A8%D8%B1%D9%85%D8%AC%D8%A9%20processing/" rel="">هذه السلسلة</a> التي شرحنا فيها كيفية إنشاء لعبة بسيطة جدًا. ومع ذلك، فإن ما قمنا به في هذا المقال هو مجرد غيض من فيض. باستخدام لغة برمجة بروسيسنج Processing، حيث يمكنك تحقيق أي شيء بها تقريبًا، ويمكن القول أنها أفضل أداة لبرمجة ما كل ما تتخيله، والهدف الأساسي من هذه السلسلة التعليمية هي إثبات أن البرمجة ليست بهذه الصعوبة بدلًا من تدريس بروسيسنج Processing وبناء اللعبة الخاصة بك. إن صناعة لعبتك الخاصة ليس مجرد حلم. وأردنا أن نوضح لك أنه مع القليل من الجهد والحماس، يمكنك القيام بذلك بسهولة.
</p>

<p>
	ترجمة -بتصرف- لمقال <a href="https://www.toptal.com/game/ultimate-guide-to-processing-simple-game" rel="external nofollow">Ultimate Guide to the Processing Language Part II: Building a Simple Game</a> لكاتبه Oguz Gelal.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/game-development/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D9%84%D9%84%D8%BA%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-processing-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D9%84%D8%B1%D8%B3%D9%85-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%A7%D8%B9%D9%84-%D9%85%D8%B9-%D8%AF%D8%AE%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-r2432/" rel="">دليلك للغة برمجة Processing | الجزء الثاني: الرسم والتفاعل مع دخل المستخدم</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-r2304/" rel="">دليلك الشامل إلى برمجة الألعاب</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D9%85%D8%B7%D9%88%D8%B1-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8/" rel="">مطور الألعاب: من هو وما هي مهامه</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D9%85%D8%A8%D8%A7%D8%AF%D8%A6-%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-%D9%85%D8%AA%D8%B3%D9%82%D8%A9-%D9%88%D9%85%D9%81%D9%87%D9%88%D9%85%D8%A9-r2178/" rel="">مبادئ كتابة جافا سكريبت متسقة ومفهومة</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D9%85%D8%B4%D8%B1%D9%88%D8%B9-%D9%84%D8%B9%D8%A8%D8%A9-%D9%85%D9%86%D8%B5%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1355/" rel="">مشروع لعبة منصة باستخدام جافاسكربت</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2437</guid><pubDate>Mon, 28 Oct 2024 15:01:01 +0000</pubDate></item><item><title>&#x62F;&#x644;&#x64A;&#x644;&#x643; &#x644;&#x644;&#x63A;&#x629; &#x628;&#x631;&#x645;&#x62C;&#x629; Processing | &#x627;&#x644;&#x62C;&#x632;&#x621; &#x627;&#x644;&#x62B;&#x627;&#x646;&#x64A;: &#x627;&#x644;&#x631;&#x633;&#x645; &#x648;&#x627;&#x644;&#x62A;&#x641;&#x627;&#x639;&#x644; &#x645;&#x639; &#x62F;&#x62E;&#x644; &#x627;&#x644;&#x645;&#x633;&#x62A;&#x62E;&#x62F;&#x645;</title><link>https://academy.hsoub.com/programming/game-development/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D9%84%D9%84%D8%BA%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-processing-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D9%84%D8%B1%D8%B3%D9%85-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%A7%D8%B9%D9%84-%D9%85%D8%B9-%D8%AF%D8%AE%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-r2432/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_10/----Processing-------.png.e114137ff010fdfa7463122fe5383cc8.png" /></p>
<p>
	بعد أن تعرّفنا في <a href="https://academy.hsoub.com/programming/game-development/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D9%84%D9%84%D8%BA%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%B1%D9%88%D8%B3%D9%8A%D8%B3%D9%8A%D9%86%D8%AC-processing-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-r2422/" rel="">مقالتنا السابقة</a> على لغة برمجة بروسيسنج وبيئة التطوير الخاصة بها وهيكلة البرنامج، سنتابع في الجزء الثاني التعرف على أبرز توابع الرسم الموجودة فيها وكيفية استخدامها، بالإضافة للتفاعل مع دخل المستخدم.
</p>

<h2 id="">
	رسم الأشكال والنصوص
</h2>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="PNG" data-fileid="160186" href="https://academy.hsoub.com/uploads/monthly_2024_10/528153260_.PNG.405c40ad6b6a191fb0ff97bd5c2a8652.PNG" rel=""><img alt="إحداثيات الرسم" class="ipsImage ipsImage_thumbnailed" data-fileid="160186" data-ratio="100.25" data-unique="9bg9aycv2" style="width: 300px; height: auto;" width="400" src="https://academy.hsoub.com/uploads/monthly_2024_10/.thumb.PNG.8e33d937ea05876f4d3aab0b180fa6aa.PNG"> </a>
</p>

<p>
	الآن سنتحدث عن بعض الأمور البصرية بما أننا أصبحنا نعرف كيفية ضبط المشروع باستخدام كتلة التهيئة ونعرف كيفية عمل رسم كتلة الرسم، كما سنتعلم الأمور المسلية في بروسيسنج processing مثل كيفية رسم الأشكال.
</p>

<p>
	قبل البدء يجب أن نفهم محاور الإحداثيات، إذ يجب تحديد إحداثيات كل شكل يتم رسمه على الشاشة في بروسيسنج processing. الواحدات مقدرة بالبيكسل والمبدأ (نقطة البداية) هي الزاوية اليسرى العلوية، يجب أن تحدد الإحداثيات بالنسبة لهذه النقطة. شيء آخر يجب أن تعرفه هو أن لكل شكل نقطة مرجعية مختلفة، مثلًا النقطة المرجعية للدالة <code>react()</code><code>‎</code> هي الزاوية اليسرى العلوية، أما بالنسبة لدالة <code>ellipse()‎</code> فهي المركز. ويمكنك تغيير النقطة المرجعية باستخدام التوابع مثل <code>rectMode()‎</code> و <code>ellipseMode()‎</code> التي سنشرحها في قسم الخصائص والإعدادات.
</p>

<p>
	سنقتصر على توفير رؤية عامة أساسية للغة بروسيسنج ولن نتطرق لأي أشكال معقدة مثل الأشعة والأشكال ثلاثية البعد، فالأشكال ثنائية البعد تكفي لإنشاء لعبتنا، رأينا في الصورة أمثلة عن كيفية رسم الأشكال، كل شكل لديه صياغة خاصة به ليُنشئ، والفكرة الأساسية هي إما تحديد أبعاده أو حجمه أو كلاهما، التالي هو بعض الأمثلة التي يجب أن تتعلمها (تعني القيم <code>x</code> و <code>y</code> الإحداثيات مقدرة بالبيكسل لكل من المحاور الإحداثية x و y، وتعني القيم <code>h</code> و <code>w</code> العرض والطول مقدرة بالبيكسل أيضًا).
</p>

<ul>
	<li>
		<strong>الدالة </strong><code><strong>point()</strong>‎</code><span>:</span> تستخدم لرسم نقطة بسيطة تحتاج قيمة إحداثيات واحدة، وتستخدم كما يلي:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_371_46" style=""><span class="pln">point</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">)</span><span class="pln">

point</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> z</span><span class="pun">)</span><span class="pln"> </span><span class="com">// في حالة الأبعاد الثلاثية</span></pre>

<ul>
	<li>
		<strong>الدالة </strong><code><strong>line()</strong>‎</code> - لإنشاء سطر، يمكنك إنشاء سطر بتحديد نقطة البداية والنهاية، وتستخدم كما يلي:
	</li>
</ul>

<p>
	 
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_371_44" style=""><span class="pln">line</span><span class="pun">(</span><span class="pln">x1</span><span class="pun">,</span><span class="pln"> y1</span><span class="pun">,</span><span class="pln"> x2</span><span class="pun">,</span><span class="pln"> y2</span><span class="pun">)</span><span class="pln">
line</span><span class="pun">(</span><span class="pln">x1</span><span class="pun">,</span><span class="pln"> y1</span><span class="pun">,</span><span class="pln"> z1</span><span class="pun">,</span><span class="pln"> x2</span><span class="pun">,</span><span class="pln"> y2</span><span class="pun">,</span><span class="pln"> z2</span><span class="pun">)</span><span class="pln"> </span><span class="com">// في حالة الأبعاد الثلاثية</span></pre>

<ul>
	<li>
		<strong>الدالة <code>triangle()‎</code> </strong>- لإنشاء المثلثات، وتستخدم كما يلي:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_371_8" style=""><span class="pln">triangle(x1, y1, x2, y2 ,x3 ,y3)‎</span></pre>

<ul>
	<li>
		<strong>الدالة <code>quad()‎</code> </strong>- لإنشاء المضلعات الرباعية، وتستخدم كما يلي:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_371_14" style=""><span class="pln">quad(x1, y1, x2, y2 ,x3, y3, x4 ,y4)‎</span></pre>

<ul>
	<li>
		<strong>الدالة <code>rect()‎</code></strong><span>: </span>تستخدم لرسم المربعات والأشكال المستطيلة. وتقع النقطة المرجعية في في الزاوية اليسارية العليا (راجع الصورة السابقة)، تستخدم الدالة كما يلي:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_371_50" style=""><span class="pln">rect</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> w</span><span class="pun">,</span><span class="pln"> h</span><span class="pun">)</span><span class="pln">

rect</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> w</span><span class="pun">,</span><span class="pln"> h</span><span class="pun">,</span><span class="pln"> r</span><span class="pun">)</span><span class="pln"> </span><span class="com">// تعني‫ r نصف القطر مقدرًا بالبيكسل لجعل زوايا المربع مدورة</span><span class="pln">

rect</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> w</span><span class="pun">,</span><span class="pln"> h</span><span class="pun">,</span><span class="pln"> tl</span><span class="pun">,</span><span class="pln"> tr</span><span class="pun">,</span><span class="pln"> br</span><span class="pun">,</span><span class="pln"> bl</span><span class="pun">)</span><span class="pln"> </span><span class="com">// أنصاف الأقطار للزاويتين العلوية اليسرى واليمنى والسفلية اليمنى واليسرى على الترتيب مقدرة بالبيكسل</span></pre>

<ul>
	<li>
		<strong>الدالة <code>ellipse()‎</code>: </strong>تستخدم لرسم قطع ناقص ومن أجل رسم الدوائر إذ يجب تحديد قيم متساوية للعرض والارتفاع، إن النقطة المرجعية هي افتراضيًا في المنتصف. وتستخدم كما يلي:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_371_20" style=""><span class="pln">ellipse</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> w</span><span class="pun">,</span><span class="pln"> h</span><span class="pun">)</span></pre>

<ul>
	<li>
		<strong>الدالة <code>arc()‎</code></strong><span>:</span> ترسم قوس، وتستخدم كما يلي:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_371_26" style=""><span class="pln">arc</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> w</span><span class="pun">,</span><span class="pln"> h</span><span class="pun">,</span><span class="pln"> start</span><span class="pun">,</span><span class="pln"> stop</span><span class="pun">)</span><span class="pln"> </span></pre>

<p>
	تشير المتغيرات‫ <code>start</code> و <code>stop</code> في هذه الحالة إلى زاوية بداية ونهاية رسم القوس مقدرة بالراديان، ‫ويمكن استخدام القيم التالية <code>PI</code> و <code>HALF_PI</code> و <code>HALF_PI</code> و <code>TWO_PI</code> لحساب الأقواي والمسارات الدائرية بسهولة أكبر.
</p>

<p>
	كما تستخدم أيضًا على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_371_28" style=""><span class="pln">arc</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> w</span><span class="pun">,</span><span class="pln"> h</span><span class="pun">,</span><span class="pln"> start</span><span class="pun">,</span><span class="pln"> stop</span><span class="pun">,</span><span class="pln"> mode</span><span class="pun">)</span><span class="pln"> </span></pre>

<p>
	يحدد المتغير‫ <code>mode</code> ‏هنا طريقة تصيير أو رسم القوس وتمرر قيمته كسلسلة نصية وتكون الخيارت المتاحة لقيم ذلك المتغير ‫هي "<code>OPEN</code>" لرسم القوس فقط دون إغلاق الأطراف، و "<code>PIE</code>" لرسم قوس ووصل طرفيه بالمركز حيث نحصل على ما يشبه قطعة من فطيرة و "<code>CHROD</code>" لرسم القوس مع وصل طرفيه بخط مستقيم.
</p>

<p style="text-align: center;">
	<img alt="arc_mode.png" class="ipsImage ipsImage_thumbnailed" data-fileid="160190" data-ratio="35.75" data-unique="vrpmi2f5s" style="width: 400px; height: auto;" width="636" src="https://academy.hsoub.com/uploads/monthly_2024_10/arc_mode.png.74f4f4153349f545abf273597591ec6b.png">
</p>

<p>
	إن إظهار النصوص على الشاشة يشابه إظهار الأشكال عن طريق تحديد إحداثيات مكان إظهار النص
</p>

<ul>
	<li>
		<strong>الدالة <code>text()‎</code></strong> تظهر النصوص، وتستخدم كما يلي:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_371_42" style=""><span class="pln">text</span><span class="pun">(</span><span class="pln">c</span><span class="pun">,</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">)</span><span class="pln"> </span><span class="com">// يشير المعامل‫ c إلى المحرف، ويتم إظهار أي محرف أبجدي</span><span class="pln">
text</span><span class="pun">(</span><span class="pln">c</span><span class="pun">,</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> z</span><span class="pun">)</span><span class="pln"> </span><span class="com">// في حال العمل في الأبعاد الثلاثة</span><span class="pln">
text</span><span class="pun">(</span><span class="pln">str</span><span class="pun">,</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">)</span><span class="pln"> </span><span class="com">// ‫تعني str السلسلة النصية المراد إظهارها</span><span class="pln">
text</span><span class="pun">(</span><span class="pln">str</span><span class="pun">,</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> z</span><span class="pun">)</span><span class="pln"> </span><span class="com">// في حال العمل في الأبعاد الثلاثة</span><span class="pln">
text</span><span class="pun">(</span><span class="pln">num</span><span class="pun">,</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">)</span><span class="pln"> </span><span class="com">// تعني‫ num القيمة الرقمية المراد إظهارها</span><span class="pln">
text</span><span class="pun">(</span><span class="pln">num</span><span class="pun">,</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> z</span><span class="pun">)</span><span class="pln"> </span><span class="com">// في حال العمل في الأبعاد الثلاثة</span></pre>

<h2 id="-1">
	الإعدادات والخصائص
</h2>

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

<p>
	تذكر عند ضبط الخاصية أن الشيفرة تُنفّذ من الأعلى إلى الأسفل، لنقل أنك تريد ضبط خاصية التعبئة اللونية "<code>fill</code>" باللون الأحمر، وبالتالي كل الكائنات التي ستُرسم بعدها ستكون تعبئتها حمراء حتى نعيد تعريف الخاصية، والأمر نفسه ينطبق أيضًا على باقي الخصائص. لكن الجدير بالملاحظة هنا أن بعض الخصائص لا تستبدل بعضها البعض. على سبيل المثال، خاصية لا تستبدل خاصية لون الحد <code>stroke </code>قيمة خاصية التعبئة <code>fill</code> لأنهما يعملان مع بعضهما. يسهل الشكل التالي فهم <span ipsnoautolink="true">منطق عمل الخاصيات</span><span style="display: none;">       </span>. لاحظ كيف تؤثر أوامر <code>fill </code>و <code>stroke</code> على جميع الأشكال المرسومة بعدها حتى تقوم بتغييرها أو إلغائها.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="JPG" data-fileid="160192" href="https://academy.hsoub.com/uploads/monthly_2024_10/code_execution.JPG.a286578eb190e5a2441c4482ce4beb34.JPG" rel=""><img alt="code_execution.JPG" class="ipsImage ipsImage_thumbnailed" data-fileid="160192" data-ratio="110.00" data-unique="ob9in7kzo" style="width: 400px; height: auto;" width="545" src="https://academy.hsoub.com/uploads/monthly_2024_10/code_execution.thumb.JPG.db5871b45cdcd6c8db887f39d1693eb9.JPG"></a>
</p>

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

<p>
	فيما يلي نشرح بعض الخصائص والإعدادات المتداولة في التصميم.
</p>

<h3 id="-2">
	إعدادات التصميم
</h3>

<ul>
	<li>
		<strong>الدالة </strong><code><strong>fill()</strong>‎</code>:  تضبط نوع اللون لتعبئة الكائنات، تستخدم هذه الدالة لتلوين النصوص، ويكفي مبدئيًا تعلم الاستخدامات التالية:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_371_40" style=""><span class="pln">fill</span><span class="pun">(</span><span class="pln">r</span><span class="pun">,</span><span class="pln"> g</span><span class="pun">,</span><span class="pln"> b</span><span class="pun">)</span><span class="pln"> </span><span class="com">// قيم الأحمر والأخضر والأزرق كأعداد صحيحة</span><span class="pln">
fill</span><span class="pun">(</span><span class="pln">r</span><span class="pun">,</span><span class="pln"> g</span><span class="pun">,</span><span class="pln"> b</span><span class="pun">,</span><span class="pln"> a</span><span class="pun">)</span><span class="pln"> </span><span class="com">// ‫قيمة alpha إضافية، قيمتها العظمى هي 225</span></pre>

<ul>
	<li>
		<strong>الدالة <code>noFill()‎</code></strong>: تستخدم لضبط لون التعبئة ليكون شفاف
	</li>
	<li>
		<strong>الدالة <code>stroke()‎</code>:</strong> تستخدم لضبط نوع لون خط الحدود للكائنات، يمكن تطبيق خاصية الحد للخطوط والحدود حول الكائنات، ويكفي مبدئيًا تعلم الاستخدامات التالية:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_371_54" style=""><span class="pln">stroke</span><span class="pun">(</span><span class="pln">r</span><span class="pun">,</span><span class="pln"> g</span><span class="pun">,</span><span class="pln"> b</span><span class="pun">)</span><span class="pln"> </span><span class="com">// قيم الأحمر والأخضر والأزرق كأعداد صحيحة</span><span class="pln">
stroke</span><span class="pun">(</span><span class="pln">r</span><span class="pun">,</span><span class="pln"> g</span><span class="pun">,</span><span class="pln"> b</span><span class="pun">,</span><span class="pln"> a</span><span class="pun">)</span><span class="pln"> </span><span class="com">// ‫قيمة alpha إضافية، قيمتها العظمى هي 225 </span></pre>

<ul>
	<li>
		<strong>الدالة <code>noStrok()</code></strong><code>‎</code><span>:</span> تًزيل الحد
	</li>
	<li>
		<strong>الدالة <code>strokeWeight()‎</code></strong><span>: </span> تضبط سماكة خط الحد، وفيما يلي مثال على طريقة الاستخدام:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_371_56" style=""><span class="pln">strokeWeight</span><span class="pun">(</span><span class="pln">x</span><span class="pun">)</span><span class="pln"> </span><span class="com">// ‫إن قيمة x هو عدد صحيح يمثل عرض الخط مقدرة بالبيكسل</span></pre>

<ul>
	<li>
		<strong>الدالة <code>background()‎</code></strong>: تضبط لون الخلفية، ويكفي مبدئيًا تعلم الاستخدامات التالية:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_371_58" style=""><span class="pln">background(r, g, b) // تمرر قيم الأحمر والأخضر والأزرق كأعداد صحيحة.
background(r, g, b, a) // ‫قيمة alpha إضافية، وقيمتها العظمى هي 225</span></pre>

<h3 id="-3">
	إعدادات المحاذاة
</h3>

<ul>
	<li>
		<strong>الدالة <code>ellipseMode()‎</code></strong><span>:</span><strong><span> </span></strong>تستسخدم لضبط موضع النقطة المرجعية للقطع الناقص، وهذا مثال عن طريقة الاستخدام:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_371_60" style=""><span class="pln">ellipseMode(mode) </span></pre>

<p>
	يملك المعامل‫ <code>mode</code> القيم الممكنة التالية التي تتحكم في كيفية رسم القطع الناقص:
</p>

<ul style="margin-right: 40px;">
	<li>
		<code>CENTER(default)</code> يحدد المركز كنقطة المرجعية وتكون قيم   w و h هي عرض وارتفاع القطع الناقص ككل.
	</li>
	<li>
		<code>RADIUS</code> ‫تأخذ المركز كالنقطة المرجعية، ولكن في هذا النمط تكون قيم w و h المحددة هي نصف العرض ونصف الارتفاع (كما في حالة نصف قطر الدائرة بدل القطر بالكامل)
	</li>
	<li>
		<code>CORNER</code> تحدد الزاوية اليسرى العلوية كنقطة مرجعية، وتكون قيم w و h هما قيمتي العرض والارتفاع.
	</li>
	<li>
		<code>CORNERS</code> تحدد المعاملين الأول والثاني (x و y) كموقع الزاوية اليسرى العلوية، والمعاملين الثالث والرابع (w و h) كموقع الزاوية اليسرى السفلية للقطع الناقص، لذا في هذا النمط قيم العرض و الطول غير مهمتان، ولفهم أفضل يمكن اعتبار الدالة بالشكل التالي <code>ellipse(x_tl,y_tl,x_br,y_br)‎</code> لتكون أكثر منطقية.
	</li>
	<li>
		<strong>الدالة <code>rectMode()‎</code></strong>: تحدد مكان النقطة المرجعية لمحاذاة المستطيلات، وفيما يلي طريقة الاستخدام:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_371_62" style=""><span class="pln">rectMode</span><span class="pun">(</span><span class="pln">mode</span><span class="pun">)</span><span class="pln"> </span></pre>

<p>
	يملك المعامل <code>mode</code> لديه المعاملات التالية :
</p>

<ul style="margin-right: 80px;">
	<li>
		<code>CENTER</code> يحدد المركز كنقطة المرجعية للمستطيل وتكون قيم w و h المحددة هي عرض وارتفاع المستطيل.
	</li>
	<li>
		<code>RADIUS</code> تأخذ المركز كالنقطة المرجعية، ولكن في هذا النمط تكون قيم w و h المحددة هي نصف العرض ونصف الارتفاع.
	</li>
	<li>
		<code>CORNER</code> تحدد الزاوية اليسرى العلوية كنقطة مرجعية وقيم w و h المحددة هي عرض وارتفاع المستطيل هذه هي القيمة الافتراضية للمتغير.
	</li>
	<li>
		<code>CORNERS</code> تحدد المعاملين الأول والثاني (x و y) كموقع الزاوية اليسرى العلوية، والمعاملين الثالث والرابع (w و h) كموقع الزاوية اليسرى السفلية للمستطيل، لذا في هذا النمط تكون قيم العرض و الطول غير مهمين، ولفهم أفضل يمكن اعتبار الدالة بالشكل التالي <code>rect(x_tl,y_tl,x_br,y_br)‎</code> لتكون أكثر منطقية.
	</li>
</ul>

<h3 id="-4">
	إعدادات متعلقة بالنص
</h3>

<ul>
	<li>
		<strong>الدالة <code>textSize()‎</code></strong>: تحدد حجم الخط، وطريقة الاستخدام كالتالي:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_371_66" style=""><span class="pln">testSize</span><span class="pun">(</span><span class="pln">size</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><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>

<ul>
	<li>
		<strong>الدالة <code>textLeading()‎</code></strong>:  تحدد ارتفاع الخط للنصوص، الاستخدام:
	</li>
	<li>
		<strong>الدالة <code>textLeading(lineheight)</code></strong>: تحدد قيمة الفراغ بين الأسطر مقدر بالبيكسل
	</li>
	<li>
		<strong>الدالة <code>textAlign()‎</code></strong><span><span>:</span></span> تحدد أين هي النقطة المرجعية لمحاذاة النصوص، وهذا مثال على طريقة الاستخدام:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_371_71" style=""><span class="pln">    textAlign</span><span class="pun">(</span><span class="pln">alignX</span><span class="pun">)</span><span class="com">// تستخدم alignX للمحاذاة الأفقية وتأخذ القيم LEFT,CENTER,RIGHT</span><span class="pln">
    textAlign</span><span class="pun">(</span><span class="pln">alignX</span><span class="pun">,</span><span class="pln">alignY</span><span class="pun">)</span><span class="pln"> </span><span class="com">//تستخدم alignY للمحاذاة الشاقولية وتأخذ القيم TOP,BOTTOM,CENTER,BASELINE</span></pre>

<h2 id="-5">
	التحريك
</h2>

<p>
	تعلمنا حتى الآن كيفية رسم الكائنات والنصوص، ولكنها كلها كائنات ثابتة لا تتحرك؛ سنتعلم الآن تحريكها عن طريق إعطاء الإحداثيات قيم متغيرة بدلًا من أعداد صحيحة ثابتة ويمكننا زيادتها أو إنقاصها، لاحظ الشيفرة التالية
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_558_8" style=""><span class="com">// تهيئة‫ x و y إلى القيمة 0</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> x</span><span class="pun">=</span><span class="lit">0</span><span class="pun">;</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> y</span><span class="pun">=</span><span class="lit">0</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">void</span><span class="pln"> setup</span><span class="pun">(){</span><span class="pln">
  size</span><span class="pun">(</span><span class="lit">800</span><span class="pun">,</span><span class="lit">600</span><span class="pun">);</span><span class="pln">
  background</span><span class="pun">(</span><span class="lit">255</span><span class="pun">);</span><span class="pln"> </span><span class="com">// ضبط لون الخلفية للون الأبيض</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">void</span><span class="pln"> draw</span><span class="pun">(){</span><span class="pln">
  fill</span><span class="pun">(</span><span class="lit">255</span><span class="pun">,</span><span class="lit">0</span><span class="pun">,</span><span class="lit">0</span><span class="pun">);</span><span class="pln"> </span><span class="com">// لون التعبئة أحمر</span><span class="pln">
  stroke</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="lit">0</span><span class="pun">,</span><span class="lit">255</span><span class="pun">);</span><span class="pln"> </span><span class="com">// لون خط الحد هو الأزرق</span><span class="pln">
  ellipseMode</span><span class="pun">(</span><span class="pln">CENTER</span><span class="pun">);</span><span class="pln"> </span><span class="com">// نقطة المرجع هي المركز</span><span class="pln">

  ellipse</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> </span><span class="lit">20</span><span class="pun">,</span><span class="pln"> </span><span class="lit">20</span><span class="pun">);</span><span class="pln"> </span><span class="com">// رسم القطع الناقص</span><span class="pln">

  </span><span class="com">// زيادة‫ x و y</span><span class="pln">
  x</span><span class="pun">+=</span><span class="lit">5</span><span class="pun">;</span><span class="pln">
  y</span><span class="pun">+=</span><span class="lit">5</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	هل تستطيع معرفة كيفية إدارة عملية التحريك؟ لقد ضبطنا قيم x و y كمتغيرات عامة بقيم أولية 0، وأنشأنا في حلقة الرسم القطع الناقص، ثم ضبطنا لون التعبئة للأحمر، ولون خط الحد للأزرق وضبطنا قيم الإحداثيات x و y، إذ تغير الكرة مكانها عندما نزيد قيمتي x و y ولكن هناك مشكلة في الشيفرة هل تستطيع معرفتها كاختبار لك؟ حاول تجربتها:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="gif" data-fileid="160187" href="https://academy.hsoub.com/uploads/monthly_2024_10/356721416_.gif.68c72b65b04301572bca121d25453b86.gif" rel=""><img alt="التحريك" class="ipsImage ipsImage_thumbnailed" data-fileid="160187" data-unique="iaysej020" src="https://academy.hsoub.com/uploads/monthly_2024_10/.thumb.gif.d3081ded0b37b04a7f836c15995ac6fa.gif"> </a>
</p>

<p>
	الهدف هنا أن تعرف كيفية عمل الحلقات في لغة بروسيسنج، تذكر المثال الذي أوردناه في قسم كتلة الرسم في المقال السابق ولماذا حصلنا على "111…" بدلًا من "1234.." إنه السبب ذاته الذي جعل الكرة تترك أثرًا، حيث في كل مرة تكرّر الكرة تزداد قيمة x و y بقيمة 5 وتُرسم الكرة نحو اليمين والأسفل بمقدار 5 بيكسل، ولكن تبقى الكرة المرسومة من التكرار السابق، كيف نستطيع إزالتها؟
</p>

<p>
	لإزالة أثر الكرة نزيل ببساطة <code>background(255)‎</code> من كتلة التهيئة ونضعها في أول سطر من كتلة الرسم، فعندما كانت شيفرة الخلفية في كتلة التهيئة نُفذت الشيفرة مرة واحدة في البداية لجعل الخلفية بيضاء، ولكن ذلك لا يكفي يجب إعادة ضبط الخلفية للأبيض في كل حلقة لتغطية الكرات المرسومة من الحلقات السابقة. بما أن الخلفية هي السطر الأول فهذا يعني تنفيذه أولًا وتصبح الطبقة الأساسية، وتصبح الخلفية بيضاء في كل حلقة وتُرسم العناصر عليها لذا لا يبقى أي أثر.
</p>

<p>
	تعتمد فكرة التحريك في لغة البرمجة بروسيسنج على التلاعب بالشكل وتعديله برمجيًا لتغيير مكانه، ولكن قد تتساءل كيف نستطيع عمل أشياء أكثر تعقيدًا مثل إبقاء الكرة على الشاشة أو تطبيق الجاذبية؟ هذا ما سنتعلمه في المقال التالي عن تطبيق عملي نجرب من خلاله إنشاء لعبة كاملة وقابلة للعب ومسليّة.
</p>

<h2 id="-6">
	التفاعل مع الفأرة ولوحة المفاتيح
</h2>

<p>
	إن التفاعل مع الفأرة ولوحة المفاتيح أمر سهل في لغة بروسيسنج، إذ توجد توابع تُستدعى لكل حدث وما يُنفّذ كل ما يتم كتابته داخلها عند حصول الحدث، و هناك أيضًا متغيرات عامة مثل<code>mousePressed</code> و <code>keyPressed</code> يمكن استخدامها في كتلة الرسم للاستفادة من الحلقة، التالي هي بعض التوابع مع شرحها
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_558_10" style=""><span class="kwd">void</span><span class="pln"> setup</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  size</span><span class="pun">(</span><span class="lit">500</span><span class="pun">,</span><span class="pln"> </span><span class="lit">500</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">void</span><span class="pln"> draw</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">mousePressed</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">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">mouseButton </span><span class="pun">==</span><span class="pln"> LEFT</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="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">keyPressed</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">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">key </span><span class="pun">==</span><span class="pln"> CODED</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="com">// ‫تتحقق هذه تعليمة if هذه إذا كان المفتاح</span><span class="pln">
      </span><span class="com">// مُتَعرف عليه من لغة بروسيسنج</span><span class="pln">

      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">keyCode </span><span class="pun">==</span><span class="pln"> ENTER</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">// هو مفتاح‫ enter</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">else</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="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">void</span><span class="pln"> mousePressed</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">// ‫لاحظ أن المتغير mouseButton</span><span class="pln">
  </span><span class="com">// مستخدم هنا</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">void</span><span class="pln"> keyPressed</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">// ‫لاحظ أن المتغيرات key و keyCode</span><span class="pln">
  </span><span class="com">// مستخدمة هنا</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	كما ترى من السهل التحقق إذا ما كانت الفأرة قد تم نقرها أو هناك زر تم الضغط عليه، ولكن هناك خيارات أكثر للمتغيرات <code>mousePressed</code> و <code>keyCode</code>، إن الخيارات المتاحة للمتغير <code>mousePressed</code> هي <code>LEFT</code> و <code>RIGHT</code> و <code>CENTER</code>، وهناك العديد من الخيارات المتاحة للمتغير <code>keyCode</code> مثل (<code>UP</code> و <code>DOWN</code> و <code>LEFT</code> و <code>RIGHT</code> و <code>ALT</code> و <code>CONTROL </code> و <code>SHIFT</code> و <code>DELETE</code> ...إلخ.)
</p>

<p>
	هناك أمر وحيد يجب معرفته في متغيرات الفأرة وهو كيفية الحصول على إحداثيات مؤشر الفأرة، وهو أمر سنستخدمه كثيرًا، فللحصول على إحداثيات المؤشر يمكنك استخدام المتغيرين <code>mouseX</code> و <code>mouseY</code> مباشرة في كتلة <code>draw()‎</code>، وأخيرًا وليس آخرًا هناك العديد من الدوال التي يجب أن تتطلع عليها موثقة في <a href="https://processing.org/reference/" rel="external nofollow">مراجع لغة بروسيسنج</a>.
</p>

<h2 id="-7">
	الخاتمة
</h2>

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

<h3 id="-8">
	التمرين الأول
</h3>

<p>
	عليم رسم 4 كرات بألوان مختلفة تبدأ من 4 زوايا الشاشة وتنتقل إلى الوسط بسرعات مختلفة، ويجب أن تتوقف الكرة عندما تنقر على أحد الكرات وتستمر في ضغط الزر، وعندما تزيل الضغط تعود الكرة إلى مكانها الأساسي وتستمر في الحركة، أي شيء <a href="http://i.imgur.com/XuppyGg.gif" rel="external nofollow">كالتالي</a> تحقق من الشيفرة <a href="https://gist.github.com/oguzgelal/13d2e0b1cd98b62c3986" rel="external nofollow">هنا</a> بعد تجربة هذا التمرين
</p>

<h3 id="-9">
	التمرين الثاني
</h3>

<p>
	هل تذكر شاشة توقف DVD المشهورة التي يرتد بها شعار DVD حول الشاشة وننتظر بشوق أن تصطدم في الزاوية؟ أريد منك تكرار شاشة التوقف هذه باستخدام مستطيل بدلًا من شعار DVD، عند تشغيل التطبيق يجب أن تكون الشاشة سوداء وينطلق المستطيل من موقع عشوائي، وفي كل مرة يصطدم بزاوية يجب على المستطيل تغيير لونه (وبالطبع اتجاهه). وعند تحريك الفأرة يجب على المستطيل الاختفاء وأن تصبح الشاشة بيضاء، لن تحصل على الحل في هذه المقالة يجب عليك أن تحاول كل جهدك أن تنفذها وسنقدم الشيفرة في المقال التالي.
</p>

<p>
	ترجمة -بتصرف- لمقال <a href="https://www.toptal.com/game/ultimate-guide-to-processing-the-fundamentals" rel="external nofollow">Guide to the Processing Language Part I: Fundamentals</a> لكاتبه Oguz Gelal.
</p>

<h2 id="-3">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/game-development/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D9%84%D9%84%D8%BA%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%B1%D9%88%D8%B3%D9%8A%D8%B3%D9%8A%D9%86%D8%AC-processing-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-r2422/" rel="">دليلك للغة برمجة بروسيسينج Processing | الجزء الأول: الأساسيات</a>
	</li>
	<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/game-development/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D9%84%D9%84%D8%BA%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%B1%D9%88%D8%B3%D9%8A%D8%B3%D9%8A%D9%86%D8%AC-processing-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%B9%D8%A8%D8%A9-%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9-r2437/" rel="">دليلك للغة برمجة بروسيسينج Processing | الجزء الثالث: بناء لعبة بسيطة</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D9%83%D9%8A%D9%81-%D8%AA%D8%B5%D8%A8%D8%AD-%D9%85%D8%A8%D8%B1%D9%85%D8%AC-%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D9%81%D9%8A%D8%AF%D9%8A%D9%88-%D9%86%D8%A7%D8%AC%D8%AD-r2284/" rel="">كيف تصبح مبرمج ألعاب فيديو ناجح</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-r2304/" rel="">دليلك الشامل إلى برمجة الألعاب</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%B9%D8%A8%D8%A9-%D9%88%D8%B1%D9%82-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1111/" rel="">بناء لعبة ورق في جافا</a>
	</li>
	<li>
		 <a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%81%D8%B6%D9%84-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D8%AC-%D8%AA%D8%B5%D9%85%D9%8A%D9%85-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A%D8%A9-r2227/" rel="">تعرف على أفضل برنامج تصميم الألعاب الإلكترونية</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2432</guid><pubDate>Sat, 19 Oct 2024 15:04:00 +0000</pubDate></item><item><title>&#x62F;&#x644;&#x64A;&#x644;&#x643; &#x644;&#x644;&#x63A;&#x629; &#x628;&#x631;&#x645;&#x62C;&#x629; &#x628;&#x631;&#x648;&#x633;&#x64A;&#x633;&#x64A;&#x646;&#x62C; Processing | &#x627;&#x644;&#x62C;&#x632;&#x621; &#x627;&#x644;&#x623;&#x648;&#x644;: &#x627;&#x644;&#x623;&#x633;&#x627;&#x633;&#x64A;&#x627;&#x62A;</title><link>https://academy.hsoub.com/programming/game-development/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D9%84%D9%84%D8%BA%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%B1%D9%88%D8%B3%D9%8A%D8%B3%D9%8A%D9%86%D8%AC-processing-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-r2422/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_10/----Processing---.png.a2b0cb1ff55b48c3d4587bad6ef5c2c9.png" /></p>
<p>
	يٌعدَ إنشاء النماذج الأولية بسرعة وإنتاج نتائج بصرية سريعة من الميزات المهمة في العديد من لغات وأطر البرمجة. ومع ذلك، تتميز بعض اللغات بجعل هذه الإمكانيات من أهدافها الأساسية، مثل لغة البرمجة بروسيسنج Processing المبنية على جافا.
</p>

<p>
	تُتيح لغة Processing للمستخدمين كتابة الشيفرات ضمن بيئة تصميم رسومية، حيث تم تطويرها خصيصًا لتقديم نتائج بصرية فورية. في هذه المقالة، نقدم مقدمة بسيطة عن لغة البرمجة بروسيسنج وتقنياتها الفريدة." فإذا كنت تشعر بالملل وتريد أن تفرغ إبداعك في بناء مشروع مبهر بصريًا أو شيء فني، أو لربما تريد تعلم البرمجة وعمل شيء جميل بأسرع وقت؟ عليك بلغة برمجة بروسيسنج Processing، ويمكن القول أن هذه اللغة واحدة من أكثر اللغات متعة، فهي لغة مباشرة وسهلة التعلم والاستخدام بالإضافة لكونها أداة قوية، فاستخدامها يمنحك الشعور بأنك ترسم على لوحة بيضاء بأسطر برمجية، ولا يوجد قوانين صارمة تحد إبداعك، الحد الوحيد هنا هو خيالك.
</p>

<p>
	ما سنتعلمه في هذه السلسلة من المقالات هو التعرف على لغة البرمجة بروسيسنج <a href="https://processing.org/" rel="external nofollow">Processing</a> وتصميم ألعاب بسيطة باستخدامها. تتكون هذه المقالات من ثلاثة أقسام:
</p>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D9%84%D9%84%D8%BA%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%B1%D9%88%D8%B3%D9%8A%D8%B3%D9%8A%D9%86%D8%AC-processing-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-r2422/" rel="">في المقال الأول سنتحدث عن اللغة Processing ونوفر نظرة عامة عنها.</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D9%84%D9%84%D8%BA%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-processing-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D9%84%D8%B1%D8%B3%D9%85-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%A7%D8%B9%D9%84-%D9%85%D8%B9-%D8%AF%D8%AE%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-r2432/" rel="">في المقال الثاني نوفر دليلًا شاملًا للغة Processing وبعض النصائح حولها</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D9%84%D9%84%D8%BA%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%B1%D9%88%D8%B3%D9%8A%D8%B3%D9%8A%D9%86%D8%AC-processing-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%B9%D8%A8%D8%A9-%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9-r2437/" rel="">في المقال الثالث سننشئ لعبة بسيطة خطوة بخطوة كما سنحوّل شيفرة اللعبة إلى لغة جافاسكريبت JavaScript باستخدام P5js لتعمل على متصفح الويب.</a>
	</li>
</ul>

<h2 id="">
	ماذا يجب أن تعرفه أولًا
</h2>

<p>
	يجب عليك أن تعرف مبادئ البرمجة الأساسية لفهم ومتابعة هذه المقالة بسهولة، لأننا لن نتحدث عنها، ولن نتحدث عن مفاهيم برمجية معقدة أيضًا لذا يجب أن يكون لديك معرفة <a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9/" rel="">بأساسيات البرمجة</a>. وسنتطرق في بعض الأجزاء عن مفاهيم برمجية ذات مستوى منخفض low-level مثل مفهوم البرمجة كائنية التوجه Object Oriented Programming أو <a href="https://academy.hsoub.com/programming/general/%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-oop/" rel=""><abbr title="Object-Oriented Programming | البرمجة كائنية التوجه"><abbr title="Object-Oriented Programming | البرمجة كائنية التوجه">OOP</abbr></abbr> </a> اختصارًا، ولكن هذه المفاهيم ليست أساسية للعمل على برمجة اللعبة، إنما هي موجهة للقراء المهتمين ببنية اللغة البرمجية وبالتالي يمكنك تخطي هذه الأجزاء إذا لا ترغب بمعرفتها. عدا ذلك ستحتاج إلى حافز تعلم اللغة والشغف <a href="https://academy.hsoub.com/programming/game-development/%D9%83%D9%8A%D9%81-%D8%AA%D8%B5%D8%A8%D8%AD-%D9%85%D8%A8%D8%B1%D9%85%D8%AC-%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D9%81%D9%8A%D8%AF%D9%8A%D9%88-%D9%86%D8%A7%D8%AC%D8%AD-r2284/" rel="">لبرمجة لعبة إلكترونية</a> خاصة بك.
</p>

<h2 id="-1">
	كيفية المتابعة
</h2>

<p>
	من الجيد دومًا التعلم عن طريق التجريب، فكلما تعمقت في لعبتك كلما تمكنت من إتقان لغة بروسيسنج Processing. لذا نقترح بداية بأن تعمل على تجريب كل خطوة في البيئة الخاصة بك. لدى لغة بروسيسنج Processing بيئة تطوير متكاملة (أي محرر شيفرة) سهل الاستخدام، إنه الشيء الوحيد الذي تحتاج لتنزيله وتثبيته للمتابعة. ويمكن تنزيله <a href="https://processing.org/download/" rel="external nofollow">من هنا</a><br>
	بعدها ستكون جاهزًا لتبدأ!
</p>

<h2 id="processing-1">
	ما هي لغة برمجة بروسيسنج Processing؟
</h2>

<p>
	يضم هذا القسم مقدمة تقنية مختصرة عن لغة البرمجة Processing وهيكليتها وبعض الملاحظات على عملية التصريف compiling والتنفيذ. ستتحدث التفاصيل عن معلومات متقدمة عن البرمجة وبيئة جافا. يمكنك تخطي قسم "أساسيات Processing" إذا كنت لا تريد معرفة التفاصيل البرمجية النظرية وترغب في البدء بشكل عملي في برمجة اللعبة الخاصة بك. إن لغة بروسيسنج Processing هي لغة برمجة بصرية تسمح لك بالرسم عن طريق الشيفرة، ولكنها ليست لغة برمجة بحد ذاتها، فهي تدعى "شبيهة جافا Java-esque" أي أنها مبنية على منصة جافا ولكنها ليست لغة جافا، وهي تُعالج الشيفرة وتحوَّل بكاملها إلى شيفرة جافا عند التنفيذ. إن صنف جافا <a href="https://processing.github.io/processing-javadocs/core/processing/core/PApplet.html" rel="external nofollow">PApplet</a> هو الصنف الأساسي لكل رسومات بروسيسنج Processing. لنأخذ كتلتي شيفرة بروسيسنج Processing كمثال.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8728_6" style=""><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> setup</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="pun">}</span><span class="pln">
</span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> draw</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="pun">}</span></pre>

<p>
	سوف تتحول كل من كتلتي الشيفرة في الأعلى إلى شيء يشبه التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8728_8" style=""><span class="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">ExampleFrame</span><span class="pln"> extends </span><span class="typ">Frame</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

     </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">ExampleFrame</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
         super</span><span class="pun">(</span><span class="str">"Embedded PApplet"</span><span class="pun">);</span><span class="pln">

         setLayout</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">BorderLayout</span><span class="pun">());</span><span class="pln">
         </span><span class="typ">PApplet</span><span class="pln"> embed </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Embedded</span><span class="pun">();</span><span class="pln">
         add</span><span class="pun">(</span><span class="pln">embed</span><span class="pun">,</span><span class="pln"> </span><span class="typ">BorderLayout</span><span class="pun">.</span><span class="pln">CENTER</span><span class="pun">);</span><span class="pln">

         embed</span><span class="pun">.</span><span class="pln">init</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="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Embedded</span><span class="pln"> extends </span><span class="typ">PApplet</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> setup</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="pun">}</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> draw</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="pun">}</span><span class="pln">

</span><span class="pun">}</span></pre>

<p>
	سترى أنه تم تغليف كتلة شيفرة بروسيسنج Processing بصنف ممتد extended class من PApplet الخاص <span ipsnoautolink="true">بلغة جافا</span>. لذا يجري التعامل مع كل الأصناف المعرفة في شيفرة بروسيسنج كأنها <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A3%D8%B5%D9%86%D8%A7%D9%81-%D8%A7%D9%84%D9%85%D8%AA%D8%AF%D8%A7%D8%AE%D9%84%D8%A9-nested-classes-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1115/" rel="">أصناف داخلية</a> inner classes.
</p>

<p>
	إن معرفة أن بروسيسنج Processing مبنية من <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A3%D8%B5%D9%86%D8%A7%D9%81-%D8%A7%D9%84%D9%85%D8%AA%D8%AF%D8%A7%D8%AE%D9%84%D8%A9-nested-classes-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1115/" rel="">لغة جافا</a> له العديد من الميزات خصوصًا إذا كنت مطور جافا، ليس فقط لكون الصياغة مألوفة، ولكن هذه المعرفة تمنحنا القدرة على تضمين شيفرة جافا والمكاتب وملفات JAR في رسوماتك واستخدام تطبيقات بروسيسنج Processing مباشرة من Eclipse إذا أردت أخذ بعض الوقت لتهيئته. شيء واحد لا تستطيع عمله هو استخدام مكونات AWT و Swing في رسومات بروسيسنج لأنها تتضارب مع طبيعة لغة بروسيسنج ولكن لا تقلق لن نتطرق لذلك في هذه المقالة.
</p>

<h2 id="processing-2">
	أساسيات بروسيسنج Processing
</h2>

<p>
	تتألف شيفرة لغة بروسيسنج Processing من قسمين أساسيين، هما كتلتَي التهيئة والرسم. تُنفذ كتل التهيئة عندما تُنفَّذ الشيفرة وتستمر كتل الرسم بالتنفيذ دومًا. إن الفكرة الأساسية خلف بروسيسنج Processing هي تنفيذ ما يتم كتابته في كتلة الرسم 60 مرة في الثانية من الأعلى إلى الأسفل لحين إغلاق البرنامج. سنبني كل شيء اعتمادًا على هذه الفكرة، وسنحرك الكائنات ونعرف النتيجة ونتوقع الاصطدامات ونطبق الجاذبية ونعمل أي شيء باستخدام هذه الخاصية. حلقة التحديث هذه هي نبض المشروع. سنشرح كيفية الاستفادة من ذلك لجعل الشيفرة تنبض بالحياة في الأقسام اللاحقة، ولكن أولًا لنتحدث عن بيئة التطوير المتكاملة لبروسيسنج Processing.
</p>

<h2 id="processingide">
	بيئة التطوير المتكاملة لبروسيسنج Processing IDE
</h2>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="PNG" data-fileid="159335" href="https://academy.hsoub.com/uploads/monthly_2024_10/Processing.PNG.fe0dd89c8b2e82f29acb759a5efade96.PNG" rel=""><img alt="بيئة تطوير لغة Processing" class="ipsImage ipsImage_thumbnailed" data-fileid="159335" data-ratio="87.25" data-unique="1l8rfx90c" style="width: 400px; height: auto;" width="688" src="https://academy.hsoub.com/uploads/monthly_2024_10/Processing.thumb.PNG.d9cefd26735ea8e68cdd4d6f162b7845.PNG"></a>
</p>

<p>
	<a href="https://processing.org/download/" rel="external nofollow">نزّل</a> بيئة التطوير المتكاملة لبروسيسنج Processing إذا لم تفعل ذلك لحد الآن. سنحدد بعض المهام السهلة لتنفذها لوحدك خلال المقالة ويمكنك أن تجربها فقط إذا كانت لديك بيئة التطوير المتكاملة.القسم التالي هو مقدمة عن بيئة التطوير المتكاملة لبروسيسنج Processing وهي بسيطة ومفهومة لذا سنختصر الشرح حولها.
</p>

<p>
	يقوم زرَّا "إيقاف Stop" و"تنفيذ Run" بما تتوقعه، فعند نقر زر "تنفيذ Run" تُصرَّف الشيفرة وتُنفَّذ. وتعمل برامج بروسيسنج Processing دون توقف إذا لم تُقاطِع تنفيذها. ويمكنك إنهاء التنفيذ برمجيًا أو يمكنك استخدام زر "إيقاف Stop".
</p>

<p>
	الزر الذي يشبه الفراشة علي يمين زر "إيقاف Stop" و "تنفيذ Run" هو <strong>المُنقّح Debugger</strong>، واستخدام المُنقِّح يحتاج لمقالة خاصة وهو خارج نطاق هذه المقالة ويمكن تجاهله الآن. أما اللائحة المنسدلة بجانب زر المُنقح فهي تستخدم عند إضافة وتهيئة الأنماط، إذ تقدم <strong>الأنماط Mods</strong> بعض الوظائف وتسمح لك بكتابة شيفرة لأندرويد وبايثون وما إلى ذلك. إن الأنماط هي أيضًا خارج نطاق المقالة ويمكنك تجاهلها أيضًا. النافذة في محرر الشيفرة هي مكان تنفيذ الرسوم، وهي سوداء في الصورة السابقة لأننا لم نحدد بعد خاصية مثل الحجم أو لون الخلفية ولم نرسم أي شيء. لا يوجد شيء آخر للتحدث عنه في محرر الشيفرة إنه ببساطة مكان لكتابة الشيفرة. وهناك عدد أسطر  -لم تحوي إصدارات بروسيسنج Processing السابقة عليه-
</p>

<p>
	الصندوق الأسود السفلي هو الطرفية Console، سنستخدمها لطباعة الأشياء لتنقيح الأخطاء بسرعة. تظهر تبويبة الأخطاء Errors بجانب الطرفية الأخطاء عند ظهورها. هذه ميزة مفيدة تأتي مع إصدار بروسيسنج Processing 3.0. فقد كانت تُطبع الأخطاء في الإصدارت السابقة إلى الطرفية وكانت هناك صعوبة في تتبعها.
</p>

<h2 id="setupblock">
	كتلة التهيئة Setup Block
</h2>

<p>
	تُنفَّذ كتلة التهيئة كما تحدثنا سابقًا عند تنفيذ البرنامج، ويمكن استخدامها للضبط ولأشياء تريد تنفيذها مرة واحدة مثل تحميل الصور والأصوات. فيما يلي مثال يوضح كتلة تهيئة، نفذ الشيفرة في البيئة الخاصة بك وشاهد النتائج بنفسك:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8728_15" style=""><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> setup</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// حجم المشروع هو‪ 600x800</span><span class="pln">
  </span><span class="com">// واستخدم محرك التصيير‪ P2D</span><span class="pln">
  size</span><span class="pun">(</span><span class="lit">800</span><span class="pun">,</span><span class="pln"> </span><span class="lit">600</span><span class="pun">,</span><span class="pln"> P2D</span><span class="pun">);</span><span class="pln">

  </span><span class="com">// يمكن استخدام هذه الدالة بدلًا من‪ size()‎</span><span class="pln">
  </span><span class="com">// fullScreen(P2D);</span><span class="pln">

  </span><span class="com">// سيكون لون خلفية المشروع هو الأسود</span><span class="pln">
  </span><span class="com">//افتراضيًا، إذا لم يحدد غير ذلك</span><span class="pln">
  background</span><span class="pun">(</span><span class="lit">0</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">// background(loadImage("test.jpg"));</span><span class="pln">

  </span><span class="com">// يتم تعبئة الأشكال والكائنات باللون الأحمر افتراضيًا</span><span class="pln">
  </span><span class="com">// مالم يُشار عكس ذلك</span><span class="pln">
  fill</span><span class="pun">(</span><span class="lit">255</span><span class="pun">,</span><span class="lit">0</span><span class="pun">,</span><span class="lit">0</span><span class="pun">);</span><span class="pln">

  </span><span class="com">// يكون للأشكال والكائنات حد أبيض افتراضيًا </span><span class="pln">
  </span><span class="com">// مالم يُشار عكس ذلك</span><span class="pln">
  stroke</span><span class="pun">(</span><span class="lit">255</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	سنشرح التوابع المتعلقة بالتصميم (مثل الخلفية والتعبئة وثخانة الخط) في <a href="https://academy.hsoub.com/programming/game-development/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D9%84%D9%84%D8%BA%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-processing-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D9%84%D8%B1%D8%B3%D9%85-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%A7%D8%B9%D9%84-%D9%85%D8%B9-%D8%AF%D8%AE%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-r2432/" rel="">المقال التالي</a> ما تحتاج معرفته الآن هو كيفية تأثير الضبط والإعدادات على المشروع بأكمله بالإضافة لفهم التوابع التالية.
</p>

<ul>
	<li>
		تستخدم الدالةsize()<code>‎</code> -كما يشير اسمها- لضبط حجم الرسم. يجب أن تكون في أول كتلة شيفرة التهيئة، ويمكن استخدامها بالطرق التالية:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8728_19" style=""><span class="pln">size</span><span class="pun">(</span><span class="pln">width</span><span class="pun">,</span><span class="pln">height</span><span class="pun">);</span><span class="pln">
size</span><span class="pun">(</span><span class="pln">width</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">,</span><span class="pln"> renderer</span><span class="pun">);</span></pre>

<p>
	يمكن تحديد عرض وارتفاع القيم بالبيكسل. وتقبل دوال الحجم معامل أو وسيط ثالث هو المُصيّر أو العارض renderer المستخدم لتحديد أي محرك تصيير سيتم استخدامه. والمصير الافتراضي هو P2D. والمُصيّرات المتوافرة هي P2D (للمعالجة ثنائية البعد) و P3D (للمعالجة ثلاثية البعد، ويجب استخدامه إذا تضمنت الرسومات رسوميات ثلاثية البعد) و PDF (حيث ترسم الرسوميات ثنائية البعد مباشرة في ملف PDF). لمزيد من المعلومات انقر <a href="https://processing.org/reference/libraries/pdf/" rel="external nofollow">هنا</a> . ستستخدم كل من P2D و P3D العتاد الرسومي المتوافق مع OpenGL.
</p>

<ul>
	<li>
		يمكن استخدام دالة الشاشة الكاملة <code>()fullScreen</code>  بدلًا من دالة <code>size()‎</code> من إصدار بروسيسنج Processing 3.0 ، وكما في دالة <code>size()‎</code> يجب أن تكون في أول سطر من كتلة التهيئة أيضًا ويكون استخدامها كالتالي:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3421_7" style=""><span class="pln">fullScreen</span><span class="pun">();</span><span class="pln">
fullScreen</span><span class="pun">(</span><span class="pln">display</span><span class="pun">);</span><span class="pln">
fullScreen</span><span class="pun">(</span><span class="pln">renderer</span><span class="pun">);</span><span class="pln">
fullScreen</span><span class="pun">(</span><span class="pln">display</span><span class="pun">,</span><span class="pln"> renderer</span><span class="pun">);</span><span class="pln"> </span></pre>

<p>
	إذا استخدمتها بدون أي معامل سيكون رسم بروسيسنج Processing على الشاشة كاملة وسيُنفذ على شاشتك الأساسية. يشير معامل <code>display</code> للإشارة إلى الشاشة التي سيُنفذ الرسم عليها. مثلًا إذا وصلت شاشات خارجية لحاسوبك يمكنك ضبط متغير الإظهار إلى 2 (أو 3 أو 4 إلخ.) وسيُنفذ الرسم هناك. تم شرح معامل <code>renderer</code> في قسم <code>size()‎</code> في الأعلى.
</p>

<h2 id="settingsblock">
	كتلة الإعدادات Settings Block
</h2>

<p>
	أضيفت هذه الميزة في إصدارات بروسيسنج Processing الجديدة، وهي كتلة شيفرة مثل التهيئة والرسم وتفيد عند تعريف توابع size()<code>‎</code> و <code>fullScreen()‎</code> بمعاملات متغايرة. من الضروري أيضًا تعريف <code>size()‎</code> وخصائص التصميم الأخرى مثل <code>smooth()‎</code> في كتلة الشيفرة هذه إذا كنت تستخدم بيئات غير بيئة التطوير المتكاملة الخاصة ببروسيسنج Processing مثل Eclipse، ولكن لن تحتاجه في معظم الحالات وبالتأكيد ليس في هذه المقالة.
</p>

<h2 id="drawblock">
	كتلة الرسم Draw Block
</h2>

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

<p>
	إن كل الشيفرة التي سنتحدث عنها في المقالة ستكون في كتلة الرسم لذا من الضروري أن تفهم كيفية عمل هذه الكتلة. لنقدم عرض عن ذلك. جرب التالي: لاحظ أننا يمكن أن نطبع أي شيء في الطرفية باستخدام التابعَين <code>print()‎</code> أو <code>println()‎</code>. تطبع دوال الطباعة إلى الطرفية لكن <code>println</code> تطبع وتبدأ بسطر جديد في النهاية لذا تُطبع كل <code>println()‎</code> في سطر مختلف.
</p>

<p>
	شاهد كتلة الشيفرة التالية وحاول أن تعرف ما سيُطبع في الطرفية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8728_21" style=""><span class="kwd">void</span><span class="pln"> setup</span><span class="pun">(){</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> draw</span><span class="pun">(){</span><span class="pln">
  </span><span class="typ">int</span><span class="pln"> x </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
  x </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">
  print</span><span class="pun">(</span><span class="pln">x</span><span class="pun">+</span><span class="str">" "</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	إذا قلت "1234…."، فهذا جواب متوقع، ولكنه للأسف جوال خاطئ! هذه واحدة من أوجه سوء الفهم في بروسيسنج processing. تذكر أن الكتلة تُنفَّذ بشكل متكرر. فعندما عرّفت المتغير هنا فسيُعرَّف في كل حلقة مرة تلو مرة. وفي كل تكرار قيمة x هي 0 وتزداد بمقدار 1 ويتم طباعتها في الطرفية لذا ستكون النتيجة "111…" هذا المثال واضح نوعًا ما، ولكن قد يكون أكثر صعوبة عندما تتعقد الأمور. لا نريد أن يُعاد تعريف x لذا كيف يمكننا تحقيق ذلك وأن نحصل على النتيجة "1234…"؟ الجواب هو عن طريق استخدام المتغيرات العامة global variables. لذا نعرف المتغير خارج كتلة الرسم كي لا يُعاد تعريفه في كل تكرار، أيضًا سيكون نطاق المتغير قابل للوصول داخل مشروعك. لاحظ الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8728_23" style=""><span class="kwd">int</span><span class="pln"> x </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">void</span><span class="pln"> setup</span><span class="pun">(){</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> draw</span><span class="pun">(){</span><span class="pln">
  x </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">
  print</span><span class="pun">(</span><span class="pln">x</span><span class="pun">+</span><span class="str">" "</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ربما تتساءل كيف يمكن أن يعمل متغير معرف خارج النطاق؟ ولماذا لم نستخدم كتلة <code>setup()‎</code> طالما تُنفَّذ مرة في البداية؟ إن الجواب متعلق بالبرمجة كائنية التوجه والنطاق scope. تذكر كيف شرحنا أن شيفرة بروسيسنج processing تتحول إلى جافا وكيف تُغلف بصنف جافا؟ إن المتغيرات المكتوبة خارج كتل <code>setup()‎</code> و draw()<code>‎</code> تُغلَّف أيضًا. واستخدام x+=1 هو نفس استخدام <code>this.x+=1</code>، ويؤدي الغرض ذاته في حالتنا، لا يُعرَّف متغير باسم x في نطاق draw()<code>‎</code> ويتم البحث في النطاق الخارجي الذي هو نطاق <code>this</code>. وسبب عدم تعريفنا المتغير x في قسم setup()<code>‎</code> هو: إذا فعلنا بذلك سيُعرف نطاق x في نطاق دالة setup ولن تستطيع كتلة <code>draw()‎</code> الوصول إليه.
</p>

<h2 id="-2">
	الخلاصة
</h2>

<p>
	تعرّفنا في الجزء الأول من هذه السلسلة على لغة بروسيسنج وميزاتها، ومن ثم بيئة التطوير وهيكل البرنامج فيها. سنتعرّف في الجزء القادم من السلسلة على التوابع المُستخدمة في الرسم وعلى معاملاتها المختلفة.
</p>

<p>
	ترجمة -بتصرف- لمقال <a href="https://www.toptal.com/game/ultimate-guide-to-processing-the-fundamentals" rel="external nofollow">Guide to the Processing Language Part I: Fundamentals</a> لكاتبه Oguz Gelal.
</p>

<h2 id="-3">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/game-development/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D9%84%D9%84%D8%BA%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-processing-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D9%84%D8%B1%D8%B3%D9%85-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%A7%D8%B9%D9%84-%D9%85%D8%B9-%D8%AF%D8%AE%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-r2432/" rel="">دليلك للغة برمجة Processing | الجزء الثاني: الرسم والتفاعل مع دخل المستخدم</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%87%D9%85%D9%8A%D8%A9-%D8%B5%D9%86%D8%A7%D8%B9%D8%A9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A%D8%A9-r2228/" rel="">تعرف على أهمية صناعة الألعاب الإلكترونية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D9%83%D9%8A%D9%81-%D8%AA%D8%B5%D8%A8%D8%AD-%D9%85%D8%A8%D8%B1%D9%85%D8%AC-%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D9%81%D9%8A%D8%AF%D9%8A%D9%88-%D9%86%D8%A7%D8%AC%D8%AD-r2284/" rel="">كيف تصبح مبرمج ألعاب فيديو ناجح</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-r2304/" rel="">دليلك الشامل إلى برمجة الألعاب</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%83%D8%A7%D9%85%D9%84%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-r1150/" rel="">بناء تطبيقات كاملة باستعمال مكتبة جافا إف إكس JavaFX</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%B9%D8%A8%D8%A9-%D9%88%D8%B1%D9%82-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1111/" rel="">بناء لعبة ورق في جافا</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2422</guid><pubDate>Sun, 06 Oct 2024 15:00:00 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x627;&#x644;&#x625;&#x634;&#x627;&#x631;&#x627;&#x62A; Signals &#x641;&#x64A; &#x62C;&#x648;&#x62F;&#x648; Godot</title><link>https://academy.hsoub.com/programming/game-development/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%A5%D8%B4%D8%A7%D8%B1%D8%A7%D8%AA-signals-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2385/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_08/--Signals---Godot.png.3361898f1fed32463aad46e222545519.png" /></p>
<p>
	سنتحدث في هذا الدرس عن الإشارات Signals في محرك الألعاب جودو، التي هي عبارة عن رسائل تصدرها العقد عندما يحصل داخلها شيء أو حدث معين، مثل حدث الضغط على أحد الأزرار عندها يطلق هذا الزر إشارة، ويمكن أن تتصل عقد أخرى مع هذه الإشارة وتستدعي دالة كي تنفذ عند حصول الحدث. تعد الإشارات طريقة للتفويض delegation مضمّنة في محرك الألعاب جودو فهي تسمح لكائن ما بالتفاعل لتغيير كائن آخر دون الحاجة إلى أن تكون الكائنات متصلة ببعضها البعض مباشرة، مما يقلل من الترابط بين الكائنات ويبقى الكود مرنًا.
</p>

<p>
	مثلًا قد يكون لديك شريط حياة health bar يمثل صحة اللاعب، تريد أن يتغير شريط الحياة عندما يتضرر اللاعب أو يستخدم عقار لزيادة الصحة، يجب استخدام الإشارات للقيام بذلك.
</p>

<p>
	ملاحظة: الإشارات في جودو هي تطبيق لنمط المراقب <a href="https://wiki.hsoub.com/Design_Patterns/observer" rel="external">Observer Pattern</a> في البرمجة وهو نمط تصميم يُستخدم لتمكين كائن (المراقب) من الاستجابة لتغييرات تحدث في كائن آخر دون أن يكون هناك ارتباط مباشر بينهما لتقليل الترابط بين الأجزاء المختلفة من البرنامج وتسهيل إدارة التغييرات والتحديثات بشكل أكثر مرونة.، يمكنك التعلم المزيد عنه <a href="https://gameprogrammingpatterns.com/observer.html" rel="external nofollow">هنا</a> وفي الفقرات التالية سنستخدم الإشارات لجعل أيقونة جودو التي حركناها في <a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B9-%D9%84%D9%85%D8%AF%D8%AE%D9%84%D8%A7%D8%AA-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2384/" rel="">الدرس السابق</a> تتحرك وتتوقف عن طريق ضغط الأزرار.
</p>

<h2 id="">
	إعداد المشهد
</h2>

<p>
	لإضافة زر للعبة يجب إنشاء مشهد أساسي جديد يضم كلًّا من الزر والمشهد <code>sprite_2d.tscn</code> الذي أنشأناه في <a href="https://academy.hsoub.com/programming/game-development/%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D8%AC%D9%83-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D9%88%D8%AF%D9%88-godot-r2382/" rel="">درس كتابة أول كود برمجي خاص بك في جودو</a>.
</p>

<p>
	أنشئ مشهد جديد في جودو عن طريق الذهاب إلى قائمة "مشهد" Scene ومن ثم اختر "مشهد جديد" New Scene
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="155465" href="https://academy.hsoub.com/uploads/monthly_2024_08/1.png.bf644364aea73d38e2705ed24b487e75.png" rel=""><img alt="1" class="ipsImage ipsImage_thumbnailed" data-fileid="155465" data-unique="jdmstuogs" src="https://academy.hsoub.com/uploads/monthly_2024_08/1.png.bf644364aea73d38e2705ed24b487e75.png"> </a>
</p>

<p>
	في قائمة "المشهد" Scene انقر زر 2D الذي سيضيف عقدة Node2D كجذر للمشهد.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="155466" href="https://academy.hsoub.com/uploads/monthly_2024_08/2.png.dcee894f1b3f9d652dda15c0f25f7888.png" rel=""><img alt="2" class="ipsImage ipsImage_thumbnailed" data-fileid="155466" data-unique="204ftr1eh" src="https://academy.hsoub.com/uploads/monthly_2024_08/2.png.dcee894f1b3f9d652dda15c0f25f7888.png"> </a>
</p>

<p>
	انقر واسحب ملف <code>sprite_2d.tscn</code> المحفوظ سابقًا في Node2D في قائمة FileSystem لاستنساخه.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="155467" href="https://academy.hsoub.com/uploads/monthly_2024_08/3.png.ea5a789505f1fb1c98d4e5853c25ca30.png" rel=""><img alt="3" class="ipsImage ipsImage_thumbnailed" data-fileid="155467" data-unique="070dlh5fg" src="https://academy.hsoub.com/uploads/monthly_2024_08/3.png.ea5a789505f1fb1c98d4e5853c25ca30.png"> </a>
</p>

<p>
	انقر بالزر الأيمن للفأرة على Node2D واختر إضافة عقدة لإضافة عقدة أخرى Sprite2D كشقيق أي في نفس المستوى العقدة المحددة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="155468" href="https://academy.hsoub.com/uploads/monthly_2024_08/4.png.a80027ddc727403a039db533e87cdfae.png" rel=""><img alt="4" class="ipsImage ipsImage_thumbnailed" data-fileid="155468" data-unique="y3s2z5j54" src="https://academy.hsoub.com/uploads/monthly_2024_08/4.png.a80027ddc727403a039db533e87cdfae.png"> </a>
</p>

<p>
	ابحث عن عقدة الزر Button وأضفها كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="155469" href="https://academy.hsoub.com/uploads/monthly_2024_08/5.png.ca8ecaeccc5620197b19d39fd681c905.png" rel=""><img alt="5" class="ipsImage ipsImage_thumbnailed" data-fileid="155469" data-unique="6qnhfshoj" src="https://academy.hsoub.com/uploads/monthly_2024_08/5.png.ca8ecaeccc5620197b19d39fd681c905.png"> </a>
</p>

<p>
	تكون هذه العقدة صغيرة افتراضيًا، انقر واسحب المقبض من الزاوية اليمينية السفلى للزر في مجال العرض لتغيير حجمها
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="155470" href="https://academy.hsoub.com/uploads/monthly_2024_08/6.png.6a544da4773f90ab536dcc9266071197.png" rel=""><img alt="6" class="ipsImage ipsImage_thumbnailed" data-fileid="155470" data-unique="rapy95dam" src="https://academy.hsoub.com/uploads/monthly_2024_08/6.png.6a544da4773f90ab536dcc9266071197.png"> </a>
</p>

<p>
	إذا لم تظهر لك المقابض تأكد من أن أداة الاختيار مفعلة في شريط الأدوات.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="155471" href="https://academy.hsoub.com/uploads/monthly_2024_08/7.png.aef850bf353676b9b12dd47ccdd76980.png" rel=""><img alt="7" class="ipsImage ipsImage_thumbnailed" data-fileid="155471" data-unique="bbfs7l9zl" src="https://academy.hsoub.com/uploads/monthly_2024_08/7.png.aef850bf353676b9b12dd47ccdd76980.png"> </a>
</p>

<p>
	انقر على الزر واسحبه لتقريبه أكثر من الأيقونة. يمكنك أيضًا كتابة عنوان للزر عن طريق تعديل خاصية النص الخاصة به في الفاحص Inspector، وادخل على <code>Toggle motion</code>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="155472" href="https://academy.hsoub.com/uploads/monthly_2024_08/8.png.954069a106622830fc1e82e7a3aab879.png" rel=""><img alt="8" class="ipsImage ipsImage_thumbnailed" data-fileid="155472" data-unique="ihj7hc9uk" src="https://academy.hsoub.com/uploads/monthly_2024_08/8.png.954069a106622830fc1e82e7a3aab879.png"> </a>
</p>

<p>
	يجب أن تكون شجرة المشهد وإطار الرؤية على النحو التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="155473" href="https://academy.hsoub.com/uploads/monthly_2024_08/9.png.83db48383d4c7ded711269f8a8bdc78b.png" rel=""><img alt="9" class="ipsImage ipsImage_thumbnailed" data-fileid="155473" data-unique="yfj8vp0du" src="https://academy.hsoub.com/uploads/monthly_2024_08/9.thumb.png.40cf01e2e3077b0f1832a13dd3339fea.png"> </a>
</p>

<p>
	احفظ المشهد باسم <code>node_2d.tscn</code> إن لم تقم بذلك حتى الآن، يمكنك تشغيله عن طريق <code>F6</code> (أو <code>Cmd+R</code> على macOS)، إن الزر سيكون واضح، ولكن لن يحصل شيء إذا ضغطته.
</p>

<h2 id="-1">
	ربط الإشارة بالمحرر
</h2>

<p>
	نريد ربط إشارة ضغط الزر مع Sprite2D الخاص بنا، ونريد استدعاء دالة جديدة تفعّل أو تبطل حركته، إذ نحن بحاجة شيفرة برمجية مرتبط مع عقدة Sprite2D الذي قمنا به في <a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B9-%D9%84%D9%85%D8%AF%D8%AE%D9%84%D8%A7%D8%AA-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2384/" rel="">الدرس السابق</a>.
</p>

<p>
	يمكن ربط الإشارات في قائمة العقدة عن طريق اختيار زر العقدة على يمين المحرر والنقر على التبويبة المسماة "عقدة Node" بجانب "الفاحص Inspector".
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="155474" href="https://academy.hsoub.com/uploads/monthly_2024_08/10.png.800698bfc800e230e4736d5de2d3d7b5.png" rel=""><img alt="10" class="ipsImage ipsImage_thumbnailed" data-fileid="155474" data-unique="7ahxiob69" src="https://academy.hsoub.com/uploads/monthly_2024_08/10.png.800698bfc800e230e4736d5de2d3d7b5.png"> </a>
</p>

<p>
	تظهر القائمة عدد من الإشارات المتوافرة للعقدة المختارة
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="155475" href="https://academy.hsoub.com/uploads/monthly_2024_08/11.png.6229369172bf3617176e5bf1d6f13edb.png" rel=""><img alt="11" class="ipsImage ipsImage_thumbnailed" data-fileid="155475" data-unique="ku1z0nwc1" src="https://academy.hsoub.com/uploads/monthly_2024_08/11.png.6229369172bf3617176e5bf1d6f13edb.png"> </a>
</p>

<p>
	انقر نقرة مزدوجة على إشارة "مضغوط pressed" لفتح نافذة ارتباط العقدة
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="155476" href="https://academy.hsoub.com/uploads/monthly_2024_08/12.png.48283bc706bd98333083de8ec5864e26.png" rel=""><img alt="12" class="ipsImage ipsImage_thumbnailed" data-fileid="155476" data-unique="0xm3i6e7b" src="https://academy.hsoub.com/uploads/monthly_2024_08/12.png.48283bc706bd98333083de8ec5864e26.png"> </a>
</p>

<p>
	يمكنك هنا ربط الإشارة مع عقدة Sprite2D التي تحتاج إلى تابع مستقبل أي إلى دالة تستدعيها جودو عندما يصدر الزر الإشارة، وسينشئ المحرر هذه الدالة لك تلقائيًا، تسمى هذه العقد اصطلاحًا "‎‏‏‎<em>on</em>node<em>name</em>signal<em>name" وقد أسميناها هنا "‎</em>‎on<em>button</em>pressed".
</p>

<p>
	ملاحظة: يمكننا استخدام نمطين عند ربط الإشارات باستخدام قائمة محرر العقدة، يسمح لك النمط الأول بربط العقد التي لديها أكواد برمجية مرتبطة معها ويُنشئ تلقائيًا دالة رد نداء عليهم كي تُنفذ عندما يتم إرسال الإشارة وبالتالي يمكنك تحديد ما يجب فعله عند استلام الإشارة من خلال كتابة الأكواد داخل دالة رد النداء هذه.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="155477" href="https://academy.hsoub.com/uploads/monthly_2024_08/13.png.10705faecc26b5d584302065fb0a60c9.png" rel=""><img alt="13" class="ipsImage ipsImage_thumbnailed" data-fileid="155477" data-unique="65x9x57rr" src="https://academy.hsoub.com/uploads/monthly_2024_08/13.png.10705faecc26b5d584302065fb0a60c9.png"> </a>
</p>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="155478" href="https://academy.hsoub.com/uploads/monthly_2024_08/14.png.48f1899e1625b4600224ec2337ed8c1a.png" rel=""><img alt="14" class="ipsImage ipsImage_thumbnailed" data-fileid="155478" data-unique="2cbkswvre" src="https://academy.hsoub.com/uploads/monthly_2024_08/14.png.48f1899e1625b4600224ec2337ed8c1a.png"> </a>
</p>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="155479" href="https://academy.hsoub.com/uploads/monthly_2024_08/15.png.3b297a72d192fdf122948fd7258d77e8.png" rel=""><img alt="15" class="ipsImage ipsImage_thumbnailed" data-fileid="155479" data-unique="8z9tajvpj" src="https://academy.hsoub.com/uploads/monthly_2024_08/15.png.3b297a72d192fdf122948fd7258d77e8.png"> </a>
</p>

<p>
	لنستبدل السطر الذي يتضمن الكلمة المفتاحية <code>pass</code> في الشيفرة بشيفرة فعلية تنفذ حركة العقدة. ستتحرك Sprite2D بفضل الشيفرة في دالة <code>‎_process()‎</code>، إذ تقدم جودو تابعًا مضمنًا لتفعيل أو تعطيل المعالجة ويمكنك تفعيل أو تعطيل المعالجة المستمرة للدالة ‎<code>_‎process()</code>‎‎‎‎ على عقدة معينة فإذا مررت <code>true</code> للتابع <code>Node.set_process()‎</code> سينفذ ‎<code>_‎process()</code>‎‎‎‎ في كل إطار مما يسمح لك بتحديث الموقع أو الحالة بمرور الوقت؛ وإذا مررت <code>false</code> فلن تنفذ وستتوقف التحديثات المستمرة للعقدة. يفيد تابع آخر لصنف العقدة <code>is_processing‎()‎</code> في التحقق مما إذا كانت المعالجة المستمرة المنفذة في الدالة ‎<code>_‎process()</code>‎‎‎‎ مفعلة أم لا حيث تعيد القيمة <code>true</code> إذا كانت المعالجة الخاصة بالعقدة مفعلة والدالة ‎<code>_‎‎process()</code>‎‎‎‎ تنفذ في كل إطار، وتعيد <code>false</code> إذا كانت معالجة العقدة غير نشطة، ويمكننا استخدام الكلمة المفتاحية <code>not</code> لعكس القيمة. بلغة GDScript:
</p>

<pre class="ipsCode">func _on_button_pressed():
    set_process(not is_processing())
</pre>

<p>
	بلغة C#‎:
</p>

<pre class="ipsCode">private void OnButtonPressed()
{
    SetProcess(!IsProcessing());
}
</pre>

<p>
	تفعل هذه السيفرة دالة المعالجة والتي بدورها تفعل أو تعطل حركة الأيقونة عند ضغط الزر. نحتاج قبل تجربة اللعبة لتبسيط دالة <code>‎_Process()‎</code> لنقل العقدة تلقائيًا والانتظار لمدخلات المستخدم، استبدلها بالشيفرة التالية التي شاهدناها منذ <a href="https://academy.hsoub.com/programming/game-development/%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D8%AC%D9%83-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D9%88%D8%AF%D9%88-godot-r2382/" rel="">درسين سابقين</a>.
</p>

<p>
	بلغة GDScript:
</p>

<pre class="ipsCode">func _process(delta):
    rotation += angular_speed * delta
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta
</pre>

<p>
	بلغة C#‎:
</p>

<pre class="ipsCode">public override void _Process(double delta)
{
    Rotation += _angularSpeed * (float)delta;
    var velocity = Vector2.Up.Rotated(Rotation) * _speed;
    Position += velocity * (float)delta;
}
</pre>

<p>
	يجب أن تكون شيفرة <code>sprite_2d.gd</code> كالتالي:
</p>

<p>
	بلغة GDScript:
</p>

<pre class="ipsCode">extends Sprite2D

var speed = 400
var angular_speed = PI


func _process(delta):
    rotation += angular_speed * delta
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta


func _on_button_pressed():
    set_process(not is_processing())
</pre>

<p>
	بلغة C#‎:
</p>

<pre class="ipsCode">using Godot;

public partial class MySprite2D : Sprite2D
{
    private float _speed = 400;
    private float _angularSpeed = Mathf.Pi;

    public override void _Process(double delta)
    {
        Rotation += _angularSpeed * (float)delta;
        var velocity = Vector2.Up.Rotated(Rotation) * _speed;
        Position += velocity * (float)delta;
    }

    private void OnButtonPressed()
    {
        SetProcess(!IsProcessing());
    }
}
</pre>

<p>
	شغل المشهد الآن وانقر على الزر لترى كيفية تحرك وتوقف الأيقونة مع النقر.
</p>

<h2 id="-2">
	ربط الإشارة باستخدام الشيفرة
</h2>

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

<p>
	لنستخدم عقدة مختلفة، إن لجودو عقدة مؤقت زمني <a href="https://docs.godotengine.org/en/stable/classes/class_timer.html#class-timer" rel="external nofollow">Timer</a> تفيد في تحديد أوقات المهارات أو أوقات إعادة تلقيم الأسلحة النارية في الألعاب.
</p>

<p>
	عد إلى مساحة العمل عن طريق النقر على "2D" أعلى الشاشة أو الضغط على <code>Ctrl+F1</code> (أو <code>Ctrl+Cmd+1</code>على macOS)
</p>

<p>
	انقر بالزر الأيمن على عقدة Sprite2D في قائمة "المشهد" Scene وأضف عقدة ابن جديدة، وابحث عن المؤقت Timer وأضف العقدة المرافقة، يجب أن يكون المشهد كالتالي
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="155480" href="https://academy.hsoub.com/uploads/monthly_2024_08/16.png.45a678bb0d3ea66a61ed708f8ef4b582.png" rel=""><img alt="16" class="ipsImage ipsImage_thumbnailed" data-fileid="155480" data-unique="ybfryzi2w" src="https://academy.hsoub.com/uploads/monthly_2024_08/16.png.45a678bb0d3ea66a61ed708f8ef4b582.png"> </a>
</p>

<p>
	اذهب إلى "الفاحص" Inspector بعد اختيار عقدة المؤقت لتفعيل خاصية البدء التلقائي.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="155481" href="https://academy.hsoub.com/uploads/monthly_2024_08/17.png.b2ca921b134f551667f5aaab44afda7e.png" rel=""><img alt="17" class="ipsImage ipsImage_thumbnailed" data-fileid="155481" data-unique="znlyf7c4h" src="https://academy.hsoub.com/uploads/monthly_2024_08/17.png.b2ca921b134f551667f5aaab44afda7e.png"> </a>
</p>

<p>
	انقر على أيقونة السكريبت بجانب Sprite2D للانتقال إلى مكان عمل البرنامج النصي
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="155482" href="https://academy.hsoub.com/uploads/monthly_2024_08/18.png.d1f05eb94e0a700b88639113d04cedc3.png" rel=""><img alt="18" class="ipsImage ipsImage_thumbnailed" data-fileid="155482" data-unique="ijlns1pfy" src="https://academy.hsoub.com/uploads/monthly_2024_08/18.png.d1f05eb94e0a700b88639113d04cedc3.png"> </a>
</p>

<p>
	سنحتاج إلى إجراء عمليتين لربط العقد عن طريق الشيفرة البرمجية:
</p>

<ul>
	<li>
		الحصول على مرجع من المؤقت إلى Sprite2D
	</li>
	<li>
		استدعاء التابع <code>connect()‎</code> على إشارة المؤقت "timeout"
	</li>
</ul>

<p>
	<strong>ملاحظة</strong>: تحتاج استدعاء تابع <code>connect()‎</code> للإشارة التي تريد أن تستمع إليها لربط الإشارة باستخدام الشيفرة، وفي حالتنا نريد الاستماع لإشارة "timeout" الخاصة بالمؤقت.
</p>

<p>
	نريد ربط الإشارة عند استنساخ المشهد، ويمكنن القيام بذلك باستخدام الدالة المضمّنة <code>Node._ready()‎</code> التي يتم استدعائها تلقائيًا عن طريق المحرك عندما يتم استنساخ المشهد بالكامل.
</p>

<p>
	نستخدم الدالة <code>Node.get_node()‎</code> للحصول على مرجع بالنسبة للعقدة الحالية، ويمكننا تخزين المرجع في متغير.
</p>

<p>
	بلغة GDScript:
</p>

<pre class="ipsCode">func _ready():
    var timer = get_node("Timer")
</pre>

<p>
	بلغة C#‎:
</p>

<pre class="ipsCode">public override void _Ready()
{
    var timer = GetNode&lt;Timer&gt;("Timer");
}
</pre>

<p>
	تنظر الدالة <code>get_node()‎</code> إلى أبناء Sprite2D وتحصل على العقد بأسمائها، مثلًا إذا أعدت تسمية عقدة المؤقت إلى "BlinkingTimer" في المحرر، فيجب عليك تغيير الاستدعاء إلى <code>get_node("BlinkingTimer")‎</code>
</p>

<p>
	يمكننا الآن ربط المؤقت إلى Sprite2D في دالة <code>‎_ready()‎</code>
</p>

<p>
	بلغة GDScript:
</p>

<pre class="ipsCode">func _ready():
    var timer = get_node("Timer")
    timer.timeout.connect(_on_timer_timeout)
</pre>

<p>
	بلغة C#‎:
</p>

<pre class="ipsCode">public override void _Ready()
{
    var timer = GetNode&lt;Timer&gt;("Timer");
    timer.Timeout += OnTimerTimeout;
}
</pre>

<p>
	يُقرأ السطر كالتالي: ربطنا إشارة المؤقت "timeout" للعقدة التي يرتبط بها السكربت، وعندما يصدر المؤقت <code>timeout</code> نريد استدعاء الدالة <code>‎_on_timer_timeout()‎</code> التي نحتاج لتعريفها، لنضف ذلك إلى أسفل الشيفرة البرمجية ونستخدمه لتفعيل شفافية الأيقونة.
</p>

<p>
	<strong>ملاحظة</strong>: تسمى دالة رد النداء callback اصطلاحًا باسم GDScript "OnNodeNameSignalName"‎ وستكون هنا باسم "_on_timer_timeout" في كود GDScript وباسم "()OnTimerTimeout" في كود #C.
</p>

<p>
	بلغة GDScript:
</p>

<pre class="ipsCode">func _on_timer_timeout():
    visible = not visible
</pre>

<p>
	بلغة C#‎:
</p>

<pre class="ipsCode">private void OnTimerTimeout()
{
    Visible = !Visible;
}
</pre>

<p>
	إن الخاصية <code>visible</code> بوليانية وتتحكم بشفافية العقدة، يتفعل السطر <code>visible = not visible</code> وإذا كانت القيمة <code>visible</code> هي <code>true</code> تصبح <code>false</code> والعكس صحيح سترى الأيقونة الآن تظهر وتختفي كل ثانية إذا شغلت المشهد
</p>

<h2 id="-3">
	البرنامج النصي الكامل
</h2>

<p>
	لقد انتهينا من أيقونة جودو المتحركة التي تومض والملف التالي هو ملف <code>sprite_2d.gd</code> كاملًا كمرجع. بلغة GDScript:
</p>

<pre class="ipsCode">extends Sprite2D

var speed = 400
var angular_speed = PI


func _ready():
    var timer = get_node("Timer")
    timer.timeout.connect(_on_timer_timeout)


func _process(delta):
    rotation += angular_speed * delta
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta


func _on_button_pressed():
    set_process(not is_processing())


func _on_timer_timeout():
    visible = not visible
</pre>

<p>
	بلغة C#‎:
</p>

<pre class="ipsCode">using Godot;

public partial class MySprite2D : Sprite2D
{
    private float _speed = 400;
    private float _angularSpeed = Mathf.Pi;

    public override void _Ready()
    {
        var timer = GetNode&lt;Timer&gt;("Timer");
        timer.Timeout += OnTimerTimeout;
    }

    public override void _Process(double delta)
    {
        Rotation += _angularSpeed * (float)delta;
        var velocity = Vector2.Up.Rotated(Rotation) * _speed;
        Position += velocity * (float)delta;
    }

    private void OnButtonPressed()
    {
        SetProcess(!IsProcessing());
    }

    private void OnTimerTimeout()
    {
        Visible = !Visible;
    }
}
</pre>

<h2 id="-4">
	الإشارات المخصصة
</h2>

<p>
	هذا القسم هو مرجع لكيفية تعريف واستخدام الإشارات الخاصة بك، ولا يبني على المشروع المُنشئ في الدروس السابقة
</p>

<p>
	يمكنك تعريف إشارات مخصصة في برنامج نصي. مثلًا تريد أن تظهر شاشة "انتهت اللعبة" عندما تصل حياة اللاعب للصفر، يمكنك تعريف إشارة اسمها "died" أو "health_depleted" للقيام بذلك عندما تصل حياتهم للصفر.
</p>

<p>
	إليك الشيفرة بلغة GDScript:
</p>

<pre class="ipsCode">extends Node2D

signal health_depleted

var health = 10
</pre>

<p>
	وبلغة C#‎:
</p>

<pre class="ipsCode">using Godot;

public partial class MyNode2D : Node2D
{
    [Signal]
    public delegate void HealthDepletedEventHandler();

    private int _health = 10;
}
</pre>

<p>
	<strong>ملاحظة</strong>: تمثل الإشارات أحداثًا حصلت للتو، ويمكننا استخدام أفعال بالزمن الماضي في تسميتها.
</p>

<p>
	تعمل إشاراتك الخاصة مثل تلك المضمّنة في محرك جودو أي تظهر في تبويبة العقد ويمكنك ربطها بنفس الطريقة
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="155483" href="https://academy.hsoub.com/uploads/monthly_2024_08/19.png.c6404685ddb545dc4c25f71be321188c.png" rel=""><img alt="19" class="ipsImage ipsImage_thumbnailed" data-fileid="155483" data-unique="tv38s54m0" src="https://academy.hsoub.com/uploads/monthly_2024_08/19.png.c6404685ddb545dc4c25f71be321188c.png"> </a>
</p>

<p>
	لِبَثّ إشارة في برنامج النصي تحتاج لاستدعاء<code>emit()‎</code> على الإشارة
</p>

<p>
	بلغة GDScript:
</p>

<pre class="ipsCode">func take_damage(amount):
    health -= amount
    if health &lt;= 0:
        health_depleted.emit()
</pre>

<p>
	بلغة C#‎:
</p>

<pre class="ipsCode">public void TakeDamage(int amount)
{
    _health -= amount;

    if (_health &lt;= 0)
    {
        EmitSignal(SignalName.HealthDepleted);
    }
}
</pre>

<p>
	ويمكن أن تصرح الإشارة على وسيط واحد أو أكثر من خلال تحديد أسماء الوسطاء ما بين قوسين.
</p>

<p>
	بلغة GDScript:
</p>

<pre class="ipsCode">extends Node

signal health_changed(old_value, new_value)

var health = 10
</pre>

<p>
	بلغة C#‎:
</p>

<pre class="ipsCode">using Godot;

public partial class MyNode : Node
{
    [Signal]
    public delegate void HealthChangedEventHandler(int oldValue, int newValue);

    private int _health = 10;
}
</pre>

<p>
	<strong>ملاحظة</strong>: تظهر وسائط الإشارة في قائمة محرر العقد، وتستخدمها جودو لإنشاء رد نداء callback خاص بك، ولكن يمكنك إرسال أي عدد من الوسائط عند بث الإشارة، لذا يجب عليك بث القيم الصحيحة.
</p>

<p>
	لبث القيم باستخدام الإشارة أضف بعض الوسائط الإضافية إلى الدالة <code>emit()‎</code>
</p>

<p>
	بلغة GDScript:
</p>

<pre class="ipsCode">func take_damage(amount):
    var old_health = health
    health -= amount
    health_changed.emit(old_health, health)
</pre>

<p>
	بلغة C#‎:
</p>

<pre class="ipsCode">public void TakeDamage(int amount)
{
    int oldHealth = _health;
    _health -= amount;
    EmitSignal(SignalName.HealthChanged, oldHealth, _health);
}
</pre>

<h2 id="-5">
	الخلاصة
</h2>

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

<p>
	على سبيل المثال إذا كان هناك عنصر في اللعبة (مثل قطعة نقدية) يمكن للاعب التقاطها أو جمعها ممثلة بعقدة Area2D فإنها ستصدر أو تَبُثّ إشارة <code>body_entered</code> عندما يدخل جسم اللاعب شكل الاصطدام الخاص بها مما يسمح لك بمعرفة إذا ما تم التقطها من قبل اللاعب أم لا.
</p>

<p>
	سنتعلم في القسم التالي كيفية إنشاء <a href="https://academy.hsoub.com/programming/game-development/%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-%D8%A7%D9%84%D8%A8%D8%B9%D8%AF-%D8%B9%D8%A8%D8%B1-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-godot-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%AA%D8%AC%D9%87%D9%8A%D8%B2-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D9%88%D8%B6%D8%A8%D8%B7-%D8%A7%D9%84%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF%D8%A7%D8%AA-r2280/" rel="">لعبة ثنائية الأبعاد كاملة في محرك ألعاب جودو</a>، ونطبق كل ما تعلمناه من مفاهيم على أرض الواقع.
</p>

<p>
	ترجمة - وبتصرف - لقسم <a href="https://docs.godotengine.org/en/stable/getting_started/step_by_step/signals.html" rel="external nofollow">Using signals</a> من توثيق جودو الرسمي.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B9-%D9%84%D9%85%D8%AF%D8%AE%D9%84%D8%A7%D8%AA-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2384/" rel="">الاستماع لمدخلات اللاعب في جودو Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%A7%D9%84%D9%81%D9%8A%D8%AF%D9%8A%D9%88-%D8%AA%D8%B7%D9%88%D8%B1%D9%87%D8%A7-%D9%88%D8%A3%D9%87%D9%85%D9%8A%D8%AA%D9%87%D8%A7-%D9%88%D8%AE%D8%B7%D9%88%D8%A7%D8%AA-%D8%A8%D8%B1%D9%85%D8%AC%D8%AA%D9%87%D8%A7-r2290/" rel="">ألعاب الفيديو: تطورها وأهميتها وخطوات برمجتها</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D9%85%D8%AA%D8%A7%D8%AD%D8%A9-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2371/" rel="">لغات البرمجة المتاحة في جودو Godot</a>
	</li>
	<li>
		<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 Engines</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2385</guid><pubDate>Sat, 17 Aug 2024 15:02:23 +0000</pubDate></item></channel></rss>
