You are on page 1of 54

‫بسم هللا الرحمن الرحيم‪.

‬‬

‫} اللهم إفتح لي أبواب حكمتك و أنشر علّي من رحمتك وأمنن علّي بالحفظ والفهم ‪ُ ،‬‬
‫سبحانك ال ِعلم لنا إال ما علمتنا إنك أنت العليم الحكيم{‬

‫السالم عليكم ورحمه هللا وبركاته‪.‬‬


‫االول‪ ،‬الطلائرات‪ ،‬السليارات‪ ،‬القلوار وغيرهلا)‬ ‫في الفترة االخيرة قررت البدء بعمل عدة مشاريع احاول فيها محاكاة حركة الكائنات مثلل ممنولور الشل‬
‫فقررت أنشاء هذا الكتا ألشارك معكم تجاربي التي توصلت اليها نتيجة تلك العمليات التي كلفتنا الوقت والجهد للتوصل لنتائج مرضية فيها‪.‬‬

‫هلو‬ ‫ان هذا الكتا موجهه لألش ا الذين يمتلكون برة فوق المبتدأ نورا ً للميكانيكيات التي سنست دمها ولن نتطرق الى كل شيء بسيط وتفصيله فلالرر‬
‫توصيل المعلومة ومن ثم تقوم بتطويرها‪.‬‬

‫كت سابقة من تأليفي‪:‬‬

‫‪https://drive.google.com/file/d/0B- :Unity Scripting Part 1 ‬‬


‫‪W_VzeA116VRDFGY2JlUjhhcjQ/view?usp=drive_web‬‬

‫‪https://drive.google.com/file/d/0B-W_VzeA116Vc0dpZWg5VUFuaEk/view :Unity Scripting Part 2 ‬‬

‫‪ ‬تصميم وبرمجة لعبة ‪:Cube Bird‬‬


‫‪http://www.mediafire.com/download/95g4l6br78vqxud/UnityScripting+%28Cube+Bird%29.pdf‬‬

‫‪ ‬ورشة عمل لعبة ‪https://drive.google.com/file/d/0B- :Cannon of Lights2D‬‬


‫‪W_VzeA116VTXA3NFVqNE1CaG8/view‬‬
‫اخيرا ً مستندات اليونتي والتي توفر شروحات لجميع الدوال والمتغيرات والفئات‪:‬‬

‫‪/http://docs.unity3d.com/ScriptReference :Scripting AIP ‬‬

‫وال يقتصر االمر على هذه المراجع فقط‪ ،‬بل يجب عليك تعلم كل جديد والبحث عن اجوبة لألسئئلة التئي تطرحهئا‪ ،‬وال يعتمئد االمئر علئى البرمجئة فقئط بئل علئى‬
‫مكونات البرنامج ايضا ً فعليك فهم مكونات البرنامج والتعرف عليها وفهم كيفية التعامل معها و معرفة استخدامها في البرمجة و هذا االمر سيفيدك‪.‬‬

‫مالحظة‪ - :‬هذا الكتاب مجاني للجميع وال يحق ألي شخص نشرة ألغراض تجارية او ربحية‪ .‬المعرفة ليست ملك الحد و من حق الجميع الحصول عليها‬
‫بالمجان‪ ,‬كل ما اطلبه حق هذا الكتا هو الدعاء لي و لوالدي و لجميع المسلمين و اتمنى في النهاية اعجابكم و نقدكم و آرائكم و اتمنى لكم قراءة طيبة ‪. ‬‬

‫الصفحة ‪1‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫المحتويات‬
‫‪ ‬المؤلف‪3 ..................................................................................................................................................... - :‬‬

‫‪ ‬مقدمة‪4 ......................................................................................................................................................... :‬‬

‫‪ ‬ملفات الورشة‪5 ............................................................................................................................................... :‬‬

‫‪ ‬محاكاة حركة الكرة‪6 ......................................................................................................................................... :‬‬

‫حركة الكاميرا‪7 .......................................................................................................................................................:‬‬


‫‪ ‬محاكاة السيارة ثنائية االبعاد‪8 ............................................................................................................................. :‬‬

‫انشاء الملف ‪8 ........................................................................................................................... :Car2DMovement‬‬


‫حركة الكاميرا‪9 .......................................................................................................................................................:‬‬
‫‪ ‬محاكاة االنفجار ثنائي االبعاد (‪11 ............................................................................................... :)Explosion 2D‬‬

‫انشاء الملف ‪11........................................................................................................................ :Rigidbody2DExpl‬‬


‫الكود النهائي‪13 .................................................................................................................................................................................... :‬‬
‫‪ ‬محاكاة الكاميرا االستراتيجية ‪14 ................................................................................................... :Camera RTS‬‬

‫انشاء الملف ‪14.................................................................................................................................. :CameraRTS‬‬


‫الكود النهائي‪18 .................................................................................................................................................................................... :‬‬
‫‪ ‬محاكاة منظور الشخص االول ‪20 .................................................................................................................... :FP‬‬

‫انشاء الملف ‪20................................................................................................................................... :CameraFPS‬‬


‫الكود النهائي‪22 .................................................................................................................................................................................... :‬‬
‫‪ ‬محاكاة الطائرة النفاثة‪23 ................................................................................................................................... :‬‬

‫لمحة عن االحتكاكات‪23............................................................................................................................................ :‬‬


‫االحتكاك في ‪23............................................................................................................................................ :Unity‬‬
‫محاور الحركة‪23.................................................................................................................................................... :‬‬
‫انشاء الملف ‪24...................................................................................................................... :AirplaneController‬‬
‫انشاء الملف ‪28............................................................................................................................. :ParticaleEffect‬‬
‫انشاء الملف ‪30..............................................................................................................................:AudioAirplane‬‬
‫حركة الكاميرا‪32.....................................................................................................................................................:‬‬
‫‪ ‬محاكاة حركة السيارة ثالثية االبعاد‪34 ................................................................................................................... :‬‬

‫المكون ‪34................................................................................................................................... :Wheel Collider‬‬


‫انشاء الملف ‪35....................................................................................................................................:CarPhysics‬‬
‫انشاء الملف ‪37................................................................................................................................ :ControlDrive‬‬
‫انشاء الملف ‪38............................................................................................................................... :MeshsWheels‬‬
‫انشاء الملف ‪40............................................................................................................................ :FrictionWheels‬‬
‫برمجة حركة الكاميرا‪42........................................................................................................................................... :‬‬
‫‪ ‬محاكاة حركة القوارب ‪44 ..........................................................................................................................:Boat‬‬

‫الطفو (االجسام العائمة)‪44......................................................................................................................................... :‬‬


‫انشاء الملف ‪44............................................................................................................................. :FloatingObject‬‬
‫أنشاء الملف ‪48............................................................................................................................. :BoatController‬‬
‫انشاء الملف ‪50.....................................................................................................................:PhysicsInformation‬‬
‫‪ ‬خاتمة‪52 ....................................................................................................................................................... :‬‬

‫‪ ‬مصادر‪53 ...................................................................................................................................................... :‬‬

‫الصفحة ‪2‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫المؤلف‪- :‬‬

‫مؤلف الكتاب‪ :‬عماد عارف التوي‪.‬‬

‫البلد‪ :‬اليمن – عدن‪.‬‬

‫الهوايات‪ :‬مبرمج العا ‪ ،‬مصمم ‪ ،3D‬كات ‪.‬‬

‫المدونة ‪/http://www.emadarif.blogspot.com :‬‬

‫يوتيوب‪http://youtube.com/homeofgamesnews :‬‬

‫فيسبوك‪https://www.facebook.com/homeofgamesnews :‬‬

‫للتواصل على بريد قناتي‪homeofgamesnews@gmail.com :‬‬

‫نبذة عني‪ :‬عماد عارف التوي‪ ،‬العمر ‪ 18‬سنة‪ ،‬منذ عام ‪ 2012‬بدأت اول طواتي في عالم برمجة االلعا على محرك يونتي ومن يومها احاول التوصل‬
‫الى نتائج قوية واحتراف هذا الجان بشكل كبير عبر محاكاة عمل بع االلعا والميكانيكيات المعروفة وتطويرها الى االفضل‪ ،‬من جان آ ر اقوم بنشر‬
‫هذا الجان باللرة العربية ألثراء المحتوى العربي وتوسيعه بشكل كبير امالً في التوصل الى نتائج اقوى وأدق بأذن هللا‪.‬‬

‫الصفحة ‪3‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫مقدمة‪:‬‬
‫ان الجان البرمجي في يونتي مهم جدا ً فاغل الراغبين بالعمل او دراسة هذا المحرك تواجههم مشكلة البرمجة وفعالً هي جان صع وممتع في نفس الوقت‬
‫والتفكير في برمجة لعبة هو امر ممل والسب هو كمية األكواد التي يج عليك انهائها ش صيا ً فلهذا دعنا من هذا االمر ولنبدأ باإلجابة على بع االسئلة‬
‫ومحاولة انجاز المشاريع التي توقفنا عندها سابقا ً وهي محاكاة حركة الكائنات‪.‬‬

‫االول ‪ FPS‬مالذي ركزت علية بشكل كبير وهو كان الشيء‬ ‫عند بدأي في عمل بع المشاريع او باألصح محاولة محاكاة عدة اشياء ومنها منوور الش‬
‫لذي اجا على العديد من االسئلة في رأسي) قمت بإنجازه بالطريقة المعروفة ولكن سرعان ما عرفت ان الطريقة التي اتعامل بها هي الطريقة ال اطئة او‬
‫باألصح ليست الطريقة المريحة فعالً فبحس تجربتي لبع االلعا من نفس المنوور رأيت ان الحركة أجمل وتباذر في ذهني سؤال وهو كيفية محاكاة تلك‬
‫الحركة في اليونتي؟‬

‫االول واحتمال ان االغلبية مرت على الملف ‪ ،MouseLook‬ان هذا الملف جميل‬ ‫بدأت بالبحث وربما جميعنا بحث عن طريقة عمل منوور الش‬
‫وربما قد ترى ان مفهومة معقد في بع االشياء ولكن ما اريد توصيلة االن هو ان هذا الملف هو ما جعلنا اقوم بتوسيع بحثي بهذا الشكل الى ان وصلت الى‬
‫النتيجة المرجوة وعرفت ان عمل الملف ‪ MouseLook‬هو بسيط جدا ً ولكن هناك امر جميل فيه عند توفيره للـ ‪ enum‬ولكن لن اعتمد علية في كتابي و‬
‫سنناقش طريقة أ رى‪.‬‬

‫االول سرعان ما عرفت ان عملة هو امر سهل ولكن كنت أجهله سابقا ً ولكن لم اتوقف وبدأت‬ ‫بعد ان توصلت للنتيجة المطلوبة في محاكاة منوور الش‬
‫بتوسيع بحثي مجددا ً وتوصلت الى العديد من األمور وأتمنى ان أصل الى أكثر من ذلك‪.‬‬

‫الشيء الذي جذ بنا بشكل كبير هو كيف استطيع فهم الكاميرا فهي الجان المعقد في االمر فكما تالحو جميع المشاريع تعتمد على مدى تأثر الكاميرا فيها ففي‬
‫النهاية الكاميرا هي ما ستنقل جمال اللعبة الى الالع ‪ ،‬عند لع لعبة ش صيا ً انت الكاميرا و تالحو ان اكثر شيء تستطيع تحريكه هو الكاميرا نفسها و هذا‬
‫ما يدل على ان للكاميرا أهمية كبير في عالم األلعا وهي ليست كاميرا لتصوير المشهد فقط‪ ،‬انا ال اتحدث بصفتي اني سأصنع كاميرا ارقة ال بل سنقوم‬
‫بمحاكاة حركة الكاميرا في كل مشروع يطل ذلك وسنحاول جعلها احترافية قدر االمكان‪ ،‬في حاله تبسط لنا المفهوم االولي لتحريك الكاميرا وماهي‬
‫الميكانيكية الجيدة سنستطيع است دامها بأكثر من طريقة لتوفير الراحة التامة اثناء اللع ‪.‬‬

‫هناك العديد من االشياء التي لم تعرفها حق معرفتها في يونتي ويج عليك المبادرة وتوسيع بحثك والتوصل الى اي نتيجة استطعت التوصل اليها وبدورها‬
‫هي من ستنقلك الى الجان الممتع وستربي فيك الرغبة ومحاولة الوصول الى نتائج اقوى من سابقتها‪.‬‬

‫المعلومات بالشكل الصحيح وتعريفك بالطريقة الفضلى لمحاكاة حركة الكائنات وتجن المشاكل و معرفة‬ ‫في النهاية الرر من هذا الكتا هو توصيل بع‬
‫اين تست دم األكواد و بالشكل الصحيح‪.‬‬

‫الصفحة ‪4‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫ملفات الورشة‪:‬‬
‫هناك عدة اشياء يج ان تتوفر معك قبل البدء بتطبيق محتوى الكتا فمن الضروري ان تتوفر لديك نس ة محرك يونتي ‪ Unity 5.1.2f1‬وما فوق نورا ً‬
‫لوجود بع التريرات الطفيفة في النسخ المحدثة الى أنى املك هذه النس ة وقمت بتطبيق االمر عليها ولن تجد مشكلة في اسقاط ما تراه في النسخ الجديدة من‬
‫المحرك‪.‬‬

‫هذا الكتا موجه بشكل كبير للفئة المتوسطة في برمجة أللعا في محرك يونتي‪ ،‬اما من لم يملك برة في البرمجة فيفضل زيارة مستندات الموقع ومراجعة‬
‫الكت السابقة لتوسيع معرفته في هذا المجال‪.‬‬

‫قبل البدء بعمل اي شيء تأكد من أنك قمت بتنزيل الباكيج م‪ )Pro Project – Emad Arif‬ال ا بالكتا لتطبيق المحتوى البرمجي علية‪ ،‬هذا الباكيج‬
‫عبارة عن عدة مشاريع قمت بعملها وا ذها من مصادر مثل مشاريع يونتي الجاهزة مالموديالت) وقمت بالتعديل عليها واضافتها الى هذا الباكيج لتسهيل‬
‫عليك امر تطبيق الكود‪.‬‬

‫عند تحميل الباكيج ستالحو انه على شكل ملف مضروط ويج عليك است راجه الى ملف فرعي ومن ثم استيراده الى دا ل اليونتي‪ ،‬عند استيراده ستجد عدة‬
‫ملفات مثل االصوات‪ ،‬المشاهد‪ ،‬الملفات البرمجية والـ ‪ Prefabs‬وغيرها من االشياء ولكن االهم هو المشاهد وستجد ان في كل مشهد يوجد الكائن الذي‬
‫ستطبق علية الشرح باالضافة الى الكاميرا ال اصة به وفي كل فقرة نمر عليها سنناقش مسألة الكاميرا وكيف قمت بتركيبها‪.‬‬

‫ا يرا ً يفضل تحميل المشروع المبني م‪ )Build Pro Project – Emad Arif‬لكي يساعدك على مقارنة عملك فيه وتحديد المستوى الذي وصلت له‬
‫وماهي المشاكل التي تواجهك‪ ،‬عند فتح المشروع المبني ستوهر لك هذه القائمة وبكل بساطة تستطيع االنتقال الى اي مشروع كما هو موضح‪.‬‬

‫الصفحة ‪5‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


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

‫‪ ‬في الملف ‪ Projects‬ستجد ملف باسم ‪ Ball‬يحتوي على موديل الكرة و امة الفيزياء التي تحدد نوع المحاكاة الفيزيائية اي ان هذا الكائن عبارة عن‬
‫كرة مطاطية او شيء ثقيل او ما شابه ذلك‪ .‬االن افتلها في المشروع‪.‬‬
‫‪ ‬بالنسبة للكاميرا فستجدها بأسم ‪ Auto Camera‬في المشهد‪ ،‬تجد ان هناك عدة ابناء يتفرعون منها وتالحو ان كل ابن يمثل محور معين م‪)y, x‬‬
‫ا يرا ً االبن اال ير وهو الكاميرا وتجد ان الزاوية االفتراضية للدوران هي تساعدك على رؤية الكرة ولكن سندع الكاميرا برمجيا ً تنور الى الكرة‪.‬‬

‫بما ان كل شيء جاهز فسيتبقى لنا انشاء الملف البرمجي والذي سيتحكم بالكرة وآ ر يتحكم بالكاميرا‪ ،‬اذن قم بأنشاء ملف برمجي يمثل الكرة وليكن‬
‫‪ BallMovement‬وأضف الكود التالي‪:‬‬
‫‪public‬‬ ‫الكائن الذي سيدور على المحور االفقي‪Transform pivotRotY; //‬‬
‫‪public‬‬ ‫سرعة الحركة‪float speedMove = 50f; //‬‬
‫‪public‬‬ ‫سرعة القفز‪float speedJump = 20f; //‬‬
‫‪public‬‬ ‫اقصى سرعة زاوية‪float maxAnguler = 10f; //‬‬

‫مرجع للمكون الفيزيائي‪private Rigidbody rb; //‬‬

‫{ )( ‪void Start‬‬

‫اجلب المكون الفيزيائي‪rb = GetComponent<Rigidbody>(); //‬‬


‫خزن اقصى سرعة زاوية‪rb.maxAngularVelocity = maxAnguler; //‬‬
‫}‬

‫)(‪void FixedUpdate‬‬
‫{‬
‫حرك الكرة‪Move(); //‬‬
‫}‬

‫الحركة‪void Move() //‬‬


‫{‬
‫االزاحة االفقية‪float h = Input.GetAxis("Horizontal") * speedMove; //‬‬
‫االزاحة العمودية‪float v = Input.GetAxis("Vertical") * speedMove; //‬‬

‫خزن الحركة العمودية للكرة‪Vector3 moveForward = pivotRotY.forward * speedMove * Time.deltaTime; //‬‬


‫خزن الحركة االفقية للكرة‪Vector3 moveRight = pivotRotY.right * speedMove * Time.deltaTime; //‬‬

‫خزن االزاحات مضروبة في مقدار االزاحة لكل اتجاه‪Vector3 movemnet = -moveForward * h + moveRight * v; //‬‬

‫اضف عزم حركي بناء على تلك االزاحات‪rb.AddTorque(movemnet); //‬‬

‫هل الكائن على األرض وتم الضغط على مفاتح "المسافة" ؟‪if(IsGrounded() && Input.GetKeyDown("space")) //‬‬
‫اضف سرعة الى األعلى مضروبة بسرعة القفز‪rb.AddForce (Vector3.up * speedJump, ForceMode.Impulse); //‬‬

‫ارسم شعاع لرؤية طولة‪Debug.DrawRay(transform.position, Vector3.down * 1.2f, Color.red); //‬‬


‫}‬

‫الكائن على االرض‪bool IsGrounded() //‬‬


‫{‬
‫ارجع معلومات تصادم الشعاع‪//‬‬
‫;)‪return Physics.Raycast(transform.position, Vector3.down, 1.2f‬‬
‫}‬

‫في حالة تمعنت النور في هذا الكود سترى ان العملية بسيطة وهي اضافة عزم دوران الى هذه الكرة‪ ،‬صحيح ان هناك عدة طرق لمحاولة تحريك الكرة وهي‬
‫ال ت فى علينا ولكن في هذه الحالة اريد ان أحرك الكرة ال ان اعطي لها سرعة عبر است دام المترير ‪ Velocity‬او ‪ AddForce‬مع ان جميعها تعمل بشكل‬
‫صحيح‪ ،‬هذه الطريقة ستمكننا من عمل عزم دوران للكرة واعطائها الدفع نتيجة احتكاكها فقط ولكن هناك عدة اشياء ضرورية لتحسين هذه الحركة‪.‬‬

‫المكونات‬ ‫في البداية عرفت عدة متريرات ومنها مترير يمثل سرعة الحركة والقفز واقصى دوران للكرة وهذا امر مهم جداً‪ ،‬في الدالة ‪ Start‬بدأت في جل‬
‫واست دمت المترير ‪ maxAnguler‬إلعطاء اقصى دوران ممكن‪.‬‬

‫الدالة ‪ Move‬تمثل كود الكرة على المحاور فكما تالحو المتريرات تأ ذ المحاور االفقية والعمودية ونضربها في السرعة‪ ،‬المتريرات من نوع ‪Vector3‬‬
‫تمثل الحركة الى االمام مأل ذ المحور العمودي) وآ ر الى اليسار مأل ذ المحور االفقي) وهي ت زن موقع الكائن ‪ pivotRotY‬وهو الكائن الذي يدور على‬
‫المحور ‪ Y‬وستأ ذ الكرة هذه المحاور وتعيد است دامها في عملية التدوير للتحرك مع منوور الكاميرا‪ ،‬ا يرا ً المترير ‪ movement‬ي زن جميع الحركات‪.‬‬

‫بارامتر من نوع ‪ Vector3‬ي زن‬ ‫الدالة ‪ AddTorque‬تقوم بعمل عزم دوران لهذا الكائن الفيزيائي وبما انها كرة فهذا االمر ينطبق عليها‪ ،‬الدالة تطل‬
‫الحركات فيه فكما تالحو قمنا باست دام المترير ‪ movement‬بشكل مباشر‪.‬‬

‫مسألة القفز تستطيع حلها عبر عدة طرق ومنها طريقة اال شعة و انا افضلها بشكل كبير‪ ،‬العملية تعتمد على اطالق شعاع من مركز الكرة الى اسفلها بحيث‬
‫يساوي نصف قطرها او يزيد عنه ومن ثم التحقق من تالمس هذا الشعاع مع االر و هل تالمس ويتم تطبيق القفز‪ ،‬العملية بسيطة فكما تالحو في الدالة‬
‫‪ IsGrounded‬هي دالة من نوع ‪ bool‬و تعيد لنا الشعاع الساقط مع وضع احداثياته و طولة م‪ )1.2f‬هي نصف القطر و تزيد عنها بنسبة بسيطة و تلقائيا‬
‫سيعيد لنا القيمة ‪ true‬في حالة تالمس مع االر ‪ ،‬يمكنك التحقق من حالة الشعاع و طولة عبر الدالة ‪ DrawRay‬عبر الفئة ‪.Debug‬‬

‫الصفحة ‪6‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫ا يرا ً في الشرط سيتم التحقق من حالة الدالة والضرط على المفتاح "‪ "Space‬وسيدفع الكرة الى االعلى‪ ،‬شيء ا ير تالحو ان نوع القوة هو ‪ impulse‬اي‬
‫قوة دافعة وفيزيائيا ً سيعطي نتيجة أفضل‪.‬‬

‫حركة الكاميرا‪:‬‬
‫معلومات الحركة الصحيحة‪ ،‬لتوضيح‬ ‫بالنسبة للكاميرا فسنتعرف على الطريقة البسيطة لتدويرها حول الكرة وسنرى عمل المترير ‪ movement‬في جل‬
‫العملية أكثر تأمل هذه الصورة‪:‬‬

‫‪2‬‬ ‫‪1‬‬
‫كما تالحو في ان الكاميرا تقوم بالدوران او باألصح الكائن ‪ Pivot Rot Y‬يقوم بالدوران مكما في الصورة ‪ )1‬بينما دوران الكرة مازال على حالة اي لم‬
‫يترير بعد‪ ،‬فباالعتماد على زاوية الدوران في المترير ‪ pivotRotY‬في ملف الكرة ستقوم الكرة بات اذ المحور االمامي والجانبية لهذا الكائن‪.‬‬

‫هناك عدة طرق لتدوير الكائن )‪ (Pivot Rot Y‬و )‪ )Pivot Rot X‬بحيث ان هناك طرق تساعدك على عمل تحكم احترافي في عملية الدوران ولكن سنست دم‬
‫الطريقة البسيطة اي الدالة ‪ ،RotateAround‬مفهوم هذه المترير يعتمد على تحديد موقع الكائن الذي سيكون نقطة االرتكاز ومن ثم المحور الذي ستدور‬
‫علية و سرعة الدوران‪ ،‬قم بأنشاء ملف برمجي يمثل الكاميرا و ليكن ‪ CameraController‬ويفضل وضعة الى الكائن ‪ ،Auto Camera‬ا يرا ً اضف الكود‬
‫التالي لنناقشه‪:‬‬
‫‪public‬‬ ‫‪Transform‬‬ ‫كائن التتبع التلقائي‪autoMove; //‬‬
‫‪public‬‬ ‫‪Transform‬‬ ‫الكائن الذي سيدور على المحور االفقي‪pivotRotY; //‬‬
‫‪public‬‬ ‫‪Transform‬‬ ‫الكائن الذي سيدور على المحور العمودي‪pivotRotX; //‬‬
‫‪public‬‬ ‫‪Transform‬‬ ‫الهدف‪target; //‬‬

‫سرعة الماوس‪public float speedMouse = 7f; //‬‬

‫{ )( ‪void FixedUpdate‬‬
‫انتقل من موقعك الى موقع الهدف‪//‬‬
‫;)‪autoMove.position = Vector3.Lerp(autoMove.position, target.position, Time.deltaTime * 7f‬‬

‫خزن االزاحة االفقية للماوس‪float y = Input.GetAxis("Mouse Y") * speedMouse; //‬‬


‫خزن االزاحة العمودي للماوس‪float x = Input.GetAxis("Mouse X") * speedMouse; //‬‬

‫دور الكائن العمودي حول المحور العمودي بمقدار إزاحة الماوس العمودية‪//‬‬
‫;)‪pivotRotY.RotateAround(target.position, Vector3.up, x‬‬
‫دور الكائن االفقي حول المحور االفقي بمقدار إزاحة الماوس االفقية‪//‬‬
‫;)‪pivotRotX.RotateAround(target.position, Vector3.left, y‬‬
‫اغلق المحور الجانبي مع إبقاء باقي المحاور مفتوحة‪//‬‬
‫;)‪pivotRotX.eulerAngles = new Vector3(pivotRotX.localEulerAngles.x, pivotRotX.localEulerAngles.y, 0‬‬
‫}‬

‫اذن كما تالحو الكود بسيطة فالمتريرات االولية هي لجل محاور الكائنات التي ستتحكم بحركة الكاميرا و تتبع الالع ومترير لجل محاور الكرة و ا يرا ً‬
‫سرعة التحكم‪ ،‬است دام الكود في الدالة ‪ FixedUpdate‬هو لمساواة فترات التحديث فالدلة ‪ Update‬تقوم بتحديث اسرع من سابقتها و عند است دامها‬
‫من‬ ‫يعود الى فترات التحديث الرير منتومة فبهذا الشكل اي عند است دام الدالة ‪ FixedUpdate‬سنت ل‬ ‫ستجد ان هناك تأ ر في تتبع الكرة و السب‬
‫المشكلة وسيعطينا نفس فترات التحديث‪.‬‬

‫في البداية جعلت المترير ‪ autoMove‬يتبع الكرة فقط بما انه لن يدور‪ ،‬اسفلة المتريرين ‪ x, y‬ي زنان احداثيات الماوس على المحوري االفقي و العمودي‪،‬‬
‫اسفلة است دمت الدالة ‪ RotateAround‬للدوران على الكرة و تالحو ان االولى تأ ذ المحور ‪ up‬اي االفقي مالمحور ‪ )y‬اما الثانية تأ ذ ‪ left‬اي‬
‫الجانبي مالمحور ‪ ، )x‬ا يرا ً است دمت المتريرين ‪ x, y‬لتحديد سرعة الدوران‪ ،‬آ ر شيء وهو اقفال المحور ‪ z‬للت ل من الدوران علية‪.‬‬

‫ا يرا ً قم بملء المريرات بأسماء الكائنات وا تبر االمر و ستجد ان في كل مرة تقوم بتحريك الكرة و تدوير الكاميرا ستقوم الكرة بات اذ المحور االمامي‬
‫للكائن ‪ ،pivotRotY‬بهذا الشكل نكون قد انهينا العمل على الكرة و كما قلت سابقا ً انه ليس ضروري ان تحتكر هذه الطريقة فقط و هي فقط لالست دام في‬
‫الحاالت الضرورية و يمكنك است دام اي طريقة ا رى تعمل بشكل جيد و لكن تأكد من كتابتها في الدوال التي تمثلها ‪.‬‬

‫الصفحة ‪7‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫محاكاة السيارة ثنائية االبعاد‪:‬‬
‫الشك في ان الكثير يحاول البحث او التوصل الى محاكاة السيارة ثنائية االبعاد في اليونتي و بطبيعة الحال السيارة تتحرك على المحور االفقي فقط‪ ،‬السيارة‬
‫ثنائية االبعاد ت تلف عن ثالثية االبعاد فهنا هي تتحرك على المحور االفقي و ربما االمر سهل لتحريك هذه السيارة و هناك عدة طرق لمحاكاة حركتها ولكن‬
‫الطريقة الفيزيائية هي االفضل‪ ،‬في هذه الفقرة سنتعرف على الطريقة الم تصرة لتحريك هذه السيارة و هي عبارة عن معجلتين و جسم السيارة) و سنعتمد‬
‫على المكون الفيزيائي المحاكي لعجالت السيارة الثنائية االبعاد ‪ WheelJoint2D‬وسنست دمه مرتين لسح العجالت اليه‪.‬‬

‫العجالت‬ ‫السيارة ثنائية االبعاد مكونة من مجسم السيارة وعجلتان) مكونات بسيطة والمكون ‪ WheelJoint2D‬يوضع على جسم السيارة ومن ثم تسح‬
‫اليه‪ ،‬العملية بسيطة‪ ،‬اذن المشهد الذي يمثل السيارة ثنائية االبعاد ‪ 2D Car‬في الملف ‪ Scenes‬ستجد عدة كائنات في المشهد ومنها السيارة والكاميرا‬
‫الرئيسية التي ستتبع حركة السيارة‪.‬‬

‫بينما تتفرع منه عدة كائنات وهي العجالت و مركز الثقل ‪ Center of Mass‬ويمثل الكائن‬ ‫دعنا نراجع كائن السيارة‪ ،‬تجد ان الجسم هو الكائن اال‬
‫‪ Container‬و موقعة اسفل الجسم بالتحديد بين العجالت لوضع الثقل في تلك النقطة وللت ل من الدوران حول نفسه‪ ،‬العملية البرمجية سنقوم بتحديد‬
‫مركز الث قل للسيارة و من ثم الحصول على مكون العجالت المرئية الفيزيائية و التعامل مع قيم السرعة و سنناقش الفكرة بالتفصيل‪.‬‬

‫المكون ‪ WheelJoint2D‬في جسم السيارة افتراضيا ً المكون مضاف مرتين ونفس المعلومات الموجودة على االول هي الموجودة على الثاني ماعدا ال يار‬
‫ان تحتوي‬ ‫العجلة ويج‬ ‫لمحاكاة العجالت ثنائية االبعاد فهو يطل‬ ‫‪ Connected Rigid Body‬اي االتصال بجسم فيزيائي وبما ان المكون م ص‬
‫هذه العجلة على المكون ‪ Rigidbody2D‬ويمكنك رؤية المكون مضاف على العجالت في السيارة‪.‬‬

‫ال يارين ‪ Anchor‬و ‪ Connected Anchor‬يحددان نقطة االتصال و نقطة االرتكاز‪ ،‬فنقطة االرتكاز هي النقطة التي ستدور عليها العجلة بينما نقطة‬
‫االتصال هي النقطة التي ستتصل العجلة فيها و يج ان تكون نقطة االرتكاز في وسط نقطة االتصال لجعل العجلة تدور بشكل مناس ‪.‬‬

‫لعجالت السيارة ثالثية االبعاد‪ ،‬هذا النوام‬ ‫نوام التعليق ‪ Suspension‬في هذا المكون ي تلف عن الموجود في المكون ‪ Wheel Collider‬الم ص‬
‫مبسط فهو يتكون من ما صدمات ‪ Damping Ratio‬ومقدار التردد ‪ Frequency‬المكون يعطيك قيم افتراضية لهما و هي مناسبة و يمكن ترييرها‬
‫لتتناس مع نوع السيارة التي تست دمها‪.‬‬

‫تجد يار في االسفل وهو ‪ Use Motor‬وهذا ال يار يسمح لك بالتحكم بعجالت السيارة عن طريق اضافة سرعة ففي حال تفعيلة ستجد ان العجالت غير‬
‫اضعة لوزن السيارة اي تتحرك من تلقاء نفسها عند االنزالقات او المنحنيات‪ ،‬اما ال يار ‪ Motor‬يوفر لك يارات السرعة مثل اقصى دفع ممكن‬
‫‪ ،Maximum Motor Force‬و السرعة الحالية ‪ Motor Speed‬و هذه القيم قابلة للقراءة فقط اي ال يمكن التعديل عليها عبر فئتها مباشرة بل عن‬
‫طريق است دام فئة ت زن القيم و من ثم تعيد است دامها‪.‬‬

‫انشاء الملف ‪:Car2DMovement‬‬


‫برمجيا ً سنقوم بتحريك السيارة عن طريق المكون العجالت بالتحديد عبر ال يار ‪ ،Motor‬اذن انشئ ملف برمجي يمثل السيارة وليكن‬
‫‪ Car2DMovement‬وأضف الكود التالي‪:‬‬
‫مصفوفة لجلب مكون العجالت الفيزيائية ‪private WheelJoint2D[] wheels; //‬‬
‫مرجع للمكون الفيزيائي ثنائي االبعاد‪private Rigidbody2D rb2D; //‬‬
‫يستخدم لتخزين قيم السرعة ‪private JointMotor2D motor; //‬‬

‫مركز الثقل ‪public Transform centerOfMass; //‬‬


‫سرعة القيادة ‪public float speedDrive = 500f; //‬‬
‫سرعة الكبح ‪public float speedBrake = 100f; //‬‬

‫الصفحة ‪8‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫{ )( ‪void Start‬‬
‫احصل على مكونات العجالت الفيزيائية ‪wheels = GetComponents<WheelJoint2D>(); //‬‬
‫اجلب المكون الفيزيائي ثنائي االبعاد‪rb2D = GetComponent<Rigidbody2D>(); //‬‬
‫اعتمد على المتغير في تخزين مركز الثقل ‪rb2D.centerOfMass = centerOfMass.position; //‬‬

‫)‪for(int i = 0; i <= wheels.Length - 1; i++‬‬


‫اجلب المعلومات المحرك من جميع العجالت الفيزيائية‪motor = wheels[i].motor; //‬‬
‫}‬

‫{ )( ‪void FixedUpdate‬‬

‫طبق التحكم‪ApplyControl(); //‬‬


‫}‬

‫طبق التحكم‪void ApplyControl() //‬‬


‫{‬
‫خزن االزاحة االفقية‪float horiznotal = Input.GetAxis("Horizontal"); //‬‬

‫اعتمد على المتغير في تحديد اقصى عزم ممكن‪motor.maxMotorTorque = speedDrive; //‬‬

‫هل هناك عزم الى اليسار ؟‪if(horiznotal < 0f) //‬‬


‫;)‪motor.motorSpeed = Mathf.Lerp(motor.motorSpeed, motor.maxMotorTorque, Mathf.PI * Time.deltaTime‬‬

‫هل هناك عزم الى اليمين ؟‪if(horiznotal > 0f) //‬‬


‫;)‪motor.motorSpeed = Mathf.Lerp(motor.motorSpeed, -motor.maxMotorTorque, Mathf.PI * Time.deltaTime‬‬

‫هل توقف العزم ؟‪if(horiznotal == 0f) //‬‬


‫أوقف العزم تلقائيا‪motor.motorSpeed = Mathf.Lerp(motor.motorSpeed, 0f, Mathf.PI * Time.deltaTime); //‬‬

‫هل تم تطبيق الكبح ؟‪if(Input.GetKey("space")) //‬‬


‫أوقف العزم‪motor.motorSpeed = Mathf.Lerp(motor.motorSpeed, 0f, speedBrake * Time.deltaTime); //‬‬

‫)‪for(int i = 0; i <= wheels.Length - 1; i++‬‬


‫قم باستعمال القيم المخزنة للمحرك‪wheels[i].motor = motor; //‬‬
‫}‬

‫كما تالحو عملية الحصول على القيم تعتمد على المترير ‪ Motor‬من الفئة الفيزيائية ‪ JointMotor2D‬ومن اللها نستطيع ت زين معلومات السرعة ومن‬
‫ثم نستطيع قراءتها من المصفوفة ‪.wheels‬‬

‫في ‪ Start‬بعد جل المكونات والحصول على مركز الثقل قمنا بجعل المترير ‪ motor‬يحصل على القيم االولية او قيم السرعة من المصفوفة ‪wheels‬‬
‫إلعادة استعمال تلك القيم بشكل صحيح‪.‬‬

‫بالنسبة لعملية التحريك ستكون فيزيائية وعلينا ات اذ الدالة ‪ FixedUpdate‬لتشريل الدالة ‪ ApplyControl‬الن محتواها فيزيائي‪ ،‬فيها سيتم تطبيق عملية‬
‫التحكم بشكل كامل‪ .‬المترير ‪ horizontal‬سيساعدنا على معرفة اتجاه الحركة للسيارة ولكن لن نست دمه في اضافة سرعة او ما شابه‪ ،‬في حالة تحقق الشرط‬
‫و تم التأكد من انه هناك اتجاه للحركة فسيتم نقل المترير ‪ motorSpeed‬الذي دائما ً قيمتة هي مصفر) من قيمتة الحالية الى اقصى قيمة عبر المترير‬
‫‪ maxMotorTorque‬و لكن في كالً من الشرطين سيعطينا القيمة الموجبة و السالبة لتدوير العجالت باالتجاهين‪ ،‬اما في حالة لم تتم عملية االد ال‬
‫فستتوقف العجلة تدريجياً‪.‬‬

‫تطبيق الكبح امر بسيط وهو نقل المترير ‪ motorSpeed‬من حالته الى القيمة صفر وستتوقف العجلة بشكل مباشر‪ .‬ا يرا ً المصفوفة ‪ wheels‬ستست دم القيم‬
‫الم زنة في المترير ‪ motor‬لتحريك العجالت‪.‬‬

‫بالعودة الى المشروع قم بملء المتريرات و ا تبر االمر‪ ،‬هذه العملية تعتبر عملية بسيطة و ايضا ً فعالة في محاكاة حركة السيارة‪ ،‬و ت تلف نوع الحركة من‬
‫سيارة الى ا رى‪.‬‬

‫حركة الكاميرا‪:‬‬
‫بما اننا نتعامل مع سيارة ‪ 2D‬فأننا سنتعامل مع كاميرا ‪ 2D‬فالكاميرا الموجودة في المشروع تواجه المنوور ثنائي االبعاد وتالحو مدة ترطيتها للسيارة‪ ،‬هناك‬
‫امور يج ان نقوم بها عدا تحريك الكاميرا مع السيارة وهي جعل الكاميرا تحتوي السيارة بشكل يتيح لك رؤية القادم‪.‬‬

‫الكاميرا ستتحرك على المحوري ‪ x, y‬وستتبع السيارة في حركتها‪ ،‬حسنا ً لنقم باألمر بدايةً قم بأنشاء ملف برمجي يمثل الكاميرا و ليكن‬
‫‪ Camera2DControl‬واضف الكود التالي‪:‬‬
‫الهدف (السيارة)‪public Transform target; //‬‬
‫سرعة التتبع‪public float speedFollow = 10f; //‬‬
‫مقدار االزاحة على المحور العمودي‪public float deltaY = 4f; //‬‬

‫موقع الكاميرا على المحور االفقي‪private float posX; //‬‬


‫لجلب االزاحة االفقية ‪private float deltaX; //‬‬
‫اقصى إزاحة ممكنة على المحور االفقي‪public float maxDeltaX = 10f; //‬‬

‫{ )( ‪void FixedUpdate‬‬

‫خزن محاور الكائن‪Vector3 pos = transform.position; //‬‬

‫اتبع السيارة على المحور االفقي ‪ +‬اضف إزاحة افقية‪//‬‬


‫‪pos.x = Mathf.SmoothStep(pos.x, target.position.x + deltaX, speedFollow * Time.deltaTime); //‬‬
‫اتبع السيارة على العمودي االفقي ‪ +‬اضف إزاحة عمودية‪//‬‬
‫;)‪pos.y = Mathf.SmoothStep(pos.y, target.position.y + deltaY, speedFollow * Time.deltaTime‬‬
‫;‪pos.z = -10f‬‬

‫الصفحة ‪9‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫استخدم القيم الموقع المخزنة‪transform.position = pos; //‬‬

‫هل هناك إزاحة الى اليمين ؟‪if (Input.GetAxis ("Horizontal") > 0) //‬‬
‫الزيادة في مقدار االزاحة االفقية‪deltaX = maxDeltaX; //‬‬
‫هل هناك إزاحة الى اليسار ؟‪if(Input.GetAxis("Horizontal") < 0) //‬‬
‫التناقص في مقدار االزاحة االفقية‪deltaX = -maxDeltaX; //‬‬

‫ال توجد إزاحة؟ ‪if(Input.GetAxis("Horizontal") == 0) //‬‬


‫عد الى الموقع االصلي‪posX = Mathf.Lerp(posX, 0, Time.deltaTime * 2f); //‬‬
‫هناك إزاحة‪ ,‬قم بتطبيق االزاحة االفقية للكاميرا ‪else //‬‬
‫;)‪posX = Mathf.Lerp(posX, deltaX, Time.deltaTime * 2f‬‬
‫}‬

‫دعنا نناقش هذا الكود‪ ،‬كما ترى ال توجد تعقيدات كثير في العملية انما فقط انتقاالت بين القيم وات اذها لتريير موقع الكاميرا على المحور االفقي وجعلها تتبع‬
‫السيارة على المحورين م‪ .)x,y‬القسم األول من المتريرات لجل الهدف وتحديد سرعة التتبع باإلضافة الى تحديد الزيادة على المحور ‪ Y‬للتحكم بالمحور بشكل‬
‫ادق‪.‬‬

‫القسم الثاني لجل معلومات االنتقال االفقي او االزاحة االفقية وتحديد اقصى قيمة ممكنة لجعل الكاميرا تتحرك مع السيارة وتعطيك رؤيا أكبر للعالم‪ .‬في‬
‫‪ FixedUpdate‬تالحو الكود العروف في جعل كائن يتبع كائن على المحورين ولكن ترى ان المتريرين يقوموا بإعطاء الزيادة على المحورين لعمل إزاحة‬
‫للكاميرا فيهم كما في الصورة‪:‬‬

‫في حالة تم التحقق من وجود إزاحة ستجد ان المترير ‪ deltaX‬يت ذ القيم الموجبة والسالبة للمترير ‪ maxDeltaX‬لقل اتجاه االزاحة‪ ،‬في‬
‫حالة لم تكن هناك إزاحة سيتم إعادة الكاميرا الى موقعها األصلي عن طريق نقل المترير ‪ posX‬الى الحالة صفر‪ ،‬غير ذلك سنقوم بتطبيق‬
‫االزاحة في المترير ‪ .posX‬في األ ير قيم االزاحة معتمد على ت زينا في المترير ‪ deltaX‬لعمل إزاحة افقية للكاميرا‪.‬‬

‫الصفحة ‪10‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫محاكاة االنفجار ثنائي االبعاد (‪:)Explosion 2D‬‬
‫ربما تكون قد تساءلت عن انشاء تأثير انفجار ثنائي االبعاد بحيث يؤثر على الكائنات ذات ال وا الفيزيائية ثنائية االبعاد‪ ،‬الفئة ‪ Rigidbody2D‬ال توفر‬
‫مترير مثل ‪ AddExplosionForce‬الموجود في الفئة ‪ Rigidbody‬وهنا تبدأ التساؤالت هل على ان ابرمج االنفجار اي أنشاء دالة توفر نفس عمل الدالة‬
‫الموجودة في الفئة اال رى؟ نعم ان االمر ضروري لكن ستتعرف في هذه الفقرة على محاكاة االنفجار ثنائي االبعاد وهو كود تم انشاؤه مسبقا ً ولكن قمت‬
‫بالتعديل علية بحيث يصبح أكثر فاعلية و قابلية لالست دام‪.‬‬

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

‫في هذه العملية سنست دم الدالة ‪ AddForce‬ومن اللها نستطيع ان نحدد نوع االنفجار عبر الفئة ‪ ForceMode‬ومن ثم نطبق عملية االنفجار اعتمادا ً على‬
‫قطر دائرة االنفجار‪.‬‬

‫سنست دم ملف برمجي يعيد لنا دول ثابتة الست دمها بشكل مباشر عبر فئتها‪ ،‬ايضا ً لن ستست دم متريرات ترث من ‪ MonoBehaviour‬فكل ما سنست دمه‬
‫هو الفئة ‪ Rigidbody2D‬بشكل اساسي‪.‬‬

‫انشاء الملف ‪:Rigidbody2DExpl‬‬


‫ال طوة االولى انشئ ملف برمجي وليكن بأسم ‪ Rigidbody2DExpl‬ويج ان يمكون ‪ Static‬وال يرث من ‪ ،MonoBehaviour‬ا يرا ً أضف الدالة االولى‬
‫ووويفتها هو حسا مدى االنفجار لتطبيقه في الدالة الثانية‪:‬‬
‫;‪using UnityEngine‬‬
‫;‪using System.Collections.Generic‬‬

‫{‪public static class Rigidbody2DExpl‬‬


‫معلومات االنفجار ثنائي االبعاد‪//‬‬
‫‪private static void Explosion2D(this Rigidbody2D rb2D, float explForce, Vector2 explPos, float explRadius,‬‬
‫)‪float explUp = 0f, ForceMode2D mode = ForceMode2D.Force‬‬
‫{‬
‫خزن القيمة الفاصلة بين موقع االنفجار و موقع الكائن الفيزيائي‪//‬‬
‫;‪Vector2 explDir = rb2D.position - explPos‬‬
‫اجلب المسافة بناء على مقدار االتجاه‪//‬‬
‫;‪float explDis = explDir.magnitude‬‬
‫ليس هناك قوة الى االعلى‪//‬‬
‫)‪if(explUp == 0f‬‬
‫{‬
‫اعد القيمة صفر‪ ,‬ليس هناك دفع الى األعلى‪explUp /= explDis; //‬‬
‫هناك قوة دفع الى االعلى‪}else //‬‬
‫{‬
‫زيادة المتجه االفقي بمقدار الدفع‪explDir.y += explUp; //‬‬
‫;)(‪explDir.Normalize‬‬
‫}‬
‫اضف القوة الى االجسام الداخلة في القطر كال ستأثر به القوة حسب بعدة عن مركز االنفجار‪//‬‬
‫;)‪rb2D.AddForce(Mathf.Lerp(0f, explForce ,(explRadius - explDis)) * explDir, mode‬‬
‫}‬
‫}‬
‫جميع الدوال سنقوم بجعلها ‪ Static‬لمناداتها بشكل مباشر من الفئة‪.‬‬

‫‪ this‬تشير الى مكون ‪ Rigidobdy2D‬ال اصة بهذا الكائن او هذه الفئة‪ ،‬وتالحو ايضا ً ان هناك عدة بارامترات وهي‪:‬‬

‫الصفحة ‪11‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫‪ :explForce‬قوة االنفجار‪.‬‬
‫‪ :explPos‬موقع االنفجار‪.‬‬
‫‪ :explRadius‬قطر االنفجار‪.‬‬
‫‪ :explUp‬قوة االنفجار الى االعلى وافتراضيا ً هي صفر اي ليس هناك قوة دافعة الى االعلى‪.‬‬
‫‪ mode‬مترير يحدد نوع االنفجار‪.‬‬

‫اذن هذه البارامترات ضرورية لتطبيق االنفجار‪ ،‬في البداية عرفت المترير ‪ explDir‬وهو يحدد موقع االنفجار وسيتم تطبيقه بين موقع الكائن الفيزيائي وموقع‬
‫االنفجار وافتراضيا ً هو مركز الكائن نفسه‪.‬‬

‫المترير ‪ explDis‬يحدد مسافة االنفجار وسي زن حجم المترير ‪.explDir‬‬

‫في الشرط سيتحقق من ان هناك قيمة لعمل دفع الى االعلى‪ ،‬إذا كانت صفر فأن القيمة ستقسم على المسافة وناتجها مصفر) ولن يتم تطبيق اي دفع الى‬
‫االعلى‪ ،‬اما اذا كانت هناك قيمة قد اعطت فأن الزيادة ستكون في قيمة المحور ‪ y‬و لكما زادت القوة كان الدفع اقوى‪.‬‬

‫في الدالة ‪ AddForce‬تالحو است دامي للمترير ‪ !Lerp‬عبر هذا المترير سنقوم بنقل االنفجار تدريجيا ً من قيمة الحالية الى قيمة قوة االنفجار ‪explForce‬‬
‫وسرعته ستعتمد على القطر ناق المسافة اي النتيجة الوسطى هي سرعة الدفع‪.‬‬

‫اذن بهذا الشكل نكون قد هيئنا القيم االولية وسنست دمها في الدالة التالية‪.‬‬

‫الدالة التالية ستكون ‪ AddExplosion2D‬ووويفتها تطبيق االنفجار اعتمادا ً على القيمة الم زنة في الدالة ‪ ،Explosion2D‬أضف الدالة التالية لنناقشها‪:‬‬

‫‪public static void AddExplosion2D(float explForce, Vector2 explPos, float‬‬ ‫‪explRadius,‬‬ ‫‪float‬‬ ‫‪explUp‬‬ ‫=‬ ‫‪0f,‬‬
‫)‪ForceMode2D mode = ForceMode2D.Force‬‬
‫{‬
‫مصفوفة لتخزين جميع االجسام الداخلة في دائرة االنفجار‪//‬‬
‫;)‪Collider2D[] colliders = Physics2D.OverlapCircleAll(explPos, explRadius‬‬
‫مصفوفة لجلب االجسام الفيزيائية الداخلة في االنفجار‪//‬‬
‫;)(>‪List<Rigidbody2D> rbs2D = new List<Rigidbody2D‬‬

‫)‪foreach(Collider2D col in colliders‬‬


‫هل هذه االجسام تحتوي على المكون الفيزيائية ‪ +‬لما يتم اضافتها الى المصفوفة مسبقا؟‪//‬‬
‫))‪if(col.attachedRigidbody != null && !rbs2D.Contains(col.attachedRigidbody‬‬
‫اذن اضف هذه االجسام الفيزيائية الى المصفوفة‪rbs2D.Add(col.attachedRigidbody); //‬‬

‫)‪foreach(Rigidbody2D rb in rbs2D‬‬
‫طبق االنفجار على جميع االجسام الفيزيائية‪//‬‬
‫;)‪rb.Explosion2D(explForce, explPos, explRadius, explUp, mode‬‬
‫}‬

‫هذه الدالة بسيطة جدا ً ووويفتها هو رسم دائما ً تتحقق من تالمس الكائنات معها و من ثم تطبيق االنفجار عليها‪ ،‬تالحو ان البارامترات المعرفة هي نفسها في‬
‫الدالة السابقةً لكي نحدد وويفة كل منها‪.‬‬

‫المصفوفة ‪ Colliders‬تقوم برسم دائرة تتحقق من الكائنات المتصادمة فكما ترى ان موقع الدائرة هو موقع االنفجار ايضا ً القطر‪ ،‬ايضا ً تالحو است دامي للفئة‬
‫‪ List‬وهي تساعدنا على ا نشاء مصفوفة من نوع مكون او كائن وهنا المصفوفة هي من نوع ‪ Rigidbody2D‬لت زين الكائنات المتصادمة ويج ان تحتوي‬
‫على نفس المكون‪.‬‬

‫الحلقة ‪ foreach‬تقوم بالتحقق من ان الكائنات المتصادمة تحتوي بالفعل على المكون ايضا ً التأكد من ان المصفوفة لم تقم بت زين اي كائن مسبقاً‪ ،‬في حالة‬
‫تحقق الشرط ستقوم المصفوفة بت زين الكائنات المتصادمة‪.‬‬

‫الحلقة الثانية ‪ foreach‬تحيطنا بجميع الكائنات المتصادمة التي تحتوي على المكون ‪ Rigidbody2D‬لتطبيق الدالة ‪ Explosion2D‬فيها فكما تالحو اننا‬
‫است دمنا الدالة ووضعنا القيم فيها بحس البارامترات التي عرفناها مسبقاً‪.‬‬

‫بشكل م تصر العملية تعتمد على احتواء جميع الكائنات التي تصادمت مع الطائرة المرسومة ومن ثم اضافة القوة اليهم لتدميرهم او قذفهم‪ ،‬تبقى فقط جعل‬
‫هذه القنبلة تست دم الدالة ‪ AddExplosion2D‬لتطبيق االمر على الكائنات‪ ،‬االن انشئ ملف برمجي وتأكد من وضعه في القنبلة التي ستجدها في الملف‬
‫‪ Projects/Explosion2D‬ا يرا ً اضف الكود التالي‪:‬‬
‫‪public‬‬ ‫‪float‬‬ ‫الوقت المنقض‪timeleft; //‬‬
‫‪public‬‬ ‫‪float‬‬ ‫قوة االنفجار‪force = 1000f; //‬‬
‫‪public‬‬ ‫‪float‬‬ ‫قطر االنفجار‪radius = 7f; //‬‬
‫‪public‬‬ ‫‪float‬‬ ‫قوة االنفجار الى االعلى‪forceUp = 2f; //‬‬

‫{ )( ‪IEnumerator Start‬‬

‫ض الوقت‪yield return new WaitForSeconds(timeleft); //‬‬


‫انتظر حتى ينقِ‬
‫طبق االنفجار من موقع الجسم الحالي‪//‬‬
‫;)‪Rigidbody2DExpl.AddExplosion2D(force, transform.position ,radius, forceUp‬‬
‫دمر هذا الكائن‪//‬‬
‫;)‪Destroy(gameObject‬‬
‫}‬

‫الصفحة ‪12‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


Start ‫ الدالة‬،static ‫ القطر وقوة االنفجار الى االعلى) و التي ستمرر في الدالة على فئتها مباشرة ألنها‬،‫ موقع االنفجار‬،‫تالحو المتريرات الضرورية مالقوة‬
. ‫ ا يرا ً تبقى لك ا تبار الدالة ورؤية االمر ينطبق على ار الواقع‬،AddExplosion2D ‫ وتعيد لنا القوت المتبقي لتطبيق الدالة‬Coroutine ‫هي‬

:‫الكود النهائي‬
using UnityEngine;
using System.Collections.Generic;

public static class Rigidbody2DExpl{

private static void Explosion2D(this Rigidbody2D rb2D, float explForce, Vector2 explPos, float
explRadius, float explUp = 0f, ForceMode2D mode = ForceMode2D.Force)
{
Vector2 explDir = rb2D.position - explPos;
float explDis = explDir.magnitude;

if(explUp == 0f)
{
explUp /= explDis;
}else
{
explDir.y += explUp;
explDir.Normalize();
}

rb2D.AddForce(Mathf.Lerp(0f ,explForce ,(explRadius - explDis)) * explDir, mode);


}

public static void AddExplosion2D(float explForce, Vector2 explPos, float explRadius, float explUp
= 0f, ForceMode2D mode = ForceMode2D.Force)
{
Collider2D[] colliders = Physics2D.OverlapCircleAll(explPos, explRadius);
List<Rigidbody2D> rbs2D = new List<Rigidbody2D>();

foreach(Collider2D col in colliders)


if(col.attachedRigidbody != null && !rbs2D.Contains(col.attachedRigidbody))
rbs2D.Add(col.attachedRigidbody);

foreach(Rigidbody2D rb in rbs2D)
rb.Explosion2D(explForce, explPos, explRadius, explUp, mode);
}
}

13 ‫الصفحة‬ Facebook.com/EMAD.Unity3D
‫محاكاة الكاميرا االستراتيجية ‪:Camera RTS‬‬
‫هذا الموضوع يعتبر من اهم المواضيع بنوري ألنه يتعلق باست دام الفئة ‪ Quaternion‬والتي ربما قد واجه الكثير مشكلة في است دامها‪ ،‬هذه الفئة ناتجة عن‬
‫عمليات رياضية معقدة جدا ً وناتج تلك العملية هي دوال ومتريرات بسيطة جدا ً واست دامها أكثر من رائع‪ ،‬هذه الفئة تهتم بمحاور الدوران للكائن وتوفر لنا‬
‫دوال مثل ‪ Euler‬للتحكم بالمحاور‪ LookRotation ،‬النور الى كائن عبر الدوران اليه ودوال كثيرة ت تصر عمليات معقدة كما قلت‪.‬‬

‫هنا الكاميرا االستراتيجية ستعتمد على عدة امور ا تصرها في هذه النقاط‪:‬‬

‫‪ ‬التحكم بحركة الكاميرا عبر الماوس والكيبورد‪.‬‬


‫‪ ‬تدوير الكاميرا في جميع االتجاهات عبر الماوس وإعادة ضبط موضعها االصلي‪.‬‬
‫‪ ‬التقري والتبعيد عبر عجلة الماوس‬

‫جميع هذه الصفات التي ستحتويها الكاميرا ستكون احترافية بأذن هللا ومريحة لالع اثناء التحكم بها‪.‬‬

‫قبل البدء في الولوج الى البرمجة يج ان اتأكد من أنك قمت باست دام مشروع الكاميرا االستراتيجية فمن الملف ‪ Scenes‬ستجد المشهد ‪CameraRTS‬‬
‫وستجد الكاميرا موضوعة في المشهد‪ ،‬في حالة قمت بمراجعة الكائن ‪ Auto Move‬ستالحو انه يحتوي على العديد من الكائنات و منها‪:‬‬

‫‪ ‬كائن يدور على المحور ‪.Y‬‬


‫‪ ‬كائن يدور على المحور ‪.X‬‬
‫‪ ‬الكاميرا الرئيسية‪.‬‬

‫بينما الكائن اال وويفته هي تتبع الكائن ‪ Free Move‬وهو يحمل المكون ‪ Rigidbody‬لتحريكه فيزيائياً‪ ،‬مسألة الحركة تعتمد على ان الكائن ‪Free Move‬‬
‫سيتحرك ويدور بينما الكائن ‪ Auto Move‬سيتبعه في الحركة فقط وليس الدوران‪ ،‬ا يرا ً الكائنات التي ستدور على المحورين ‪ X, Y‬سيبقيان الكاميرا‬
‫الرئيسية ثابته في مكانها وهذا جميل ألنه سيتيح لنا عمل الكثير في الكاميرا الرئيسية و سنتعامل معها على اساس انها لم تتحرك قط‪.‬‬

‫انشاء الملف ‪:CameraRTS‬‬


‫سنقسم العملية البرمجية الى عدة نقاط و في كل نقطة سنناقش دالة معينة الن الكود المتعلق بحركة الكاميرا االستراتيجية طويل نوعا ً ما ولكن يول مفهومه‬
‫ثابت و تستطيع تطبيق االمر على عدة اشياء وسنمر علية و سترى ان ليس هناك ا تالف كبير بل ان الفكرة واحدة‪.‬‬

‫‪ ‬العملية البرمجية ستعتمد على ملف واحد فقط وهو سنجعله يتحكم بالكاميرا بشكل كامل‪ .‬اذن قم بأنشاء ملف برمجي وضعة في الكاميرا‪ ،‬ا يرا ً اضف‬
‫اول المتريرات لكي نناقشها‪:‬‬
‫‪public‬‬ ‫‪Transform‬‬ ‫الكائن الذي سنقوم بتحريكه‪freeMove; //‬‬
‫‪public‬‬ ‫‪Transform‬‬ ‫الكاميرا التي ستتبع الكائن الذي سنقوم بتحريكه‪autoMove; //‬‬
‫‪public‬‬ ‫‪Transform‬‬ ‫الكائن الذي سيدور على المحور االفقي ‪pivotRotateY; //‬‬
‫‪public‬‬ ‫‪Transform‬‬ ‫الكائن الذي سيدور على المحور العمودي ‪pivotRotateX; //‬‬
‫‪public‬‬ ‫‪Transform‬‬ ‫الكاميرا الرئيسية في المشهد ‪cam; //‬‬

‫سرعة التتبع ‪public float speedFollow; //‬‬


‫سرعة الحركة ‪public float speedMove; //‬‬
‫سرعة الدوران‪public float speedRotate; //‬‬

‫قائمة بالمتغيرات الخاصة بالتقريب ‪[Header("Zoom Camera")] //‬‬


‫اقصى مسافة تقريب ‪public float maxZoom; //‬‬
‫اقل مسافة تقريب ‪public float minZoom; //‬‬
‫سرعة التقريب ‪public float zoomSpeed = 2f; //‬‬
‫فارق تقريب الكاميرا ‪private float deltaZoomCamera; //‬‬
‫متغير يحد من خشونة التقريب ‪private float lerpZoomCamera; //‬‬

‫قائمة بالمتغيرات التي ستختص بالدوران‪[Header("Clamp Rotate X")] //‬‬


‫اقصى دوران على المحور العمودي‪public float minX; //‬‬
‫اقل دوران على المحور العمودي‪public float maxX; //‬‬
‫متغير لتخزين زاوية الدوران على المحور االفقي ‪private float angleY; //‬‬
‫متغير لتخزين زاوية الدوران على المحور العمودي‪private float angleX; //‬‬

‫متغير للحصول على المكون الفيزيائي ‪private Rigidbody rb; //‬‬

‫كما ترى المتريرات منطقية وال يررك عدد المتريرات في الكود ألنك عاجالً ام آجالً ستست دمها في هذا المشروع وهي ضرورية جداً‪ .‬تالحو ان المتريرات‬
‫االولية وويفتها هي الحصول على موقع الكائنات التي ت ت بالحركة والدوران والحصول على موقع الكاميرا الرئيسية ألننا سنطبق عملية التقري فيها‪.‬‬
‫بها هو ‪ public‬إلتاحة ترييرها من‬ ‫المتريرات الم تصة بالسرعة مهمة فهي التي ستحدد سرعة الحركة‪ ،‬التتبع والدوران معرف الوصول ال ا‬
‫االنسبيكتور‪.‬‬

‫القائمة ‪ Zoom Camera‬مهتمة بإعدادات التقري ونقل الكاميرا بشكل ناعم من موقعها الحالي الى الموقع التالي فالمترير ‪ zoomSpeed‬بشكل عادي يقوم‬
‫بتحديد سرعة التقري ‪ ،‬بينما المترير ‪ lerpZoomCamera‬سي زن المسافة التي يمكن التقري اليها ومن ثم يأتي دور المترير ‪ deltaZoomCamera‬لنقل‬
‫قيمتة من الصفر الى المترير ‪ lerpZoomCamera‬لتت ل من ال شونة اثناء التقري وسنعتمد المترير ‪ deltaZoomCamera‬في التقري بينما المترير‬
‫‪ lerpZoomCamera‬يقوم بعمل ‪ Clamp‬او حدود للتقري ‪.‬‬

‫الصفحة ‪14‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫بما ان االمر اتضح سنقوم بتعريف الدالة ‪ Start‬لت زين اول قيم وللحصول على المكونات الضرورية‪ ،‬اضف الكود التالي‪:‬‬
‫)(‪void Start‬‬
‫{‬
‫اجلب المعدل التقريب بين اقصى و اقل قيمة‪float avgZoom = maxDelta - minDelta; //‬‬
‫اتخذ هذا المعدل كنسبة أولية للبدء‪lerpZoomCamera = avgZoom; //‬‬

‫اجلب قيمة المحور العمودي للدوران‪angleX = pivotRotateX.rotation.x; //‬‬

‫اجلب المكون الفيزيائي‪rb = freeMove.GetComponent<Rigidbody>(); //‬‬


‫}‬

‫اول العملية بسيطة جدا ً فكما ترى سنقوم بات اذ القيمة الوسطى بين اقصى واقل مسافة يمكن التقري اليها‪ .‬المترير ‪ angleX‬سي زن قيمتة االولية من محور‬
‫الدوران ‪ x‬ال ا بالمترير ‪ pivotRotateX‬ألنه هو الذي سيقوم بالدوران على المحور العمودي‪ .‬ا يرا ً نقوم بالحصول على المحور ‪ Rigidbody‬من‬
‫الكائن ‪ freeMove‬لتحريكه فيزيائياً‪.‬‬

‫‪ ‬العملية الثانية هي جعل الكاميرا تنور الى نقطة االرتكاز‪ ،‬أضف الكود التالي‪:‬‬
‫الكاميرا تنظر الى نقطة االرتكاز‪void CameraLookAtPivotPoint() //‬‬
‫{‬
‫خزن المسافة الفاصلة بين موقع الكاميرا و الكائن المتحرك‪//‬‬
‫;‪Vector3 pos = autoMove.position - cam.position‬‬
‫خزن النظر الى تلك المسافة‪//‬‬
‫;)‪Quaternion lookAt = Quaternion.LookRotation(pos‬‬
‫خزن دوران الكاميرا لتلك النقطة‪//‬‬
‫;)‪Quaternion rotation = Quaternion.Lerp(cam.rotation, lookAt, Time.deltaTime * 3f‬‬
‫قم بتدوير الكاميرا‪//‬‬
‫;‪cam.rotation = rotation‬‬
‫}‬

‫ميكانيكية هذا الكود تعتمد على حسا فارق المسافة بين الكاميرا ونقطة االرتكاز ومن ثم ت زين الناتج في المترير ‪ ،pos‬اما المترير ‪ lookAt‬سي زن قيمة‬
‫الدالة ‪ LookRotation‬ووويفتها هي النور الى كائن معين وهي تطل بارامتر من نوع ‪ Vector3‬وبما اننا قمنا بتجهيزه فأننا سنست دم المترير ‪.pos‬‬

‫المترير ‪ rotation‬سيقوم بنقل الدوران من موقعة الحالي الى قيم المترير ‪ ،lookAt‬ا يرا ً الكاميرا الرئيسية كما هو موضح في السطر اال ير ستقوم بات اذ‬
‫المرير ‪ rotation‬للحصول على محاور دوران ثانية‪.‬‬

‫العملية بسيطة فجل ما تتحدث عنه هو النور الى كائن معين‪ ،‬يمكنك بكل بساطة است دام الدالة )(‪ transform. LookAt‬في حالة وجدت مشكلة في تطبيق‬
‫هذا الكود مع العلم ان هذا الكود يعمل بشكل صحيح‪.‬‬

‫جعل الكائن ‪ Free Move‬يتحرك على المحورين ‪ x, z‬عن طريق مفاتيح الكيبورد‪ ،‬ستكون هذه العملية فيزيائية عبر است دام‬ ‫‪ ‬العملية الثانية تتل‬
‫الدالة )(‪ rb. MovePosition‬لتحريك الكائن لكن اوالً يج علينا تعريف مترير ي زن محاور الحركة و من ثم استعمال هذا المترير في التحرك‪.‬‬
‫اذن أضف الكود التالي لنناقشه‪:‬‬
‫تحريك الكائن الحر بناء على المدخالت‪void MoveFreeMove (string xAxis, string yAxis) //‬‬
‫{‬
‫قم بالدوران مع الكائن االفقي‪//‬‬
‫;‪freeMove.rotation = pivotRotateY.localRotation‬‬
‫حرك الكائن الى االمام و الخلف بناء على سرعة إزاحة المدخل‪//‬‬
‫;‪Vector3 forward = freeMove.forward * Input.GetAxis(yAxis) * speedMove * Time.deltaTime‬‬
‫حرك الكائن الى اليسار و اليمين بناء على سرعة إزاحة المدخل‪//‬‬
‫;‪Vector3 right = freeMove.right * Input.GetAxis(xAxis) * speedMove * Time.deltaTime‬‬
‫خزن متجهات الحركة‪//‬‬
‫;‪Vector3 movement = forward + right‬‬

‫هل يتم الضغط على الزر االيسر للماوس ؟‪if(!Input.GetMouseButton(0)) //‬‬


‫طبق الحركة الفيزيائية للكائن‪rb.MovePosition(rb.position + movement); //‬‬
‫}‬

‫ان مفهوم الكود الذي تراه امامك بسيط جدا ً فكل ما يركز علية هو ت زين محاور الحركة ومن ثم است دامها في الدالة )(‪ .MoveRotation‬كما تالحو الدالة‬
‫ت زن بارامترين يحددان اسمي محاور الحركة يعني اما عن طريق الكيبورد م‪ )Horizontal or Vertical‬او الماوس م‪ )Mouse X or Mouse Y‬لجعل‬
‫الالع يتحكم بحركة الكاميرا اما عن طريق الماوس او الكيبورد وهذا امر منطقي وتراه في االلعا االستراتيجية‪.‬‬

‫دعنا نناقش هذا الكود‪ ،‬تالحو ان الكائن ‪ freeMove‬دائما ً سي زن محاور الدوران ال اصة بالكائن ‪ pivotRotateY‬للدوران على المحور االفقي‪ ،‬هذه‬
‫االمر ستبقى ثابت طوال عملية البرمجة لت زين القيم الدوران دائماً‪.‬‬

‫المتريرين ‪ forward‬و ‪ Right‬ي زنان محاور الحركة الجانبية و االمامية فمثالً المترير ‪ forward‬سيجل لنا المحور االمامي ال ا بالكائن ‪freeMove‬‬
‫و ضربه بقيمة محور الحركة الذي سي زنه البارامتر ‪ yAxis‬وضربه في السرعة‪ ،‬نفس الشيء ينطبق على المترير ‪ right‬ولكن على المحور الجانبي‪ ،‬تجد‬
‫ان المتريرين من نوع ‪ Vector3‬وكل منهما ي زن اتجاه الحركة‪ ،‬المترير ‪ movement‬ي زن يجمع الحركات باضافة المحور االمامي مع المحور الجانبي‬
‫لعملية التحريك ونقوم بضر ازرار التحكم فيه‪ ،‬ا يرا ً يج ان نتحقق من انه لم يتم الضرط على زر الماوس االيمن إلضافة الحركة الى محاور المكون‬
‫‪ Rigidbody‬لتحريك الجسم فيزيائيا‪.‬‬

‫الى االن لم يتم استدعاء اي دالة في ‪ Update‬او ‪ FixedUpdate‬وبطبيعة الحال نحن نتعامل مع الحركة فيزيائيا ً ولهذا سنستدعي دالة الحركة في الدالة‬
‫‪.FixedUpdate‬‬

‫الصفحة ‪15‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫‪ ‬العملية التالية تت ل في ان الكائن ‪ autoMove‬سيتبع الكائن ‪ FreeMove‬في الحركة‪ ،‬العملية بسيط كل ماعليك فعلة اهو اضافة هذه الدالة الى‬
‫الملف البرمجي وستجد ان مفهومها سهل جداً‪:‬‬
‫اتبع الهدف‪void FollowTarget() //‬‬
‫{‬
‫;)‪autoMove.position = Vector3.Lerp(autoMove.position, freeMove.position, speedFollow * Time.deltaTime‬‬
‫}‬

‫في انه سيتم نقل الكائن ‪ autoMove‬الى موقع الكائن ‪ freeMove‬والسرعة يحددها المترير ‪ speedFollow‬الذي‬ ‫كما ترى ال شيء معقد العملية تت ل‬
‫عرفناه سابقاً‪.‬‬

‫او عمل‬ ‫ان نناقشهما وكالهما مهمتان جداً‪ ،‬الدالة االولى تركز على تدوير الكاميرات اما الثانية تهتم بالتقري‬ ‫وصلنا الى نهاية الطريق وتبقى لنا دالتان يج‬
‫‪ Zoom‬للكاميرا الرئيسية‪.‬‬

‫‪ ‬الدالة التالية ستكون دالة تدوير الكاميرات‪ ،‬اضف الكود التالي‪:‬‬


‫الدوران حول نقطة االرتكاز اعتمادا على بارامتر السرعة‪void RotatePivotAround(float speed) //‬‬
‫{‬
‫خزن االزاحة االفقية للماوس‪float x = Input.GetAxis("Mouse X") * speed; //‬‬
‫خزن االزاحة العمودية للماوس‪float y = Input.GetAxis("Mouse Y") * -speed; //‬‬
‫الزيادة بمقدار االزاحة االفقية‪angleY += x; //‬‬
‫الزيادة بمقدار االزاحة العمودية‪angleX += y; //‬‬
‫اعمل حدود لإلزاحة العمودية‪angleX = Mathf.Clamp(angleX, -minX, maxX); //‬‬

‫وظيفة هذا الكود هو تدوير الكائن الذي يدور افقيا مع احداثي الماوس االفقي ‪//‬‬
‫;‪Quaternion q_pivotY = pivotRotateY.localRotation‬‬
‫;)‪q_pivotY = Quaternion.Euler(0f ,angleY, 0f‬‬
‫= ‪pivotRotateY.localRotation‬‬
‫‪Quaternion.Lerp(pivotRotateY.localRotation,‬‬
‫‪q_pivotY,‬‬
‫;)‪speedFollow * Time.deltaTime‬‬

‫وظيفة هذا الكود هو تدوير الكائن الذي يدور عموديا مع احداثي الماوس العمودي ‪//‬‬
‫;‪Quaternion q_pivotX = pivotRotateX.localRotation‬‬
‫;)‪q_pivotX = Quaternion.Euler(angleX, 0f, 0f‬‬
‫‪pivotRotateX.localRotation = Quaternion.Lerp(pivotRotateX.localRotation,‬‬
‫‪q_pivotX,‬‬
‫;)‪speedFollow * Time.deltaTime‬‬
‫}‬

‫مسألة هذا الكود بسيطة جدا ً وفي حال امعنت النور ستجد ان الكود الذي يدور الكائن ‪ pivotRotateY‬منسوخ اسفله ولكن معدل ليقوم بتدوير الكائن‬
‫‪ pivotRotateX‬عمودياً‪ ،‬تت ل عملية الدوران في ت زين احداثي محاور الماوس االفقية و العمودية في متريرات تمثلها‪ ،‬مثالً المترير ‪ x‬ي زن احداثي‬
‫الماوس على المحور العمودي نفسي الشيء ينطبق على المترير ‪ y‬ولكن على المحور االفقي‪ .‬المتريرين ‪ angleX‬و ‪ angleY‬وويفتهما هي زيادة قيمتهما‬
‫تباعا ً لحركة لقيمة المترير ‪ x, y‬حس سرعة الماوس على المحورين قبل عودة القيمة الى صفر ميمكنك طباعة المترير ‪ x‬او ‪ y‬لتحقق من ترير قيمتهما)‬
‫فباالعتماد على المترير ‪ speed‬سنحد من تسارعهما‪ ،‬الحقا ً سيكون للمترير ‪ speed‬وويفه مهمه وهي تصفير قيم الحركة إلعادة ضبط موضع الكاميرات في‬
‫مكانها االفتراضي‪.‬‬

‫التعليقات في االعلى تدل على ماهي عمل هذا الكود فلو الحوت تجد ان الكود االول هو نفسه الكود الثاني ولكن مع تريير جميع ال ‪ Y‬الى ‪ X‬للدوران على‬
‫المحور العمودي‪ localRotation .‬و ‪ Rotation‬جميعها متريرات من نوع ‪ Quaternion‬ويمكنك مراجعتها من مستندات اليونتي وستجد ان االمر‬
‫صحيح‪ ،‬تتم عملية ت زين قيم المتريرات في مترير من نوع ‪ Quaternion‬فكما تالحو في الكود االول مالذي يقوم بتدوير الكائن ‪ pivotRotateY‬على‬
‫المحور االفقي) انه تم ت زين قيمة االولية في المترير ‪ q_pivotY‬من نوع ‪ Quaternion‬اسفلة يقوم المترير بتصفير جميع محاور الدوران ماعدا المحور ‪Y‬‬
‫عبر الدالة ‪ Euler‬ا يرا ً المترير ‪ pivotRotateY‬يت ذ القيم التي تم ت زينها في ‪ q_pivotY‬عبر االنتقال من قيمتة الحالية الى قيمة المترير عبر الدالة‬
‫‪ Quaternion.Lerp‬من ثم نعتمد على المترير ‪ speedFollow‬في تحديد سرعة ال‪ smooth‬هذا ليس شرط وتستطيع است دام مترير يمثل االنتقال بشكل‬
‫منفصل‪.‬‬

‫تالحو االن انه تم اسقاط الكود االول مع تريير قيم معينة لجعل الكائن ‪ pivotRotateX‬يدور على المحور االفقي والدالة ‪ Euler‬تقوم بتصفير جميع المحاور‬
‫ماعدا المحور ‪ X‬للدوران علية‪ .‬بطبيعة الحالة نست دم كال من المتريرين ‪ angleX‬و ‪ angleY‬في الدالة ‪ Euler‬للدوران على تلك االحداثيات الم زنة‪.‬‬

‫فوائد است دام مترير مثل ‪ angle‬يساعد على عمل ‪ Clamp‬للدوران بشكل بسيط كما عملت في البداية بحيث ان المترير ‪ angleX‬يقوم بعمل حدود للدوران‬
‫عبر ات اذ المتريرين ‪ minX‬و ‪ maxX‬لعمل حدود دوران على المحور ‪.x‬‬

‫‪ ‬ا يرا ً وليس آ را ً تبقى لنا جعل الكاميرا الرئيسية تعمل ‪ Zoom‬عبر عجلة الماوس او كما يعبر عنة في اليونتي م‪ ،)Mouse ScrollWheel‬اضف‬
‫الدالة التالية‪:‬‬
‫التقريب‪void CameraZoom() //‬‬
‫{‬
‫;)‪deltaZoomCamera = Mathf.Lerp(deltaZoomCamera, lerpZoomCamera, Time.deltaTime * speedRotate‬‬
‫;‪cam.position = pivotRotateX.position - pivotRotateX.forward * deltaZoomCamera‬‬

‫هل عجلة الماوس تدور الى االمام ؟‪if(Input.GetAxis("Mouse ScrollWheel") > 0f) //‬‬
‫التناقص بمقدار التقريب‪lerpZoomCamera -= speedZoom; //‬‬
‫هل عجلة الماوس تدور الى الخلف ؟‪if(Input.GetAxis("Mouse ScrollWheel") < 0f) //‬‬
‫الزيادة بمقدار التقريب‪lerpZoomCamera += speedZoom; //‬‬
‫اعمل حدود للتقريب‪lerpZoomCamera = Mathf.Clamp(lerpZoomCamera, minZoom, maxZoom); //‬‬
‫}‬

‫الصفحة ‪16‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫بالكائن‬ ‫ان تتحرك الكاميرا على المحور االمامي ال ا‬ ‫فيج‬ ‫بالتقري‬ ‫دعنا نناقش هذه العملية‪ ،‬الدالة ‪ CameraZoom‬بطبيعة الحالة ت ت‬
‫‪ pivotRotateX‬الن الكاميرا هي آ ر ابن ولهذا الكائن‪.‬‬

‫في جعل المترير ‪ deltaZoomCamera‬ينتقل من قيمتة الحالية الى قيمة المترير ‪ lerpZoomCamera‬وبدورة اال ير تزيد قيمتة وتقل‬ ‫الموضوع يتل‬
‫مع قيمة المترير ‪ speedZoom‬عبر تدوير عجلة الماوس الى االمام او ال لف لزيادة وإنقا القيمة‪.‬‬

‫كما تالحو في السرط االول ان المترير ‪ deltaZoomCamera‬ينتقل من قيمتة الى قيمة ‪ lerpZoomCamera‬بينما الكاميرا تقوم بحسا فارق المسافة‬
‫بين موقع الكائن ‪ pivotRotateX‬ومحوره االمامي وضر تلك القيمة في المترير ‪ deltaZoomCamera‬إلضافتها الى المسافة الفارقة وهي ستتحكم‬
‫القيمة او زيادتها كما تالحو في‬ ‫بالتقري والتبعيد‪ .‬زيادة قيمة المترير ‪ lerpZoomCamera‬هي عبر التحقق من حالة دوران العجلة ومن ثم اناق‬
‫الشرطين‪ ،‬ا يرا ً نقوم بعمل ‪ Clamp‬للمترير ‪ lerpZoomCamera‬وهو آ ر سطر لتأكد من انه تم تطبيق جميع القيم ومن ثم عمل الحدود لها لكيال تتجاوز‬
‫المسافة المسموح بها او تسلك سلوك شاذ عند آ ر قيم‪.‬‬

‫الى هنا ونكون قد عرفنا جميع الدوال في ملف واحد يمثل حركة الكاميرا االستراتيجية‪ ،‬ا يرا ً تبقى لنا استدعائها في الدالتين ‪ FixedUpdate‬و‪Update‬‬
‫بطبيعة الحالة الحركة عبارة عن كود فيزيائي وسنستدعي الدالة ال اصة بالحركة في ‪ FixedUpdate‬اضف الكود التالي‪:‬‬
‫)(‪void FixedUpdate‬‬
‫{‬
‫هل تم الضغط على زر الماوس االيسر او األيمن مع الضغط على مفتاح "شيفت" االيسر‪//‬‬
‫))‪if(!Input.GetMouseButton(0) && !Input.GetMouseButton(1) && Input.GetKey(KeyCode.LeftShift‬‬
‫{‬
‫طبق التحكم بالحركة عن طريق الماوس‪MoveFreeMove("Mouse X", "Mouse Y"); //‬‬
‫لم يتحقق اي من ذلك اذن‪}else{ //‬‬
‫طبق التحكم بالحركة عن طريق الكيبورد‪MoveFreeMove("Horizontal", "Vertical"); //‬‬

‫}‬
‫}‬

‫اذن االمر بسيط جدا ومحاط بشرط يتحقق من ان تم الضرط على مفتاح معين لتطبيق حركة ما‪ ،‬فمثالً بالضرط على ‪ Left Shift‬ستستطيع تحريك الكاميرا‬
‫عن طريق الماوس‪ ،‬غير ذلك ستقوم بتحريكها عن طريق الكيبورد‪ .‬التحقق من عدم ضرط الزر االيمن او االيس للماوس هو لتدوير الكاميرا وإعادة ضبط‬
‫موضعها سنناقشه في الدالة ‪.Update‬‬

‫الدالة الثانية ستكون ‪ ،Update‬أضف الكود التالي‪:‬‬


‫{ )( ‪void Update‬‬

‫اتبع الهدف‪FollowTarget(); //‬‬

‫انظر الى نقطة االرتكاز‪CameraLookAtPivotPoint(); //‬‬

‫قرب‪CameraZoom(); //‬‬

‫هل تم الضغط على الزر االيسر للماوس ؟‪if(Input.GetMouseButton(0)) //‬‬


‫تمكين التدوير‪RotatePivotAround(speedRotate); //‬‬

‫هل تم الضغط على الزر االيمن للماوس ؟‪if(Input.GetMouseButton(1)) //‬‬


‫{‬
‫أوقف التدوير‪RotatePivotAround(0); //‬‬
‫اعد المحورين الى هذه الحالة بناء على انها الحالة االفتراضية ألحداثي دوران الكاميرا‪//‬‬
‫;)‪angleY = Mathf.Lerp(angleY, 45f, Time.deltaTime * speedRotate‬‬
‫;)‪angleX = Mathf.Lerp(angleX, 20f, Time.deltaTime * speedRotate‬‬
‫}‬
‫}‬

‫اوالً نقوم بتعريف الدوال التي تحوي اي بارامتر ثم نقوم بالتحقق من عملية الدوران عبر الضرط على الزر االيسر للماوس وتالحو انه سيتم استبدال‬
‫البارامتر ‪ speed‬بالمترير ‪ speedRotate‬وسيتم تطبيق عملية الدوران عبر الماوس‪ .‬الشرط الثاني يتحقق من الضرط على الزر االيمن للماوس وسيتم‬
‫تصفير السرعة لعدم وضع اي سرعة للدوران او اتاحة التحكم وا يرا ً المتريرين ‪ angleX‬و ‪ angleY‬سينتقالن الى القيم المعطاة لضبط موضع الكاميرا عبر‬
‫تلك القيم اي ان الكائن ‪ pivotRotateX‬سيعتمد القيمة ‪ 20f‬على المحور ‪ x‬بينما الكائن ‪ pivotRotateY‬سيعتمد القيمة ‪ 45f‬على المحور ‪ Y‬وبهذا الشكل‬
‫سنعيد الكاميرات الى وضعيتها االفتراضية عند تحقق الشرط‪.‬‬

‫والنور‪،‬‬ ‫كما ترى العملية طويله نوعا ً ما ولكنها فعالة جدا ً وفي حالة قمت بتجربة الكاميرا سترى انها مريحة لعملية التحكم واعادة ضبط الموضع والتقري‬
‫ا يرا ً تأكد من انه تم ملء المتريرات كما هو موضح او قم بترييرها لتتناس مع ماتريد الوصول اليه‪:‬‬

‫الصفحة ‪17‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


:‫الكود النهائي‬
:‫ا يرا ً اعطيك الشكل النهائي للملف البرمجي ويمكنك نس ة ولصقة للتأكد‬
using UnityEngine;
using System.Collections;

public class CameraRTS : MonoBehaviour {

public Transform freeMove;


public Transform autoMove;
public Transform pivotRotateY;
public Transform pivotRotateX;
public Transform cam;
public float speedFollow = 7f;
public float speedMove = 50f;
public float speedRotate = 7f;

[Header("Zoom Camera")]
public float maxZoom = 20f;
public float minZoom = 1f;
public float speedZoom = 2f;
private float deltaZoomCamera;
private float lerpZoomCamera;

[Header("Clamp Rotate X")]


public float minX = 0f;
public float maxX = 80f;
private float angleY;
private float angleX;

private Rigidbody rb;

void Start()
{

float avgZoom = maxZoom - minZoom;


lerpZoomCamera = avgZoom;

angleX = pivotRotateX.rotation.x;

rb = freeMove.GetComponent<Rigidbody>();
}

void Update () {

FollowTarget();

CameraLookAtPivotPoint();

CameraZoom();

if(Input.GetMouseButton(0))
RotatePivotAround(speedRotate);

if(Input.GetMouseButton(1))
{
RotatePivotAround(0);
angleY = Mathf.Lerp(angleY, 45f, Time.deltaTime * speedRotate);
angleX = Mathf.Lerp(angleX, 20f, Time.deltaTime * speedRotate);
}
}

void FixedUpdate()
{

18 ‫الصفحة‬ Facebook.com/EMAD.Unity3D
if(!Input.GetMouseButton(0) && !Input.GetMouseButton(1) && Input.GetKey(KeyCode.LeftShift))
{
MoveFreeMove("Mouse X", "Mouse Y");
}else{
MoveFreeMove("Horizontal", "Vertical");

}
}

void CameraLookAtPivotPoint()
{
Vector3 pos = autoMove.position - cam.position;
Quaternion lookAt = Quaternion.LookRotation(pos);
Quaternion rotation = Quaternion.Lerp(cam.rotation, lookAt, Time.deltaTime * 3f);
cam.rotation = rotation;
}

void MoveFreeMove (string xAxis, string yAxis) {

freeMove.rotation = pivotRotateY.localRotation;

Vector3 forward = freeMove.forward * Input.GetAxis(yAxis) * speedMove * Time.deltaTime;


Vector3 right = freeMove.right * Input.GetAxis(xAxis) * speedMove * Time.deltaTime;

Vector3 movement = forward + right;


if(!Input.GetMouseButton(0))
rb.MovePosition(rb.position + movement);
}

void RotatePivotAround(float speed)


{
float x = Input.GetAxis("Mouse X") * speed;
float y = Input.GetAxis("Mouse Y") * -speed;
angleY += x;
angleX += y;
angleX = Mathf.Clamp(angleX, -minX, maxX);

Quaternion q_pivotY = pivotRotateY.localRotation;


q_pivotY = Quaternion.Euler(0, angleY, 0);
pivotRotateY.localRotation = Quaternion.Lerp
(pivotRotateY.localRotation,
q_pivotY,
speedFollow * Time.deltaTime);

Quaternion q_pivotX = pivotRotateX.localRotation;


q_pivotX = Quaternion.Euler(angleX, 0, 0);
pivotRotateX.localRotation = Quaternion.Lerp
(pivotRotateX.localRotation,
q_pivotX,
speedFollow * Time.deltaTime);

void FollowTarget()
{
autoMove.position = Vector3.Lerp
(autoMove.position,
freeMove.position,
speedFollow * Time.deltaTime);
}

void CameraZoom()
{
deltaZoomCamera = Mathf.Lerp(deltaZoomCamera, lerpZoomCamera, Time.deltaTime * speedRotate);
cam.position = pivotRotateX.position - pivotRotateX.forward * deltaZoomCamera;

if(Input.GetAxis("Mouse ScrollWheel") > 0f)


lerpZoomCamera -= speedZoom;
if(Input.GetAxis("Mouse ScrollWheel") < 0f)
lerpZoomCamera += speedZoom;

lerpZoomCamera = Mathf.Clamp(lerpZoomCamera, minZoom, maxZoom);


}

19 ‫الصفحة‬ Facebook.com/EMAD.Unity3D
‫محاكاة منظور الشخص االول ‪:FP‬‬
‫االول‪ ،‬هذا النوع من المحاكاة تكون فيها الكاميرا هي الالع وتوهر في العديد‬ ‫االول او مطلق الش‬ ‫‪ First Person‬با تصار ‪ FP‬وهو منوور الش‬
‫من االلعا المشهورة مثل ‪ Call of Duty‬و ‪ Battlefield‬ودائما ً يكون الالع حامالً لسالح او ما شابه ذلك‪ ،‬المهم انك تستطيع تحريك الكاميرا او باألصح‬
‫تدوير الكاميرا على المحورين ‪ x, y‬وتطبيق هذه الميكانيكية ت تلف من محرك الى آ ر ولكن في حالة استطعت تطبيق الميكانيكية التي سنست دمها سترى انك‬
‫تستطيع اسقاطها على اي محرك آ ر شبيه‪.‬‬

‫في الفقرة السابقة ناقشنا مسألة الكاميرا وكيف تدور ومشكلة ان تدوير الكاميرا نفسها على المحورين ‪ x, y‬ال ا بها هو امر سيء للراية وتوصلنا الى‬
‫طريقة وهي عمل عدة ابناء وكل ابن يدور على محور معين وا يرا ً الكاميرا هي آ ر ابن آل ر كائن في التسلسل الهرمي‪ .‬هنا سنطبق نفس العملية ولكن من‬
‫سدور على المحور االفقي هو الالع نفسها بينما الكاميرا تدور على المحور العمودي فقط وستجد ان االمر جيد حال تطبيقه‪.‬‬

‫اذن في المشهد ‪ First Person Shooter‬تجد الش صية ‪ Character FPS‬يتفرع من هذه الش صية عدة ابناء كما هو موضح في الصورة‪:‬‬

‫كما ترى هناك عدة ابناء فمالً الكائن ‪ Pivot‬هو نقطة ارتكاز الكاميرا‪ ،‬بينما الكائن ‪ Camera‬هو من سيقوم بتدوير الكاميرا وفي حال الحوت موقعة ستجد‬
‫انه يقع في نفس محاور نقط االرتكاز‪ ،‬يتفرع من اال ير عدة ابناء وهم‪ :‬الكاميرا الرئيسية‪ ،‬نقطة حمل السالح‪ ،‬كائن ا ير است دمته لقذف القنابل وهو غير‬
‫االول مع اتاحة الفرصة لتعديل القيم بحيث تتناس مع متطلباتك‪.‬‬ ‫ضروري هنا‪ .‬ما يهم االن هو كيف سنقوم بمحاكاة منوور الش‬

‫انشاء الملف ‪:CameraFPS‬‬


‫العملية بسيطة فكما طبقنا االمر على الكاميرا االستراتيجية سنطبق االمر هنا‪ ،‬انشئ ملف برمجي وليكن ‪ Camera FPS‬وضعة في الش صية كمكون‪ ،‬ا يرا ً‬
‫اضف المتريرات التالية‪:‬‬
‫سرعة الماوس‪public float speedMouse = 2f; //‬‬
‫مقدار اللف على المحور الجانبي‪public float steerRotateZ = 2f; //‬‬
‫لتخزين مقدار اللف على المحور الجانبي‪private float steerZ; //‬‬

‫اقصى زاوية عمودية‪public float maxX = 60f; //‬‬


‫اقل زاوية عمودية‪public float minX = 90f; //‬‬
‫لتخزين زاوية الدوران العمودي‪private float angleX; //‬‬

‫النعومة (نعومة التحكم)‪public float smoothing = 15f; //‬‬


‫هل تريد استخدام النعومة ؟ نعم‪public bool smooth = true; //‬‬

‫محاور الكاميرا و الشخصية لتطبيق االمر عليهم‪//‬‬


‫;‪public Transform myCamera,myCharacter‬‬
‫لتخزين دوران الكاميرا و الشخصية‪//‬‬
‫;‪private Quaternion q_camera, q_character‬‬

‫االول‪ .‬تجد المتريرات االولى الثالثة وهي لتحديد سرعة الماوس ومقدار‬ ‫اذن كما تالحو المتريرات االولية ستتيح لنا التحكم الكامل بمحاكاة منوور الش‬
‫الدوران على المحور ‪z‬بينما المترير ‪ steerZ‬ينقل قيمتة الى قيمة المترير ‪ ،steerRotateZ‬غالبا ً ال يتم الدوران على المحور ‪ z‬ولكن في حالة رغبت فهذا‬
‫سيعطي جمالية للمنوور بع الشيء‪.‬‬

‫المترير ‪ angleX‬كما تعلم هو لعمل ‪ Clamp‬للدوران وتجد المتريرين ‪ maxX‬و ‪ minX‬لوضع الحدود‪ .‬قد تسألنا لماذا لم اعرف المترير ‪angleY‬؟ االمر بكل‬
‫بساطة هو اننا نريد عمل حدود دوران على المحور العمودي ‪ X‬فقط بينما المحور االفقي ‪ Y‬لن نرير فيه شيء‪ ،‬اساسا ً ال يجوز عمل ‪ Clamp‬للمحور ‪ Y‬واال‬
‫كيف ستدور حول نفسك اكثر من مرة ؟ المهم اننا سنطبق عملية وضع الحدود كما في المرة السابقة‪.‬‬

‫تجد المتريرين ‪ smoothing‬و ‪ smooth‬وسيحدد لك هل سيتم تطبيق االنتقال االنسيابي او التمهيدي من محور الى آ ر؟ وسيتم تطبيق االمر في حالة كانت‬
‫قيمة المترير ‪smooth‬هي ‪.true‬‬

‫ا يرا ً المتريرين ‪ myCamera‬و ‪ myCharacter‬ضروريان لجل محاور الدوران من الكائنين بحيث يقوم المتريرين ‪ q_camer‬و ‪q_character‬‬
‫بت زين القيمة االفتراضية لمحاور الدوران في الكائنين ومن ثم سنست دم المتريرين في الدوران عن طريق احداثيات الماوس وا يرا ً الكائنين سي زنان قيمة‬
‫المتريرين من نوع ‪.Quaternion‬‬

‫االن في الدالة ‪ Start‬يج ت زين القيم الحالية لمحاور الدوران في الكاميرا والش صية الحو الكود‪:‬‬
‫)(‪void Start‬‬
‫{‬
‫خزن محاور دوران الكاميرا‪q_camera = myCamera.rotation; //‬‬
‫خزن محاور دوران الشخصية‪q_character = myCharacter.rotation; //‬‬
‫}‬

‫الصفحة ‪20‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫ت زين قيم المترير ‪ rotation‬و ‪ localRotation‬للكاميرا مبما انها ابن للش صية) في المتريرين من نوع‬ ‫في جل‬ ‫كما ترى العملية بسيطة جدا ً وتت ل‬
‫‪.Quaternion‬‬

‫بتطبيق التحكم بشكل ناعم وبطبيعة الحال ستعمل في حالة كانت قيمة المترير ‪ smooth‬هي‬ ‫الدالة الثانية ستكون ‪ ApplySmoothingControl‬وست ت‬
‫‪ ،true‬اضف الكود التالي‪:‬‬
‫طبق التحكم الناعم للكاميرا و الشخصية‪void ApplySmoothingControl(Transform camera, Transform character) //‬‬
‫{‬
‫قم بتدوير الشخصية‪//‬‬
‫;)‪character.rotation = Quaternion.Lerp(character.rotation, q_character , Time.deltaTime * smoothing‬‬
‫قم بتدوير الكاميرا‪//‬‬
‫;)‪camera.localRotation = Quaternion.Lerp(camera.localRotation, q_camera , Time.deltaTime * smoothing‬‬
‫}‬

‫كما ترى العملية بسيطة جدا ً فقي حال فهمت السطر االول فستفهم بكل تأكد السطر الثاني ألنه نس ة منه‪ ،‬دعنا نناقش الش صية فكما تالحو تقوم الش صية‬
‫باالنتقال من موقع دورانها الحالي الى القيم الم زنة في المترير ‪ q_character‬ولكن حاليا ً ال توجد قيمة م زنة في المترير سوى التي زناها في البداية! ال‬
‫تقلق سنقوم بجعلة ي زن مقدار الترير في حركة الماوس‪ .‬تجد ان الدوران االنسيابي يعتمد على قيمة المترير ‪ smoothing‬لالنتقال بحس تلك القيمة‬
‫وافتراضيا ً هي ‪ 15‬وستجدها مناسبة ولك الحرية في ترييرها‪.‬‬

‫الدالة الثانية ستكون ‪ SteerAngleZ‬وهذه الدالة ستقوم بتدوير المحور ‪ z‬ولكن في النهاية هي تعتمد على قيمة المترير ‪ steerRotateZ‬ففي حالة كانت صفر‬
‫فلن تقوم بتدور المحور‪ ،‬اضف الكود التالي‪:‬‬
‫)(‪void SteerAngleZ‬‬
‫{‬
‫ابقي المحور العمودي و اغلق المحور االفقي و اضف الف الى المحور الجانبي‪//‬‬
‫;)‪myCamera.localEulerAngles = new Vector3(myCamera.localEulerAngles.x, 0f, steerZ‬‬

‫هل الماوس يتحرك الى اليمين ؟‪if(Input.GetAxis("Mouse X") > 0f) //‬‬
‫الزيادة في اللف‪steerZ = Mathf.Lerp(steerZ, steerRotateZ, Time.deltaTime); //‬‬

‫هل الماوس يتحرك الى اليسار ؟‪else if(Input.GetAxis("Mouse X") < 0f) //‬‬
‫التناقص في اللف‪steerZ = Mathf.Lerp(steerZ, -steerRotateZ, Time.deltaTime); //‬‬

‫الماوس ال يتحرك‪else //‬‬


‫أوقف اللف‪steerZ = Mathf.Lerp(steerZ, 0f, Time.deltaTime * 2f); //‬‬
‫}‬

‫اذن لنناقش مسألة هذا الكود‪ ،‬كما تعلم ان الكاميرا هي من سيدور على المحور ‪ x‬وهذا يفسر سب تصفير المحور ‪ Y‬وترك المحور ‪ ،x‬بينما المحور ‪ z‬سيدور‬
‫اعتمادا ً على قيمة المترير ‪ .steerZ‬الشروط تتحقق من كون الماوس يتحرك على المحور االفقي ففي حالة كانت القيمة أكبر من صفر سينتقل عكس اتجاه‬
‫حركة الماوس اعتمادا ً على قل اشارة المترير ‪ steerRotateZ‬ونفس الشيء في الشرط الثاني‪ ،‬ا يرا ً في حالة لم يتحقق اي من الشرطين اي ان الماوس لم‬
‫يتحرك او توقف عن الحركة سيعيد المترير ‪ steerZ‬نفسة الى القيمة صفر وهذا بدورة سيجعل المحور ‪ z‬يرجع الى القيم صفر‪.‬‬

‫الى هنا ونعود الى الدالة ‪ Update‬لمناداة الدالتين‪ ،‬قبل مناداتهما يج ان نعرف متريرين من نوع ‪ float‬ي زنان مقدار تحرك الماوس على المحور االفقي و‬
‫العمودي ومن ثم سنستعمل هذه القيم في زيادة قيمة المترير ‪ angleX‬وعمل ‪ Clamp‬لها وتدوير الش صية‪ ،‬اضف الكود التالي في ‪:Update‬‬
‫)(‪void Update‬‬
‫{‬
‫خزن إزاحة الماوس االفقية‪float xRot = Input.GetAxis("Mouse X") * speedMouse; //‬‬
‫خزن إزاحة الماوس العمودية‪float yRot = Input.GetAxis("Mouse Y") * speedMouse; //‬‬

‫الزيادة في مقدار االزاحة العمودية‪angleX += yRot; //‬‬


‫اعمل حدود لزاوية الدوران العمودية‪angleX = Mathf.Clamp(angleX, -minX, maxX); //‬‬
‫قم بتدوير الشخصية على المحور االفقي‪//‬‬
‫;))‪q_character *= Quaternion.Euler(new Vector3(0f, xRot, 0f‬‬
‫قم بتدوير الكاميرا على المحور العمودي‪//‬‬
‫;))‪q_camera = Quaternion.Euler(new Vector3(-angleX, 0f, 0f‬‬
‫}‬

‫كما ترى العملية بسيطة جدا ً وهي نفسها التي است دمناها في الكاميرا االستراتيجية‪ ،‬االن تجد المترير ‪ q_character‬و ‪ q_camera‬ي زنان محاور‬
‫الدوران ولكن هناك ا تالف بسيط في ان الكاميرا تعتمد على مقدار الترير في المترير ‪ angleX‬بينما الش صية تقوم بضر القيمة الحالية في مقدار ترير‬
‫المترير ‪ xRot‬على المحور ‪ ،Y‬بهذا الشكل يكون كالً من المتريرين الم تصين بدوران الكاميرا و الش صية ي زنان مقدار الترير في حركة الماوس‪.‬‬

‫االن يج ان نتحقق من ان قيمة المترير ‪ smooth‬هي ‪ true‬ومن ثم سنشرل الدالة ‪ ApplySmoothingControl‬غير ذلك سنجعل التحكم عادي‪ .‬أضف‬
‫الكود التالي أسفل هذا الكود‪:‬‬
‫تم تفعيل النعومة‪if(smooth) //‬‬
‫{‬
‫طبق التحكم على الكاميرا و الشخصية‪ApplySmoothingControl (myCamera, myCharacter); //‬‬

‫لم يتم تفعيل النعومة‪}else{ //‬‬


‫اجلب محاور الدوران للشخصية بشكل مباشر‪myCharacter.localRotation = q_character; //‬‬
‫اجلب محاور الدوران للكاميرا بشكل مباشر‪myCamera.rotation = q_camera; //‬‬
‫}‬
‫دالة لف المحور الجانبي‪SteerAngleZ(); //‬‬

‫الصفحة ‪21‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


q_character ‫كما تالحو سيتم تطبيق الدالة في حالة كانت النعومة مفعلة غير ذلك سيعتمد كالً من الكاميرا والش صية على القيمة الم زنة في المتريرين‬
.‫ بشكل مباشر‬q_camera‫و‬

‫ اذن ليست العملية صعبة‬.ً‫ ويج ان تكون آ ر سطر لضمان تدوير الكاميرا والش صية اوال‬Update ‫ في‬SteerAngleZ ‫آ ر شيء تبقى هو مناداة الدالة‬
‫ حقيقة االمر هذه الطريقة هي طريقة نتائجها‬،‫كما كنت تفكر ففي حالة قمت بتطبيق ميكانيكية الكاميرا االستراتيجية فستعرف مدى بساطة هذا الكود وفعاليته‬
‫رائعة واعتمدنا على المتريرات الالزمة والمناسبة لعملية الدوران وبعيدا ً عن الشروحات المعقدة فانا حاولت ان اقدم الشرح بأبسط طريقة ممكنة مع مناقشة‬
.‫ في عملية تدوير االجسام بشكل احترافي‬Quaternion ‫اهم االجزاء وفي الفقرات التالية بأذن هللا ستعرف المدى الكبير في است دامنا للفئة‬

:‫الكود النهائي‬
using UnityEngine;
using System.Collections;

public class CameraFPS : MonoBehaviour {

public float speedMouse = 2f;


public float steerRotateZ = 2f;
private float steerZ;

public float maxX = 60f;


public float minX = 90f;
private float angleX;

public float smoothing = 15f;


public bool smooth = true;

public Transform myCamera,myCharacter;


private Quaternion q_camera, q_character;

void Start()
{
q_camera = myCamera.rotation;
q_character = myCharacter.localRotation;
}

void Update()
{
float xRot = Input.GetAxis("Mouse X") * speedMouse;
float yRot = Input.GetAxis("Mouse Y") * speedMouse;

angleX += yRot;
angleX = Mathf.Clamp(angleX, -minX, maxX);

q_character *= Quaternion.Euler(new Vector3(0f, xRot, 0f));


q_camera = Quaternion.Euler(new Vector3(-angleX, 0f, 0f));

if(smooth)
{
ApplySmoothingControl(myCamera, myCharacter);

}else{
myCharacter.localRotation = q_character;
myCamera.rotation = q_camera;
}

SteerAngleZ();
}

void ApplySmoothingControl(Transform camera, Transform character)


{
character.rotation = Quaternion.Lerp(character.rotation, q_character, Time.deltaTime *
smoothing);
camera.localRotation = Quaternion.Lerp(camera.localRotation, q_camera, Time.deltaTime *
smoothing);
}

void SteerAngleZ()
{
myCamera.localEulerAngles = new Vector3(myCamera.localEulerAngles.x, 0f, steerZ);

if(Input.GetAxis("Mouse X") > 0f)


steerZ = Mathf.Lerp(steerZ,steerRotateZ, Time.deltaTime);
else if(Input.GetAxis("Mouse X") < 0f)
steerZ = Mathf.Lerp(steerZ,-steerRotateZ, Time.deltaTime);
else
steerZ = Mathf.Lerp(steerZ, 0f, Time.deltaTime * 2f);
}
}

22 ‫الصفحة‬ Facebook.com/EMAD.Unity3D
‫محاكاة الطائرة النفاثة‪:‬‬
‫سأناقش في هذه الفقرة الميكانيكية الصحيحة لمحاكاة حركة الطائرة‪ ،‬تعتبر هذه الفقرة جزء مهم في هذا الكتا ولعل الكثير قد حاول محاكاة حركة طائرة على‬
‫اليونتي واون ان البع لم يتوصل الى نتائج مرضيه‪ ،‬هنا سنقوم بأنشاء كود برمجي يتعامل مع الطائرة فيزيائيا ً بحيث نقوم بمحاكاة حركة الطائرة بشكل‬
‫احترافي بالتعامل مع محرك الفيزياء اعتمادا ً على االحتكاكات‪.‬‬

‫لمحة عن االحتكاكات‪:‬‬
‫االحتكاك‪ :‬هي القوة المقاومة التي تحدث عند تحرك سطحين متالصقين باتجاهين متعاكسين عندما يكون بينهما قوة ضاغطة تعمل على تالحمهما معا موزن‬
‫أحد الجسمين مثال)‪ .‬وتنتج كمية من الحرارة‪.‬‬

‫يحدث االحتكاك بين المواد الصلبة‪ ،‬السائلة والرازية أو أي تشكيلة منهم‪.‬‬

‫‪ :‬القوة الضاغطة بين‬ ‫‪ X µ‬حيث‪ :‬قح‪ :‬قوة االحتكاك ق‬ ‫وقوة االحتكاك هي حاصل ضر القوة الضاغطة بين الجسمين في معامل االحتكاك‪ .‬قح = ق‬
‫الجسمين أو القوة العمودية على السطح الفاصل بينهما‪.‬‬

‫معامل االحتكاك‪ ،‬إما الساكن مس) أو الحركي مح)‪.‬‬

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

‫معامل االحتكاك‪ :‬معامل االحتكاك هو كمية عددية تست دم للتعبير عن النسبة بين قوة االحتكاك بين جسمين والقوة الضاغطة بينهما‪ ،‬وليس له وحدة قياس‪.‬‬
‫ويعتمد على مادتي الجسمين‪ .‬مثال الجليد على المعدن لهما معامل احتكاك قليل مأي إنهما ينزلقان على بع بسهولة)‪ .‬أما المطاط على األسفلت فلهما معامل‬
‫احتكاك عالي جدا مال ينزلقان على بع )‪µ .‬س السطح ‪ 2‬السطح ‪ 0.06 1‬جليد ش ‪ 0.1 - 0.02‬ثلج نحاس أصفر ‪ 0.07‬معدن ممشحم) معدن ‪0.25‬‬
‫ش بلوط ش بلوط ‪ 0.9 - 0.5‬رسانة ممبللة) مطاط ‪ 1 - 0.7‬رسانة جافة مطاط‪.‬‬

‫يعتبر معامل االحتكاك كمية تجريبية‪ ،‬أي انه يج قياسه عن طريق التجربة وال يمكن حسابه بالمعادالت الرياضية‪ .‬كما أن معوم المواد الجافة مع بعضها‬
‫تعطي معامل احتكاك بين ‪ 0.3‬و‪ .0.6‬ومن الصع الحصول على قيمة ارج هذا المجال‪ .‬إن قيمة ‪ 0‬لمعامل االحتكاك تعني انه ال يوجد احتكاك بالمرة‬
‫وسينزلق الجسمان على بعضهما إلى ما ال نهاية‪ .‬ويكون معامل االحتكاك الساكن أكبر من الحركي ألن النتوءات والفجوات الموجودة بين أسطح األجسام‬
‫المتالصقة تتدا الن في بعضهما فتسببان مقاومة السطحين لالنزالق‪ .‬ولكن إذا بدأ الجسم في االنزالق فلن يتوفر الوقت الالزم للسطحين لكي يتالحما تماما ً كل‬
‫مع اآل ر‪ .‬ونرمز له ‪ Fr‬بالنسبة إلى االحتكاك المقاوم و‪ Fm‬لالحتكاك المتحرك‪.‬‬

‫االحتكاك في ‪:Unity‬‬
‫عند اضافة المكون ‪ Rigidbody‬الى اي جسم في اليونتي يتم تطبيق الحركة الفيزيائية فيه بحيث ان هذا الجسم يحاكي الحركة في الواقع باالضافة الى ان‬
‫قوانين نيوتن ومعادالت االحتكاك ال طي والزاوي تنطبق علية لكي تساعد على جعل الجسم يحاكي الحركة الفيزيائية الواقعية باالعتماد على محرك الفيزياء‪.‬‬

‫يتم التعامل مع االحتكاك ال طي والزاوي عبر المكون ‪ Rigidbody‬عن طريق تريير قيم المتريرين ‪ Drag‬ماالحتكاك ال طي) و‪ Drag Angular‬ماالحتكاك‬
‫الزاوي)‪ ،‬تعطيك هذه القيم نتائج مماثلة للواقع ولكن يج عليك االستعانة بمصادر علمية أل ذ معامل االحتكاك ال ا باألجسام‪ .‬عمليا ً سنعتمد على تريير‬
‫قيمة المترير ‪ drag‬للت ل من مشكلة تأثير الجاذبية على الطائرة اي سنزيد قوة االحتكاك في الهواء فكلما تسارعت الطائرة زادت نسبة احتكاكها مع الهواء‪.‬‬

‫محاور الحركة‪:‬‬

‫تدور الطائرة حول ‪ 3‬محاور متعامدة على بعضها البع ومتقاطعة عند نقطة مركز الثقل للطائرة ‪ .center of gravity CG‬عند التحكم بالموقع والوجهة‬
‫يج على الطيار أن يكون قادرا على الدوران بالطائرة حول كل محور من تلك المحاور‪.‬‬

‫الصفحة ‪23‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫‪ ‬المحور العمودي يمر المحور العمودي ‪ vertical axis‬الل الطائرة من األعلى لألسفل‪ .‬والدوران حول ذلك المحور يسمى االنعراج ‪.YAW‬فعندما‬
‫يرير االنعراج االتجاه فإن مقدمة الطائرة تتحرك يمينا أو يسارا‪ .‬وسطح التوجيه الرئيسي لذلك االنعراج هو الدفة‪ .‬ويعطي الجنيح تأثير ثانوي على‬
‫االنعراج‪.‬‬
‫‪ ‬المحور العمودي يمر المحور العمودي ‪ vertical axis‬الل الطائرة من األعلى لألسفل‪ .‬والدوران حول ذلك المحور يسمى االنعراج ‪.YAW‬فعندما‬
‫يرير االنعراج االتجاه فإن مقدمة الطائرة تتحرك يمينا أو يسارا‪ .‬وسطح التوجيه الرئيسي لذلك االنعراج هو الدفة‪ .‬ويعطي الجنيح تأثير ثانوي على‬
‫االنعراج‪.‬‬
‫‪ ‬المحور األفقي يمر المحور األفقي أو الجانبي ‪ lateral axis‬الل باع الجناح والدوران حول هذا المحور يسمى االنحدار ‪ .PITCH‬االنحدار يرير من‬
‫اتجاه مقدمة الطائرة العمودي مأسفل أو أعلى)‪ .‬سطح التوجيه الرئيسي لذلك االنحدار هو الرافع‪ .‬من المهم اإلشارة بأن تلك المحاور تتأثر بترير حركة‬
‫الطائرة وأيضا بالنسبة لسطح األر ‪ ،‬أي بمعنى لو أن الجناح األيسر يكون متجها نحو سطح األر فإن المحور العمودي يكون موازيا لسطح‬
‫لألر بينما المحور األفقي يكون عموديا عليها‪.‬‬

‫انشاء الملف ‪:AirplaneController‬‬


‫ربما انك قد ا ذت فكرة عن االحتكاك ومحاور الدوران فسنطبق االمر هنا برمجياً‪ ،‬سأقسم هذه الفقرة الى عدة فقرات تتفرع منها بحيث انه في كل فقرة‬
‫سنناقش ملف برمجي وسأحاول ترطية جميع الجوان مثل االصوات والمؤثرات المرئية ففي حالة قمت بالد ول الى المشهد‪ Airplane‬ستجد كائن الطائرة‬
‫‪ AircraftJet‬قمت بأ ذ هذا الموديل من احد مشاريع اليونتي وبما ان كل شيء مضاف اليه فسأقوم بتوضيح مما تترك الطائرة في المشهد‪.‬‬

‫اذن في حالة راجعت الطائرة ستجد ان هناك عدة ابناء تتفرع منها كما في الصورة‪:‬‬

‫تجد كائن الطائرة وعدة ابناء وهم‪:‬‬

‫‪ :Colliders‬يحوي كائنات تحتوي على مكون التصادم وتالحو ان الكائنات موزعة على الطائرة بشكل كامل‪.‬‬

‫الكائن الثاني والثالث هما لمجسم الطائرة فهم غير مهمين حالياً‪.‬‬

‫‪ Wheels‬هذا الكائن يحتوي على عجالت الطائرة وفي حالة فتحته ستجد العجالت الثالث االمامية و ال لفية على اليمين و اليسار لكن لن اناقش العجالت هنا‬
‫وسنتركها للفقرة القادمة‪.‬‬

‫الكائن ‪ Particle‬يحوي العديد من الكائنات كما تالحو في الصورة‪:‬‬

‫تجد الكائن ‪Trail‬وهو موضوع في طرفي الجناح االيمن وااليسر‪ Effect Right .‬قمت بإنجازه ش صيا ً وهذا هو تأثير احتراق الوقود في المحرك‪ ،‬تالحو‬
‫ان هناك ابناء يتفرعون منه وهم‪ Smoke :‬الد ان الناتج عن تأثير االحتراق‪ First Fire ،‬هذا المؤثر يعمل اوالً وهو يوهر احتراق الوقود في البداية‪.‬‬

‫جميع هذه المؤثرات مهيأة لتريير قيمها برمجيا ً وستالحو الحقا ً عند برمجة المؤثرات المرئية كيف ان المؤثرات تعمل بشكل جميل‪.‬‬

‫والطائرة‪.‬‬ ‫ا يرا ً تجد الكائن ‪ Point Ray‬وهو من سيقوم بحسا المسافة بين االر‬

‫‪ .‬االن انشئ‬ ‫اول ملف سيتحكم بحركة الطائرة وهذا ضروري مبدئياً‪ ،‬بدايةً سنعرف المتريرات الضرورية لتحريك الطائرة وحسا المسافة بينها وبين االر‬
‫ملف برمجي وسمه ‪ AirplaneController‬واضف المتريرات التالية‪:‬‬

‫نوع القوة هو دفع‪public ForceMode typeForce = ForceMode.Impulse; //‬‬


‫سرعة الدوران‪public float speedRotate = 2f; //‬‬
‫اقصى سرعة للطائرة‪public float maxSpeed = 100f; //‬‬

‫الصفحة ‪24‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫نسبة تغير االحتكاك الزاوي‪public float changeDragAngle = 30f; //‬‬
‫نسبة تغير االحتكاك الخطي‪public float changeDrag = 30f; //‬‬

‫لتخزين محاور دوران الطائرة‪private Quaternion rotAir; //‬‬


‫نقطة انطالق الشعاع للتحقق من وقوف الطائرة على االرض‪private Transform pointRay; //‬‬
‫سرعة الطائرة‪public float speedFlay {get; private set;} //‬‬
‫السرعة االنسيابية للطائرة‪private float speedFlayLerp; //‬‬
‫المكون الفيزيائي‪private Rigidbody rb; //‬‬
‫العجالت الفيزيائية للطائرة‪private WheelCollider[] wheels; //‬‬

‫كما ترى المتريرات واضحة من اسمها‪ ،‬المترير ‪ typeForce‬لتحديد نوع الدفع‪ ،‬وتجد مترير لتحديد سرعة الدوران ايضا ً اقصى سرعة‪ ،‬وتجد المتريرين‬
‫‪ changeDragAngle‬و ‪ changeDrag‬هما لترير نسبة احتكاك الطائرة في الهواء لمنع الجاذبية من التأثير عليها بشكل كبير وهي في الهواء‪ ،‬القيم التي‬
‫تراها ال تعبر عن نسبة االحتكاك الفعلي ال بل سنقسم هذه القيمة على سرعة الطائرة وسيعيد لنا االحتكاك مع سرعة الحركة تقريبا ً هذه القيم ستعطيك النتيجة‬
‫‪ 3.3f‬لالحتكاك ال طي و الزاوي في المكون ‪ Rigidbody‬الموضوع على جسم الطائرة‪.‬‬

‫المتريرات ال اصة هي لت زين محاور الدوران زيادة السرعة والحصول على الكائنات التي تحتوي على المكون ‪ Wheel Collider‬او العجالت المرئية‬
‫وبطبيعة الحال هي العجالت الموجودة في الطائرة‪.‬‬

‫في الدالة ‪ Start‬علينا جل المكونات باالضافة على عجالت الطائرة وعمل عزم دوران بسيط لها كي تنزلق وال تواجه مشكلة في االقالع‪ ،‬اذن اضف الدالة‬
‫‪ Start‬بهذا الشكل‪:‬‬
‫{ )( ‪void Start‬‬

‫ابحث عن احد أبناء الطائرة الذي سيطلق الشعاع‪pointRay = transform.FindChild("Point Ray"); //‬‬
‫اجلب المكون الفيزيائي‪rb = GetComponent<Rigidbody>(); //‬‬
‫خزن محاور حركة الطائرة‪rotAir = transform.rotation; //‬‬

‫اجلب جميع مكونات العجالت الفيزيائية في االبناء‪wheels = GetComponentsInChildren<WheelCollider>(); //‬‬


‫)‪foreach(WheelCollider wheel in wheels‬‬
‫{‬
‫اضف عزم حركي لكل عجلة فيزيائية‪wheel.motorTorque = 1f; //‬‬
‫}‬
‫}‬

‫المترير ‪ pointRay‬يبحث عن الكائن الذي سيمثله ألطالق شعاع من الطائرة الى االر والتحقق من مالمسة الطائرة لألر ‪ ،‬تجد ايضا ً المترير ‪rotAir‬‬
‫ي زن محاور دوران الطائرة‪ ،‬ا يرا ً العجالت وتالحو ان المترير ‪ wheels‬يقوم بالبحث عن الكائنات التي تحتوي على المكون ‪ WheelCollider‬ومن ثم‬
‫اضافة عزم دوران لجميع العجالت بمقدار بسيط جدا ً كما تراه في الحلقة ‪.foreach‬‬

‫بعمل حدود لسرعة الطيران والسرعة االنسيابية‪ ،‬الحو الدالة‪:‬‬ ‫الدالة التالية ستكون ‪ ClampValues‬وست ت‬
‫اضف حدود للقيم‪void ClampValues() //‬‬
‫{‬
‫حدود السرعة االنسيابية للطائرة‪//‬‬
‫;)‪speedFlayLerp = Mathf.Lerp(speedFlayLerp, speedFlay, Time.deltaTime * 3f‬‬
‫حدود سرعة الطائرة‪//‬‬
‫;)‪speedFlay = Mathf.Clamp(speedFlay, 0f, maxSpeed‬‬
‫}‬

‫كما ترى االمر بسيط‪ ،‬يقوم المترير ‪ speedFlayLerp‬باالنتقال من قيمتة الى قيمة المترير ‪ speedFlay‬وبدورة اال ير يقوم بعمل ‪ Clamp‬بين اقل قيمة و‬
‫اعلى قيمة له‪.‬‬

‫الدالة التالية ستكوم ‪ MoveAir‬وويفتها من اسمها وهو تحريك الطائرة وسيتم عبر اعطاء الطائرة دفعة الى االمام بحيث ان المترير ‪ typeForce‬سيحدد نوع‬
‫الدفع‪ ،‬الحو الدالة‪:‬‬
‫حركة الطائرة‪void MoveAir() //‬‬
‫{‬
‫خزن المحور االمامي للطائرة مضروب في السرعة‪//‬‬
‫;‪Vector3 moveForward = transform.forward * speedFlay * Time.deltaTime‬‬
‫اضف قوة الى المتجه مضروب في السرعة مع تحديد نوع القوة‪//‬‬
‫;)‪rb.AddForce(moveForward * speedFlay * Time.deltaTime * 2f, typeForce‬‬
‫}‬

‫تالحو ان المترير ‪ moveForward‬يقوم بت زين محور الطائرة االمامي ومن ثم يضربه بالسرعة‪ ،‬االن عبر المترير ‪ rb‬سنست دم الدالة ‪ AddForce‬ويج‬
‫علينا اعطاء دفعة للطائرة عبرها‪ ،‬اذن يتم است دام المترير ‪ moveForward‬ونفسي الشيء نضربه بالسرعة وا يرا ً نحدد نوع القوة‪ ،‬عندما قمنا بتعريف‬
‫المترير قمت بإعطاء المترير ‪ typeForce‬قيمة اولية وهي ‪ Impulse‬وتعني دفع وهذا مناس مع الطائرة‪ ،‬يمكنك است دام باقي ال يارات في المترير لتجربة‬
‫االمر‪.‬‬

‫الدالة القادمة هي ‪ ChangeDrags‬ووويفتها هي تريير االحتكاك ال طي والزاوي للطائرة فكلما زادت سرعة الطائرة زاد احتكاكها في الهواء وهذا االمر‬
‫جميل هنا لجعل الطائرة تقلع دون مشاكل‪ ،‬أضف الدالة التالية‪:‬‬
‫غير االحتكاكات‪void ChangeDrags() //‬‬
‫{‬
‫السرعة مقسومة على االحتكاك الزاوي‪ ,‬قيمة جلب االحتكاك الزاوي‪//‬‬
‫;‪float speedChangeDragAngle = speedFlay/changeDragAngle‬‬
‫السرعة مقسومة على االحتكاك الخطي‪ ,‬قيمة جلب االحتكاك الخطي‪//‬‬

‫الصفحة ‪25‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫;‪float speedChangeDrag = speedFlay/changeDrag‬‬
‫اعمل الحدود لالحتكاك الخطي و الزاوي بناء على القيم المخزنة‪//‬‬
‫;)‪rb.angularDrag = Mathf.Clamp(rb.angularDrag, speedChangeDragAngle, speedChangeDragAngle‬‬
‫;)‪rb.drag = Mathf.Clamp(rb.drag, speedChangeDrag, speedChangeDrag‬‬
‫}‬

‫كما ترى المتريرات تقوم بوويفتها تلقائياً‪ ،‬تجد انني عرفت متريرين لتمثيل نسبة االحتكاك ال طي و الزاوي فمآلً المترير ‪ speedChangeDrag‬يقوم بقسمة‬
‫المترير ‪ speedFlay‬على ‪ changeDrag‬وسيعطينا نس االحتكاك ال طي‪ ،‬نفسي الشيء في مترير االحتكاك الزاوي‪.‬‬

‫بالوصول الى المترير عبر المترير ‪ rb‬نقوم بعمل ‪ Clamp‬لهم والقيمة الصررى والكبرى تأ ذها المتريرات التي تعيد قسمة السرعة في االحتكاك‪ ،‬مثل ما‬
‫تالحو االمر بسيط جداً‪ .‬سابقا ً اعطينا قيم اولية للمتريرين وهي ‪ 30‬وستجدها مناسبة ويمكنك ترييرها الحقاً‪.‬‬

‫بالتحكم بالطائرة عبر تدويرها على محاورها الثالثة ‪ .x,y,z‬سنضيف دالة‬ ‫الدالة التالية ستكون ‪ AirControl‬و‪ RotateHorizontal‬وهما دوال ت ت‬
‫ا رى لتحقق من مقربة الطائرة من سطح االر ‪ .‬اذن أضف الدالة لكي نناقشها‪:‬‬
‫)(‪void AirControl‬‬
‫{‬
‫خزن االزاحة العمودية‪float xRot = Input.GetAxis("Vertical") * speedRotate; //‬‬
‫خزن االزاحة االفقية‪float zRot = Input.GetAxis("Horizontal") * speedRotate; //‬‬

‫هذه القيمة تعني ان الطائرة تستطيع اإلقالع حينها‪if(speedFlay >= 50f) //‬‬
‫{‬
‫دور الطائرة على المحور العمودي‪rotAir *= Quaternion.Euler(new Vector3(xRot, 0f, 0f)); //‬‬
‫دور الطائرة على المحور الجانبي‪rotAir *= Quaternion.Euler(new Vector3(0f, 0f, -zRot)); //‬‬

‫هل مازال الضغط على مفتاح مستمر ‪if(Input.GetKey("e")) //e‬‬


‫دور الطائرة على المحور االفقي باالتجاه الموجب‪//‬‬
‫;))‪rotAir *= Quaternion.Euler(new Vector3(0f, 15f * Time.deltaTime, 0f‬‬
‫هل مازال الضغط على مفتاح مستمر ‪if(Input.GetKey("q"))//q‬‬
‫دور الطائرة على المحور االفقي باالتجاه السالب‪//‬‬
‫;))‪rotAir *= Quaternion.Euler(new Vector3(0f, -15f * Time.deltaTime, 0f‬‬
‫}‬

‫طائرة تتحرك ببطء وهي ليس على األرض ‪if(speedFlay < 50f && AirOnGround() == false) //‬‬
‫{‬
‫دور الطائرة على المحور الجانبي ببطء‪//‬‬
‫;))‪rotAir *= Quaternion.Euler(new Vector3(0f, 0f, -zRot/3f‬‬
‫دور الطائرة على المحور العمودي ببطء‪//‬‬
‫;))‪rotAir *= Quaternion.Euler(new Vector3(xRot/3f, 0f, 0f‬‬
‫}‬

‫الطائرة تتحرك ببطء وهي على االرض‪if(speedFlay < 50f && speedFlay > 0f && AirOnGround() == true)//‬‬
‫دور الطائرة على المحور االفقي بناء على هذه السرعة‪RotateHorizontal(zRot, 20f); //‬‬

‫طبق الدوران الناعم للطائرة‪//‬‬


‫;)‪transform.rotation = Quaternion.Lerp(transform.rotation, rotAir, Time.deltaTime * 2f‬‬

‫}‬

‫التدوير بشكل افقي‪void RotateHorizontal(float yRot) //‬‬


‫{‬
‫دور الطائرة بشكل افقي بناء على قيمة البارامتر الممرر‪//‬‬
‫;))‪rotAir *= Quaternion.Euler(new Vector3(0f, yRot/2f, 0f‬‬
‫}‬

‫الطائرة على الرض‪bool AirOnGround() //‬‬


‫{‬
‫ارجع الشعاع الساقط‪return Physics.Raycast(pointRay.position, -transform.up, -3f); //‬‬
‫}‬

‫حسنا ً بعد تأملك للدالة ستالحو انا القسم االكبر منها عبارة عن شروط تتحقق دائما ً من ان السرعة وصلت لمستوى معين فتطبق امر‪ .‬في البداية تجد‬
‫المتريرين المشهورين ‪ xRot‬و ‪ zRot‬وبطبيعة الحالة مضروبين مترير نسبة الدوران‪ .‬الشرط االول سيتحقق من ان السرعة اكبر من ‪ 50‬فيقوم بتطبيق التحكم‬
‫عبر جعل المترير ‪ rotAir‬ي زن محاور الدوران ‪ x, z‬م‪ )Pitch, Roll‬باالضافة الى تدوير الطائرة على المحور االفقي م‪ )Yaw‬عبر المفتاحين ‪.e, q‬‬

‫الشرط الثاني يتحقق من كون السرعة اقل من ‪ 50‬و ان الطائرة ليست على االر عبر قيمة الدالة ‪ AirOnGround‬مسأناقشها في آ ر الكود) او قريبة منها‬
‫فيقوم بدور الطائرة على نفس المحاور السابقة ولكن هذه المرة بسرعة دوران اقل واالمر بسيط فتجد انها نفس المتريرات ولكن مقسومة على ‪.3‬‬

‫الشرط اال ير يتحقق من ان السرعة اصرر من ‪ 50‬اي ليست في مجال التحكم الكلي ايضا ً اكبر من ‪ 0‬اي ليست واقفة ا يرا ً هي على االر ‪ ،‬ففي حالة‬
‫تحققت هذه الشروط سيتم تدوير الطائرة على المحور االفقي ‪ Yaw‬ولكن هذه المرة باالعتماد على الدالة ‪ RotateHorizontal‬وهي تقوم بتدوير الطائرة افقيا ً‬
‫وتأ ذ مترير من نوع ‪ float‬لتحديد محور الدوران وهي بسيطة جداً‪ .‬ا يرا ً بعد هذه الشروط نقوم بات اذ القيم الموجودة في المترير ‪ rotAir‬بنقل القيمة‬
‫الحالية الى تلك القيمة عبر الدالة ‪ Lerp‬كما كنا نعمل سابقاً‪.‬‬

‫وتعيد القيمة ‪ true‬غير ذلك تعيد القيمة ‪ ،false‬تقوم هذه الدالة بأطالق شعاع‬ ‫ا يرا ً الدالة ‪ AirOnGround‬وهي تتحقق من ان الطائرة موجودة على االر‬
‫من موقع الكائن ‪ pointRay‬الى اسفل الطائرة باالعتماد على محورها العلوي‪.‬‬

‫الصفحة ‪26‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫اذن بعد ان قمنا بتفصيل هذه الدالة بشكل كبير نكون قد انهينا الجزء االكبر من هذا الملف وتبقى لنا ا يرا ً كود بسيط جدا ً وهو لجعل المترير ‪speedFlay‬‬
‫يزيد العطاء دفع للطائرة او ينق لتوقيف الدفع‪ ،‬اضف الدالة التالية‪:‬‬

‫التحكم بالماوس‪void MouseControl() //‬‬


‫{‬
‫مازال الضغط على الزر األيمن للماوس‪if(Input.GetMouseButton(1)) //‬‬
‫انقاص سرعة الطائرة‪speedFlay -= 15f * Time.deltaTime; //‬‬
‫لم يتم الضغط على الزر‪else //‬‬
‫زيادة سرعة الطائرة‪speedFlay += 10f * Time.deltaTime; //‬‬

‫}‬

‫الدوال‬ ‫الكود الذي تحوية الدالة‪ .‬تالحو ان بع‬ ‫ا يرا ً تبقى لنا استدعاء دوال الحركة والتدوير وغيرها في الدوال ‪ Update‬و ‪ FixedUpdate‬حس‬
‫اآل ر ال و التقسيم سيكون كالتي‪:‬‬ ‫محتواها فيزيائي والبع‬

‫الدوال الفيزيائية‪- :‬‬

‫‪AirControl ‬‬
‫‪MoveAir ‬‬
‫‪ChangeDrags ‬‬

‫الدوال الرير فيزيائية‪- :‬‬

‫‪MouseControl ‬‬
‫‪ClampValues ‬‬

‫قد تتسائل حول ما عالقة الدالة ‪ AirControl‬بالفيزياء؟ هناك شيء يج ان تعرفة وهو انه عند تحريك جسم فيزيائي وتدويره اال فيزيائيا ً تنتج عنه مشكلة‬
‫تأ ر الحركة عن الدوران‪ ،‬كما تعرف فأن الدالة ‪ Update‬اسرع تحديثا ً من الدالة ‪ FixedUpdate‬ولو استدعينا الحرك في اال يرة و الدوران في االولى‬
‫فأن الدوران سيسبق الحركة في االطارات اي لو افترضنا ان االطار ‪ 50‬قد حدث الدوران فاحتمال ان الحركة لن تحدث في هذا االطار وهذه مشكلة‬
‫وللت ل منها نقوم باستدعاء الدوال التي لها عالقة ببع في دالة واحدة لضمان التحديث الموحد‪.‬‬

‫ا يرا ً شكل الدوال سيكون كالتي‪:‬‬


‫{ )( ‪void Update‬‬

‫التحكم بالماوس‪MouseControl(); //‬‬

‫حدود القيم‪ClampValues(); //‬‬


‫}‬

‫)(‪void FixedUpdate‬‬
‫{‬
‫التحكم بالطائرة‪AirControl(); //‬‬

‫حركة الطائرة‪MoveAir(); //‬‬

‫تغيير االحتكاكات‪ChangeDrags(); //‬‬


‫}‬

‫يمكنك االن ا تبار الطائرة وستجد ان النتائج جميلة جداً‪ .‬شيء ا ير قد ربما لم تتذكره وهو المترير ‪ speedFlayLerp‬حقيقةً قمت بتعريف هذا المترير‬
‫الست دامه في نف يعر لي مدى سرعة الطائرة‪ .‬يمكنك انشاء ‪ Text‬وتسميته ‪ textSpeed‬و اضافة المترير التالي في ‪:Update‬‬
‫; "‪textSpeed.text = "Speed Flay: " + Mathf.RoundToInt(speedFlayLerp) + "km / " + Mathf.RoundToInt(maxSpeed) + "km‬‬

‫الصفحة ‪27‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫انشاء الملف ‪:ParticaleEffect‬‬
‫هذه الفقرة ستساعدك على اعطاء جمالية للطائرة‪ ،‬كما تعرف ان الطائرة نفاثة و سابقا ً شرحت لك المؤثرات المرئية ال اصة بالمحرك‪ ،‬هنا سنقوم ببرمجة هذه‬
‫المؤثرات و تلقائيا ً ستعمل بالتناس مع سرعة الطائرة اي كلما اسرعت الطائرة كلما زادت نسبة المؤثرات المرئية‪.‬‬

‫علينا انشاء ملف برمجي جديد يمثل المؤثرات المرئية ال اصة بالطائرة‪ .‬اذن قم بأنشاء ملف برمجي باسم ‪ ParticaleEffect‬واضف هذه المتريرات‪:‬‬
‫{ ‪public class ParticaleEffect : MonoBehaviour‬‬

‫مقدار تغير النار‪public float deltaChangeFireFire = 10f; //‬‬


‫مقدار تغير النار الالحقة‪public float deltaChangeLastFire = 10f; //‬‬
‫مقدار تغير الدخان‪public float deltaChangeSmoke = 10f; //‬‬

‫مؤثرات الناء المنطلق‪public ParticleSystem[] firstFire; //‬‬


‫مؤثرات النار المتالحق‪public ParticleSystem[] lastFire; //‬‬
‫مؤثرات الدخان‪public ParticleSystem[] smoke; //‬‬

‫مرجع لملف التحكم بالطائرة‪private AirplaneController ac; //‬‬


‫}‬

‫كما ترى المتريرات الثالثة االولية ستساعدك على تحديد نسبة زيادة كل مؤثر وافتراضيا ً هي ‪ 10‬واجدها مناسبة معي‪ ،‬المتريرات الثانية او باألصح‬
‫المصفوفات هي للتحديد على جميع المؤثرات من نفس النوع وتطبيق االمر عليها مثل مؤثر الد ان وعددهم اثنان ونفس الشيء ينطبق على باقي المؤثرات‬
‫المرئية‪.‬‬

‫حسنا ً يج علينا تثبيت القيم االولى للمتريرات وجعل المترير ‪ ac‬يحصل على المكون ‪ AirplaneController‬للوصول الى متريرات السرعة‪ ،‬أضف الدالة‬
‫‪ Start‬بهذا الشكل‪:‬‬
‫{ )( ‪void Start‬‬
‫اجلب ملف التجكم بالطائرة‪ac = GetComponent<AirplaneController>(); //‬‬

‫ثبت قيم النار المنطلق‪SetupValues (firstFire); //‬‬


‫ثبت قيم النار المتالحق‪SetupValues (lastFire); //‬‬
‫ثبت قيم الدخان‪SetupValues (smoke); //‬‬
‫}‬

‫تمرير بارامتر من نوع‬ ‫عملية جل المكون بسيطة ولكن ترى ان الدالة ‪ SetupValues‬غير موجودة فال تقلق سنقوم بتعريفها حالياً‪ ،‬هذه الدالة تطل‬
‫‪ ParticleSystem‬على شكل مصفوفة لتطبيق االمر على المؤثرات المتشابهة او المنسو ة في الجهتين‪ ،‬اذن اضف الدالة‪:‬‬
‫ثبت القيم‪void SetupValues(ParticleSystem[] ps) //‬‬
‫{‬
‫)‪for(int i = 0; i < ps.Length; i++‬‬
‫{‬
‫ليس هناك عمر لبدء تصاعد المؤثرات‪ps[i].startLifetime = 0f; //‬‬
‫ليس هناك عمر لسرعة المؤثرات‪ps[i].startSpeed = 0f; //‬‬
‫}‬
‫}‬

‫الدالة بسيطة جدا ً وتجد انها توفر البارامتر السابق‪ ،‬العملية هنا تكمن في جعل الحلقة ‪ for‬تتحقق من جميع الكائنات في المصفوفة و اعطائها القيمة صفر في‬
‫نسبة البقاء و السرعة االبتدائية‪.‬‬

‫علينا جعل السرعة و مدة بقاء المؤثرات تتناس مع سرعة الطائرة تناس طردي‪ ،‬سنعتمد على دالتان تقومان بعمل ‪ Clamp‬لمدة البقاء وللسرعة‪ ،‬اضفهم‪:‬‬
‫حدود عمر المؤثر‪void ClampStartLifetime(ParticleSystem ps, float min, float max) //‬‬
‫{‬
‫اعمل حدود لعمل المؤثر‪ps.startLifetime = Mathf.Clamp(ps.startLifetime, min, max); //‬‬
‫}‬

‫الصفحة ‪28‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫حدود سرعة المؤثر‪void ClampStartSpeed(ParticleSystem ps, float min, float max) //‬‬
‫{‬
‫اعمل حدود لسرعة المؤثر‪ps.startSpeed = Mathf.Clamp(ps.startSpeed, min, max); //‬‬
‫}‬

‫تالحو ان هذه المرة البارامتر ‪ ParticleSystem‬ليس مصفوفة انما مترير ولنست دمه في الوصول الى مترير واحد فقط في كالً من الدالتين كما تالحو‪.‬‬
‫الى هنا والعملية بسيطة جداً‪.‬‬

‫الدالة اال ير مقبل ‪ )Update‬ستقوم بتريير كالً من السرع و مدة البقاء اعتمادا ً على سرعة الطائرة‪ ،‬اضف الدالة التالية وهي بسيطة كما سترى‪:‬‬
‫قوة المؤثر‪void PowerPartical(ParticleSystem[] ps, float speedChange) //‬‬
‫{‬
‫)‪for(int i = 0; i < ps.Length; i++‬‬
‫{‬
‫اضف عمر للمؤثر بناء على البارامتر الممرر‪ps[i].startLifetime = speedChange; //‬‬
‫اضف سرعة للمؤثر بناء على البارامتر الممرر‪ps[i].startSpeed = speedChange; //‬‬
‫}‬
‫}‬

‫في جعل المؤثرات تنتقل تدريجيا ً مسرعة ومدة بقاء) بالتعامل مع مترير السرعة‪.‬‬ ‫حسناً‪ ،‬العملية بسيطة جدا ً وتتل‬

‫في الدالة ‪ Update‬سنقوم باست دام هذه الدوال لجعل المؤثرات المرئية تعمل تلقائيا ً مع حركة الطائرة‪ ،‬في البداية سنقوم بتعريف ‪ 3‬متريرات في الدالة وهي‬
‫فارق زيادة المؤثرات المرئية‪ ،‬العملية بسيطة فهي تعتمد على جعل كل مترير ي زن القيمة الناتجة عن قيمة السرعة بمترير نرير المؤثر‪ ،‬الحو هذا السرط في‬
‫الدالة‪:‬‬
‫{ )( ‪void Update‬‬

‫معدل التغير في سرعة النار الالحقة‪float d_lastFire = ac.speedFlay/deltaChangeLastFire; //‬‬


‫معدل التغير في سرعة النار المنطلقة‪float d_firstFire = ac.speedFlay/deltaChangeFireFire; //‬‬
‫معدل التغير في سرعة الدخان‪float d_smoke = ac.speedFlay/deltaChangeSmoke; //‬‬

‫اضف قوة للنار الالحقة‪PowerPartical(lastFire, d_lastFire); //‬‬


‫اضف قوة للنار المنطلقة‪PowerPartical(firstFire, d_firstFire); //‬‬
‫اضف قوة للدخان‪PowerPartical(smoke, d_smoke); //‬‬
‫}‬

‫كما ترى االمر بسيط جداً‪ ،‬ايضا ً جعلنا الدالة ‪ PowerPartical‬تت ذ هذه المتريرات كقيمة اساسية لترير سرعة زيادة المؤثرات المرئية‪.‬‬

‫علينا االن عمل ‪ Clamp‬للسرعة ومدة البقاء ال اصة بكل مؤثر‪ ،‬سنقوم في العملية التالية بالتحقق من جميع المؤثرات الموجودة في المصفوفة التي تمثلها‬
‫ومن ثم عمل ‪ Clamp‬لها‪ ،‬اضف الكود التالي اسفل السابق‪:‬‬
‫)‪for(int i = 0; i < lastFire.Length; i++‬‬
‫{‬
‫اضف حدود لعمر النار الالحقة‪ClampStartLifetime(lastFire[i], 0f, 2f * d_lastFire/5f); //‬‬
‫اضف حدود لسرعة للنار الالحقة‪ClampStartSpeed(lastFire[i], 0f, 3f); //‬‬
‫}‬

‫)‪for(int i = 0; i < firstFire.Length ; i++‬‬


‫{‬
‫اضف حدود لعمر النار المنطلقة‪ClampStartLifetime(firstFire[i],0f ,2f * d_lastFire/5f); //‬‬
‫اضف حدود لسرعة للنار المنطلقة‪ClampStartSpeed(firstFire[i], 0f, 1f); //‬‬
‫}‬

‫)‪for(int i = 0; i < smoke.Length; i++‬‬


‫{‬
‫اضف حدود لعمر للدخان‪ClampStartLifetime(smoke[i], 0f, 2f * d_lastFire/5f); //‬‬
‫اضف حدود لسرعة للدخان‪ClampStartSpeed(smoke[i], 0f, 5f); //‬‬
‫}‬

‫تأمل هذا الكود جيدا ً وستجد ان الحلقة ‪ for‬تكررت ‪ 3‬مرات وفي كل مصفوفة نقوم بنفس االمر وعمل ‪ Clamp‬للسرعة ومدة البقاء ال اصة بكل مؤثر‪ .‬الحلقة‬
‫االولى تقوم بعمل ‪ Clamp‬للمؤثر ‪ lastFire‬اما الثاني لـ ‪ FirstFire‬والثالثة لـ ‪ .Smoke‬تجد ان كل حلقة تست دم المترير الذي يمثلها وتعتمد عليها في جعلة‬
‫اقصى قيمة للترير بينما تقسمه على ‪ ،5‬هي ليست قيمة ثابته ولكن يمكنك تجري جميع القيم الى ان تستقر على واحدة‪.‬‬

‫العملية بسيطة جدا ً وهذا هو شكل الملف ‪ ParticaleEffect‬كامل‪:‬‬


‫{ ‪public class ParticaleEffect : MonoBehaviour‬‬

‫;‪public float deltaChangeFireFire‬‬


‫;‪public float deltaChangeLastFire‬‬
‫;‪public float deltaChangeSmoke‬‬

‫;‪public ParticleSystem[] firstFire‬‬


‫;‪public ParticleSystem[] lastFire‬‬
‫;‪public ParticleSystem[] smoke‬‬

‫;‪private AirplaneController ac‬‬

‫{ )( ‪void Start‬‬

‫;)(>‪ac = GetComponent<AirplaneController‬‬

‫الصفحة ‪29‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


SetupValues(firstFire);
SetupValues(lastFire);
SetupValues(smoke);

void Update () {

float d_lastFire = ac.speedFlay/deltaChangeLastFire;


float d_firstFire = ac.speedFlay/deltaChangeFireFire;
float d_smoke = ac.speedFlay/deltaChangeSmoke;

PowerPartical(lastFire, d_lastFire);
PowerPartical(firstFire, d_firstFire);
PowerPartical(smoke, d_smoke);

for(int i = 0; i < lastFire.Length; i++)


{
ClampStartLifetime(lastFire[i], 0, 2 * d_lastFire/5);
ClampStartSpeed(lastFire[i], 0, 3);
}

for(int i = 0; i < firstFire.Length ; i++)


{
ClampStartLifetime(firstFire[i],0,2 * d_lastFire/5);
ClampStartSpeed(firstFire[i], 0, 1);
}

for(int i = 0; i < smoke.Length; i++)


{
ClampStartLifetime(smoke[i], 0, 2 * d_lastFire/5);
ClampStartSpeed(smoke[i], 0, 5);
}
}

void PowerPartical(ParticleSystem[] ps, float speedChange){

for(int i = 0; i < ps.Length; i++)


{
ps[i].startLifetime = speedChange;
ps[i].startSpeed = speedChange;
}
}

void ClampStartLifetime(ParticleSystem ps, float min, float max)


{
ps.startLifetime = Mathf.Clamp(ps.startLifetime, min, max);
}

void ClampStartSpeed(ParticleSystem ps, float min, float max)


{
ps.startSpeed = Mathf.Clamp(ps.startSpeed, min, max);
}

void SetupValues(ParticleSystem[] ps)


{
for(int i = 0; i < ps.Length ; i++)
{
ps[i].startLifetime = 0;
ps[i].startSpeed = 0;
}
}
}

:AudioAirplane ‫انشاء الملف‬


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

‫ تعتمد العملية على جعل الصوت يعمل تلقائيا ً مع‬.‫ ستجد صوتين االول للمحرك الطائرة واآل ر للرياح‬Audio ‫ دا ل المجلد‬Airplane ‫بالبحث عن المجلد‬
.‫ مثل ما عملنا مع المؤثرات المرئية سنقوم بتطبيق االمر على االصوات ولن يكون هناك ا تالف كبير‬،‫السرعة اي يتناس تناس طردي معها‬

‫ اذن قم بأنشاء ملف برمجي يمثل‬،ً‫هذه المرة سأقوم بوضع الملف البرمجي الذي سيتحكم باالصوات بشكل كامل هنا وسنناقشه نورا ً ألنه ليس طويل جدا‬
:‫ واضف الكود التالي فيه‬AudioAirplane ‫صوت الطائرة وليكن‬

30 ‫الصفحة‬ Facebook.com/EMAD.Unity3D
‫{ ‪public class AudioAirplane : MonoBehaviour‬‬

‫معدل سرعة المحرك‪public float deltaChangedEngine = 5f; //‬‬


‫معدل سرعة الرياح‪public float deltaChangedWind = 150f; //‬‬

‫مقطع صوتي للمحرك‪public AudioClip engineClip; //‬‬


‫مقطع صوتي للرياح‪public AudioClip windClip; //‬‬

‫مكون الصوت الضافة مقطع صوت المحرك‪private AudioSource m_engine; //‬‬


‫مكون الصوت الضافة مقطع صوت الرياح‪private AudioSource m_wind; //‬‬

‫مرجع لملف التحكم بالطائرة‪private AirplaneController ac; //‬‬

‫{ )( ‪void Awake‬‬

‫اجلب ملف التحكم بالطائرة‪ac = GetComponent <AirplaneController> (); //‬‬

‫اضف مكون الصوت للمحرك‪m_engine = gameObject.AddComponent<AudioSource>(); //‬‬


‫اضف مكون الصوت للرياح‪m_wind = gameObject.AddComponent<AudioSource>(); //‬‬

‫المقطع الصوتي للمكون هو صوت المحرك‪m_engine.clip = engineClip; //‬‬


‫المقطع الصوتي للمكون هو صوت الرياح‪m_wind.clip = windClip; //‬‬

‫ثبت قيم صوت المحرك‪SetupValues(m_engine); //‬‬


‫ثبت قيم صوت الرياح‪SetupValues(m_wind); //‬‬
‫}‬

‫{ )( ‪void Update‬‬

‫قوة صوت المحرك‪UpdateAudio(m_engine, deltaChangedEngine * 5f); //‬‬


‫قوة صوت الرياح‪UpdateAudio(m_wind, deltaChangedWind); //‬‬

‫قم بعمل حدود للصوت وحدة الصوت في الريح‪ClampPitch(m_wind, 0f, 0.9f); //‬‬
‫}‬

‫حدث االصوات‪//‬‬
‫)‪void UpdateAudio(AudioSource audio, float deltaChange‬‬
‫{‬
‫سرعة تغير األصوات تعتمد على سعة الطائرة‪float speedAudio = ac.speedFlay / deltaChange; //‬‬
‫مستوى الصوت‪audio.volume= Mathf.Clamp(audio.volume speedAudio, speedAudio); //‬‬
‫حدة الصوت‪audio.pitch = Mathf.Clamp(audio.pitch, speedAudio, speedAudio); //‬‬
‫}‬
‫حدود حدة الصوت‪//‬‬
‫)‪void ClampPitch(AudioSource audio, float min, float max‬‬
‫{‬
‫الصوت لحدة حدود اعمل‪audio.pitch = Mathf.Clamp(audio.pitch, min, max); //‬‬
‫}‬

‫تثبيت القيم االولية للصوت‪//‬‬


‫)‪void SetupValues(AudioSource audio‬‬
‫{‬
‫اقل مسافة لسماع الصوت‪audio.minDistance = 20f; //‬‬
‫اقصى مسافة لسماع الصوت‪audio.maxDistance = 100f; //‬‬

‫هل الصوت قابل للتكرار؟ نعم‪audio.loop = true; //‬‬


‫هل يعمل الصوت عند االستيقاظ؟ نعم ‪audio.playOnAwake = true; //‬‬
‫مستوى الصوت‪audio.volume = 0f; //‬‬
‫حدة الصوت‪audio.pitch = 0f; //‬‬
‫شغل الصوت‪audio.Play(); //‬‬
‫}‬
‫}‬

‫اذن كما ترى فأن كل دالة تقوم بوويفة معينة تمكننا من جعل الصوت قابل للترير تلقائيا ً مع السرعة‪ ،‬هذه العملية جميلة جدا ً بحيث اننا سنجعل المترير ‪pitch‬‬
‫وهو يمثل حدة الصوت بتريير قيمتة ابتداء من الصفر الى ان يصل الى قيمة معينه وهي ناتج لقسمته على السرعة‪.‬‬

‫في البداية تجد المتريرين الم تصين بتريير وضوح الصوت وحدة الصوت ال اصة بكل مقطع صوتي‪ ،‬القيم االفتراضية ستعطيك النتيجة التالية‪:‬‬

‫صوت المحرك‪:‬‬
‫‪1 = Volume ‬‬
‫‪4 = Pitch ‬‬

‫صوت الرياح‪:‬‬
‫‪0.8 = Volume ‬‬
‫‪0.8 = Pitch ‬‬

‫في الواقع ال يمكن التنبؤ بهذه القيم اال عن طريق التجربة‪.‬‬

‫بكالً من المتريرين من نوع ‪ AudioSource‬وهو اال ير بدورة سيضيف المكونين الى الطائرة‬ ‫المتريرين من نوع ‪ AudioClip‬سنجعلهما الـ ‪ Clip‬ال ا‬
‫وفي نفس الوقت سنضيف كل مقطع الى كل مكون‪.‬‬

‫الصفحة ‪31‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫في الدالة ‪ Start‬بعد اضافة المكونات نقوم بجعل كل مكون يأ ذ ‪ Clip‬يمثله والعملية بسيطة كما ترى‪ ،‬بعدها يتم تثبيت القيم االولى لكل مكون‪.‬‬

‫القيم التي تريدها‪ ،‬تجد ان الدالة توفر بارامتر من نوع‬ ‫وويفة الدالة ‪ SetupValues‬بسيطة جدا ً وهي اعطاء قيم ابتدائية لكل مكون ويمكن افترا‬
‫‪ AudioSource‬لتحديد المكون الذي سي ضع لهذه العملية‪.‬‬

‫العملية في قسمة السرعة على البارامتر ‪ deltaChange‬بحيث‬ ‫الدالة ‪ UpdateAudio‬تقوم بتحديث االصوات عن طريق تريير حدتها ووضوحها‪ ،‬تتل‬
‫ان المترير الدا لي ‪ speedAudio‬ي زن ناتج العملية ومن ثم نعمل ‪ Clamp‬للـ ‪ Volume‬والـ ‪.Pitch‬‬

‫بالنسبة للدالة ‪ ClampPitch‬هي بسيطة جدا ً ووويفتها من اسمها وهو عمل حدود لحدة الصوت‪ ،‬حقيقة االمر است دمتها لعمل ‪ Clamp‬لحدة صوت الرياح‬
‫ويمكن است دامها بشكل مباشر دون وضعها في دالة واالمر يعود لك وهذا لن يشكل فرق‪.‬‬

‫ا يرا ً نأتي على الدالة ‪ Update‬ونقوم فيها باستدعاء الدالة ‪ UpdateAudio‬وتمرير البارامترات وتجد ان الدالة االولى تمثل صوت المحرك بينما الثانية‬
‫لصوت الرياح‪ ،‬ا يرا ً نست دم الدالة ‪ ClampPitch‬لعمل حدود لحدة صوت الرياح والقيم التي است دمتها ات ذتها عن طريق التجربة‪.‬‬

‫اذن الملف بسيط وليس مشع بشكل كبير‪ ،‬يمكنك االن وضعة كمكون على الطائرة وقم بملء المتريرات كما هو موضح‪:‬‬

‫يمكنك االن رؤية جميع الملفات البرمجية التي أنشأناها سابقا ً وضبط قيمك معها وتجربة الطائرة واالستمتاع بها مع االصوات و المؤثرات المرئية ‪.‬‬

‫حركة الكاميرا‪:‬‬
‫الكاميرا عامل مهم جدا ً و صوصا ً في الطائرات ويج ان تكون الكاميرا مريح باالضافة الى اعطائك مساحة روية كبيرة وسلسلة دون نقل اهتزازات‬
‫الطائرة وما شابه ذلك‪ .‬غالبا ً يتم وضع الكاميرا كأبن لكائن الطائرة وهذا االمر اطئ ألنه سيعطي نتائج غير مرضية باالضافة الى انه سينقل كل اطراء‬

‫الصفحة ‪32‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫فمثلما عملنا مع الكاميرا االستراتيجية سنقوم بتطبيق االمر هنا‬ ‫يحدث للطائرة اثناء عملها‪ .‬هناك طرق أفضل لتصميم تلك الكاميرا وجعلها مريحة لالع‬
‫ولكن بطريقة م تلفة وبسيطة‪.‬‬

‫في نفس المشروع ستجد الكائن ‪ Cameras‬هذا الكائن يحتوي على الكاميرا الرئيسية ‪ .Main Camera‬عملية برمجة الكاميرا ستكون بسيطة جدا ً وفكرتها‬
‫سهله وهي جعل الكائن اال يتبع الطائرة بينما الكاميرا تنور الى الطائرة‪ .‬لتطبيق االمر برمجيا ً قم بأنشاء ملف برمجي وليكن ‪ CameraAirplane‬واضف‬
‫الكود التالي‪:‬‬
‫{ ‪public class CameraAirplane : MonoBehaviour‬‬

‫‪public‬‬ ‫الهدف‪Transform target; //‬‬


‫‪public‬‬ ‫الكاميرا الرئيسية‪Transform cam; //‬‬
‫‪public‬‬ ‫سرعة النظر الى الطائرة‪float speedLookAt; //‬‬
‫‪public‬‬ ‫سرعة التتبع‪float speedFollow; //‬‬
‫‪public‬‬ ‫فارق المسافة على المحور االمامي‪float deltaZ; //‬‬

‫)(‪void FixedUpdate‬‬
‫{‬
‫التتبع التلقائي للهدف‪AutoFollowTarget(); //‬‬
‫الكاميرا تنظر الى الكائن االب‪AutoCameraLookAt(cam, transform); //‬‬
‫الكائن االب ينظر الى الطائرة‪AutoCameraLookAt(transform, target); //‬‬
‫}‬

‫التتبع التلقائي للهدف‪//‬‬


‫)(‪void AutoFollowTarget‬‬
‫{‬

‫* ‪transform.position = Vector3.Lerp(transform.position, target.position - target.forward‬‬


‫;)‪deltaZ, speedFollow * Time.deltaTime‬‬
‫}‬

‫النظر التلقائي الى الهدف‪//‬‬


‫)‪void AutoCameraLookAt(Transform _cam, Transform _target‬‬
‫{‬
‫;‪Vector3 deltaPosRot = transform.position - target.position‬‬
‫;)‪Quaternion lookRot = Quaternion.LookRotation(-deltaPosRot‬‬
‫‪Quaternion rotation = Quaternion.Lerp(transform.rotation,‬‬
‫‪lookRot,‬‬
‫;)‪speedLookAt * Time.deltaTime‬‬
‫;‪transform.rotation = rotation‬‬
‫}‬
‫}‬

‫كما ترى الكود قد مر علينا سابقا ً وهو بسيط جداً‪ ،‬في الدالة ‪ AutoFollowTarget‬سيقوم الكائن اال بتتبع الطائرة‪ ،‬اما في الدالة ‪AutoCameraLookAt‬‬
‫سيقوم الكائن اال بالنور الى الطائرة بينما الكاميرا الرئيسية ستنور الى الكائن اال ويمكنك االطالع على هذا من الدالة ‪.FixedUpdate‬‬

‫سب است دام الدالة ‪FixedUpdate‬؟ كما تعلم فأننا قمنا بتحريك الطائرة فيزيائيا ً وبطبيعة الحال الدالة ‪ MoveAir‬باالضافة الى ‪ AirControl‬كانوا في‬
‫‪ FixedUpdate‬ولهذا جعلنا دوال الكاميرا في نفس الدالة لتتناس سرعة التحديث ولكيال تحصل مشكلة في التتبع او التأ ر في التتبع‪ ،‬يمكنك مالحوة االمر‬
‫عند استدعاء الدوال السابقة في ‪ Update‬وستجد ان هناك تأ ر في النور والتتبع كما قلت‪.‬‬

‫الصفحة ‪33‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫محاكاة حركة السيارة ثالثية االبعاد‪:‬‬
‫تعرفنا سابقا ً على المكون ‪ Wheel Join2D‬الم ص محاكاة حركة السيارة ثنائية االبعد وقمنا بالتعامل معة برمجياً‪ ،‬هنا سنتعرف على مكون جديد وهو‬
‫م ص محاكاة العجالت الفيزيائية للسيارات ثالثية االبعاد وهو المكون ‪.Wheel Collider‬‬

‫لنا هذه‬ ‫لشرحه بطريقة مفصلة يفضل أنك تفتح مشروع ‪ Car‬في الملف ‪ Scenes‬وستجد سيارة ‪ Car‬موضوعة باالضافة الى الكاميرا ال اصة وست ص‬
‫الكاميرا الراحة الكبيرة في ترطية عالم المشروع‪.‬‬

‫دعنا نراجع كائن السيارة‪ ،‬في حالة وجدت السيارة ستالحو ان لها عدة ابناء مرتبين بهذا الشكل‪:‬‬

‫اساسا ً السيارة مأ وذة من أحد مشاريع اليونتي ولكن قمت بالتعديل عليها مثل فصل عدة اشياء وترتيبها لتبسيط شرحها‪.‬‬
‫الكائن االبن االول وهو ‪ Collider‬ومن اسمة يقوم بتوزيع الـ ‪ Colliders‬على السيارة لتحديد االماكن القابلة للتصادم‪.‬‬
‫الكائن الثاني ‪ SkyCar‬ويحوي جسم السيارة باالضافة الى االضواء‪.‬‬
‫‪ Wheels‬يحتوي على عجالت السيارة م‪ 4‬كائنات فارغة مضاف الى كل واحد منها المكون ‪ )Wheel Collider‬ولن توهر العجالت اال في حالة كان الكائن‬
‫اال يحتوي على المكون ‪ Rigidbody‬النها وبطبيعة الحال عجالت فيزيائية‪.‬‬
‫ا يرا ً الكائن ‪ Meshs‬يحتوي على العجالت فقط لكي نقوم بربطها مع المكون‪.‬‬

‫المكون ‪:Wheel Collider‬‬


‫حسنا ً وبما ان كل شيء اتضح لها فسنقوم هنا بشرح المكون ‪ ،Wheel Collider‬تأمل هذه الصورة‪:‬‬

‫بفتح الكائن ‪ Wheels‬ستجد ان العجالت المرئية مسماة بحيث ان كل اسم يمثل موقعها مثالً العجالت االمامية على اليسار‪ ،‬ونفس الشيء على اليمين وهذا‬
‫ينطبق ايضا ً على العجالت ال لفية ويمكنك رؤية موقعهم في المكون ‪ Transform‬وهم موزعين بالتساوي‪ .‬اذن قم بالضرط على اي عجلة وستجد انها‬
‫تحتوي على المكون ‪:Wheel Collider‬‬

‫لك ‪ 4‬اقسام لنناقشها‪:‬‬ ‫دعنا نناقش هذا المكون‪ .‬في البداية ستجد ان هذا المكون ي ص‬

‫‪:‬‬ ‫القسم االول‪ :‬ال صائ‬

‫‪ :Mass ‬وزن العجلة‪.‬‬

‫الصفحة ‪34‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫‪ :Radius‬قطر العجلة‪.‬‬ ‫‪‬‬
‫‪ :Wheel Damping Rate‬مقدار ارتداد الناب ‪.‬‬ ‫‪‬‬
‫‪ :Suspension Distance‬مسافة حركة الناب ‪.Spring‬‬ ‫‪‬‬
‫‪ :Force App Point Distance‬النقطة التي سيتم تطبيق القوة فيها‪.‬‬ ‫‪‬‬

‫االعدادات بسيطة جدا ً ويمكنك التحكم فيها واسقاط السيارة من االعلى لرؤية عملها بشكل واضح‪.‬‬

‫القسم الثاني‪ Suspension Spring :‬منوام التعليق)‪:‬‬

‫يقوم نوام التعليق بعدم نقل وعورة الطريق الى السائق فعملة يكون عن طريق جعل ما الصدمات ‪ Damper‬ي زن مقدار الضرط بحس وزن السيارة‬
‫بينما الناب ‪ Spring‬يقوم بتحويل العزم الميكانيكية مقوة‪ ،‬فتل‪ ،‬ضرط) الى قدرة ميكانيكية بحيث ينكمش ويتمدد ويمنع نقل وعورة الطريق الى السائق‪.‬‬

‫للتحكم فيه مثل‪:‬‬ ‫تجد نوام التعليق يوفر ‪ 3‬صائ‬

‫الصدمات‬ ‫‪ :Damper ‬ما‬


‫‪ :Spring ‬الناب ‪.‬‬
‫‪ :Target Position ‬الموقع الهدف الذي سيتم تطبيق االمر عليها‪.‬‬

‫ستجد ال يارات الموضوع مناسبة ويمكنك ايضا ً ترييرها بحيث تتناس مع متطلباتك‪.‬‬

‫القسم الثالث والرابع‪ Forward/Sideways Friction :‬االنزالقات االمامية والجانبية‪:‬‬

‫جمعت القسمين مع بع الن لهما نفس العمل‪ .‬تقوم هذه االنزالقات بجعل السيارة تنزلق اما على العجالت االمامية او ال لفية او حتى على عجلة واحد او‬
‫جميع العجالت بحيث تقوم بت صي العجالت القابلة لالنزالق‪ .‬يتم تطبيق االنزالقات عبر تحديد مدى احتكاك االطارات مع االر فكلما كانت نسبة‬
‫االحتكاك قليلة كلما زادت حدة االنزالق‪ .‬تجد في كالً من االنزالقين ال يار ‪ Stiffness‬وهذا ال يار يحدد مدى احتكاك االطارات مع االر ‪ ،‬اما بالنسبة‬
‫لباقي ال يارات فأتركها كما هي او تستطيع تعديلها لمعرفة لرؤية نتائجها‪.‬‬

‫يعتبر وزن السيارة عامل مهم جدا ً لعمل نوام التعليق واحتكاك االطارات في االر بشكل صحيح‪ ،‬تتراوح اوزان السيارات الصرير من طن م‪ 1000‬كيلو)‬
‫الى ‪ 2500‬كيلو‪ ،‬بينما الشاحنات يكون وزنها بين ‪ 5‬و‪ 10‬طن ويمكنك التعرف على وزن كل سيارة من مصادر علمية‪ .‬يسب وزن السيارة ضرط على‬
‫العجالت مما يؤدي الى جعل نوام التعليق يفر القيم التي تم تعيينها بحيث ينقل وعورة الطريق الى ما الصدمات‪ ،‬تأمل الصورة‪:‬‬

‫تجد ان العجلة االمامية على اليسار مرتفعة والسب هو ما الصدمات و الناب ‪ ،‬فكلما كانت النس صريرة كلما نقلت السيارة وعورة الطريق الى السائق‪.‬‬
‫يمكنك تجر المثال في اصورة بأنشاء ‪ Cylinder‬وجعل قيمة الـ ‪ Damper = 200‬و ‪ Spring = 20‬في العجلة االمامية على اليمين‪.‬‬

‫بالنسبة لالنزالقات‪ ،‬من نورتك يتم تطبيق عبر تريير قيم االنزالقات وتفكيرك بالفعل سليم ولكن ماذا بشأن االحتكاك الزاوي ‪ ،Drag Angler‬نعم هو عامل‬
‫مهم لمنع السيارة من االنزالق الجانبي والدوران حول نفسها عند المنعطفات‪ ،‬وغالبا ً يتم تريير نسبة االحتكاك الزاوي للسيارة مع سرعتها بحيث يمنعها من‬
‫االنزالق كثير‪.‬‬

‫يارات االنزالق في المكون ‪ Wheel Collider‬مهمة جدا ً فمثالً عند عمل كبح تستطيع جعل العجالت االمامية تنزلق الى االمام بينما ال لفية على الجوان‬
‫وهذا االمر جميل وتستطيع تطبيق هذا االمر برمجياً‪.‬‬

‫انشاء الملف ‪:CarPhysics‬‬


‫بما أنك قد ا ذت دورة سريعة على السيارة وتعرفت على المكون ‪ Wheel Collider‬واالنزالقات وغيرها سنقوم االن ببرمجة حركة السيارة‪ .‬بالنسبة‬
‫للسيارة فأننا لن نقوم بإعطاء اي دفع للسيارة عن طريق تطبيق قوة من فئة ‪ Rigidbody‬ال بل سنجعل العجالت هي من تحرك السيارة نفسها‪.‬‬

‫اذن قم بأنشاء ملف برمجي باسم ‪ CarPhysics‬سيقوم هذا الملف بت زين معلومات الحركة والكبح وما الى ذلك لكي نست دمها في ملفات ا رى‪ .‬ا يرا ً أضف‬
‫المتريرات التالية في الملف‪:‬‬

‫الصفحة ‪35‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫{ ‪public class CarPhysics : MonoBehaviour‬‬

‫المكون الفيزيائي‪private Rigidbody rb; //‬‬


‫مصفوفة لجلب مكون عجالت السيارة‪public WheelCollider[] Wheels; //‬‬
‫مركز ثقل السيارة‪public Vector3 centerOfMass; //‬‬
‫قيمة العزم الحركي‪public float ValueMotorTorque = 1000f; //‬‬
‫اقصى عزم حركي‪public float maxMotorTorque = 1000f; //‬‬
‫اقصى عزم للكبح‪public float MaxBrakeTorque = 2000f; //‬‬
‫زاوية توجيه العجالت االمامية وغالب هي ‪public float Steering = 30f; //30‬‬
‫سيخزن سرعة السيارة‪private float speed; //‬‬
‫سنستخدمه لقراءة سرعة السيارة‪public float GetSpeed{get {return speed;}} //‬‬
‫اقصى سرعة‪public int maxSpeed; //‬‬

‫دعنا نناقش المتريرات‪ ،‬تجد ان كل مترير يوفر معلومة بسيطة عن طريق التعليق ال ا به‪ .‬تقوم المتريرات االولية بجل عجالت السيارة باالضافة الى‬
‫تحديد مركز ثقل السيارة ايضا ً تعريف اقصى عزم دوران و كبح مع تحديد مقدار لفة العجالت‪ .‬المترير ‪ speed‬سي زن سرعة السيارة بنا ًء على سرعتها‬
‫الفيزيائية بينما المترير ‪ GetSpeed‬بت زين مقدار ترير السرعة وتالحو اننا است دمنا ال صائ ‪ Properties‬وهذا المترير قابل للقراءة فقط‪ ،‬ا يرا ً تجد‬
‫المترير الذي يحدد اقصى سرعة ممكنة‪.‬‬

‫اذن المتريرات منطيقة و ستجد انك عند انشاء اي سيارة مستقبالً ستقوم باست دام هذه المتريرات النها توفر تحكم اكبر بالسيارة‪.‬‬

‫في الدالة ‪ Start‬يج علينا جل المكونات مع تحديد مركز الثقل‪ ،‬اذن اضف الدالة في الملف‪:‬‬
‫)(‪void Start‬‬
‫{‬
‫اجلب المكون الفيزيائي‪rb = GetComponent<Rigidbody>(); //‬‬

‫اعتمد تقصى قيمة للعزم‪ValueMotorTorque = maxMotorTorque; //‬‬

‫اتخذ القيم في المتغير للحصول على مركز الثقل‪rb.centerOfMass = centerOfMass; //‬‬


‫}‬

‫كما ترى االمر بسيط وهو جل المكون ‪ Rigidbody‬مع تحديد اقصى سرعة ايضا ً مركز الثقل‪.‬‬

‫الدالة التالية ستكون ‪ FixedUpdate‬اضفها‪:‬‬


‫)( ‪void FixedUpdate‬‬
‫{‬
‫اقرأ سرعة السيارة‪speed = rb.velocity.magnitude; //‬‬
‫قم بعمل حدود لسرعة السيارة‪speed = Mathf.Clamp(speed, 0f, maxSpeed); //‬‬
‫اطبع السرعة‪print (GetSpeed); //‬‬
‫}‬

‫تجد ان المترير ‪ speed‬ي زن سرعة السيارة فيزيائيا ً بينما يعمل حدود للسرعة وا يرا ً نقوم بطبع قيمة المترير ‪ GetSpeed‬ألنه يعيد مقدار ترير السرعة في‬
‫المترير ‪.speed‬‬

‫سنضيف االن دالتان‪ ،‬االولى لتحريك العجالت بينما الثانية لتدوير العجالت االمامية‪ ،‬اضف الدوال‪:‬‬
‫ادخل رقم العجلة ‪ +‬مفتاح التحريك‪public void ApplyMove(int i, float input) //‬‬
‫{‬
‫طبق العزم الحركي للعجلة‪Wheels[i].motorTorque = ValueMotorTorque * input; //‬‬
‫}‬

‫ادخل رقم العجلة ‪ +‬مفتاح تدوير (لف) العجلة‪public void ApplySteerAngle(int i, float input) //‬‬
‫{‬
‫قم بلف العجلة‪Wheels[i].steerAngle = Steering * input; //‬‬
‫}‬

‫لنناقش الدالة ‪ ApplyMove‬ومهمتها هي تطبيق حركة العجالت‪ ،‬تجد البارامترات واحد لتحديد رقم العجلة و اال ر يحدد مفتاح التحريك اي ‪Vertical‬‬
‫وهكذا‪ .‬المصفوفة ‪ Wheels‬ستقوم بتطبيق العزم الحركي للعجلة عبر است دام المترير ‪ motorTorque‬وهو يقوم باضافة عزم حركي الى العجلة ونضربه‬
‫بسرعة العزم وا يرا بمفتاح التحريك والعملية بسيطة كما ترى‪.‬‬

‫نفس الشيء في الدالة ‪ ApplySteerAngle‬ولكن هنا قمت باست دام المترير ‪ steerAngle‬وضربته بزاوية الدوران وا يرا ً مفتاح االد ال‪SteerAngle .‬‬
‫هو مصطلح تدوير العجالت على المحور االفقي ‪ Y‬وغالبا ً تكون زاوية الدوران هي ‪ 30‬درجة‪ ،‬هذا المترير يتكفل بإعطائك الزاوية باإلشارتين الموجبة و‬
‫السالبة مثل ‪ 30‬و ‪.-30‬‬

‫بعمل كبح للعجالت اضفها‪:‬‬ ‫الدالة التالية ست ت‬


‫ادخل رقم العجلة ‪ +‬قوة الكبح‪void BrakeValues(int i, float maxBrake) //‬‬
‫{‬
‫اتخذ القيمة المضروبة كنسبة لكبح العجلة‪MaxBrakeTorque = ValueMotorTorque * maxBrake; //‬‬
‫قم بتطبيق الكبح‪Wheels[i].brakeTorque = MaxBrakeTorque; //‬‬
‫اوقف العزم الحركي‪Wheels[i].motorTorque = 0f; //‬‬
‫}‬

‫بتحديد رقم العجلة مع اعطاء مضاعفات الكبح‪ .‬يقوم المترير‬ ‫الحو معي الدالة ‪ BrakeValues‬تقوم هذه الدالة بتوفير بارامترات ت ت‬
‫‪ MaxBrakeTorque‬بضر قيمتة بالسرعة الحالية وبمضاعفات الكبح‪ .‬اما في المصفوفة ‪ Wheels‬سيتم تطبيق الكبح عبر المترير ‪ brakeTorque‬بينما‬
‫سنجعل السرعة صفر‪ .‬سيتم تطبيق االمر حالما يتم استدعاء الدالة وبطبيعة الحالة المفتاح ‪ Space‬يكبح السيارة وبدورها الدالة ستعمل عند الضرط علية‪.‬‬

‫الصفحة ‪36‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫االن سنصمم دالة الكبح اي ستحدد مفتاح الذي سيقوم بكبح السيارة وسيتم تطبيق الدالة ‪ ،BrakeValues‬اضفها‪:‬‬
‫طبق الكبح‪ ,‬ادخل مفتاح تطبيق الكبح‪public void ApplyBrake(KeyCode key) //‬‬
‫{‬
‫هل تم الضغط على المفتاح ؟‪if(Input.GetKey(key)) //‬‬
‫{‬
‫)‪for(int i = 0; i < 4; i++‬‬
‫{‬
‫اكبح جميع العجالت‪BrakeValues(i, 10f); //‬‬
‫}‬

‫لم يتم الضغط على المفتاح‪}else{ //.‬‬


‫)‪for(int i = 0; i < 4; i++‬‬
‫{‬
‫أوقف الكبح‪Wheels[i].brakeTorque = 0f; //‬‬
‫}‬
‫}‬
‫}‬

‫القيمة في ‪ .10‬في‬ ‫اذن العملية بسيطة جدا ً وتعتمد على ت حقق الشرط ومن ثم تستدعي دالة الكبح وجعلها تتطبق على جميع العجالت االربع بينما يتم ضر‬
‫حالة لم يتحقق الشرط سيتم ايقاف الكبح عبر جعل قيمتة صفر‪.‬‬

‫انشاء الملف ‪:ControlDrive‬‬


‫الى هنا تجد ان جميع الدوال السابقة معرف الوصول لها هو ‪ public‬لكي نستدعيها في هذا الملف‪ ،‬يقوم هذا الملف بالتحكم بالحركة عبر تحديد مفاتيح‬
‫التحريك و الكبح‪ .‬سنجعل السيارة ذات دفع رباعي و لفي و امامي اي يمكنك تحديد نوع الدفع الذي تريده‪ .‬يعتمد هذا االمر على تعريف ‪ enum‬يوفر ‪3‬‬
‫انواع للدفع كما ذكرناها ومن ثم يحرك العجالت ال اصة بالدفع‪ .‬اذن قم بأنشاء ملف برمجي جديد وسمه ‪ ControlDrive‬واضف المتريرات التالية‪:‬‬
‫{ ‪public class ControlDrive : MonoBehaviour‬‬

‫حالة القيادة – نوع الدفع‪public enum TypeDrive{ //‬‬


‫دفع رباعي‪FourDrive = 0, //‬‬
‫دفع امامي‪FrontDrive, //‬‬
‫دفع خلفي‪RearDrive //‬‬
‫}‬
‫مرجع الختيار نوع الدفع‪public TypeDrive typeDrive; //‬‬
‫مرجع لملف السيارة الفيزيائية وهو يحتوي على معلومات الحركة‪private CarPhysics cp; //‬‬

‫مفاتح التحريك‪public string keyMove = "Vertical"; //‬‬


‫مفاتح لف العجالت‪public string keySteer = "Horizontal"; //‬‬
‫مفاتح الكبح‪public KeyCode keyBrake = KeyCode.Space; //‬‬
‫}‬

‫المتريرات بسيطة جداً‪ enum ،‬يعرف انواع القيادة عبر تحديد نوع الدفع مثالً الدفع الرباعي ‪ FourDrive‬وهكذا‪ .‬تجد ايضا ً مرجع للسكربت ‪.CarPhysics‬‬
‫ا يرا ً تجد مفاتيح التحكم ‪ keyMove‬مفتاح التحرك الى االمام وتجد ايضا ً مفتاح تدوير العجالت و الكبح‪ .‬يمكنك توسيع االمر حس رغبتك‪.‬‬

‫سنقوم االن باست دام المبدل ‪ switch‬لتحقق من نوع الدفع وسنقوم بتطبيق العجالت ال اصة بذلك‪ .‬االن اضف الدالة ‪ ApplyDrive‬الى الملف‪:‬‬
‫قم بتطبيق القيادة‪void ApplyDrive() //‬‬
‫{‬
‫طبق عملية اللف على العجلتين االماميتين فقط‪//‬‬
‫;))‪cp.ApplySteerAngle(0, Input.GetAxis(keySteer‬‬
‫;))‪cp.ApplySteerAngle(1, Input.GetAxis(keySteer‬‬

‫تحقق من نوع الدفع‪switch(typeDrive) //‬‬


‫{‬
‫رباعي‪ ,‬قم بتحريك جميع العجالت‪case TypeDrive.FourDrive: //‬‬

‫‪cp.ApplyMove(0,‬‬ ‫;))‪Input.GetAxis(keyMove‬‬
‫‪cp.ApplyMove(1,‬‬ ‫;))‪Input.GetAxis(keyMove‬‬
‫‪cp.ApplyMove(2,‬‬ ‫;))‪Input.GetAxis(keyMove‬‬
‫‪cp.ApplyMove(3,‬‬ ‫;))‪Input.GetAxis(keyMove‬‬
‫;‪break‬‬

‫امامي‪ ,‬قم بتحريك العجلتين االماميتين‪case TypeDrive.FrontDrive: //‬‬


‫;))‪cp.ApplyMove(0, Input.GetAxis(keyMove‬‬
‫;))‪cp.ApplyMove(1, Input.GetAxis(keyMove‬‬
‫;‪break‬‬

‫خلفي‪ ,‬قم بتحريك العجلتين الخلفيتين‪case TypeDrive.RearDrive: //‬‬


‫;))‪cp.ApplyMove(2, Input.GetAxis(keyMove‬‬
‫;))‪cp.ApplyMove(3, Input.GetAxis(keyMove‬‬
‫;‪break‬‬
‫}‬
‫}‬

‫الحو االن‪ ،‬تجد في البداية ان تدوير العجالت او عمل الـ‪ SteerAngle‬سيكون فقط للعجالت االمامية وهذا ما نراه في السيارات الواقعية‪ ،‬رقم العجالت‬
‫االمامية هو ‪ 0, 1‬بينما ‪ 2, 3‬رقم العجالت ال لفية حس ترتيبهم في المصفوفة ‪.Wheels‬‬

‫االن في المبدل ‪ Switch‬سيتحقق من حالة الدفع فإذا كان رباعي سيتم تحريك جميع العجالت االربع‪ .‬اما إذا كان دفع امامي تجد انه يحرك العجالت االمامية‬
‫فقط ‪ ،0, 1‬واذا كان لفي سيحرك العجالت ال لفية ‪ .2, 3‬االن يمكنك العودة الى المشروع وضبط قيمك مع هذه القيم‪:‬‬

‫الصفحة ‪37‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


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

‫اذن بالعودة الى الملف ‪ CarPhysics‬يج علينا اضافة دالة بسيطة ولكن من الضروري تعريف مترير يمثل الملف ‪ ControlDrive‬للوصول الى مفاتيح‬
‫الحركة‪ .‬اذن في الملف ‪ CarPhysics‬عرف المترير التالي الى جان المتريرات اال رى‪:‬‬
‫مرجع لملف التحكم بالسيارة‪private ControlDrive control; //‬‬

‫االن يج ان تحصل علية في الدالة ‪ Start‬قم باضافة السطر التالي‪:‬‬


‫اجلب ملف التحكم بالسيارة‪control = GetComponent<ControlDrive> (); //‬‬

‫جميل جداً‪ ،‬االن سنقوم بأنشاء الدالة ‪ ChangeDrag‬ووويفتها هي تريير سرعة السيارة‪ ،‬تأمل الدالة‪:‬‬
‫تغيير االحتكاك الخطي و الزاوي‪void ChangeDrag() //‬‬
‫{‬
‫هل السيارة تتحرك ؟‪if(Input.GetAxis(control.keyMove) != 0) //‬‬
‫{‬
‫قم بتقليل نسبة االحتكاك الخطي اعتمادا على السرعة‪rb.drag -= GetSpeed/100; //‬‬
‫قم بزيادة االحتكاك الزاوي‪rb.angularDrag += GetSpeed/100; //‬‬
‫}‬

‫هل توقفت السيارة او تم تطبيق الكبح‪//‬‬


‫))‪else if(Input.GetAxis(control.keyMove) == 0 || Input.GetKey(control.keyBrake‬‬
‫{‬
‫زيادة االحتكاك الخطي‪rb.drag += GetSpeed/100; //‬‬
‫تقليل االحتكاك الزاوي‪rb.angularDrag -= GetSpeed/100; //‬‬
‫}‬

‫اعمل حدود لالحتكاك الخطي و الزاوي‪//‬‬


‫;)‪rb.drag = Mathf.Clamp(rb.drag, 0.1f, 0.5f‬‬
‫;)‪rb.angularDrag = Mathf.Clamp(rb.angularDrag, 0.1f, 1‬‬
‫}‬

‫كما ترى قمنا بالوصول الى مفاتيح التحكم عبر المترير ‪ control‬ومن ثم طبقنا الشروط على المفاتيح ففي حالة كانت السيارة تتحرك سيتم إنقا نسبة‬
‫االحتكاك ال طي بينما يرتفع االحتكاك الزاوي لمنعها من الدوران بشكل حاد‪ .‬اما إذا تم تطبيق الكبح سيزيد االحتكاك ال طي لتوقف السيارة بينما ينق‬
‫االحتكاك الزاوي لجعل السيارة تنزلق‪.‬‬

‫اذن الى هنا نكون قد أنهينا العمل على الملف ‪ ControlDrive‬ولكن سنعود الى الملف ‪ CarPhysics‬الضافة االنزالقات عند الكبح‪.‬‬

‫انشاء الملف ‪:MeshsWheels‬‬


‫سابقا ً كانت عجالت السيارة ثابتة في مكانها اي ان العجالت المرئية فقط هي من تعمل وتدور بينما العجالت في الكائن ‪ Meshs‬ثابته في مكانها‪ .‬هنا سنقوم‬
‫بربط تلك العجالت مع العجالت المرئية‪ .‬سنقوم هنا بأنشاء ملف جديد يمثل هذه العملية وي زن فيه العجالت من الكائن ‪ Meshs‬ويج ان يكون ترتي‬

‫الصفحة ‪38‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫ان ينطبق‬ ‫العجالت متساوي مع المصفوفة ‪ Wheels‬مثالً لو كانت العجلة االمامية على اليمين في المصفوفة ‪ Wheels‬تساوي صفر فأن نفس الشيء يج‬
‫على العجالت الرير فيزيائية‪.‬‬

‫اذن لتطبيق هذا االمر قم بأنشاء ملف برمجي باسم ‪ MeshsWheels‬وأضف الكود التالي اليه‪:‬‬
‫{ ‪public class MeshsWheels : MonoBehaviour‬‬

‫مصفوفة لجلب عجالت السيارة‪public Transform[] Meshs; //‬‬

‫مرجع لملف التحكم بالسيارة‪private CarPhysics cp; //‬‬

‫)(‪void Start‬‬
‫{‬
‫اجلب ملف التحكم بالسيارة‪cp = GetComponent<CarPhysics> (); //‬‬
‫}‬
‫}‬

‫العملية بسيطة جدا ً تعتمد على عمل مصفوفة ت زن عجالت السيارة‪ ،‬ايضا ً الحصول على السكربت ‪ CarPhysics‬للوصول الى المصفوفة ‪.Wheels‬‬

‫حسنا ً يج علينا انشاء دالة ثانية تقوم بربط العجالت المرئية مع عجالت السيارة‪ ،‬اضف الدالة ‪:MoveMeshsWithWheels‬‬
‫حركة عجالت السيارة مع العجالت الفيزيائية‪void MoveMeshsWithWheels(int i) //‬‬
‫{‬
‫متغير لجلب معلومات تصادم الشعاع‪RaycastHit hit; //‬‬
‫متغير لتخزين موقع العجالت‪Vector3 wheelsPos; //‬‬

‫‪if(Physics.Raycast(cp.Wheels[i].transform.position,- cp.Wheels[i].transform.up, out hit,‬‬


‫))‪cp.Wheels[i].radius+cp.Wheels[i].suspensionDistance‬‬
‫{‬
‫خزن موقع تالمس الشعاع مضاف الى ارتفاع وقطر العجلة‪//‬‬
‫;‪wheelsPos = hit.point + cp.Wheels[i].transform.up * cp.Wheels[i].radius‬‬
‫}‬
‫{‪else‬‬
‫اعد لنا المسافة الفارقة بين موقع العجلة و محورها العمودي‪//‬‬
‫* ‪wheelsPos = cp.Wheels[i].transform.position - cp.Wheels[i].transform.up‬‬
‫;‪cp.Wheels[i].suspensionDistance‬‬
‫}‬
‫احصل على موقع العجالت الفيزيائية‪Meshs[i].position = wheelsPos; //‬‬
‫}‬

‫لنناقش هذه العملية نوعا ً ما هي معقدة ولكن نتائجها دقيقة‪ .‬تجد ان الدالة توفر بارامتر يعرف يحدد رقم العجلة مثل الدوال السابقة‪ ،‬االن نقوم بربط العجالت‬
‫المرئية مع عجالت السيارة عن طريق اطالق شعاع من العجالت المرئية الى االسفل‪ ،‬من ثم نست دم المترير ‪ hit‬لجل معلومات التصادم‪ ،‬ا يرا ً يطل مننا‬
‫المسافة وهي حجم القطر زايد مسافة تعليق القطر سيعطيك النتيجة ‪ 0.635‬وهي المسافة التي ستنقل العجلة المرئية الى عجلة السيارة‪.‬‬

‫عند تحقق الشرط سيتم ربط العجلة عن طريق المترير ‪ wheelsPos‬وسي زن نقطة الشعاع مضاف اليها موقع العجلة على المحور العمودي مضروبة في‬
‫حجم القطر‪.‬‬

‫غير ذلك سيجعل عجلة السيارة تتحرك الى االعلى واالسفل مع العجلة المرئية‪.‬‬

‫ان تنتبه له وهو ان تكون العجالت المرئية‬ ‫ا يرا ً نقوم باعتماد قيمة المترير ‪ wheelsPos‬الست دام القيم الم زنة في العجلة التي تريدها‪ ،‬لكن هنا امر يج‬
‫مع عجالت السيارة متساوية في ارقامها دا ل المصفوفات‪.‬‬

‫بهذا االمر‪ ،‬اذن أضف الدالة ‪:SteerMeshsWheels‬‬ ‫االن يج علينا عمل ‪ Steer‬لعجالت السيارة مع العجالت المرئية‪ .‬سنقوم بأنشاء دالة ت ت‬
‫لف عجالت السيارة مع العجالت الفيزيائية‪void SteerMeshsWheels() //‬‬
‫{‬

‫‪Meshs[0].localEulerAngles = new Vector3‬‬


‫‪(Meshs[0].localEulerAngles.x,cp.Wheels[0].steerAngle‬‬
‫;)‪- Meshs[0].localEulerAngles.z,Meshs[0].localEulerAngles .z‬‬

‫‪Meshs[1].localEulerAngles = new Vector3‬‬


‫‪(Meshs[1].localEulerAngles.x,cp.Wheels[1].steerAngle‬‬
‫;)‪- Meshs[1].localEulerAngles.z,Meshs[1].localEulerAngles .z‬‬
‫}‬

‫كما ترى ان العجالت االمامية ‪ 0, 1‬هي المقصودة بعمل ‪ Steer‬لها‪ .‬نقوم عن طريق المترير ‪ LocalEulerAngles‬بتدوير العجلتين على المحور ‪ y‬فقط فكما‬
‫ترى ان المحور ‪ x‬و ‪ z‬لم يرير فيهما شيء بينما المحور االفقي ‪ y‬يعطيك المسافة الفاصلة بين دوران العجلة المرئية و عجلة السيارة وتلقائيا ً ستالحو ان‬
‫العجلة تعمل ‪ .Steer‬السطر الثاني منسوخ من السطر االول ولكن مع تريير رقم العجلة الى واحد‪.‬‬

‫الدالة اال ير ست ت بتدوير العجالت على المحور العمودي اي دوران وليس عمل ‪ Steer‬كما في الدالة السابقة‪ .‬نقوم بتطبيق هذا االمر عبر تحويل‬
‫السرعة من دورة لكل دقيقة الى درجة لكل ثانية اي ان العملية ستكون ‪ rpm‬مدورة كل دقيقة) مقسومة على ‪ 60‬عدد الثواني مضروبة في ‪ 360‬درجة‪ .‬اذن‬
‫أضف الدالة لكي تفهم العملية‪:‬‬
‫)‪void RotateMeshsWheels(int i‬‬
‫{‬
‫تحويل الدورة لكل دقيقة الى دورة لكل ثانية‪float rps = cp.Wheels [i].rpm / 60f; //‬‬
‫نقوم بتحويل الدورة لكل ثانية الى دورة لكل درجة ‪float dps = rps * 360f; //‬‬
‫طبق عملية الدوران على المحور العمودي‪Meshs[i].Rotate(dps * Time.deltaTime, 0f, 0f); //‬‬

‫الصفحة ‪39‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫}‬

‫كما ترى العملية واضحة بشكل كبير‪ ،‬نقوم بتحويل السرعة من دورة‪/‬دقيقة الى درجة‪/‬ثانية ومن ثم نقوم بتدوير العجلة باالعتماد على الدالة ‪ Rotate‬على‬
‫المحور ‪ x‬االفقي‪.‬‬

‫ا يرا ً علينا انشاء الدالة ‪ MeshsWheelsInforamtion‬ومن ثم مناداتها في ‪ ،FixedUpdate‬حسنا ً أضف الدالتين‪:‬‬


‫)(‪void FixedUpdate‬‬
‫{‬
‫معلومات عجالت السيارة و العجالت الفيزيائية‪MeshsWheelsInforamtion (); //‬‬
‫}‬

‫معلومات عجالت السيارة و العجالت الفيزيائية‪void MeshsWheelsInforamtion() //‬‬


‫{‬

‫قم بتحريك جميع العجالت مع العجالت الفيزيائية‪//‬‬


‫;)‪MoveMeshsWithWheels(0‬‬
‫;)‪MoveMeshsWithWheels(1‬‬
‫;)‪MoveMeshsWithWheels(2‬‬
‫;)‪MoveMeshsWithWheels(3‬‬

‫قم بتدوير جميع العجالت مع العجالت الفيزيائية‪//‬‬


‫;)‪RotateMeshsWheels(0‬‬
‫;)‪RotateMeshsWheels(1‬‬
‫;)‪RotateMeshsWheels(2‬‬
‫;)‪RotateMeshsWheels(3‬‬

‫دالة لف العجالت االمامية‪SteerMeshsWheels (); //‬‬


‫}‬

‫الحو ان جميع العجالت سترتبط بالعجالت المرئية بينما جميع العجالت ايضا ً ستدور وهذا شيء طبيعي‪ ،‬بينما يتم تطبيق الـ ‪ Steer‬في اال ير وا يرا ً‬
‫نستدعي الدالة في ‪.FixedUpdate‬‬

‫االن بالعودة الى المشروع قم بملء المصفوفة كما قمت بترتي العجالت في المصفوفة ‪ Wheels‬وا يرا ً جر السيارة وستجد ان العجالت تعمل بشكل جيد‪.‬‬
‫وبهذا الشكل نكون قد أنهينا العمل على هذا الملف‪.‬‬

‫انشاء الملف ‪:FrictionWheels‬‬


‫هذا الملف مهم جدا ً ووويفته من اسمه او كما يقال‪" :‬االسم على جسم"‪ .‬عموما ً يقوم هذا الملف بتطبيق االنزالقات الجانبية واالمامية للعجالت االمامية‬
‫وال لفية‪.‬‬

‫دعني اعطيك فكرة عن است دام متريرات االنزالقات في المكون ‪ Wheel Collider‬في الواقع است دام المتريرات بشكل مباشر مثل تحديد االحتكاك االمامي‬
‫للعجالت االمامية من المكون نفسه هو غير صحيح الن جميع قيم االنزالقات قابلة للقراءة فقط ال للكتابة‪ .‬هنا يوفر لنا اليونتي الفئة ‪WheelFrictionCurve‬‬
‫هذه الفئة ت ت بتحديد منحنى االحتكاك في العجالت وعبرها ن زن قيم االحتكاك‪.‬‬
‫لنفكر في ماذا لو قمنا بتريير نسبة االحتكاك فكيف سنعيدها الى حالتها االصلية؟ هل عن طريق تعريف مترير لكل اصية في االنزالق؟ هذا االمر متع فعال ً‬
‫ولكن هنا يوجد حل بديل وهو عن طريق ت زين القيم االفتراضية لالنزالقات في العجالت ومن ثم است دامها عند توقف الكبح‪ .‬حسنا ً قم بأنشاء ملف برمجي‬
‫باسم ‪ FrictionWheels‬ومن ثم أضف الكود التالي‪:‬‬
‫{ ‪public class FrictionWheels : MonoBehaviour‬‬

‫مرجع لملف التحكم بالسيارة‪private CarPhysics cp; //‬‬

‫مرجع للفئة لتغيير االنزالقات االمامية‪public WheelFrictionCurve OrignalForwardFriction; //‬‬


‫مرجع للفئة لتغيير االنزالقات الجانبية‪public WheelFrictionCurve OrignalSidwaysFriction; //‬‬

‫)(‪void Start‬‬
‫{‬
‫اجلب ملف التحكم بالسيارة‪cp = GetComponent<CarPhysics> (); //‬‬
‫)‪for(int i = 0; i < cp.Wheels.Length; i++‬‬
‫{‬
‫خزن القيم االفتراضية لالنزالق االمامي في العجالت الفيزيائية‪//‬‬
‫;‪OrignalForwardFriction = cp.Wheels[i].forwardFriction‬‬
‫خزن القيم االفتراضية لالنزالق الجانبي في العجالت الفيزيائية‪//‬‬
‫;‪OrignalSidwaysFriction = cp.Wheels[i].sidewaysFriction‬‬
‫}‬
‫}‬
‫}‬

‫كما ترى لدينا مرجع للسكربت ‪ CarPhysics‬للوصول الى عجالت السيارة‪ ،‬تجد المتريرين من نوع ‪ WheelFrictionCurve‬ووويفتهما هي ت زين قيم‬
‫االفتراضية لالنزالقات في كل عجلة‪ .‬كما ترى الدالة ‪ Start‬توضح هذا المفهوم بشكل بسيط‪.‬‬

‫الصفحة ‪40‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫حسنا ً يج علينا انشاء الدالتان ‪ ApplyForwardFriction‬و‪ ApplySidewaysFriction‬بتطبيق االنزالقات الجانبية واالمامية مع احتكاك االطارات‬
‫باألر ‪ ،‬اذن اضف الدوال التالية‪:‬‬
‫طبق االنزالقات االمامية‪//‬‬
‫)‪public void ApplyForwardFriction(int wheelForward, float extremumSlip, float stiffness‬‬
‫{‬
‫اجلب قيمة االنزالق من العجالت االمامية‪//‬‬
‫;‪WheelFrictionCurve ff = cp.Wheels[wheelForward].forwardFriction‬‬
‫خزن اقصى تزحلق‪ff.extremumSlip = extremumSlip; //‬‬
‫خزن الصالبة‪ff.stiffness = stiffness; //‬‬
‫اعتمد القيم المخزنة في المتغير‪cp.Wheels[wheelForward].forwardFriction = ff; //‬‬

‫}‬

‫طبق االنزالقات االمامية‪//‬‬


‫)‪public void ApplySidewaysFriction(int wheelSideways, float extremumSlip, float stiffness‬‬
‫{‬
‫اجلب قيمة االنزالق من العجالت الجانبية‪//‬‬
‫;‪WheelFrictionCurve sf = cp.Wheels[wheelSideways].sidewaysFriction‬‬
‫خزن اقصى تزحلق‪sf.extremumSlip = extremumSlip; //‬‬
‫خزن الصالبة‪sf.stiffness = stiffness; //‬‬
‫اعتمد القيم المخزنة في المتغير‪cp.Wheels[wheelSideways].sidewaysFriction = sf; //‬‬
‫}‬

‫التعديالت البسيطة‪ ،‬سأقوم‬ ‫اووه‪ ...‬ما هذا الكود؟! في الحقيقة ليس باألمر الم يف ففي حالة تأملته كثيرا ً ستجد ان الدالة االولى هي نفسها الثانية مع بع‬
‫بشرح الدالة االولى وستفهم طريق عمل الثانية‪.‬‬

‫تجد الدالة االولى تمثل االنزالق االمامي وتوفر عدة بارامترات مثل رقم العجلة االمامية‪ ،‬باالضافة الى تحديد نسبة االنزالق ‪ extremumSlip‬الى جان‬
‫نسبة احتكاك االطارات ‪ .stiffness‬االن عبر جعل المترير ‪ ff‬ي زن قيم االنزالق الحالية سيقوم بتطبيق القيم الجديدة اليها فمالً السطر الثاني يقوم بتريير نسبة‬
‫االنزالق االمامي باالضافة الى االحتكاك وا يرا ً تقوم هذه العجلة باعتماد القيم الموجودة بالمترير ‪.ff‬‬

‫نفس الشيء ينطبق على الدالة الثانية ولكن يتم تطبيق االمر على االنزالق الجانبي كما هو واضح وليس هناك شيء م يف او مربك في هذه الدوال‪ .‬تجد ان‬
‫معرف الوصول ال ا بهم هو ‪ public‬لكي نستدعيهم عندما يتم الكبح‪.‬‬

‫االن يج علينا العودة الى الملف ‪ CarPhysics‬وقم بالذها الى الدالة ‪ ApplyBrake‬التي انشأناها سابقا ً وعدلها بحيث تصبح بهذا الشكل‪:‬‬

‫طبق الكبح‪ ,‬ادخل مفتاح الكبح‪public void ApplyBrake(KeyCode key) //‬‬


‫{‬
‫هل تم تطبيق الكبح ؟‪if(Input.GetKey(key)) //‬‬
‫{‬
‫)‪for(int i = 0; i < 4; i++‬‬
‫{‬
‫;)‪BrakeValues(0f, 10f‬‬
‫;)‪BrakeValues(1f, 10f‬‬
‫;)‪BrakeValues(2f, 10f‬‬
‫;)‪BrakeValues(3f, 10f‬‬

‫طبق االنزالق االمامي على العجالت رقم صفر وواحد‪//‬‬


‫;)‪fw.ApplyForwardFriction(0f, 10f, 3f‬‬
‫;)‪fw.ApplyForwardFriction(1f, 10f, 3f‬‬
‫طبق االنزالق الجانبي على العجالت رقم اثنين وثالثة‪//‬‬
‫;)‪fw.ApplySidewaysFriction(2f, 5f, 3f‬‬
‫;)‪fw.ApplySidewaysFriction(3f, 5f, 3f‬‬
‫}‬

‫{‪}else‬‬
‫)‪for(int i = 0; i < 4; i++‬‬
‫{‬
‫‪Wheels[0].brakeTorque‬‬ ‫=‬ ‫;‪0f‬‬
‫‪Wheels[1].brakeTorque‬‬ ‫=‬ ‫;‪0f‬‬
‫‪Wheels[2].brakeTorque‬‬ ‫=‬ ‫;‪0f‬‬
‫‪Wheels[3].brakeTorque‬‬ ‫=‬ ‫;‪0f‬‬

‫استعمل القيم االفتراضية األمامية لالنزالق‪//‬‬


‫;‪Wheels[i].forwardFriction = fw.OrignalForwardFriction‬‬
‫استعمل القيم االفتراضية الجانبية لالنزالق‪//‬‬
‫;‪Wheels[i].sidewaysFriction= fw.OrignalSidwaysFriction‬‬
‫}‬
‫}‬
‫}‬

‫نعم هذا هو المطلو ‪ ،‬العملية واضحة جدا ً وتجد ان الترير فقط هو اننا قمنا باست دام الدوال في حالة تم الكبح وسيتم تطبيق القيم التي قمت بإعطائها‪ ،‬غير‬
‫ذلك سنعيد است دام القيم االفتراضية لضمان حركة السيارة بشكل طبيعي‪.‬‬

‫بهذا الشكل نكون قد انهينا برمجة السيارة بشكل كامل وتبقى لنا امر واحد فقط وهو برمجة حركة الكاميرا ال اصة بها‪.‬‬

‫الصفحة ‪41‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫برمجة حركة الكاميرا‪:‬‬
‫صوصا ً في السيارات يج ان تكون الكاميرا مريحة جدا ً عند التحكم بالسيارة بحيث تستطيع تدويرها باالضافة الى جعلها تتبع السيارة تلقائياً‪ .‬سنقوم في هذه‬
‫الفقرة بصنع كاميرا جميلة تساعدك على تدويرها على محورها االفقي باالضافة الى جعلها تتبع السيارة بشكل تلقائي‪ .‬اذن لتصميم هذه الكاميرا قم بأنشاء ملف‬
‫برمجي باسم ‪ CarCamera‬واضف المتريرات التالية‪:‬‬
‫الكائن االب الذي سيتبع السيارة‪private Transform camFollow; //‬‬
‫مرجع للكاميرا الرئيسية‪private Transform mainCam; //‬‬
‫لتخزين محاور الدوران‪private Quaternion q_cam; //‬‬
‫الهدف‪public Transform target; //‬‬
‫سرعة التتبع‪public float speedFollow = 5f; //‬‬
‫سرعة النظر الى السيارة‪public float speedLook = 10f; //‬‬
‫سرعة التحكم بالماوس‪public float speedMouse = 2f; //‬‬
‫النعومة اثناء التحكم بالماوس‪public float smoothMouse = 10f; //‬‬
‫متغير لتحزين زاوية الدوران‪private float angle; //‬‬

‫جميع هذه القيم مررنا عليها في مشروع الكاميرا االستراتيجية‪[Header("Zoom")] //‬‬


‫;‪public float minDelta = -7f‬‬
‫;‪public float maxDelta = -4f‬‬
‫;‪public float speedZoom = 2f‬‬
‫;‪private float deltaCamera‬‬
‫;‪private float lerpDeltaCamera‬‬

‫اذن كما ترى االعدادات بسيطة جدا ً وتستنتج من اللها ان الكائن اال هو من سيتبع السيارة بينما الكاميرا الرئيسية ستنور الى السيارة دائما ً‪.‬‬

‫الكائنات بما انها ‪ Private‬اذن اضفها‪:‬‬ ‫يج علينا تعريف عدة دوال وسنبدأ بالدالة ‪ Start‬لجل المكونات والحصول على محاور بع‬
‫)(‪void Start‬‬
‫{‬
‫جعل الكائن االب يتخذ موقع هذا الكائن لمحور له‪camFollow = transform; //‬‬
‫اتخذ موقع الهدف كنقطة للبدء‪//‬‬
‫;)‪camFollow.position = new Vector3 (target.position.x, target.position.y, target.position.z - 1‬‬
‫جعل الكاميرا الرئيسية تتخذ محور االبن رقم صفر (هو الكاميرا الرئيسية اساسا)‪//‬‬
‫;‪mainCam = transform.GetChild (0).transform‬‬
‫اتخذ هذا المتغير العطاء قيمة للمحور ‪//z‬‬
‫;‪deltaCamera = mainCam.localPosition.z‬‬
‫قم بتخزين دوران الكاميرا االفتراضي‪//‬‬
‫;‪q_cam = camFollow.rotation‬‬
‫ابحث عن السيارة‪//‬‬
‫;‪target = GameObject.FindGameObjectWithTag ("Player").transform‬‬
‫}‬

‫العملية بسيطة جدا ً وتالحو انه في البداية سنحصل على المكون ‪ transform‬ال ا بالكائن االبن وسنزيحه الى ال لف بمقدار ‪ ،1‬ايضا ً تجد انني است دمت‬
‫الدالة ‪ GetChild‬للحصول على المكون ‪ transform‬من الكائن االبن رقم م‪ )0‬وبطبيعة الحال هو الكاميرا الرئيسية‪ ،‬وبما انها الكاميرا الرئيسية يمكنك بكل‬
‫بساطة الحصول عليها عن طريق ‪ .Camera.main.transform‬تجد ايضا ً اعدادات الـ‪ Zoom‬وجعل المترير ‪ q_cam‬ي زن محاور الكائن اال وا يرا ً‬
‫الهدف هو الالع ويج ان تحتوي السيارة على التاج ‪ Player‬للوصول اليها‪.‬‬

‫االن سنقوم بتصميم الدالة المشهورة ‪ UpdateCameraFollow‬اضفها‪:‬‬


‫اتبع السيارة‪void UpdateCameraFollow() //‬‬
‫{‬
‫انتقل من موقعك الحالي الى موقع السيارة‪//‬‬
‫;)‪camFollow.position = Vector3.Lerp (camFollow.position, target.position, speedFollow * Time.deltaTime‬‬
‫}‬

‫ليس هناك شيء جديد فكما ترى الدالة بسيطة ومعروفة مسبقاً‪.‬‬

‫الدالة الثانية هي ‪ UpdateMainCamera‬ووويفتها جعل الكاميرا الرئيسية تنور الى السيارة‪:‬‬


‫جعل الكاميرا الرئيسية تنظر الى السيارة‪void UpdateMainCamera() //‬‬
‫{‬
‫اجلب المسافة الفاصلة بين الكاميرا و السيارة‪//‬‬
‫;‪Vector3 deltaPos = target.position - mainCam.position‬‬
‫انظر الى النقطة الفاصلة‪//‬‬
‫;)‪Quaternion lookRot = Quaternion.LookRotation (deltaPos‬‬
‫قم بالدوران الى ان تنظر الى النقطة‪//‬‬
‫;)‪Quaternion rotation = Quaternion.Lerp (mainCam.rotation, lookRot, speedLook * Time.deltaTime‬‬
‫اتخذ القيم المعطاة لتطبيق الدوران و النظر‪//‬‬
‫;‪mainCam.rotation = rotation‬‬
‫}‬

‫ايضا ً هذه الدالة مررنا عليها سابقا ً ولن اضيف شيء الى كالمي السابق ويمكنك الرجوع الى مشروع الكاميرا الرئيسية ورؤيتها‪.‬‬

‫الدالة الثالثة ستكون ‪ UpdateZoomCamera‬وهي منسو ة من مشروع الكاميرا الرئيسية لعمل تقري وتبعيد للكاميرا‪ .‬اضفها‪:‬‬
‫التقريب‪void UpdateZoomCamera() //‬‬
‫{‬
‫انتقل من موقع محور الكاميرا االمامية الى القيم المعطاة للتقريب‪//‬‬
‫;)‪deltaCamera = Mathf.Lerp(deltaCamera, lerpDeltaCamera, Time.deltaTime * speedZoom‬‬
‫انقل الكاميرا من موقعها االمامي الحالي الى القيمة المخزن لتطبيق التقريب ‪ +‬على المحور العمودي ايضا‪//‬‬
‫;)‪mainCam.localPosition = new Vector3 (mainCam.localPosition.x, deltaCamera * -0.5f, deltaCamera‬‬
‫هل عجلة الماوس تدور الى االمام؟‪//‬‬
‫)‪if(Input.GetAxis("Mouse ScrollWheel") > 0f‬‬

‫الصفحة ‪42‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫زيادة مقدار التقريب‪lerpDeltaCamera += speedZoom; //‬‬
‫هل عجلة الماوس تدور الى الخلف ؟‪else if(Input.GetAxis("Mouse ScrollWheel") < 0f) //‬‬
‫اناقص مقدار التقريب‪lerpDeltaCamera -= speedZoom; //‬‬
‫اعمل حدود للتقريب‪lerpDeltaCamera = Mathf.Clamp(lerpDeltaCamera, minDelta, maxDelta); //‬‬
‫}‬

‫دالة التقري موجودة في الكاميرا االستراتيجية ايضاً‪ ،‬وتالحو ان جزء كبير من هذا الملف قد تم نس ة من الكاميرا االستراتيجية‪.‬‬

‫تبقى لنا جعل الماوس يتحكم بدوران الكاميرا‪ .‬سنقوم بتطبيق االمر عندما نقوم الضرط على الزر االيمن للماوس وفي حالة افالته ستعود الكاميرا الى موضعها‬
‫االصلي‪ .‬حسنا ً قم باضافة هذه الدالة‪:‬‬
‫تدوير الكاميرا بواسطة الماوس‪void UpdateRotateCameraWithMouse() //‬‬
‫{‬
‫هل تم الضغط على الزر األيمن االيسر للماوس ؟‪if (Input.GetMouseButton(1)) { //‬‬

‫خزن إزاحة الماوس االفقية‪float y = Input.GetAxis("Mouse X") * speedMouse; //‬‬


‫زيادة المتغير في مقدار االزاحة‪angle += y; //‬‬
‫اتخاذ لقيمة االزاحة لتدوري الكائن على المحور االفقي‪q_cam = Quaternion.Euler(0f, angle, 0f); //‬‬
‫التدوير االنسيابي للكاميرا‪//‬‬
‫;)‪camFollow.rotation = Quaternion.Lerp(camFollow.rotation, q_cam, smoothMouse * Time.deltaTime‬‬

‫اعد الكائن االب الى حالة النظر التلقائي‪} else { //‬‬


‫;‪Vector3 deltaPos = target.position - camFollow.position‬‬
‫;)‪Quaternion lookRot = Quaternion.LookRotation (deltaPos‬‬
‫;)‪q_cam = Quaternion.Lerp (camFollow.rotation, lookRot, speedLook * Time.deltaTime‬‬
‫;‪camFollow.rotation = q_cam‬‬
‫}‬
‫}‬

‫حسنا ً قد تجد بع االمور غير مفهومه وهي لماذا الكائن اال ؟ نعرف ان الكائن اال وويفته تتبع السيارة وليس النور ولكن يمكنك است دامة في جعله‬
‫يدور حول السيارة باالضافة الى النور اليها وهذا سيعيط جمالية اكثر عند القيادة‪ .‬تقوم العملية بتدوير الكائن اال على المحور االفقي ‪ y‬عن طريق الماوس‬
‫كما هو موضح في الشرط‪.‬‬

‫غير ذلك سنقوم بجعل الكائن اال ينور الى السيارة كما عملنا مع الكاميرا الرئيسية‪ ،‬اذن االمر بسيط جدا ً وليس هنا شيء معقد‪.‬‬

‫االن في الدالة ‪ FixedUpdate‬قم باستدعاء كل الدوال كما هو موضح‪:‬‬


‫{ )( ‪void FixedUpdate‬‬

‫الكاميرا االب تتبع السيارة‪UpdateCameraFollow (); //‬‬


‫الكاميرا الرئيسية تنظر الى السيارة‪UpdateMainCamera (); //‬‬
‫التقريب‪UpdateZoomCamera (); //‬‬
‫التحكم بالتدوير بواسطة الماوس‪UpdateRotateCameraWithMouse (); //‬‬
‫}‬

‫وبهذا الشكل نكون قد أنهينا العمل على الكاميرا الرئيسية ويمكنك تجر مشروع السيارة لرؤية العمل بشكل جيد‪.‬‬

‫الصفحة ‪43‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫محاكاة حركة القوارب ‪:Boat‬‬
‫ش صيا ً اعتبر هذه الفقرة مهم جدا ً جدا ً جداً‪ ،‬جميعنا قد نواجه مشكلة في تحريك القوار في الماء وربما نست دم العملية البسيطة التي نعرفها مثل الدوال‬
‫‪ AddForce‬الضافة سرعة للقوار وشيء من هذا القبيل‪ .‬نعم هذه الطريقة جميلة ولكن البع لم يعرف كيفية است دامها بالشكل الصحيح‪.‬‬

‫اعتمدت هنا على الحزمة ‪ watercraftpack_kenney‬من موقع ‪ Kenney.nl‬للحصول على موديالت للقوار فقط‪ ،‬قمت بتجهيز الموديالت بشكل كامل‬
‫اي عدلت عليها لكي استطيع تحريكها وتدويرها ويمكنك رؤية القوار بشكلها النهائي في دا ل الملف ‪ Project‬تجد الملف ‪ Boats‬دا لة ملف آ ر وهو‬
‫‪ Boats Prefabs‬وهو يحتوي على موديالت القوار وتستطيع سح واحد الى المشروع لكي نوضح تركيبته‪.‬‬

‫الحو معي تركي القار وتجد ان القار باسم ‪ watercraftpack_020‬هو آ ر كائن‪ .‬الكائن اال ‪ Boat_1‬يحتوي على العديد من الكائنات وتالحو انه‬
‫يملك المكون ‪ Rigidbody‬وتجد ان وزن القار االفتراضي موزنة في اليابسة) هو ‪ 15‬واالحتكاك ال طي ‪ 0‬بينما االحتكاك الزاوي ‪ 0.05f‬وهي القيم‬
‫االفتراضية‪ .‬بالنسبة للكائنين ‪ Axis Z‬و‪ X‬هما فقط لتدوير الجسم على المحورين ألوهام ان القار يتحرك بينما الكائن اال هو من سيدور على المحور ‪Y‬‬
‫ايضا ً سيتم توزيع نقاط الكثافة فيه لجعلة يطفوا‪.‬‬

‫لتطبيق الحركة الفيزيائية على‬ ‫علينا اوالً ان نتعرف على كيف تطفوا متعوم) الكائنات في الماء وما هو السب‬ ‫بما اننا نريد تحريك القار فيزيائيا ً فيج‬
‫قاربنا يج علينا جعله يطفوا فيزيائياً‪.‬‬

‫الطفو (االجسام العائمة)‪:‬‬


‫الطفو هو واهرة تحرك األجسام في الموائع مالسوائل والرازات) إلى األعلى إذا كان محيطها أعلى كثافة منها‪ ،‬ومثال عليها طفو ال ش على الماء‪ ،‬وطفو‬
‫الزيت على الماء والمنطاد والرواصة‪ .‬تلك القوة المؤثرة على الجسم تسمى أحيانا دفع الماء على الجسم‪ .‬وتكون قوة الطفو عبارة عن الفرق بين وزن الجسم‬
‫الحقيقي اليا من المائع مالسائل أو الرازي) وبين وزنه الواهري في الوسط المائع المحيط‪ .‬يقاس وزن الجسم الحقيقي بكتلة الجسم مضروبة × عجلة الجاذبية‬
‫األرضية‪.‬‬

‫الطفو والكثافة‪:‬‬

‫إذا كان وزن جسم ما أقل من وزن الماء المزاح مالمـُزاغ) بنفس حجم الجسم عند انرماره في الماء كليا تكون كثافة الجسم أقل من كثافة الماء‪ .‬وبالتالي فإن‬
‫الجسم سيطفو إلى أن يصبح وزن الجسم مساويا لوزن الماء المزاح‪ .‬أما إذا كان وزن الجسم أكبر من وزن الماء المزاح في حالة انرماره كليا ممثل النحاس)‪،‬‬
‫فإنه يرطس في الماء‪ ،‬لكن النحاس يستطيع الطفو إذا كان مفرغا بحيث يحتفو بحجم كاف من الهواء‪ .‬وفي تلك الحالة‪ ،‬فإن كثافة الجسم ستشمل النحاس وما‬
‫فيه من هواء‪ ،‬وتصبح أقل من كثافة السائل‪ ،‬وبالتالي فإنه يطفو‪ .‬الكثافة = إجمالي كتلة الجسم ÷ حجمه ووحدة الكثافة كيلوغرام ‪ /‬متر ‪ .3‬إذا كان الجسم‬
‫مصنوع من مادة ذات كثافة أعلى من المائع‪ ،‬كجسم من حديد مثال في الماء‪ ،‬يؤدي إلى غرق الجسم في السائل أو الراز المحيط‪ .‬وإذا كان الجسم مصنوع من‬
‫مادة ذات كثافة عالية ولكن يوجد به حيز من الهواء المحبوس‪ ،‬فقد يكفي دفع الماء لكي يطفو‪ .‬مثل السفينة‪ ..‬مصنوعة من الحديد مالفوالذ) ولكنها وعلى الرغم‬
‫من حجمها الكبير تطفو‪ ،‬وذلك لوجود حيز من الهواء‪ ،‬فيكون متوسط كثافة السفينة أقل من كثافة الماء‪ .‬قاعدة أر ميدس لألجسام الطافية‪ " :‬إذا طفا جسم على‬
‫سطح سائل ما فإن وزن الجسم المرمور يساوي وزن السائل المزاح‬

‫انشاء الملف ‪:FloatingObject‬‬


‫اذن ا ذت فكرة عن عوم االجسام فوق الماء‪ .‬في اليونتي يوجد طرق كثيرة لتطبيق هذا االمر ولكن الطريقة االكثر شيوعا ً هي عكس قيمة الجاذبية االرضية‬
‫اي نقوم بتحديد مستوى الماء عن طريق اعطاء قيمة للمحور ‪ Y‬نفتر فيها ان ارتفاع الماء هو هذا ومن ثم نتحقق من ان الجسم أصرر من هذه القيمة ونقوم‬
‫بعكس الجاذبية كما ترى لعملية بسيطة جدا ً وسأعطيك االن مثال بالكود ويمكنك وضع هذا الكود على اي جسم فيزيائي ال تبار االمر‪:‬‬

‫‪public float‬‬ ‫مستوى الماء‪waterLevel; //‬‬

‫{ )( ‪void FixedUpdate‬‬

‫هل موقع الكائن على المحور العمودي اقل من موقع الماء؟‪if (transform.position.y < waterLevel) //‬‬
‫ضع الجاذبية في الحالة االفتراضية‪Physics.gravity = new Vector3 (0f, 9.8f, 0f); //‬‬

‫الصفحة ‪44‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫هل الكائن داخل الماء ؟‪if (transform.position.y > waterLevel) //‬‬
‫قم بقلب الجاذبية لرفع الكائن‪Physics.gravity = new Vector3 (0f, -9.8f / 2f, 0f); //‬‬

‫}‬

‫اذن ما رأيك بعد تجر هذا الكود؟ ا ليس االمر جميل ولكن ال ي لوا من المشاكل واولها هو االحتكاك اي احتكاك الجسم في الماء عندما يالمسه وتريير‬
‫االحتكاك عندما يكون في الهواء‪ .‬في الواقع لن اناقش المسألة في هذا الكود ولكن يمكنك تريير الـ ‪ Drag‬حاليا ً وا تبار االمر‪ .‬يعد االحتكاك عامل مهم جدا ً‬
‫لمنع الكائن من التزحلق بشكل ما النهائي في الماء ايضا ً الدوران‪ ،‬ي تلف احتكاك الماء عن الهواء فعندما ترتطم الكرة في الماء فأن سرعتها تقل وهذا سببته‬
‫االحتكاك‪ .‬نستطيع محاكاة االمر عن طريق تريير نسبة احتكاك الكائن في الهواء والماء واعطاء قيم لتمثيل االحتكاكات بحيث تجعل الـ ‪ Drag‬ال ا بالكائن‬
‫يترير حس تلك القيم المعطاة‪.‬‬

‫ماذا بشأن المناطق القابلة للعوم؟ حسنا ً عرفنا ان كثافة الجسم عامل مهم وانه لكلما قلت كثافة الجسم عن الماء كان الجسم قابل للعوم‪ .‬في اليونتي االمر ي تلف‬
‫بحيث نعتمد على توزيع االماكن القابلة للعوم ويتم فيها تطبيق االرتداد وغيرها من االمور‪ .‬الحو هذه الصورة وهي ألسطوانة تم فيها توزيع النقاط في‬
‫طرفيها‪:‬‬

‫الصورة التالية توضح توزيع نقطة في الوسط وا رى في اليسار‪:‬‬

‫عليك ان تقوم بتوزيع النقاط‬ ‫فيج‬ ‫كما ترى ان تحديد االماكن القابلة للعوم يرير من وضعية الجسم دا ل الماء‪ ،‬فمثالً إذا اردت تطبيق العوم على القوار‬
‫بشكل متساوي بحيث نجعل القار ثابت في مكانه دون تريير دورانه او شيء من هذا القبيل‪.‬‬

‫اذن في حالة قمت بفتح مشروع القوار ستجد الكائن ‪ Water‬وله ايضا ً ‪ Tag‬بأسم ‪ Water‬ايضا ً تجد انه يحتوي على المكون ‪ Box Collider‬وطولة ‪50‬‬
‫باالضافة الى انه ‪ Is Trigger‬مفعل لمنع تصادم االجسام معه ألنه ماء وستجد ان كل ما فيه سيتم است دامه بشكل كبير مثل االسم و التاج باالضافة الى ‪Is‬‬
‫‪ Trigger‬في الكود البرمجي‪.‬‬

‫بما انك قد ا ذت فكرة بسيطة عن توزيع مركز العوم فأننا سند ل الى الكود البرمجي الذي يعطي وي زن قيم برمجية ضرورية لتطبيق هذه العملية‪ ،‬اذن قم‬
‫بأنشاء ملف برمجي بأسم ‪ FloatingObject‬وقم بأضافة هذه القائمة الطويلة بالمتريرات‪:‬‬
‫;‪using UnityEngine‬‬
‫;‪using System.Collections‬‬
‫;‪using System.Collections.Generic‬‬

‫{ ‪public class FloatingObject : MonoBehaviour‬‬

‫لتحديد مستوى الماء‪public float waterLevel = 0f; //‬‬


‫طول العوم‪[Range(0.1f, 1f)]public float floatHeight = 1f; //‬‬
‫ارتداد الجسم في الماء‪public float bounceDamp = 0.05f; //‬‬
‫هل تريد الحصول على نقاط العوم تلقائيا؟‪public bool autoCenterOfFloating = false; //‬‬
‫مصفوفة لتحديد النقاط القابلة للعوم‪public List<Vector3> centerOfFloating; //‬‬
‫متغير لجلب عامل قوة الدفع في الماء‪private float forceFactor; //‬‬

‫االحتكاكات في الهواء و الماء‪[Header("Setting Drag")] //‬‬


‫وزن الجسم خارج الماء‪private float MassQutWater; //‬‬
‫االحتكاك الخطي للجسم خارج الماء‪public float DragLineQutWater = 0f; //‬‬
‫االحتكاك الزاوي للجسم خارج الماء‪public float DragAnglerQutWater = 0.05f; //‬‬
‫هل تريد استخدام االحتكاكات داخل الماء ؟‪public bool useDragsOnWater = true; //‬‬
‫االحتكاك الخطي داخل الماء‪public float dragLineOnWater = 2f; //‬‬
‫االحتكاك الزاوي داخل الماء‪public float dragAnglerOnWater = 1f;//‬‬

‫الصفحة ‪45‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫وزن الجسم داخل الماء‪public float MassOnWater = 0f; //‬‬

‫مرجع للمكون الفيزيائي‪[System.NonSerialized]public Rigidbody rb; //‬‬


‫هل الجسم داخل الماء؟‪[System.NonSerialized]public bool isWater = false; //‬‬
‫اسم التاج الخاص بالماء‪public string nameWater = "Water"; //‬‬
‫اسم الماء‪public string tagWater = "Water"; //‬‬
‫}‬

‫الحو ان جميع هذا المتريرات تفسر العديد من االشياء في حالة قرأتها مثل هل الجسم دا ل الماء وفي حالة كان بالفعل فسيتم تطبيق جميع االمور المتعلقة‬
‫بالماء مثل االحتكاكات ووزن الجسم وتوزيع مركز العوم وغيرة اما إذا كان غير ذلك فسيتم تطبيق وضع الجسم في الهواء او ارج الماء‪.‬‬

‫المترير ‪ waterLevel‬سنقوم بوضع قيمتة يدويا ً ويمكنك عمل طريقة تتحقق من ان الكائن قد حصل على مستوى الماء تلقائيا ً وتعتمد هذه القيمة فيه‪ .‬بالنسبة‬
‫للمترير ‪ FloatHeight‬فهو يحدد نسبة ارتداد الجسم في الماء والقيمة واحد تعني متر واحد كما في الصورة‪:‬‬

‫المترير ‪ autoCenterOfFloating‬يعطيك نقطة االصل كنقطة قابلة للعوم في حالة لم تريد إضافة نقاط لعوم في الكائن‪ centerOfFloating ،‬الذي طال‬
‫الحديث حولها وهي تحدد النقاط القابلة للعوم وبطبيعة الحالة هي من نوع ‪ Vector3‬لتمكينك من وضع النقطة في اي زاوية ويج ان تعلم ان كل انة تمثل‬
‫نقطة واحدة‪ ،‬الحو الصورة وهي تعبر عن توزيع ‪ 3‬نقاط في جسم واحد‪:‬‬

‫كما ترى االمر بسيط وتجد ان المصفوفة توزع النقاط بحيث ان هناك نقطة على اليمين في المحور ‪ X‬وا رى على الشمال وواحدة في المركز وتجد ان كل‬
‫انة تمثل نقطة معينة‪ .‬قد تتسائل في كيفية تحديد تلك النقاط؟ االمر بسيط يمكنك انشاء كائن فارع في الكائن الذي يحتوي على الملف البرمجي ومن ثم تريير‬
‫موقع الكائن االبن وتنسخ قيمة‪ .‬سؤال آ ر وهو لماذا ال اقوم بأنشاء مصفوفة من نوع ‪ Transform‬وت زين االبناء كنقاط قابلة للعوم؟ سؤال مهم جدا ً ولكن‬
‫في الواقع عمل هذا الشيء سيؤدي الى جعل النقاط متحركة اي ستعتمد على محاورها المحلية ال على المحاور االصلية للمشهد لهذا انشاء مصفوفة من نوع‬
‫‪ Vector3‬هو امر جيد مقارنه مع اال رى‪.‬‬

‫القائمة ‪ Setting Drags‬وضاحة جدا ً وهي تحدد نسبة احتكاك الجسم بالماء او الهواء‪ .‬المتريرين اال يرين لتحديد اسم التاج واسم الكائن الذي سيمثل الماء‬
‫هما ا تياريان لتحديد الكائن الذي سنفتر انه ماء‪.‬‬

‫المترير ‪ IsWater‬للتأكد ان الكائن موجود في الماء وستجد ان است دامه مهم جدا ً لتريير السرعة واالحتكاكات وغيرها من االمور‪.‬‬

‫حسناً‪ ،‬في البداية يج علينا ت زين القيم االولى وبطبيعة الحالة في الدالة ‪ Start‬اضفها‪:‬‬
‫)(‪void Start‬‬
‫{‬
‫اجلب المكون الفيزيائي‪rb = GetComponent<Rigidbody> (); //‬‬
‫كتلة الجسم خارج الماء (قيم افتراضية)‪MassQutWater = rb.mass; //‬‬

‫هل تريد نقطة عوم افتراضية ؟‪if(autoCenterOfFloating) //‬‬


‫اجعلها منتصف الكائن‪centerOfFloating.Add(Vector3.zero); //‬‬
‫}‬

‫تالحو ان المترير ‪ MassQutWater‬لت زين وزن الكائن قبل سقوطه في الماء‪ ،‬تجد ايضا ً انه حال الراء تفعيل ال يار ‪ autoCenterOfFloating‬سيتم‬
‫اعطائنا مركز الكائن كنقطة عوم افتراضية‪.‬‬

‫االن يج علينا تطبق عملية العوم لتطبيق التريرات عليها مثل االحتكاكات وغيرها لكن الدالة لن تعمل االن عندما نتأكد من ان المترير ‪ IsWater‬مفعل‬
‫للتأكد من ان الكائن موجود في الماء لتشريل الدالة وتطبيق عملية العوم‪ .‬اذن الدالة التالية ستكون ‪ Floating‬اضفها‪:‬‬

‫الصفحة ‪46‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫)(‪void Floating‬‬
‫{‬
‫)‪foreach (Vector3 v3 in centerOfFloating‬‬
‫{‬
‫قم بتخزين نقاط العوم على شكل متجهات لتمثل النقاط المتحركة‪//‬‬
‫;)‪Vector3 actionPoint = transform.position + transform.TransformDirection(v3‬‬
‫معامل القوم يمثل المحور العمودي لنقطة التحريك ويتحكم بها طول العوم‪//‬‬
‫;)‪forceFactor = 1f - ((actionPoint.y - waterLevel) / floatHeight‬‬
‫تأكد من ارتفاع عامل القوة المؤثرة‪//‬‬
‫{ )‪if (forceFactor > 0f‬‬
‫نسبة االرتفاع ستحدد عن طريق ضرب الجاذبية معامل القوة ناقص السرعة لرفع الجسم بشكل انسيابي‪//‬‬
‫‪Vector3 uplift = -Physics.gravity * (forceFactor - GetComponent<Rigidbody>().velocity.y‬‬
‫;)‪* bounceDamp‬‬
‫ارفع الكائن الى نقاط الحركة وبطبيعة الحال هي متحركة فسيرتفع الكائن تلقائيا‪//‬‬
‫;)‪GetComponent<Rigidbody>().AddForceAtPosition(uplift, actionPoint‬‬
‫}‬
‫}‬

‫}‬

‫اذن الحو الحلقة الدورانية تقوم بتطبيق المصفوفة التي تمثل مركز العوم عبر الدالة ‪ TransformDirection‬التي وويفتها تحويل اتجاه الكائن ولهذا يفضل‬
‫ان تست دم مصفوفة من نوع ‪ Vector3‬لتبقى المحاور ثابته وال تتأثر بدوران الجسم‪ ،‬عموما ً المترير ‪ actionPoint‬ي زن نقطة الحركة‪.‬‬

‫السطر الثاني تجد ان المترير ‪ ForceFactor‬يعطينا المسافة بين سطح الماء ونقطة العوم اي كلما زاد نزول الجسم الى الماء زاد عامل القوم لدفع الكائن الى‬
‫االعلى وتجد العالقة مقسومة على المترير ‪ floatHeight‬ويج ان تكون قيمة اكبر من صفر وال اعطاك كمية غير معرفة مماال نهائية)‪.‬‬

‫السرط الثالث تجد الشرط يتحقق من كون عامل القوم اكبر من صفر لتطبيق قوة دفع الى االعلى وفي حالة تحقق االمر تجد المترير ‪ uplift‬مرفع) يقوم برفع‬
‫الجس م الى االعلى عبر قل الجاذبية االرضية وضربها في عامل القوة ناق سرعة الحركة على المحور ‪ y‬او يمكنك است دام ‪ magnitude‬لحسا‬
‫االطوال ما يهم هو حسا اناق القيمة من سرعة الكائن فمالً لو سقط الكائن من مسافة عالية فأنه عندما يد ل في الماء سيصل الى عمق اكبر وبدورة عامل‬
‫القوم ينق مسافته من هذه السرعة إلعادة القيمة الى الصفر او لدفع الجسم الى االعلى‪ .‬ا يرا ً نضر القيمة في المترير ‪ bounceDamp‬لتحديد نسبة‬
‫ارتداد الجسم في الماء‪.‬‬

‫السرط اال ير متعلق بالدالة ‪ AddForceAtPosition‬وهي توفر بارامترين االول لتحديد متجه القوة بينما الثاني يحدد نقطة االتجاه وبطبيعة الحال المتجه هو‬
‫‪ uplift‬ونقطة االتجاه هي ‪ actionPoint‬كما هو موضح‪.‬‬

‫التي نريدها وتساعدنا على ترييرها تلقائياً‪ ،‬الدالة التالية ستكون‬ ‫االن يج علينا تريير االحتكاكات و الوزن عبر توفير دالة تساعدنا على وضع النس‬
‫‪ DragsAndMess‬اضفها‪:‬‬
‫االحتكاكات و الكتلة‪void DragsAndMess(float drag, float dragAngler, float mass) //‬‬
‫{‬
‫;‪rb.drag = drag‬‬
‫;‪rb.angularDrag = dragAngler‬‬
‫;‪rb.mass = mass‬‬
‫}‬

‫كما ترى الدالة بسيطة جدا ً وتوفر لنا بارامترات تساعدنا على تريير القيم‪.‬‬

‫الدالة التالية تعيد لنا االحتكاك في الماء عبر ضر طول العوم في السرعة في حجم الجسم‪ ،‬الحو الدالة‪:‬‬
‫)(‪public float GetDragInWater‬‬
‫{‬
‫;‪return floatHeight * rb.velocity.magnitude + transform.localScale.magnitude‬‬
‫}‬

‫تجد ان الدالة ‪ public‬والسب لكي نستدعيها الحقاً‪.‬‬

‫الدالة التالية لحسا لتريير السرعة في الماء والهواء‪ ،‬الحو‪:‬‬


‫)‪public float SetSpeedOn(float speed, float value‬‬
‫{‬
‫;)‪return Mathf.Lerp (speed, value, Time.deltaTime * 50f‬‬
‫}‬

‫كما ترى العملية بسيطة وتعتمد على نقل البارامتر ‪ speed‬الى القيمة التي تريدها‪.‬‬

‫االن وصلنا الى الشيء المهم وهو التعرف على كائن الماء عبر التأكد من اننا تالمسنا معة ويقوم بتريير قيمة المترير‪ ، IsWater‬كما تعلم سابقا ً ان كائن الماء‬
‫له االسم ‪ Water‬و الـ ‪ Tag‬نفسة باالضافة الى انه يحتوي على المكون ‪ Box Collider‬مع تفعيل الـ ‪ Is Trigger‬لحسا االجسام المتدا لة معه‪ .‬االن‬
‫سنستفيد من هذا االمر عبر است دامنا للدالة ‪ OnTrigger‬الحو الكود‪:‬‬
‫)‪void OnTriggerEnter(Collider col‬‬
‫{‬
‫))‪if (col.gameObject.name == nameWater || col.gameObject.CompareTag(tagWater‬‬
‫;‪isWater = true‬‬
‫}‬

‫)‪void OnTriggerExit(Collider col‬‬


‫{‬
‫))‪if (col.gameObject.name == nameWater || col.gameObject.CompareTag(tagWater‬‬
‫;‪isWater = false‬‬

‫الصفحة ‪47‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫}‬

‫رج سنريرها الى ‪ false‬بهذه البساطة نستطيع التحقق من اننا‬ ‫تالحو ان العملية بسيطة جدا ً ففي حالة تالمس مع الكائن فسيتم تريير القيمة الى ‪ true‬واذا‬
‫مازلنا على الماء او ال‪.‬‬

‫اذن بهذا الشكل نكون قد أنهينا العمل على الملف البرمجي األهم والذي سيجعلنا نحاكي حركة الكائنات فوق الماء بحيث نجعلها تعوم‪ .‬سيكون شكل الملف‬
‫النهائي كما في الصورة‪:‬‬

‫عند برمجة القوار ستجد ان است دام هذه القيم ليست باألمر السهل والتوصل الى نتيجة جيدة يعتمد وزن الجسم باإلضافة الى االحتكاكات ومن اللها‬
‫تستطيع تحديد سرعة الكائن تحت هذه الوروف‪.‬‬

‫أنشاء الملف ‪:BoatController‬‬


‫اذن سابقا ً تعرفت على تركي القار وماهي الكائنات التي ستتحرك والتي ستدور‪ ،‬العملية هنا تعتمد على جعل القار يت ذ احداثي المحور االمامي للكائن‬
‫الذي سيدور على المحور ‪ Y‬وهذا بطبيعة الحال لتوجيه القار بينما الكائن الذي يمثل المحور ‪ Z‬و‪ X‬هو لتدوير الكائن علية ولن ي لق أي تأثير في الحركة‬
‫أي انه فقط ألوهام الالع بأن القار ينعطف ويتحرك الى االمام او ال لف‪.‬‬

‫انما سنتركها للملف‬ ‫العو‬ ‫يج ان تعلم ان وويفة هذا الملف البرمجي هو تحريك القار عبر دفعه الى االمام ولن يتطرق الى أي مسألة ت‬
‫‪ FloatingObject‬فقط‪ .‬اذن لنقم بأنشاء الملف البرمجي ‪ BoatController‬واضف المتريرات التالية فيه‪:‬‬
‫{‪public class BoatController : MonoBehaviour‬‬

‫نوع القوة‪public ForceMode typForce = ForceMode.Impulse; //‬‬

‫مرجع للمكون لتغيير االحتكاكات وعمل دفع للقارب‪private Rigidbody rb; //‬‬
‫;‪private Quaternion q_y‬‬
‫;‪private Quaternion q_x‬‬
‫;‪private Quaternion q_z‬‬
‫;‪public Transform transZ, transX, transY‬‬

‫هل يستخدم القارب المراوح؟‪public bool useFans = false; //‬‬


‫مصفوفة لتخزين المراوح‪public Transform[] fans; //‬‬
‫سرعة الحركة‪public float speedMove; //‬‬
‫السرعة داخل الماء وخارجة‪public float speedOnWater = 800f, speedQutWater = 0f; //‬‬
‫سرعة الدوران‪public float speedRotate = 10f; //‬‬
‫سرعة المراوح‪public float speedFans = 2f; //‬‬
‫نعومة االنتقال‪public float smooth = 2f; //‬‬
‫لتخزين زاوية الدوران االفقية فقط‪private float angle; //‬‬

‫اقصى لفة الى اليسار و اليمين‪public float maxSteeringRight = 1.5f; //‬‬


‫اقصى لفة الى األعلى و االسفل‪public float maxSteeringForward = 0.5f; //‬‬
‫}‬

‫اذن دعنا نناقش هذه القائمة بالمتريرات‪ ،‬تجد ان القائمة مقسمة الى ‪ 4‬اقسام لتوضيح وويفة كل قسم منها‪ ،‬تجد القسم األول يحدد نوع الدفع وهذه وويفة‬
‫منفصلة ومهم جدا ً لتحديد دفع القار في الماء و ارجة‪.‬‬

‫القسم الثاني بسيط جديد وهو لت زين محاور الدوران لألبناء في المتريرات من نوع ‪ Transform‬وكما تعلم لدينا ‪ 3‬أبناء وكل ابن يدور على محور معين‬
‫بينما نت ذ المحور االمامي للكائن اال والذي سيدور على المحور ‪ Y‬لتحريك القار الى االمام‪.‬‬

‫القسم الثالث مهم جدا ً وهو يوفر اعدادات التحكم بالقار من است دام القار للمراوح الى تحديد سرعة للحركة والدوران‪ .‬تجد المترير ‪ angle‬ووويفته هو‬
‫ت زين زاوية الدوران عند الضرط على مفاتيح التدوير وبدورة يقوم بت زينها وسنت ذها لتدوير القار على المحور ‪ Y‬لجعل القار ينعطف فعلياً‪.‬‬

‫القسم األ ير لجعل القار يعمل لفة على المحور ‪ Z‬و‪ X‬ألوهام الالع ان القار يتحرك ويلف وهذا لن يؤثر على حركة القار ابداً‪.‬‬

‫الصفحة ‪48‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫يتملك مراوح وتفعل ال يار ‪ useFans‬ووضع المراوح في المصفوفة وبقية القيم ستقوم بدورها ويمكنك ترييرها‬ ‫بداية االمر علية وضع الملف في قار‬
‫لمعرفة الفروقات‪.‬‬

‫المكونات وجعل المتريرات ت زن المطلو منها‪ ،‬اذن ضاف الدالة ‪ Start‬بهذا الشكل‪:‬‬ ‫االن في ‪ Start‬يج علينا جل بع‬
‫{ )( ‪void Start‬‬
‫;)( >‪rb = GetComponent<Rigidbody‬‬

‫;‪q_y = transY.localRotation‬‬
‫;‪q_x = transX.localRotation‬‬
‫;‪q_z = transZ.localRotation‬‬
‫}‬

‫كما ترى العملية بسيطة وهي جل المكون ‪ Rigidbody‬وجعل المتريرات من نوع ‪ Quaternion‬ت زن محاور الدوران في كل كائن يمثلها‪.‬‬

‫االن سند ل الى الدوال المهمة واولها الدالة ‪ Move‬ووويفتها تحريك القار عبر عمل دفع له الى االمام‪:‬‬
‫)(‪void Move‬‬
‫{‬
‫خزن االزاحة العمودية في المتغير‪float v = Input.GetAxis ("Vertical") * speedMove; //‬‬
‫هل هناك إزاحة الى االمام ؟‪if(v > 0f) //‬‬
‫اضف قوة دفع الى االمام‪rb.AddForce (transY.forward * v * Time.deltaTime, typForce); //‬‬
‫هل هناك إزاحة الى الخلف ؟‪if(v < 0f) //‬‬
‫اضف قوة دفع الى الخلف‪rb.AddForce (transY.forward * v/2f * Time.deltaTime, typForce); //‬‬
‫}‬

‫هذ ه العملية في تحريك الكائنات شائعة وتعتمد على دفع القار الى االمام في حالة ضرطنا على مفتاح التحريك الى االمام والى ال لف في حالة ضرطنا على‬
‫مفتاح التحريك الى ال لف ولكن مع تقليل السرعة وتطبق هذه المسألة بقسمة السرعة على القيمة التي تريدها وافتراضيا ً هنا هي ‪ .2‬في حالة تأملت الكود‬
‫ستجد ان القار يت ذ المحور االمامي للكائن ‪ transY‬ألنه اساسا ً هذا المترير هو القار نفسه أي عندما يطل منك تعبئته في االنسبيكتور ستقوم بحس‬
‫الكائن اال الى هذه ال انة‪.‬‬

‫الدالة التالية ستكون ‪ Rotate‬ووويفتها من اسمها وهي تدوير القار على المحور ‪ Y‬فقط‪:‬‬
‫)(‪void Rotate‬‬
‫{‬
‫خزن االزاحة االفقية بناء على سرعة القارب كعامل مهم لتحديد سرعة الدوران‪//‬‬
‫;‪float h = Input.GetAxis ("Horizontal") * rb.velocity.magnitude / speedRotate‬‬
‫الزيادة في مقدار االزاحة‪angle += h; //‬‬
‫طبق االزاحة الزاوية على المحور االفقي فقط‪q_y = Quaternion.Euler (0f, angle, 0f); //‬‬
‫;)‪transY.localRotation = Quaternion.Lerp (transY.localRotation, q_y, smooth * Time.deltaTime‬‬
‫}‬

‫الحو االن ان هذا الدوران مربوط بسرعة القار ‪ ،‬كما ترى فأن المترير ‪ h‬ي زن محور دوران القار مضرو في سرعة القار نفسه وهي مقسومة على‬
‫سرعة الدوران أي ان في حالة كان القار واقف ستكون سرعة صفر وهذا سيؤدي الى جعل القار ال يدور‪.‬‬

‫وا يرا ً كود االنتقال‬ ‫تجد المترير ‪ angle‬يزيد في قيمة محور الدوران وهذا االمر عرفنا عمله سابا ً وتجد ان الدوران سيكون على المحور ‪ Y‬في القار‬
‫االنسيابي من الدوران الحالية الى القيم المت ذة يحدد سرعتها المترير ‪.smooth‬‬

‫الدالتين التاليتين ت تصان بعمل ‪ Steer‬للكائنين ‪ transZ‬و ‪ transX‬و العملية بسيطة جدا ً الحو‪:‬‬
‫لف القارب على بشكل جانبي‪void SteerRight() //‬‬
‫{‬
‫خزن االزاحة االفقية بناء على سرعة القارب كعامل مهم لتحديد سرعة الدوران مضروب في اقصى لفة للجوانب‪//‬‬
‫;‪float axisKey = Input.GetAxis("Horizontal") * -rb.velocity.magnitude * maxSteeringRight‬‬
‫طبق االزاحة على المحور الجانبي‪q_z = Quaternion.Euler(0f , 0f, axisKey); //‬‬
‫;)‪Quaternion rot = Quaternion.Lerp (transZ.localRotation, q_z, smooth * Time.deltaTime‬‬
‫;‪transZ.localRotation = rot‬‬
‫}‬

‫لف القارب بشكل عمودي‪void SteerForward() //‬‬


‫{‬
‫خزن االزاحة العمودية بناء على سرعة القارب كعامل مهم لتحديد سرعة الدوران مضروب في اقصى لفة لألمام‪//‬‬
‫;‪float axisKey = Input.GetAxis("Vertical") * -rb.velocity.magnitude * maxSteeringForward‬‬
‫طبق االزاحة على المحور العمودي‪q_x = Quaternion.Euler(axisKey, 0f, 0f); //‬‬
‫;)‪Quaternion rot = Quaternion.Lerp (transX.localRotation, q_x, smooth * Time.deltaTime‬‬
‫;‪transX.localRotation = rot‬‬
‫}‬

‫كما ترى االمر بسيط جدا ً والدالة األولى هي نفسها الثانية مع تريير بع القيم‪ .‬تجد الدالة األولى تقوم بتدوير الكائن ‪ transZ‬على المحور ‪ Z‬ويعتمد هذا‬
‫االمر على سرعة القار مضروبة في اقصى زاوية للفة لتحديد النسبة التي نريدها‪ ،‬السطر اسفلة يقوم بوضع هذه القيمة على المحور ‪ Z‬وا يرا ً تجد كود‬
‫الدوران االنسيابي الذي عهدنا است دامه‪.‬‬

‫تبقت لنا آ ر دالة وهي لتدوير المراوح اضفها‪:‬‬


‫تدوير المراوح‪void RotateFans() //‬‬
‫{‬
‫)‪for(int i = 0; i <= fans.Length - 1; i++‬‬
‫{‬

‫الصفحة ‪49‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫يعتمد دوران المروحة على سرعة القارب مضروبة في سرعة الدوران للتحكم بالسرعة بشكل اكبر‪//‬‬
‫;)‪fans[i].Rotate(Vector3.forward * rb.velocity.magnitude * speedFans‬‬
‫}‬
‫}‬

‫اذن بهذا الكود نستطيع تدوير المراوح اعتمادا ً على سرعة القار مضروبة بسرعة دوران المراوح للتحكم بسرعة دورانها‪.‬‬

‫االن تبقى لنا انشاء الدالة ‪ FixedUpdate‬بما ا ننا نتعامل مع بع األكواد الفيزيائية والرير فيزيائية ولكن لتزامن الحركة في وقت واحد‪ ،‬نقوم في هذه الدالة‬
‫بإحاطة المراوح بالمترير ‪ useFans‬و نستدعي باقي الدوال بشكل مباشر‪:‬‬
‫{ )( ‪void FixedUpdate‬‬

‫;)( ‪Move‬‬
‫;)(‪Rotate‬‬

‫;)( ‪SteerRight‬‬
‫;)( ‪SteerForward‬‬

‫هل هذا القارب يستخدم مراوح ؟‪if(useFans) //‬‬


‫;)( ‪RotateFans‬‬
‫}‬

‫ولكن ستواجه مشكلة االحتكاك والوزن وهذا‬ ‫اذن االمر بسيط جدا ً وبهذا الشكل نكون قد أنهينا العمل على هذا الملف البرمجي واالن يمكنك ا تبار القار‬
‫ألننا لما نقم بعد بأنشاء ملف ي ت بهذا االمر بعد‪.‬‬

‫انشاء الملف ‪:PhysicsInformation‬‬


‫الماء‪ ،‬العملية بسيطة جدا ً‬ ‫ان هذه الملف يعتبر ملف مهم جدا ً ومن اللها سنتمكن من تريير سرعة القار باإلضافة الى االحتكاكات عندما يالمس القار‬
‫وتعتمد على التحقق من كون القار موجود على الماء وسنقوم بتطبيق عدة أمور اعتمادا ً على بع الدوال‪.‬‬

‫يعتمد هذا الملف على الملفين ‪ FloatingObject‬و‪ BoatController‬لتريير وبطبيعة الحال هما من يحمالن المتريرات والدوال الضرورية لتريير‬
‫االحتكاكات والدفع والوزن‪.‬‬

‫هذا الملف يعتبر الملف البرمجي األ ير في هذه العملية وسيحتوي على دالتين‪ ،‬األولى ت زن االحتكاكات دا ل الماء و األ رى ارجة‪ ،‬الملف بسيط و الي‬
‫من التعقيدات‪ ،‬اذن قم بإضافة الملف بهذا الشكل‪:‬‬
‫{ ‪public class PhysicsInformation : MonoBehaviour‬‬

‫مرجع لملف التحكم بالقارب‪private BoatController boat; //‬‬


‫مرجع لملف عوم االجسام‪private FloatingObject floating; //‬‬

‫{ )( ‪void Start‬‬
‫احصل على الملفات من الكائن‪//‬‬
‫;)( >‪boat = GetComponent<BoatController‬‬
‫;)( >‪floating = GetComponent<FloatingObject‬‬
‫}‬

‫{ )( ‪void FixedUpdate‬‬

‫االحتكاك داخل اماء‪UpdateDragsOnWater (); //‬‬


‫االحتكاك خارج الماء‪UpdateDragsQutWater (); //‬‬
‫}‬

‫االحتكاك داخل الماء‪void UpdateDragsOnWater() //‬‬


‫{‬
‫هل قارب داخل الماء؟‪if(floating.isWater) //‬‬
‫{‬
‫غير نوع القوة الى دفع‪boat.typForce = ForceMode.Impulse; //‬‬
‫االحتكاك الخطي داخل الماء‪//‬‬
‫;‪floating.rb.drag = floating.GetDragInWater() * floating.dragLineOnWater‬‬
‫االحتكاك الزاوي داخل الماء‪//‬‬
‫;‪floating.rb.angularDrag = floating.GetDragInWater() * floating.dragAnglerOnWater‬‬
‫حرك القارب بناء على السرعة المعطاة داخل الماء‪//‬‬
‫;)‪boat.speedMove = floating.SetSpeedOn(boat.speedMove, boat.speedOnWater‬‬
‫}‬
‫}‬

‫االحتكاك خارج الماء‪void UpdateDragsQutWater()//‬‬


‫{‬
‫هل القارب خارج الماء؟‪if(!floating.isWater) //‬‬
‫{‬
‫غير نوع الدفع الى قوة‪boat.typForce = ForceMode.Force; //‬‬
‫االحتكاك الخطي خارج الماء‪//‬‬
‫;‪float drag = floating.rb.velocity.magnitude / 100f‬‬
‫;‪float dragAngler = floating.rb.velocity.magnitude / 100f‬‬
‫االحتكاك الزاوي خارج الماء‪//‬‬
‫اتخذ القيم المعطاة لالحتكاك الخطي‪floating.rb.drag = drag; //‬‬
‫اتخذ القيم المعطاة لالحتكاك الزاوي‪floating.rb.angularDrag = dragAngler; //‬‬
‫حرك القارب بناء على السرعة المعطاة خارج الماء‪//‬‬
‫;)‪boat.speedMove = floating.SetSpeedOn(boat.speedMove, boat.speedQutWater‬‬

‫الصفحة ‪50‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫}‬
‫}‬
‫}‬

‫الملفين ومن ثم نعرف الدوال‬ ‫باإلضافة الى هذا‪ ،‬في البداية نقوم بجل‬ ‫كما قلت ان الملف يتمحور حول الملفين السابقين وكالهما موضوعين في القار‬
‫ونستدعيهم في ‪.FixedUpdate‬‬

‫الدالة ‪ UpdateDragsOnWater‬ت ت بتريير االحتكاكات في الماء وتالحو ان االمر محاط بشرط وهو التحقق من كون الكائن في الماء وفي حالة تحقق‬
‫الشرط سيترت عليها عدة أمور مثل تريير نوع الدفع الى ‪ Impulse‬بينما االحتكاك ال طي و الزاوي سيعتمدان على الدوال الموجودة في المترير ‪floating‬‬
‫وهذه الدوال م صصة لوضع االحتكاكات دا ل الماء‪ ،‬ا يرا ً تجد اننا نقوم بنقل السرعة في المترير ‪ Boat‬عبر الدالة ‪ SetSpeedOn‬من حالتها الى السرعة‬
‫دا ل الماء و افتراضيا ً هي ‪ 800‬ويمكنك ترييرها الحقا ً وبهذا الشكل نكون قد هيئنا القار للتحرك في الماء‪.‬‬

‫الدالة الثانية ‪ UpdateDragsQutWater‬ومهمتها هي تريير االحتكاكات ارج الماء فكما نعلم االحتكاك ارج ودا ل الماء ي تلف دائما ً ولكن لن نعتمد‬
‫ارج الماء وبعدها يترير نوع الدفع الى ‪ ،Force‬تجد ايضا ً اننا نقوم‬ ‫على قيم واقعية بل سنت ذ قيم نراها مناسبة مع القار ‪ .‬في البداية نتحقق من ان القار‬
‫بت زين سرعة القار مقسومة على ‪ 100‬إلعطائنا نسبة اقل من التي موجودة في الماء ونقوم بت زين كالً من هذه القيم في متريرين يمثالن االحتكاك ال طي‬
‫والزاوي ونقوم بجعل المترير ‪ rb‬يت ذ هذه القيم في المتريرات التي تمثلها‪ ،‬ا يرا ً نقوم بنقل السرعة الى السرعة ارجة الماء وافتراضيا ً هي صفر‪.‬‬

‫وستجد ان االمر يعتمد فقط على ضبط القيم للوصول الى النتيجة التي تريدها‪ ،‬هذه الصورة تمثل اعدادات هذا القار‬ ‫أ يرا ً تأكد من وضع الملف في القار‬
‫الذي يمتلك مراوح‪:‬‬

‫الحو توزيع نقاط العوم وتجد انها على شكل مستطيل في القار بحيث تجعله ثابت في مكانك أي انه ليس هناك مسافة أكبر من أ رى بحيث ان القار يميل‬
‫الى جان معين لهذا يج عليك التركيز على نقاط العوم بشكل كبير بحيث توزع النقاط بانتوام‪ ،‬المشروع المرفق مع الكتا يوجد به مشهد آللية عمل‬
‫القوار وستجد فيه كل شيء قمنا بعمله باإلضافة الى اعدادات أكثر من قار بحيث تالحو ان لكل قار توزيع نقاط عوم م تلفة بسب حجم القار‬
‫باإلضافة الى نسبة االحتكاكات والسرعة والكتلة‪.‬‬

‫الصفحة ‪51‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


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

‫اود ان اشكر جميع االصدقاء الذين ساعدوني ودعموني معنويا ً إلكمال العمل على هذه الكتا وان هذه النقطة لن تكون نهاية المطاف انما البداية إلنشاء أشياء‬
‫أكبر والتوصل الى نتائج قوية‪.‬‬

‫يريد الد ول الى هذا المجال‪ ،‬ويساعد على نشر المحتوى العربي وتوسيع‬ ‫أتمنى من كل مح لتطوير األلعا ان يقوم بنشر هذا الكتا ليستفيد كل ش‬
‫نطاقه‪ .‬هذا الكتا ال يعيد أي أرباح مادية انما قمت بأنشائه في سبيل نشر العلم النافع والى هنا نلتقي في ورشة أ رى بأذن هللا والسالم عليكم ورحمه هللا‬
‫وبركاته‪.‬‬

‫صلى هللا وسلم على سيدنا محمد وعلى آله وصحبه اجمعين‪.‬‬

‫الصفحة ‪52‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬


‫مصادر‪:‬‬
‫المصادر التي سأذكرها تمثل المواقع والدروس التي تعلمت منها واقتبست منها عدة معلومات باإلضافة الى مصادر تعليمية لم اضع منها شيء ولكنها ستفيدك‬
‫وستزيد معرفتك بالكتا والبرمجة في اليونتي ككل‪.‬‬

‫‪ )1‬مقالة ماالحتكاك)‪ ،‬ويكيبيديا الموسوعة الحرة العربية‪ ،‬صفحة وي‬


‫‪ )2‬مقالة مأسطح التوجيه)‪ ،‬ويكيبيديا الموسوعة الحرة العربية‪ ،‬صفحة وي‬
‫‪ ،Make a realistic boat in Unity with C# )3‬صفحة وي‬
‫‪ ، [Unity3D] Floating physics objects in Unity3D )4‬يوتيو‬
‫‪ ،Unity3D - Ride a Boat (Unity 4.3) )5‬يوتيو‬
‫‪ )6‬سكربت ‪ ،FloatingObject‬موقع ‪GitHub‬‬
‫‪ ،How to make a Car game )7‬قائمة تشريل على اليوتيو‬
‫‪ ،Making 2d Car Physics with unity 4.5 )8‬يوتيو‬
‫‪ ،Hill Climb Racing Like 2D Car Physics 2 - Add Speed Through Script )9‬صفحة وي‬
‫‪ ،Unity Tutorial: Wheel Collider )10‬يوتيو‬
‫‪ .Unity Rapid Prototyping: Flight Sim Control, Terrain Basics, Chase Cam, Skybox )11‬يوتيو‬
‫‪ ،Unity FS )12‬صفحة وي‬
‫‪ ،Unity FPS Tutorials )13‬قائمة تشريل على اليوتيو‬

‫الصفحة ‪53‬‬ ‫‪Facebook.com/EMAD.Unity3D‬‬

You might also like