Professional Documents
Culture Documents
ورشة ميكانيكية حركة الكائنات في Unity5 PDF
ورشة ميكانيكية حركة الكائنات في Unity5 PDF
} اللهم إفتح لي أبواب حكمتك و أنشر علّي من رحمتك وأمنن علّي بالحفظ والفهم ُ ،
سبحانك ال ِعلم لنا إال ما علمتنا إنك أنت العليم الحكيم{
هلو ان هذا الكتا موجهه لألش ا الذين يمتلكون برة فوق المبتدأ نورا ً للميكانيكيات التي سنست دمها ولن نتطرق الى كل شيء بسيط وتفصيله فلالرر
توصيل المعلومة ومن ثم تقوم بتطويرها.
وال يقتصر االمر على هذه المراجع فقط ،بل يجب عليك تعلم كل جديد والبحث عن اجوبة لألسئئلة التئي تطرحهئا ،وال يعتمئد االمئر علئى البرمجئة فقئط بئل علئى
مكونات البرنامج ايضا ً فعليك فهم مكونات البرنامج والتعرف عليها وفهم كيفية التعامل معها و معرفة استخدامها في البرمجة و هذا االمر سيفيدك.
مالحظة - :هذا الكتاب مجاني للجميع وال يحق ألي شخص نشرة ألغراض تجارية او ربحية .المعرفة ليست ملك الحد و من حق الجميع الحصول عليها
بالمجان ,كل ما اطلبه حق هذا الكتا هو الدعاء لي و لوالدي و لجميع المسلمين و اتمنى في النهاية اعجابكم و نقدكم و آرائكم و اتمنى لكم قراءة طيبة .
يوتيوبhttp://youtube.com/homeofgamesnews :
فيسبوكhttps://www.facebook.com/homeofgamesnews :
نبذة عني :عماد عارف التوي ،العمر 18سنة ،منذ عام 2012بدأت اول طواتي في عالم برمجة االلعا على محرك يونتي ومن يومها احاول التوصل
الى نتائج قوية واحتراف هذا الجان بشكل كبير عبر محاكاة عمل بع االلعا والميكانيكيات المعروفة وتطويرها الى االفضل ،من جان آ ر اقوم بنشر
هذا الجان باللرة العربية ألثراء المحتوى العربي وتوسيعه بشكل كبير امالً في التوصل الى نتائج اقوى وأدق بأذن هللا.
االول FPSمالذي ركزت علية بشكل كبير وهو كان الشيء عند بدأي في عمل بع المشاريع او باألصح محاولة محاكاة عدة اشياء ومنها منوور الش
لذي اجا على العديد من االسئلة في رأسي) قمت بإنجازه بالطريقة المعروفة ولكن سرعان ما عرفت ان الطريقة التي اتعامل بها هي الطريقة ال اطئة او
باألصح ليست الطريقة المريحة فعالً فبحس تجربتي لبع االلعا من نفس المنوور رأيت ان الحركة أجمل وتباذر في ذهني سؤال وهو كيفية محاكاة تلك
الحركة في اليونتي؟
االول واحتمال ان االغلبية مرت على الملف ،MouseLookان هذا الملف جميل بدأت بالبحث وربما جميعنا بحث عن طريقة عمل منوور الش
وربما قد ترى ان مفهومة معقد في بع االشياء ولكن ما اريد توصيلة االن هو ان هذا الملف هو ما جعلنا اقوم بتوسيع بحثي بهذا الشكل الى ان وصلت الى
النتيجة المرجوة وعرفت ان عمل الملف MouseLookهو بسيط جدا ً ولكن هناك امر جميل فيه عند توفيره للـ enumولكن لن اعتمد علية في كتابي و
سنناقش طريقة أ رى.
االول سرعان ما عرفت ان عملة هو امر سهل ولكن كنت أجهله سابقا ً ولكن لم اتوقف وبدأت بعد ان توصلت للنتيجة المطلوبة في محاكاة منوور الش
بتوسيع بحثي مجددا ً وتوصلت الى العديد من األمور وأتمنى ان أصل الى أكثر من ذلك.
الشيء الذي جذ بنا بشكل كبير هو كيف استطيع فهم الكاميرا فهي الجان المعقد في االمر فكما تالحو جميع المشاريع تعتمد على مدى تأثر الكاميرا فيها ففي
النهاية الكاميرا هي ما ستنقل جمال اللعبة الى الالع ،عند لع لعبة ش صيا ً انت الكاميرا و تالحو ان اكثر شيء تستطيع تحريكه هو الكاميرا نفسها و هذا
ما يدل على ان للكاميرا أهمية كبير في عالم األلعا وهي ليست كاميرا لتصوير المشهد فقط ،انا ال اتحدث بصفتي اني سأصنع كاميرا ارقة ال بل سنقوم
بمحاكاة حركة الكاميرا في كل مشروع يطل ذلك وسنحاول جعلها احترافية قدر االمكان ،في حاله تبسط لنا المفهوم االولي لتحريك الكاميرا وماهي
الميكانيكية الجيدة سنستطيع است دامها بأكثر من طريقة لتوفير الراحة التامة اثناء اللع .
هناك العديد من االشياء التي لم تعرفها حق معرفتها في يونتي ويج عليك المبادرة وتوسيع بحثك والتوصل الى اي نتيجة استطعت التوصل اليها وبدورها
هي من ستنقلك الى الجان الممتع وستربي فيك الرغبة ومحاولة الوصول الى نتائج اقوى من سابقتها.
المعلومات بالشكل الصحيح وتعريفك بالطريقة الفضلى لمحاكاة حركة الكائنات وتجن المشاكل و معرفة في النهاية الرر من هذا الكتا هو توصيل بع
اين تست دم األكواد و بالشكل الصحيح.
هذا الكتا موجه بشكل كبير للفئة المتوسطة في برمجة أللعا في محرك يونتي ،اما من لم يملك برة في البرمجة فيفضل زيارة مستندات الموقع ومراجعة
الكت السابقة لتوسيع معرفته في هذا المجال.
قبل البدء بعمل اي شيء تأكد من أنك قمت بتنزيل الباكيج م )Pro Project – Emad Arifال ا بالكتا لتطبيق المحتوى البرمجي علية ،هذا الباكيج
عبارة عن عدة مشاريع قمت بعملها وا ذها من مصادر مثل مشاريع يونتي الجاهزة مالموديالت) وقمت بالتعديل عليها واضافتها الى هذا الباكيج لتسهيل
عليك امر تطبيق الكود.
عند تحميل الباكيج ستالحو انه على شكل ملف مضروط ويج عليك است راجه الى ملف فرعي ومن ثم استيراده الى دا ل اليونتي ،عند استيراده ستجد عدة
ملفات مثل االصوات ،المشاهد ،الملفات البرمجية والـ Prefabsوغيرها من االشياء ولكن االهم هو المشاهد وستجد ان في كل مشهد يوجد الكائن الذي
ستطبق علية الشرح باالضافة الى الكاميرا ال اصة به وفي كل فقرة نمر عليها سنناقش مسألة الكاميرا وكيف قمت بتركيبها.
ا يرا ً يفضل تحميل المشروع المبني م )Build Pro Project – Emad Arifلكي يساعدك على مقارنة عملك فيه وتحديد المستوى الذي وصلت له
وماهي المشاكل التي تواجهك ،عند فتح المشروع المبني ستوهر لك هذه القائمة وبكل بساطة تستطيع االنتقال الى اي مشروع كما هو موضح.
في الملف Projectsستجد ملف باسم Ballيحتوي على موديل الكرة و امة الفيزياء التي تحدد نوع المحاكاة الفيزيائية اي ان هذا الكائن عبارة عن
كرة مطاطية او شيء ثقيل او ما شابه ذلك .االن افتلها في المشروع.
بالنسبة للكاميرا فستجدها بأسم Auto Cameraفي المشهد ،تجد ان هناك عدة ابناء يتفرعون منها وتالحو ان كل ابن يمثل محور معين م)y, x
ا يرا ً االبن اال ير وهو الكاميرا وتجد ان الزاوية االفتراضية للدوران هي تساعدك على رؤية الكرة ولكن سندع الكاميرا برمجيا ً تنور الى الكرة.
بما ان كل شيء جاهز فسيتبقى لنا انشاء الملف البرمجي والذي سيتحكم بالكرة وآ ر يتحكم بالكاميرا ،اذن قم بأنشاء ملف برمجي يمثل الكرة وليكن
BallMovementوأضف الكود التالي:
public الكائن الذي سيدور على المحور االفقيTransform pivotRotY; //
public سرعة الحركةfloat speedMove = 50f; //
public سرعة القفزfloat speedJump = 20f; //
public اقصى سرعة زاويةfloat maxAnguler = 10f; //
{ )( void Start
)(void FixedUpdate
{
حرك الكرةMove(); //
}
خزن االزاحات مضروبة في مقدار االزاحة لكل اتجاهVector3 movemnet = -moveForward * h + moveRight * v; //
هل الكائن على األرض وتم الضغط على مفاتح "المسافة" ؟if(IsGrounded() && Input.GetKeyDown("space")) //
اضف سرعة الى األعلى مضروبة بسرعة القفزrb.AddForce (Vector3.up * speedJump, ForceMode.Impulse); //
في حالة تمعنت النور في هذا الكود سترى ان العملية بسيطة وهي اضافة عزم دوران الى هذه الكرة ،صحيح ان هناك عدة طرق لمحاولة تحريك الكرة وهي
ال ت فى علينا ولكن في هذه الحالة اريد ان أحرك الكرة ال ان اعطي لها سرعة عبر است دام المترير Velocityاو AddForceمع ان جميعها تعمل بشكل
صحيح ،هذه الطريقة ستمكننا من عمل عزم دوران للكرة واعطائها الدفع نتيجة احتكاكها فقط ولكن هناك عدة اشياء ضرورية لتحسين هذه الحركة.
المكونات في البداية عرفت عدة متريرات ومنها مترير يمثل سرعة الحركة والقفز واقصى دوران للكرة وهذا امر مهم جداً ،في الدالة Startبدأت في جل
واست دمت المترير maxAngulerإلعطاء اقصى دوران ممكن.
الدالة Moveتمثل كود الكرة على المحاور فكما تالحو المتريرات تأ ذ المحاور االفقية والعمودية ونضربها في السرعة ،المتريرات من نوع Vector3
تمثل الحركة الى االمام مأل ذ المحور العمودي) وآ ر الى اليسار مأل ذ المحور االفقي) وهي ت زن موقع الكائن pivotRotYوهو الكائن الذي يدور على
المحور Yوستأ ذ الكرة هذه المحاور وتعيد است دامها في عملية التدوير للتحرك مع منوور الكاميرا ،ا يرا ً المترير movementي زن جميع الحركات.
بارامتر من نوع Vector3ي زن الدالة AddTorqueتقوم بعمل عزم دوران لهذا الكائن الفيزيائي وبما انها كرة فهذا االمر ينطبق عليها ،الدالة تطل
الحركات فيه فكما تالحو قمنا باست دام المترير movementبشكل مباشر.
مسألة القفز تستطيع حلها عبر عدة طرق ومنها طريقة اال شعة و انا افضلها بشكل كبير ،العملية تعتمد على اطالق شعاع من مركز الكرة الى اسفلها بحيث
يساوي نصف قطرها او يزيد عنه ومن ثم التحقق من تالمس هذا الشعاع مع االر و هل تالمس ويتم تطبيق القفز ،العملية بسيطة فكما تالحو في الدالة
IsGroundedهي دالة من نوع boolو تعيد لنا الشعاع الساقط مع وضع احداثياته و طولة م )1.2fهي نصف القطر و تزيد عنها بنسبة بسيطة و تلقائيا
سيعيد لنا القيمة trueفي حالة تالمس مع االر ،يمكنك التحقق من حالة الشعاع و طولة عبر الدالة DrawRayعبر الفئة .Debug
حركة الكاميرا:
معلومات الحركة الصحيحة ،لتوضيح بالنسبة للكاميرا فسنتعرف على الطريقة البسيطة لتدويرها حول الكرة وسنرى عمل المترير 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; //
{ )( void FixedUpdate
انتقل من موقعك الى موقع الهدف//
;)autoMove.position = Vector3.Lerp(autoMove.position, target.position, Time.deltaTime * 7f
دور الكائن العمودي حول المحور العمودي بمقدار إزاحة الماوس العمودية//
;)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بهذا الشكل نكون قد انهينا العمل على الكرة و كما قلت سابقا ً انه ليس ضروري ان تحتكر هذه الطريقة فقط و هي فقط لالست دام في
الحاالت الضرورية و يمكنك است دام اي طريقة ا رى تعمل بشكل جيد و لكن تأكد من كتابتها في الدوال التي تمثلها .
العجالت السيارة ثنائية االبعاد مكونة من مجسم السيارة وعجلتان) مكونات بسيطة والمكون 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و هذه القيم قابلة للقراءة فقط اي ال يمكن التعديل عليها عبر فئتها مباشرة بل عن
طريق است دام فئة ت زن القيم و من ثم تعيد است دامها.
{ )( void FixedUpdate
كما تالحو عملية الحصول على القيم تعتمد على المترير 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; //
{ )( void FixedUpdate
هل هناك إزاحة الى اليمين ؟if (Input.GetAxis ("Horizontal") > 0) //
الزيادة في مقدار االزاحة االفقيةdeltaX = maxDeltaX; //
هل هناك إزاحة الى اليسار ؟if(Input.GetAxis("Horizontal") < 0) //
التناقص في مقدار االزاحة االفقيةdeltaX = -maxDeltaX; //
دعنا نناقش هذا الكود ،كما ترى ال توجد تعقيدات كثير في العملية انما فقط انتقاالت بين القيم وات اذها لتريير موقع الكاميرا على المحور االفقي وجعلها تتبع
السيارة على المحورين م .)x,yالقسم األول من المتريرات لجل الهدف وتحديد سرعة التتبع باإلضافة الى تحديد الزيادة على المحور Yللتحكم بالمحور بشكل
ادق.
القسم الثاني لجل معلومات االنتقال االفقي او االزاحة االفقية وتحديد اقصى قيمة ممكنة لجعل الكاميرا تتحرك مع السيارة وتعطيك رؤيا أكبر للعالم .في
FixedUpdateتالحو الكود العروف في جعل كائن يتبع كائن على المحورين ولكن ترى ان المتريرين يقوموا بإعطاء الزيادة على المحورين لعمل إزاحة
للكاميرا فيهم كما في الصورة:
في حالة تم التحقق من وجود إزاحة ستجد ان المترير deltaXيت ذ القيم الموجبة والسالبة للمترير maxDeltaXلقل اتجاه االزاحة ،في
حالة لم تكن هناك إزاحة سيتم إعادة الكاميرا الى موقعها األصلي عن طريق نقل المترير posXالى الحالة صفر ،غير ذلك سنقوم بتطبيق
االزاحة في المترير .posXفي األ ير قيم االزاحة معتمد على ت زينا في المترير deltaXلعمل إزاحة افقية للكاميرا.
دعنا نتعرف على الطريقة الفضلى لمحاكاة هذا االنفجار ،ان العملية تعتمد على عدة اشياء اساسية قبل انشاء االنفجار مثل ت زين القيمة بين الكائن الفيزيائي
وموقع االنفجار الحالي ،ثم نعيد است دام هذا المترير الست راج المسافة الفاصلة بينه وبين االنفجار ،يتم تطبيق الدفع الى جميع االتجاهات عبر جعل اتجاه
المترير يقوم بعملة تلقائيا ً ولكن تطبيق الدفع الى االعلى يعتمد على مترير يقوم بهذا االمر بحيث نقوم بجعل موقع االنفجار يعطينا قيم تزيد في المترير الذي
سيدفع الكائنات الى االعلى.
في هذه العملية سنست دم الدالة AddForceومن اللها نستطيع ان نحدد نوع االنفجار عبر الفئة ForceModeومن ثم نطبق عملية االنفجار اعتمادا ً على
قطر دائرة االنفجار.
سنست دم ملف برمجي يعيد لنا دول ثابتة الست دمها بشكل مباشر عبر فئتها ،ايضا ً لن ستست دم متريرات ترث من MonoBehaviourفكل ما سنست دمه
هو الفئة Rigidbody2Dبشكل اساسي.
thisتشير الى مكون Rigidobdy2Dال اصة بهذا الكائن او هذه الفئة ،وتالحو ايضا ً ان هناك عدة بارامترات وهي:
اذن هذه البارامترات ضرورية لتطبيق االنفجار ،في البداية عرفت المترير 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(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
:الكود النهائي
using UnityEngine;
using System.Collections.Generic;
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();
}
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(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ستالحو انه يحتوي على العديد من الكائنات و منها:
بينما الكائن اال وويفته هي تتبع الكائن Free Moveوهو يحمل المكون Rigidbodyلتحريكه فيزيائياً ،مسألة الحركة تعتمد على ان الكائن Free Move
سيتحرك ويدور بينما الكائن Auto Moveسيتبعه في الحركة فقط وليس الدوران ،ا يرا ً الكائنات التي ستدور على المحورين X, Yسيبقيان الكاميرا
الرئيسية ثابته في مكانها وهذا جميل ألنه سيتيح لنا عمل الكثير في الكاميرا الرئيسية و سنتعامل معها على اساس انها لم تتحرك قط.
العملية البرمجية ستعتمد على ملف واحد فقط وهو سنجعله يتحكم بالكاميرا بشكل كامل .اذن قم بأنشاء ملف برمجي وضعة في الكاميرا ،ا يرا ً اضف
اول المتريرات لكي نناقشها:
public Transform الكائن الذي سنقوم بتحريكهfreeMove; //
public Transform الكاميرا التي ستتبع الكائن الذي سنقوم بتحريكهautoMove; //
public Transform الكائن الذي سيدور على المحور االفقي pivotRotateY; //
public Transform الكائن الذي سيدور على المحور العمودي pivotRotateX; //
public Transform الكاميرا الرئيسية في المشهد cam; //
كما ترى المتريرات منطقية وال يررك عدد المتريرات في الكود ألنك عاجالً ام آجالً ستست دمها في هذا المشروع وهي ضرورية جداً .تالحو ان المتريرات
االولية وويفتها هي الحصول على موقع الكائنات التي ت ت بالحركة والدوران والحصول على موقع الكاميرا الرئيسية ألننا سنطبق عملية التقري فيها.
بها هو publicإلتاحة ترييرها من المتريرات الم تصة بالسرعة مهمة فهي التي ستحدد سرعة الحركة ،التتبع والدوران معرف الوصول ال ا
االنسبيكتور.
القائمة Zoom Cameraمهتمة بإعدادات التقري ونقل الكاميرا بشكل ناعم من موقعها الحالي الى الموقع التالي فالمترير zoomSpeedبشكل عادي يقوم
بتحديد سرعة التقري ،بينما المترير lerpZoomCameraسي زن المسافة التي يمكن التقري اليها ومن ثم يأتي دور المترير deltaZoomCameraلنقل
قيمتة من الصفر الى المترير lerpZoomCameraلتت ل من ال شونة اثناء التقري وسنعتمد المترير deltaZoomCameraفي التقري بينما المترير
lerpZoomCameraيقوم بعمل Clampاو حدود للتقري .
اول العملية بسيطة جدا ً فكما ترى سنقوم بات اذ القيمة الوسطى بين اقصى واقل مسافة يمكن التقري اليها .المترير 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
ان مفهوم الكود الذي تراه امامك بسيط جدا ً فكل ما يركز علية هو ت زين محاور الحركة ومن ثم است دامها في الدالة )( .MoveRotationكما تالحو الدالة
ت زن بارامترين يحددان اسمي محاور الحركة يعني اما عن طريق الكيبورد م )Horizontal or Verticalاو الماوس م )Mouse X or Mouse Yلجعل
الالع يتحكم بحركة الكاميرا اما عن طريق الماوس او الكيبورد وهذا امر منطقي وتراه في االلعا االستراتيجية.
دعنا نناقش هذا الكود ،تالحو ان الكائن freeMoveدائما ً سي زن محاور الدوران ال اصة بالكائن pivotRotateYللدوران على المحور االفقي ،هذه
االمر ستبقى ثابت طوال عملية البرمجة لت زين القيم الدوران دائماً.
المتريرين forwardو Rightي زنان محاور الحركة الجانبية و االمامية فمثالً المترير forwardسيجل لنا المحور االمامي ال ا بالكائن freeMove
و ضربه بقيمة محور الحركة الذي سي زنه البارامتر yAxisوضربه في السرعة ،نفس الشيء ينطبق على المترير rightولكن على المحور الجانبي ،تجد
ان المتريرين من نوع Vector3وكل منهما ي زن اتجاه الحركة ،المترير movementي زن يجمع الحركات باضافة المحور االمامي مع المحور الجانبي
لعملية التحريك ونقوم بضر ازرار التحكم فيه ،ا يرا ً يج ان نتحقق من انه لم يتم الضرط على زر الماوس االيمن إلضافة الحركة الى محاور المكون
Rigidbodyلتحريك الجسم فيزيائيا.
الى االن لم يتم استدعاء اي دالة في Updateاو FixedUpdateوبطبيعة الحال نحن نتعامل مع الحركة فيزيائيا ً ولهذا سنستدعي دالة الحركة في الدالة
.FixedUpdate
في انه سيتم نقل الكائن autoMoveالى موقع الكائن freeMoveوالسرعة يحددها المترير speedFollowالذي كما ترى ال شيء معقد العملية تت ل
عرفناه سابقاً.
او عمل ان نناقشهما وكالهما مهمتان جداً ،الدالة االولى تركز على تدوير الكاميرات اما الثانية تهتم بالتقري وصلنا الى نهاية الطريق وتبقى لنا دالتان يج
Zoomللكاميرا الرئيسية.
وظيفة هذا الكود هو تدوير الكائن الذي يدور افقيا مع احداثي الماوس االفقي //
;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); //
}
في جعل المترير 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
قربCameraZoom(); //
اوالً نقوم بتعريف الدوال التي تحوي اي بارامتر ثم نقوم بالتحقق من عملية الدوران عبر الضرط على الزر االيسر للماوس وتالحو انه سيتم استبدال
البارامتر speedبالمترير speedRotateوسيتم تطبيق عملية الدوران عبر الماوس .الشرط الثاني يتحقق من الضرط على الزر االيمن للماوس وسيتم
تصفير السرعة لعدم وضع اي سرعة للدوران او اتاحة التحكم وا يرا ً المتريرين angleXو angleYسينتقالن الى القيم المعطاة لضبط موضع الكاميرا عبر
تلك القيم اي ان الكائن pivotRotateXسيعتمد القيمة 20fعلى المحور xبينما الكائن pivotRotateYسيعتمد القيمة 45fعلى المحور Yوبهذا الشكل
سنعيد الكاميرات الى وضعيتها االفتراضية عند تحقق الشرط.
والنور، كما ترى العملية طويله نوعا ً ما ولكنها فعالة جدا ً وفي حالة قمت بتجربة الكاميرا سترى انها مريحة لعملية التحكم واعادة ضبط الموضع والتقري
ا يرا ً تأكد من انه تم ملء المتريرات كما هو موضح او قم بترييرها لتتناس مع ماتريد الوصول اليه:
[Header("Zoom Camera")]
public float maxZoom = 20f;
public float minZoom = 1f;
public float speedZoom = 2f;
private float deltaZoomCamera;
private float lerpZoomCamera;
void Start()
{
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;
}
freeMove.rotation = pivotRotateY.localRotation;
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;
19 الصفحة Facebook.com/EMAD.Unity3D
محاكاة منظور الشخص االول :FP
االول ،هذا النوع من المحاكاة تكون فيها الكاميرا هي الالع وتوهر في العديد االول او مطلق الش First Personبا تصار FPوهو منوور الش
من االلعا المشهورة مثل Call of Dutyو Battlefieldودائما ً يكون الالع حامالً لسالح او ما شابه ذلك ،المهم انك تستطيع تحريك الكاميرا او باألصح
تدوير الكاميرا على المحورين x, yوتطبيق هذه الميكانيكية ت تلف من محرك الى آ ر ولكن في حالة استطعت تطبيق الميكانيكية التي سنست دمها سترى انك
تستطيع اسقاطها على اي محرك آ ر شبيه.
في الفقرة السابقة ناقشنا مسألة الكاميرا وكيف تدور ومشكلة ان تدوير الكاميرا نفسها على المحورين x, yال ا بها هو امر سيء للراية وتوصلنا الى
طريقة وهي عمل عدة ابناء وكل ابن يدور على محور معين وا يرا ً الكاميرا هي آ ر ابن آل ر كائن في التسلسل الهرمي .هنا سنطبق نفس العملية ولكن من
سدور على المحور االفقي هو الالع نفسها بينما الكاميرا تدور على المحور العمودي فقط وستجد ان االمر جيد حال تطبيقه.
اذن في المشهد First Person Shooterتجد الش صية Character FPSيتفرع من هذه الش صية عدة ابناء كما هو موضح في الصورة:
كما ترى هناك عدة ابناء فمالً الكائن Pivotهو نقطة ارتكاز الكاميرا ،بينما الكائن Cameraهو من سيقوم بتدوير الكاميرا وفي حال الحوت موقعة ستجد
انه يقع في نفس محاور نقط االرتكاز ،يتفرع من اال ير عدة ابناء وهم :الكاميرا الرئيسية ،نقطة حمل السالح ،كائن ا ير است دمته لقذف القنابل وهو غير
االول مع اتاحة الفرصة لتعديل القيم بحيث تتناس مع متطلباتك. ضروري هنا .ما يهم االن هو كيف سنقوم بمحاكاة منوور الش
االول .تجد المتريرات االولى الثالثة وهي لتحديد سرعة الماوس ومقدار اذن كما تالحو المتريرات االولية ستتيح لنا التحكم الكامل بمحاكاة منوور الش
الدوران على المحور 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; //
}
بتطبيق التحكم بشكل ناعم وبطبيعة الحال ستعمل في حالة كانت قيمة المترير 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); //
اذن لنناقش مسألة هذا الكود ،كما تعلم ان الكاميرا هي من سيدور على المحور 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; //
كما ترى العملية بسيطة جدا ً وهي نفسها التي است دمناها في الكاميرا االستراتيجية ،االن تجد المترير q_characterو q_cameraي زنان محاور
الدوران ولكن هناك ا تالف بسيط في ان الكاميرا تعتمد على مقدار الترير في المترير angleXبينما الش صية تقوم بضر القيمة الحالية في مقدار ترير
المترير xRotعلى المحور ،Yبهذا الشكل يكون كالً من المتريرين الم تصين بدوران الكاميرا و الش صية ي زنان مقدار الترير في حركة الماوس.
االن يج ان نتحقق من ان قيمة المترير smoothهي trueومن ثم سنشرل الدالة ApplySmoothingControlغير ذلك سنجعل التحكم عادي .أضف
الكود التالي أسفل هذا الكود:
تم تفعيل النعومةif(smooth) //
{
طبق التحكم على الكاميرا و الشخصيةApplySmoothingControl (myCamera, myCharacter); //
اذن ليست العملية صعبة.ً ويج ان تكون آ ر سطر لضمان تدوير الكاميرا والش صية اوالUpdate فيSteerAngleZ آ ر شيء تبقى هو مناداة الدالة
حقيقة االمر هذه الطريقة هي طريقة نتائجها،كما كنت تفكر ففي حالة قمت بتطبيق ميكانيكية الكاميرا االستراتيجية فستعرف مدى بساطة هذا الكود وفعاليته
رائعة واعتمدنا على المتريرات الالزمة والمناسبة لعملية الدوران وبعيدا ً عن الشروحات المعقدة فانا حاولت ان اقدم الشرح بأبسط طريقة ممكنة مع مناقشة
. في عملية تدوير االجسام بشكل احترافيQuaternion اهم االجزاء وفي الفقرات التالية بأذن هللا ستعرف المدى الكبير في است دامنا للفئة
:الكود النهائي
using UnityEngine;
using System.Collections;
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);
if(smooth)
{
ApplySmoothingControl(myCamera, myCharacter);
}else{
myCharacter.localRotation = q_character;
myCamera.rotation = q_camera;
}
SteerAngleZ();
}
void SteerAngleZ()
{
myCamera.localEulerAngles = new Vector3(myCamera.localEulerAngles.x, 0f, steerZ);
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عند التحكم بالموقع والوجهة
يج على الطيار أن يكون قادرا على الدوران بالطائرة حول كل محور من تلك المحاور.
اذن في حالة راجعت الطائرة ستجد ان هناك عدة ابناء تتفرع منها كما في الصورة:
:Collidersيحوي كائنات تحتوي على مكون التصادم وتالحو ان الكائنات موزعة على الطائرة بشكل كامل.
الكائن الثاني والثالث هما لمجسم الطائرة فهم غير مهمين حالياً.
Wheelsهذا الكائن يحتوي على عجالت الطائرة وفي حالة فتحته ستجد العجالت الثالث االمامية و ال لفية على اليمين و اليسار لكن لن اناقش العجالت هنا
وسنتركها للفقرة القادمة.
تجد الكائن Trailوهو موضوع في طرفي الجناح االيمن وااليسر Effect Right .قمت بإنجازه ش صيا ً وهذا هو تأثير احتراق الوقود في المحرك ،تالحو
ان هناك ابناء يتفرعون منه وهم Smoke :الد ان الناتج عن تأثير االحتراق First Fire ،هذا المؤثر يعمل اوالً وهو يوهر احتراق الوقود في البداية.
جميع هذه المؤثرات مهيأة لتريير قيمها برمجيا ً وستالحو الحقا ً عند برمجة المؤثرات المرئية كيف ان المؤثرات تعمل بشكل جميل.
والطائرة. ا يرا ً تجد الكائن Point Rayوهو من سيقوم بحسا المسافة بين االر
.االن انشئ اول ملف سيتحكم بحركة الطائرة وهذا ضروري مبدئياً ،بدايةً سنعرف المتريرات الضرورية لتحريك الطائرة وحسا المسافة بينها وبين االر
ملف برمجي وسمه AirplaneControllerواضف المتريرات التالية:
كما ترى المتريرات واضحة من اسمها ،المترير typeForceلتحديد نوع الدفع ،وتجد مترير لتحديد سرعة الدوران ايضا ً اقصى سرعة ،وتجد المتريرين
changeDragAngleو changeDragهما لترير نسبة احتكاك الطائرة في الهواء لمنع الجاذبية من التأثير عليها بشكل كبير وهي في الهواء ،القيم التي
تراها ال تعبر عن نسبة االحتكاك الفعلي ال بل سنقسم هذه القيمة على سرعة الطائرة وسيعيد لنا االحتكاك مع سرعة الحركة تقريبا ً هذه القيم ستعطيك النتيجة
3.3fلالحتكاك ال طي و الزاوي في المكون Rigidbodyالموضوع على جسم الطائرة.
المتريرات ال اصة هي لت زين محاور الدوران زيادة السرعة والحصول على الكائنات التي تحتوي على المكون Wheel Colliderاو العجالت المرئية
وبطبيعة الحال هي العجالت الموجودة في الطائرة.
في الدالة Startعلينا جل المكونات باالضافة على عجالت الطائرة وعمل عزم دوران بسيط لها كي تنزلق وال تواجه مشكلة في االقالع ،اذن اضف الدالة
Startبهذا الشكل:
{ )( void Start
ابحث عن احد أبناء الطائرة الذي سيطلق الشعاعpointRay = transform.FindChild("Point Ray"); //
اجلب المكون الفيزيائيrb = GetComponent<Rigidbody>(); //
خزن محاور حركة الطائرةrotAir = transform.rotation; //
المترير 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
السرعة مقسومة على االحتكاك الخطي ,قيمة جلب االحتكاك الخطي//
كما ترى المتريرات تقوم بوويفتها تلقائياً ،تجد انني عرفت متريرين لتمثيل نسبة االحتكاك ال طي و الزاوي فمآلً المترير 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(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); //
}
حسنا ً بعد تأملك للدالة ستالحو انا القسم االكبر منها عبارة عن شروط تتحقق دائما ً من ان السرعة وصلت لمستوى معين فتطبق امر .في البداية تجد
المتريرين المشهورين 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الى اسفل الطائرة باالعتماد على محورها العلوي.
}
الدوال الكود الذي تحوية الدالة .تالحو ان بع ا يرا ً تبقى لنا استدعاء دوال الحركة والتدوير وغيرها في الدوال Updateو FixedUpdateحس
اآل ر ال و التقسيم سيكون كالتي: محتواها فيزيائي والبع
AirControl
MoveAir
ChangeDrags
MouseControl
ClampValues
قد تتسائل حول ما عالقة الدالة AirControlبالفيزياء؟ هناك شيء يج ان تعرفة وهو انه عند تحريك جسم فيزيائي وتدويره اال فيزيائيا ً تنتج عنه مشكلة
تأ ر الحركة عن الدوران ،كما تعرف فأن الدالة Updateاسرع تحديثا ً من الدالة FixedUpdateولو استدعينا الحرك في اال يرة و الدوران في االولى
فأن الدوران سيسبق الحركة في االطارات اي لو افترضنا ان االطار 50قد حدث الدوران فاحتمال ان الحركة لن تحدث في هذا االطار وهذه مشكلة
وللت ل منها نقوم باستدعاء الدوال التي لها عالقة ببع في دالة واحدة لضمان التحديث الموحد.
)(void FixedUpdate
{
التحكم بالطائرةAirControl(); //
يمكنك االن ا تبار الطائرة وستجد ان النتائج جميلة جداً .شيء ا ير قد ربما لم تتذكره وهو المترير speedFlayLerpحقيقةً قمت بتعريف هذا المترير
الست دامه في نف يعر لي مدى سرعة الطائرة .يمكنك انشاء Textوتسميته textSpeedو اضافة المترير التالي في :Update
; "textSpeed.text = "Speed Flay: " + Mathf.RoundToInt(speedFlayLerp) + "km / " + Mathf.RoundToInt(maxSpeed) + "km
علينا انشاء ملف برمجي جديد يمثل المؤثرات المرئية ال اصة بالطائرة .اذن قم بأنشاء ملف برمجي باسم ParticaleEffectواضف هذه المتريرات:
{ public class ParticaleEffect : MonoBehaviour
كما ترى المتريرات الثالثة االولية ستساعدك على تحديد نسبة زيادة كل مؤثر وافتراضيا ً هي 10واجدها مناسبة معي ،المتريرات الثانية او باألصح
المصفوفات هي للتحديد على جميع المؤثرات من نفس النوع وتطبيق االمر عليها مثل مؤثر الد ان وعددهم اثنان ونفس الشيء ينطبق على باقي المؤثرات
المرئية.
حسنا ً يج علينا تثبيت القيم االولى للمتريرات وجعل المترير acيحصل على المكون AirplaneControllerللوصول الى متريرات السرعة ،أضف الدالة
Startبهذا الشكل:
{ )( void Start
اجلب ملف التجكم بالطائرةac = GetComponent<AirplaneController>(); //
تمرير بارامتر من نوع عملية جل المكون بسيطة ولكن ترى ان الدالة 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); //
}
تالحو ان هذه المرة البارامتر 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
كما ترى االمر بسيط جداً ،ايضا ً جعلنا الدالة 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تكررت 3مرات وفي كل مصفوفة نقوم بنفس االمر وعمل Clampللسرعة ومدة البقاء ال اصة بكل مؤثر .الحلقة
االولى تقوم بعمل Clampللمؤثر lastFireاما الثاني لـ FirstFireوالثالثة لـ .Smokeتجد ان كل حلقة تست دم المترير الذي يمثلها وتعتمد عليها في جعلة
اقصى قيمة للترير بينما تقسمه على ،5هي ليست قيمة ثابته ولكن يمكنك تجري جميع القيم الى ان تستقر على واحدة.
{ )( void Start
;)(>ac = GetComponent<AirplaneController
void Update () {
PowerPartical(lastFire, d_lastFire);
PowerPartical(firstFire, d_firstFire);
PowerPartical(smoke, d_smoke);
تعتمد العملية على جعل الصوت يعمل تلقائيا ً مع. ستجد صوتين االول للمحرك الطائرة واآل ر للرياحAudio دا ل المجلدAirplane بالبحث عن المجلد
. مثل ما عملنا مع المؤثرات المرئية سنقوم بتطبيق االمر على االصوات ولن يكون هناك ا تالف كبير،السرعة اي يتناس تناس طردي معها
اذن قم بأنشاء ملف برمجي يمثل،ًهذه المرة سأقوم بوضع الملف البرمجي الذي سيتحكم باالصوات بشكل كامل هنا وسنناقشه نورا ً ألنه ليس طويل جدا
: واضف الكود التالي فيهAudioAirplane صوت الطائرة وليكن
30 الصفحة Facebook.com/EMAD.Unity3D
{ public class AudioAirplane : MonoBehaviour
{ )( void Awake
{ )( void Update
قم بعمل حدود للصوت وحدة الصوت في الريح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); //
}
اذن كما ترى فأن كل دالة تقوم بوويفة معينة تمكننا من جعل الصوت قابل للترير تلقائيا ً مع السرعة ،هذه العملية جميلة جدا ً بحيث اننا سنجعل المترير pitch
وهو يمثل حدة الصوت بتريير قيمتة ابتداء من الصفر الى ان يصل الى قيمة معينه وهي ناتج لقسمته على السرعة.
في البداية تجد المتريرين الم تصين بتريير وضوح الصوت وحدة الصوت ال اصة بكل مقطع صوتي ،القيم االفتراضية ستعطيك النتيجة التالية:
صوت المحرك:
1 = Volume
4 = Pitch
صوت الرياح:
0.8 = Volume
0.8 = Pitch
بكالً من المتريرين من نوع AudioSourceوهو اال ير بدورة سيضيف المكونين الى الطائرة المتريرين من نوع AudioClipسنجعلهما الـ Clipال ا
وفي نفس الوقت سنضيف كل مقطع الى كل مكون.
القيم التي تريدها ،تجد ان الدالة توفر بارامتر من نوع وويفة الدالة SetupValuesبسيطة جدا ً وهي اعطاء قيم ابتدائية لكل مكون ويمكن افترا
AudioSourceلتحديد المكون الذي سي ضع لهذه العملية.
العملية في قسمة السرعة على البارامتر deltaChangeبحيث الدالة UpdateAudioتقوم بتحديث االصوات عن طريق تريير حدتها ووضوحها ،تتل
ان المترير الدا لي speedAudioي زن ناتج العملية ومن ثم نعمل Clampللـ Volumeوالـ .Pitch
بالنسبة للدالة ClampPitchهي بسيطة جدا ً ووويفتها من اسمها وهو عمل حدود لحدة الصوت ،حقيقة االمر است دمتها لعمل Clampلحدة صوت الرياح
ويمكن است دامها بشكل مباشر دون وضعها في دالة واالمر يعود لك وهذا لن يشكل فرق.
ا يرا ً نأتي على الدالة Updateونقوم فيها باستدعاء الدالة UpdateAudioوتمرير البارامترات وتجد ان الدالة االولى تمثل صوت المحرك بينما الثانية
لصوت الرياح ،ا يرا ً نست دم الدالة ClampPitchلعمل حدود لحدة صوت الرياح والقيم التي است دمتها ات ذتها عن طريق التجربة.
اذن الملف بسيط وليس مشع بشكل كبير ،يمكنك االن وضعة كمكون على الطائرة وقم بملء المتريرات كما هو موضح:
يمكنك االن رؤية جميع الملفات البرمجية التي أنشأناها سابقا ً وضبط قيمك معها وتجربة الطائرة واالستمتاع بها مع االصوات و المؤثرات المرئية .
حركة الكاميرا:
الكاميرا عامل مهم جدا ً و صوصا ً في الطائرات ويج ان تكون الكاميرا مريح باالضافة الى اعطائك مساحة روية كبيرة وسلسلة دون نقل اهتزازات
الطائرة وما شابه ذلك .غالبا ً يتم وضع الكاميرا كأبن لكائن الطائرة وهذا االمر اطئ ألنه سيعطي نتائج غير مرضية باالضافة الى انه سينقل كل اطراء
في نفس المشروع ستجد الكائن Camerasهذا الكائن يحتوي على الكاميرا الرئيسية .Main Cameraعملية برمجة الكاميرا ستكون بسيطة جدا ً وفكرتها
سهله وهي جعل الكائن اال يتبع الطائرة بينما الكاميرا تنور الى الطائرة .لتطبيق االمر برمجيا ً قم بأنشاء ملف برمجي وليكن CameraAirplaneواضف
الكود التالي:
{ public class CameraAirplane : MonoBehaviour
)(void FixedUpdate
{
التتبع التلقائي للهدفAutoFollowTarget(); //
الكاميرا تنظر الى الكائن االبAutoCameraLookAt(cam, transform); //
الكائن االب ينظر الى الطائرةAutoCameraLookAt(transform, target); //
}
كما ترى الكود قد مر علينا سابقا ً وهو بسيط جداً ،في الدالة AutoFollowTargetسيقوم الكائن اال بتتبع الطائرة ،اما في الدالة AutoCameraLookAt
سيقوم الكائن اال بالنور الى الطائرة بينما الكاميرا الرئيسية ستنور الى الكائن اال ويمكنك االطالع على هذا من الدالة .FixedUpdate
سب است دام الدالة FixedUpdate؟ كما تعلم فأننا قمنا بتحريك الطائرة فيزيائيا ً وبطبيعة الحال الدالة MoveAirباالضافة الى AirControlكانوا في
FixedUpdateولهذا جعلنا دوال الكاميرا في نفس الدالة لتتناس سرعة التحديث ولكيال تحصل مشكلة في التتبع او التأ ر في التتبع ،يمكنك مالحوة االمر
عند استدعاء الدوال السابقة في Updateوستجد ان هناك تأ ر في النور والتتبع كما قلت.
لنا هذه لشرحه بطريقة مفصلة يفضل أنك تفتح مشروع Carفي الملف Scenesوستجد سيارة Carموضوعة باالضافة الى الكاميرا ال اصة وست ص
الكاميرا الراحة الكبيرة في ترطية عالم المشروع.
دعنا نراجع كائن السيارة ،في حالة وجدت السيارة ستالحو ان لها عدة ابناء مرتبين بهذا الشكل:
اساسا ً السيارة مأ وذة من أحد مشاريع اليونتي ولكن قمت بالتعديل عليها مثل فصل عدة اشياء وترتيبها لتبسيط شرحها.
الكائن االبن االول وهو Colliderومن اسمة يقوم بتوزيع الـ Collidersعلى السيارة لتحديد االماكن القابلة للتصادم.
الكائن الثاني SkyCarويحوي جسم السيارة باالضافة الى االضواء.
Wheelsيحتوي على عجالت السيارة م 4كائنات فارغة مضاف الى كل واحد منها المكون )Wheel Colliderولن توهر العجالت اال في حالة كان الكائن
اال يحتوي على المكون Rigidbodyالنها وبطبيعة الحال عجالت فيزيائية.
ا يرا ً الكائن Meshsيحتوي على العجالت فقط لكي نقوم بربطها مع المكون.
بفتح الكائن Wheelsستجد ان العجالت المرئية مسماة بحيث ان كل اسم يمثل موقعها مثالً العجالت االمامية على اليسار ،ونفس الشيء على اليمين وهذا
ينطبق ايضا ً على العجالت ال لفية ويمكنك رؤية موقعهم في المكون Transformوهم موزعين بالتساوي .اذن قم بالضرط على اي عجلة وستجد انها
تحتوي على المكون :Wheel Collider
لك 4اقسام لنناقشها: دعنا نناقش هذا المكون .في البداية ستجد ان هذا المكون ي ص
االعدادات بسيطة جدا ً ويمكنك التحكم فيها واسقاط السيارة من االعلى لرؤية عملها بشكل واضح.
يقوم نوام التعليق بعدم نقل وعورة الطريق الى السائق فعملة يكون عن طريق جعل ما الصدمات Damperي زن مقدار الضرط بحس وزن السيارة
بينما الناب Springيقوم بتحويل العزم الميكانيكية مقوة ،فتل ،ضرط) الى قدرة ميكانيكية بحيث ينكمش ويتمدد ويمنع نقل وعورة الطريق الى السائق.
ستجد ال يارات الموضوع مناسبة ويمكنك ايضا ً ترييرها بحيث تتناس مع متطلباتك.
جمعت القسمين مع بع الن لهما نفس العمل .تقوم هذه االنزالقات بجعل السيارة تنزلق اما على العجالت االمامية او ال لفية او حتى على عجلة واحد او
جميع العجالت بحيث تقوم بت صي العجالت القابلة لالنزالق .يتم تطبيق االنزالقات عبر تحديد مدى احتكاك االطارات مع االر فكلما كانت نسبة
االحتكاك قليلة كلما زادت حدة االنزالق .تجد في كالً من االنزالقين ال يار Stiffnessوهذا ال يار يحدد مدى احتكاك االطارات مع االر ،اما بالنسبة
لباقي ال يارات فأتركها كما هي او تستطيع تعديلها لمعرفة لرؤية نتائجها.
يعتبر وزن السيارة عامل مهم جدا ً لعمل نوام التعليق واحتكاك االطارات في االر بشكل صحيح ،تتراوح اوزان السيارات الصرير من طن م 1000كيلو)
الى 2500كيلو ،بينما الشاحنات يكون وزنها بين 5و 10طن ويمكنك التعرف على وزن كل سيارة من مصادر علمية .يسب وزن السيارة ضرط على
العجالت مما يؤدي الى جعل نوام التعليق يفر القيم التي تم تعيينها بحيث ينقل وعورة الطريق الى ما الصدمات ،تأمل الصورة:
تجد ان العجلة االمامية على اليسار مرتفعة والسب هو ما الصدمات و الناب ،فكلما كانت النس صريرة كلما نقلت السيارة وعورة الطريق الى السائق.
يمكنك تجر المثال في اصورة بأنشاء Cylinderوجعل قيمة الـ Damper = 200و Spring = 20في العجلة االمامية على اليمين.
بالنسبة لالنزالقات ،من نورتك يتم تطبيق عبر تريير قيم االنزالقات وتفكيرك بالفعل سليم ولكن ماذا بشأن االحتكاك الزاوي ،Drag Anglerنعم هو عامل
مهم لمنع السيارة من االنزالق الجانبي والدوران حول نفسها عند المنعطفات ،وغالبا ً يتم تريير نسبة االحتكاك الزاوي للسيارة مع سرعتها بحيث يمنعها من
االنزالق كثير.
يارات االنزالق في المكون Wheel Colliderمهمة جدا ً فمثالً عند عمل كبح تستطيع جعل العجالت االمامية تنزلق الى االمام بينما ال لفية على الجوان
وهذا االمر جميل وتستطيع تطبيق هذا االمر برمجياً.
اذن قم بأنشاء ملف برمجي باسم CarPhysicsسيقوم هذا الملف بت زين معلومات الحركة والكبح وما الى ذلك لكي نست دمها في ملفات ا رى .ا يرا ً أضف
المتريرات التالية في الملف:
دعنا نناقش المتريرات ،تجد ان كل مترير يوفر معلومة بسيطة عن طريق التعليق ال ا به .تقوم المتريرات االولية بجل عجالت السيارة باالضافة الى
تحديد مركز ثقل السيارة ايضا ً تعريف اقصى عزم دوران و كبح مع تحديد مقدار لفة العجالت .المترير speedسي زن سرعة السيارة بنا ًء على سرعتها
الفيزيائية بينما المترير GetSpeedبت زين مقدار ترير السرعة وتالحو اننا است دمنا ال صائ Propertiesوهذا المترير قابل للقراءة فقط ،ا يرا ً تجد
المترير الذي يحدد اقصى سرعة ممكنة.
اذن المتريرات منطيقة و ستجد انك عند انشاء اي سيارة مستقبالً ستقوم باست دام هذه المتريرات النها توفر تحكم اكبر بالسيارة.
في الدالة Startيج علينا جل المكونات مع تحديد مركز الثقل ،اذن اضف الدالة في الملف:
)(void Start
{
اجلب المكون الفيزيائيrb = GetComponent<Rigidbody>(); //
كما ترى االمر بسيط وهو جل المكون Rigidbodyمع تحديد اقصى سرعة ايضا ً مركز الثقل.
تجد ان المترير 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
بتحديد رقم العجلة مع اعطاء مضاعفات الكبح .يقوم المترير الحو معي الدالة BrakeValuesتقوم هذه الدالة بتوفير بارامترات ت ت
MaxBrakeTorqueبضر قيمتة بالسرعة الحالية وبمضاعفات الكبح .اما في المصفوفة Wheelsسيتم تطبيق الكبح عبر المترير brakeTorqueبينما
سنجعل السرعة صفر .سيتم تطبيق االمر حالما يتم استدعاء الدالة وبطبيعة الحالة المفتاح Spaceيكبح السيارة وبدورها الدالة ستعمل عند الضرط علية.
القيمة في .10في اذن العملية بسيطة جدا ً وتعتمد على ت حقق الشرط ومن ثم تستدعي دالة الكبح وجعلها تتطبق على جميع العجالت االربع بينما يتم ضر
حالة لم يتحقق الشرط سيتم ايقاف الكبح عبر جعل قيمتة صفر.
المتريرات بسيطة جداً enum ،يعرف انواع القيادة عبر تحديد نوع الدفع مثالً الدفع الرباعي FourDriveوهكذا .تجد ايضا ً مرجع للسكربت .CarPhysics
ا يرا ً تجد مفاتيح التحكم keyMoveمفتاح التحرك الى االمام وتجد ايضا ً مفتاح تدوير العجالت و الكبح .يمكنك توسيع االمر حس رغبتك.
سنقوم االن باست دام المبدل switchلتحقق من نوع الدفع وسنقوم بتطبيق العجالت ال اصة بذلك .االن اضف الدالة ApplyDriveالى الملف:
قم بتطبيق القيادةvoid ApplyDrive() //
{
طبق عملية اللف على العجلتين االماميتين فقط//
;))cp.ApplySteerAngle(0, Input.GetAxis(keySteer
;))cp.ApplySteerAngle(1, Input.GetAxis(keySteer
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
الحو االن ،تجد في البداية ان تدوير العجالت او عمل الـ SteerAngleسيكون فقط للعجالت االمامية وهذا ما نراه في السيارات الواقعية ،رقم العجالت
االمامية هو 0, 1بينما 2, 3رقم العجالت ال لفية حس ترتيبهم في المصفوفة .Wheels
االن في المبدل Switchسيتحقق من حالة الدفع فإذا كان رباعي سيتم تحريك جميع العجالت االربع .اما إذا كان دفع امامي تجد انه يحرك العجالت االمامية
فقط ،0, 1واذا كان لفي سيحرك العجالت ال لفية .2, 3االن يمكنك العودة الى المشروع وضبط قيمك مع هذه القيم:
اذن بالعودة الى الملف CarPhysicsيج علينا اضافة دالة بسيطة ولكن من الضروري تعريف مترير يمثل الملف ControlDriveللوصول الى مفاتيح
الحركة .اذن في الملف CarPhysicsعرف المترير التالي الى جان المتريرات اال رى:
مرجع لملف التحكم بالسيارةprivate ControlDrive control; //
جميل جداً ،االن سنقوم بأنشاء الدالة ChangeDragووويفتها هي تريير سرعة السيارة ،تأمل الدالة:
تغيير االحتكاك الخطي و الزاويvoid ChangeDrag() //
{
هل السيارة تتحرك ؟if(Input.GetAxis(control.keyMove) != 0) //
{
قم بتقليل نسبة االحتكاك الخطي اعتمادا على السرعةrb.drag -= GetSpeed/100; //
قم بزيادة االحتكاك الزاويrb.angularDrag += GetSpeed/100; //
}
كما ترى قمنا بالوصول الى مفاتيح التحكم عبر المترير controlومن ثم طبقنا الشروط على المفاتيح ففي حالة كانت السيارة تتحرك سيتم إنقا نسبة
االحتكاك ال طي بينما يرتفع االحتكاك الزاوي لمنعها من الدوران بشكل حاد .اما إذا تم تطبيق الكبح سيزيد االحتكاك ال طي لتوقف السيارة بينما ينق
االحتكاك الزاوي لجعل السيارة تنزلق.
اذن الى هنا نكون قد أنهينا العمل على الملف ControlDriveولكن سنعود الى الملف CarPhysicsالضافة االنزالقات عند الكبح.
اذن لتطبيق هذا االمر قم بأنشاء ملف برمجي باسم MeshsWheelsوأضف الكود التالي اليه:
{ public class MeshsWheels : MonoBehaviour
)(void Start
{
اجلب ملف التحكم بالسيارةcp = GetComponent<CarPhysics> (); //
}
}
العملية بسيطة جدا ً تعتمد على عمل مصفوفة ت زن عجالت السيارة ،ايضا ً الحصول على السكربت CarPhysicsللوصول الى المصفوفة .Wheels
حسنا ً يج علينا انشاء دالة ثانية تقوم بربط العجالت المرئية مع عجالت السيارة ،اضف الدالة :MoveMeshsWithWheels
حركة عجالت السيارة مع العجالت الفيزيائيةvoid MoveMeshsWithWheels(int i) //
{
متغير لجلب معلومات تصادم الشعاعRaycastHit hit; //
متغير لتخزين موقع العجالتVector3 wheelsPos; //
لنناقش هذه العملية نوعا ً ما هي معقدة ولكن نتائجها دقيقة .تجد ان الدالة توفر بارامتر يعرف يحدد رقم العجلة مثل الدوال السابقة ،االن نقوم بربط العجالت
المرئية مع عجالت السيارة عن طريق اطالق شعاع من العجالت المرئية الى االسفل ،من ثم نست دم المترير hitلجل معلومات التصادم ،ا يرا ً يطل مننا
المسافة وهي حجم القطر زايد مسافة تعليق القطر سيعطيك النتيجة 0.635وهي المسافة التي ستنقل العجلة المرئية الى عجلة السيارة.
عند تحقق الشرط سيتم ربط العجلة عن طريق المترير wheelsPosوسي زن نقطة الشعاع مضاف اليها موقع العجلة على المحور العمودي مضروبة في
حجم القطر.
غير ذلك سيجعل عجلة السيارة تتحرك الى االعلى واالسفل مع العجلة المرئية.
ان تنتبه له وهو ان تكون العجالت المرئية ا يرا ً نقوم باعتماد قيمة المترير wheelsPosالست دام القيم الم زنة في العجلة التي تريدها ،لكن هنا امر يج
مع عجالت السيارة متساوية في ارقامها دا ل المصفوفات.
بهذا االمر ،اذن أضف الدالة :SteerMeshsWheels االن يج علينا عمل Steerلعجالت السيارة مع العجالت المرئية .سنقوم بأنشاء دالة ت ت
لف عجالت السيارة مع العجالت الفيزيائيةvoid SteerMeshsWheels() //
{
كما ترى ان العجالت االمامية 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); //
كما ترى العملية واضحة بشكل كبير ،نقوم بتحويل السرعة من دورة/دقيقة الى درجة/ثانية ومن ثم نقوم بتدوير العجلة باالعتماد على الدالة Rotateعلى
المحور xاالفقي.
الحو ان جميع العجالت سترتبط بالعجالت المرئية بينما جميع العجالت ايضا ً ستدور وهذا شيء طبيعي ،بينما يتم تطبيق الـ Steerفي اال ير وا يرا ً
نستدعي الدالة في .FixedUpdate
االن بالعودة الى المشروع قم بملء المصفوفة كما قمت بترتي العجالت في المصفوفة Wheelsوا يرا ً جر السيارة وستجد ان العجالت تعمل بشكل جيد.
وبهذا الشكل نكون قد أنهينا العمل على هذا الملف.
دعني اعطيك فكرة عن است دام متريرات االنزالقات في المكون Wheel Colliderفي الواقع است دام المتريرات بشكل مباشر مثل تحديد االحتكاك االمامي
للعجالت االمامية من المكون نفسه هو غير صحيح الن جميع قيم االنزالقات قابلة للقراءة فقط ال للكتابة .هنا يوفر لنا اليونتي الفئة WheelFrictionCurve
هذه الفئة ت ت بتحديد منحنى االحتكاك في العجالت وعبرها ن زن قيم االحتكاك.
لنفكر في ماذا لو قمنا بتريير نسبة االحتكاك فكيف سنعيدها الى حالتها االصلية؟ هل عن طريق تعريف مترير لكل اصية في االنزالق؟ هذا االمر متع فعال ً
ولكن هنا يوجد حل بديل وهو عن طريق ت زين القيم االفتراضية لالنزالقات في العجالت ومن ثم است دامها عند توقف الكبح .حسنا ً قم بأنشاء ملف برمجي
باسم FrictionWheelsومن ثم أضف الكود التالي:
{ public class FrictionWheels : MonoBehaviour
)(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توضح هذا المفهوم بشكل بسيط.
}
التعديالت البسيطة ،سأقوم اووه ...ما هذا الكود؟! في الحقيقة ليس باألمر الم يف ففي حالة تأملته كثيرا ً ستجد ان الدالة االولى هي نفسها الثانية مع بع
بشرح الدالة االولى وستفهم طريق عمل الثانية.
تجد الدالة االولى تمثل االنزالق االمامي وتوفر عدة بارامترات مثل رقم العجلة االمامية ،باالضافة الى تحديد نسبة االنزالق extremumSlipالى جان
نسبة احتكاك االطارات .stiffnessاالن عبر جعل المترير ffي زن قيم االنزالق الحالية سيقوم بتطبيق القيم الجديدة اليها فمالً السطر الثاني يقوم بتريير نسبة
االنزالق االمامي باالضافة الى االحتكاك وا يرا ً تقوم هذه العجلة باعتماد القيم الموجودة بالمترير .ff
نفس الشيء ينطبق على الدالة الثانية ولكن يتم تطبيق االمر على االنزالق الجانبي كما هو واضح وليس هناك شيء م يف او مربك في هذه الدوال .تجد ان
معرف الوصول ال ا بهم هو publicلكي نستدعيهم عندما يتم الكبح.
االن يج علينا العودة الى الملف CarPhysicsوقم بالذها الى الدالة ApplyBrakeالتي انشأناها سابقا ً وعدلها بحيث تصبح بهذا الشكل:
{}else
)for(int i = 0; i < 4; i++
{
Wheels[0].brakeTorque = ;0f
Wheels[1].brakeTorque = ;0f
Wheels[2].brakeTorque = ;0f
Wheels[3].brakeTorque = ;0f
نعم هذا هو المطلو ،العملية واضحة جدا ً وتجد ان الترير فقط هو اننا قمنا باست دام الدوال في حالة تم الكبح وسيتم تطبيق القيم التي قمت بإعطائها ،غير
ذلك سنعيد است دام القيم االفتراضية لضمان حركة السيارة بشكل طبيعي.
بهذا الشكل نكون قد انهينا برمجة السيارة بشكل كامل وتبقى لنا امر واحد فقط وهو برمجة حركة الكاميرا ال اصة بها.
اذن كما ترى االعدادات بسيطة جدا ً وتستنتج من اللها ان الكائن اال هو من سيتبع السيارة بينما الكاميرا الرئيسية ستنور الى السيارة دائما ً.
الكائنات بما انها 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للوصول اليها.
ليس هناك شيء جديد فكما ترى الدالة بسيطة ومعروفة مسبقاً.
ايضا ً هذه الدالة مررنا عليها سابقا ً ولن اضيف شيء الى كالمي السابق ويمكنك الرجوع الى مشروع الكاميرا الرئيسية ورؤيتها.
الدالة الثالثة ستكون 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
دالة التقري موجودة في الكاميرا االستراتيجية ايضاً ،وتالحو ان جزء كبير من هذا الملف قد تم نس ة من الكاميرا االستراتيجية.
تبقى لنا جعل الماوس يتحكم بدوران الكاميرا .سنقوم بتطبيق االمر عندما نقوم الضرط على الزر االيمن للماوس وفي حالة افالته ستعود الكاميرا الى موضعها
االصلي .حسنا ً قم باضافة هذه الدالة:
تدوير الكاميرا بواسطة الماوسvoid UpdateRotateCameraWithMouse() //
{
هل تم الضغط على الزر األيمن االيسر للماوس ؟if (Input.GetMouseButton(1)) { //
حسنا ً قد تجد بع االمور غير مفهومه وهي لماذا الكائن اال ؟ نعرف ان الكائن اال وويفته تتبع السيارة وليس النور ولكن يمكنك است دامة في جعله
يدور حول السيارة باالضافة الى النور اليها وهذا سيعيط جمالية اكثر عند القيادة .تقوم العملية بتدوير الكائن اال على المحور االفقي yعن طريق الماوس
كما هو موضح في الشرط.
غير ذلك سنقوم بجعل الكائن اال ينور الى السيارة كما عملنا مع الكاميرا الرئيسية ،اذن االمر بسيط جدا ً وليس هنا شيء معقد.
وبهذا الشكل نكون قد أنهينا العمل على الكاميرا الرئيسية ويمكنك تجر مشروع السيارة لرؤية العمل بشكل جيد.
اعتمدت هنا على الحزمة watercraftpack_kenneyمن موقع Kenney.nlللحصول على موديالت للقوار فقط ،قمت بتجهيز الموديالت بشكل كامل
اي عدلت عليها لكي استطيع تحريكها وتدويرها ويمكنك رؤية القوار بشكلها النهائي في دا ل الملف Projectتجد الملف Boatsدا لة ملف آ ر وهو
Boats Prefabsوهو يحتوي على موديالت القوار وتستطيع سح واحد الى المشروع لكي نوضح تركيبته.
الحو معي تركي القار وتجد ان القار باسم watercraftpack_020هو آ ر كائن .الكائن اال Boat_1يحتوي على العديد من الكائنات وتالحو انه
يملك المكون Rigidbodyوتجد ان وزن القار االفتراضي موزنة في اليابسة) هو 15واالحتكاك ال طي 0بينما االحتكاك الزاوي 0.05fوهي القيم
االفتراضية .بالنسبة للكائنين Axis Zو Xهما فقط لتدوير الجسم على المحورين ألوهام ان القار يتحرك بينما الكائن اال هو من سيدور على المحور Y
ايضا ً سيتم توزيع نقاط الكثافة فيه لجعلة يطفوا.
لتطبيق الحركة الفيزيائية على علينا اوالً ان نتعرف على كيف تطفوا متعوم) الكائنات في الماء وما هو السب بما اننا نريد تحريك القار فيزيائيا ً فيج
قاربنا يج علينا جعله يطفوا فيزيائياً.
الطفو والكثافة:
إذا كان وزن جسم ما أقل من وزن الماء المزاح مالمـُزاغ) بنفس حجم الجسم عند انرماره في الماء كليا تكون كثافة الجسم أقل من كثافة الماء .وبالتالي فإن
الجسم سيطفو إلى أن يصبح وزن الجسم مساويا لوزن الماء المزاح .أما إذا كان وزن الجسم أكبر من وزن الماء المزاح في حالة انرماره كليا ممثل النحاس)،
فإنه يرطس في الماء ،لكن النحاس يستطيع الطفو إذا كان مفرغا بحيث يحتفو بحجم كاف من الهواء .وفي تلك الحالة ،فإن كثافة الجسم ستشمل النحاس وما
فيه من هواء ،وتصبح أقل من كثافة السائل ،وبالتالي فإنه يطفو .الكثافة = إجمالي كتلة الجسم ÷ حجمه ووحدة الكثافة كيلوغرام /متر .3إذا كان الجسم
مصنوع من مادة ذات كثافة أعلى من المائع ،كجسم من حديد مثال في الماء ،يؤدي إلى غرق الجسم في السائل أو الراز المحيط .وإذا كان الجسم مصنوع من
مادة ذات كثافة عالية ولكن يوجد به حيز من الهواء المحبوس ،فقد يكفي دفع الماء لكي يطفو .مثل السفينة ..مصنوعة من الحديد مالفوالذ) ولكنها وعلى الرغم
من حجمها الكبير تطفو ،وذلك لوجود حيز من الهواء ،فيكون متوسط كثافة السفينة أقل من كثافة الماء .قاعدة أر ميدس لألجسام الطافية " :إذا طفا جسم على
سطح سائل ما فإن وزن الجسم المرمور يساوي وزن السائل المزاح
{ )( void FixedUpdate
هل موقع الكائن على المحور العمودي اقل من موقع الماء؟if (transform.position.y < waterLevel) //
ضع الجاذبية في الحالة االفتراضيةPhysics.gravity = new Vector3 (0f, 9.8f, 0f); //
}
اذن ما رأيك بعد تجر هذا الكود؟ ا ليس االمر جميل ولكن ال ي لوا من المشاكل واولها هو االحتكاك اي احتكاك الجسم في الماء عندما يالمسه وتريير
االحتكاك عندما يكون في الهواء .في الواقع لن اناقش المسألة في هذا الكود ولكن يمكنك تريير الـ Dragحاليا ً وا تبار االمر .يعد االحتكاك عامل مهم جدا ً
لمنع الكائن من التزحلق بشكل ما النهائي في الماء ايضا ً الدوران ،ي تلف احتكاك الماء عن الهواء فعندما ترتطم الكرة في الماء فأن سرعتها تقل وهذا سببته
االحتكاك .نستطيع محاكاة االمر عن طريق تريير نسبة احتكاك الكائن في الهواء والماء واعطاء قيم لتمثيل االحتكاكات بحيث تجعل الـ Dragال ا بالكائن
يترير حس تلك القيم المعطاة.
ماذا بشأن المناطق القابلة للعوم؟ حسنا ً عرفنا ان كثافة الجسم عامل مهم وانه لكلما قلت كثافة الجسم عن الماء كان الجسم قابل للعوم .في اليونتي االمر ي تلف
بحيث نعتمد على توزيع االماكن القابلة للعوم ويتم فيها تطبيق االرتداد وغيرها من االمور .الحو هذه الصورة وهي ألسطوانة تم فيها توزيع النقاط في
طرفيها:
عليك ان تقوم بتوزيع النقاط فيج كما ترى ان تحديد االماكن القابلة للعوم يرير من وضعية الجسم دا ل الماء ،فمثالً إذا اردت تطبيق العوم على القوار
بشكل متساوي بحيث نجعل القار ثابت في مكانه دون تريير دورانه او شيء من هذا القبيل.
اذن في حالة قمت بفتح مشروع القوار ستجد الكائن Waterوله ايضا ً Tagبأسم Waterايضا ً تجد انه يحتوي على المكون Box Colliderوطولة 50
باالضافة الى انه Is Triggerمفعل لمنع تصادم االجسام معه ألنه ماء وستجد ان كل ما فيه سيتم است دامه بشكل كبير مثل االسم و التاج باالضافة الى Is
Triggerفي الكود البرمجي.
بما انك قد ا ذت فكرة بسيطة عن توزيع مركز العوم فأننا سند ل الى الكود البرمجي الذي يعطي وي زن قيم برمجية ضرورية لتطبيق هذه العملية ،اذن قم
بأنشاء ملف برمجي بأسم FloatingObjectوقم بأضافة هذه القائمة الطويلة بالمتريرات:
;using UnityEngine
;using System.Collections
;using System.Collections.Generic
الحو ان جميع هذا المتريرات تفسر العديد من االشياء في حالة قرأتها مثل هل الجسم دا ل الماء وفي حالة كان بالفعل فسيتم تطبيق جميع االمور المتعلقة
بالماء مثل االحتكاكات ووزن الجسم وتوزيع مركز العوم وغيرة اما إذا كان غير ذلك فسيتم تطبيق وضع الجسم في الهواء او ارج الماء.
المترير waterLevelسنقوم بوضع قيمتة يدويا ً ويمكنك عمل طريقة تتحقق من ان الكائن قد حصل على مستوى الماء تلقائيا ً وتعتمد هذه القيمة فيه .بالنسبة
للمترير FloatHeightفهو يحدد نسبة ارتداد الجسم في الماء والقيمة واحد تعني متر واحد كما في الصورة:
المترير autoCenterOfFloatingيعطيك نقطة االصل كنقطة قابلة للعوم في حالة لم تريد إضافة نقاط لعوم في الكائن centerOfFloating ،الذي طال
الحديث حولها وهي تحدد النقاط القابلة للعوم وبطبيعة الحالة هي من نوع Vector3لتمكينك من وضع النقطة في اي زاوية ويج ان تعلم ان كل انة تمثل
نقطة واحدة ،الحو الصورة وهي تعبر عن توزيع 3نقاط في جسم واحد:
كما ترى االمر بسيط وتجد ان المصفوفة توزع النقاط بحيث ان هناك نقطة على اليمين في المحور Xوا رى على الشمال وواحدة في المركز وتجد ان كل
انة تمثل نقطة معينة .قد تتسائل في كيفية تحديد تلك النقاط؟ االمر بسيط يمكنك انشاء كائن فارع في الكائن الذي يحتوي على الملف البرمجي ومن ثم تريير
موقع الكائن االبن وتنسخ قيمة .سؤال آ ر وهو لماذا ال اقوم بأنشاء مصفوفة من نوع Transformوت زين االبناء كنقاط قابلة للعوم؟ سؤال مهم جدا ً ولكن
في الواقع عمل هذا الشيء سيؤدي الى جعل النقاط متحركة اي ستعتمد على محاورها المحلية ال على المحاور االصلية للمشهد لهذا انشاء مصفوفة من نوع
Vector3هو امر جيد مقارنه مع اال رى.
القائمة Setting Dragsوضاحة جدا ً وهي تحدد نسبة احتكاك الجسم بالماء او الهواء .المتريرين اال يرين لتحديد اسم التاج واسم الكائن الذي سيمثل الماء
هما ا تياريان لتحديد الكائن الذي سنفتر انه ماء.
المترير IsWaterللتأكد ان الكائن موجود في الماء وستجد ان است دامه مهم جدا ً لتريير السرعة واالحتكاكات وغيرها من االمور.
حسناً ،في البداية يج علينا ت زين القيم االولى وبطبيعة الحالة في الدالة Startاضفها:
)(void Start
{
اجلب المكون الفيزيائيrb = GetComponent<Rigidbody> (); //
كتلة الجسم خارج الماء (قيم افتراضية)MassQutWater = rb.mass; //
تالحو ان المترير MassQutWaterلت زين وزن الكائن قبل سقوطه في الماء ،تجد ايضا ً انه حال الراء تفعيل ال يار autoCenterOfFloatingسيتم
اعطائنا مركز الكائن كنقطة عوم افتراضية.
االن يج علينا تطبق عملية العوم لتطبيق التريرات عليها مثل االحتكاكات وغيرها لكن الدالة لن تعمل االن عندما نتأكد من ان المترير IsWaterمفعل
للتأكد من ان الكائن موجود في الماء لتشريل الدالة وتطبيق عملية العوم .اذن الدالة التالية ستكون Floatingاضفها:
}
اذن الحو الحلقة الدورانية تقوم بتطبيق المصفوفة التي تمثل مركز العوم عبر الدالة 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
}
كما ترى العملية بسيطة وتعتمد على نقل البارامتر speedالى القيمة التي تريدها.
االن وصلنا الى الشيء المهم وهو التعرف على كائن الماء عبر التأكد من اننا تالمسنا معة ويقوم بتريير قيمة المترير ، IsWaterكما تعلم سابقا ً ان كائن الماء
له االسم Waterو الـ Tagنفسة باالضافة الى انه يحتوي على المكون Box Colliderمع تفعيل الـ Is Triggerلحسا االجسام المتدا لة معه .االن
سنستفيد من هذا االمر عبر است دامنا للدالة OnTriggerالحو الكود:
)void OnTriggerEnter(Collider col
{
))if (col.gameObject.name == nameWater || col.gameObject.CompareTag(tagWater
;isWater = true
}
رج سنريرها الى falseبهذه البساطة نستطيع التحقق من اننا تالحو ان العملية بسيطة جدا ً ففي حالة تالمس مع الكائن فسيتم تريير القيمة الى trueواذا
مازلنا على الماء او ال.
اذن بهذا الشكل نكون قد أنهينا العمل على الملف البرمجي األهم والذي سيجعلنا نحاكي حركة الكائنات فوق الماء بحيث نجعلها تعوم .سيكون شكل الملف
النهائي كما في الصورة:
عند برمجة القوار ستجد ان است دام هذه القيم ليست باألمر السهل والتوصل الى نتيجة جيدة يعتمد وزن الجسم باإلضافة الى االحتكاكات ومن اللها
تستطيع تحديد سرعة الكائن تحت هذه الوروف.
انما سنتركها للملف العو يج ان تعلم ان وويفة هذا الملف البرمجي هو تحريك القار عبر دفعه الى االمام ولن يتطرق الى أي مسألة ت
FloatingObjectفقط .اذن لنقم بأنشاء الملف البرمجي BoatControllerواضف المتريرات التالية فيه:
{public class BoatController : MonoBehaviour
مرجع للمكون لتغيير االحتكاكات وعمل دفع للقاربprivate Rigidbody rb; //
;private Quaternion q_y
;private Quaternion q_x
;private Quaternion q_z
;public Transform transZ, transX, transY
اذن دعنا نناقش هذه القائمة بالمتريرات ،تجد ان القائمة مقسمة الى 4اقسام لتوضيح وويفة كل قسم منها ،تجد القسم األول يحدد نوع الدفع وهذه وويفة
منفصلة ومهم جدا ً لتحديد دفع القار في الماء و ارجة.
القسم الثاني بسيط جديد وهو لت زين محاور الدوران لألبناء في المتريرات من نوع Transformوكما تعلم لدينا 3أبناء وكل ابن يدور على محور معين
بينما نت ذ المحور االمامي للكائن اال والذي سيدور على المحور Yلتحريك القار الى االمام.
القسم الثالث مهم جدا ً وهو يوفر اعدادات التحكم بالقار من است دام القار للمراوح الى تحديد سرعة للحركة والدوران .تجد المترير angleووويفته هو
ت زين زاوية الدوران عند الضرط على مفاتيح التدوير وبدورة يقوم بت زينها وسنت ذها لتدوير القار على المحور Yلجعل القار ينعطف فعلياً.
القسم األ ير لجعل القار يعمل لفة على المحور Zو Xألوهام الالع ان القار يتحرك ويلف وهذا لن يؤثر على حركة القار ابداً.
المكونات وجعل المتريرات ت زن المطلو منها ،اذن ضاف الدالة 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
}
كما ترى االمر بسيط جدا ً والدالة األولى هي نفسها الثانية مع تريير بع القيم .تجد الدالة األولى تقوم بتدوير الكائن transZعلى المحور Zويعتمد هذا
االمر على سرعة القار مضروبة في اقصى زاوية للفة لتحديد النسبة التي نريدها ،السطر اسفلة يقوم بوضع هذه القيمة على المحور Zوا يرا ً تجد كود
الدوران االنسيابي الذي عهدنا است دامه.
اذن بهذا الكود نستطيع تدوير المراوح اعتمادا ً على سرعة القار مضروبة بسرعة دوران المراوح للتحكم بسرعة دورانها.
االن تبقى لنا انشاء الدالة FixedUpdateبما ا ننا نتعامل مع بع األكواد الفيزيائية والرير فيزيائية ولكن لتزامن الحركة في وقت واحد ،نقوم في هذه الدالة
بإحاطة المراوح بالمترير useFansو نستدعي باقي الدوال بشكل مباشر:
{ )( void FixedUpdate
;)( Move
;)(Rotate
;)( SteerRight
;)( SteerForward
ولكن ستواجه مشكلة االحتكاك والوزن وهذا اذن االمر بسيط جدا ً وبهذا الشكل نكون قد أنهينا العمل على هذا الملف البرمجي واالن يمكنك ا تبار القار
ألننا لما نقم بعد بأنشاء ملف ي ت بهذا االمر بعد.
يعتمد هذا الملف على الملفين FloatingObjectو BoatControllerلتريير وبطبيعة الحال هما من يحمالن المتريرات والدوال الضرورية لتريير
االحتكاكات والدفع والوزن.
هذا الملف يعتبر الملف البرمجي األ ير في هذه العملية وسيحتوي على دالتين ،األولى ت زن االحتكاكات دا ل الماء و األ رى ارجة ،الملف بسيط و الي
من التعقيدات ،اذن قم بإضافة الملف بهذا الشكل:
{ public class PhysicsInformation : MonoBehaviour
{ )( void Start
احصل على الملفات من الكائن//
;)( >boat = GetComponent<BoatController
;)( >floating = GetComponent<FloatingObject
}
{ )( void FixedUpdate
الملفين ومن ثم نعرف الدوال باإلضافة الى هذا ،في البداية نقوم بجل كما قلت ان الملف يتمحور حول الملفين السابقين وكالهما موضوعين في القار
ونستدعيهم في .FixedUpdate
الدالة UpdateDragsOnWaterت ت بتريير االحتكاكات في الماء وتالحو ان االمر محاط بشرط وهو التحقق من كون الكائن في الماء وفي حالة تحقق
الشرط سيترت عليها عدة أمور مثل تريير نوع الدفع الى Impulseبينما االحتكاك ال طي و الزاوي سيعتمدان على الدوال الموجودة في المترير floating
وهذه الدوال م صصة لوضع االحتكاكات دا ل الماء ،ا يرا ً تجد اننا نقوم بنقل السرعة في المترير Boatعبر الدالة SetSpeedOnمن حالتها الى السرعة
دا ل الماء و افتراضيا ً هي 800ويمكنك ترييرها الحقا ً وبهذا الشكل نكون قد هيئنا القار للتحرك في الماء.
الدالة الثانية UpdateDragsQutWaterومهمتها هي تريير االحتكاكات ارج الماء فكما نعلم االحتكاك ارج ودا ل الماء ي تلف دائما ً ولكن لن نعتمد
ارج الماء وبعدها يترير نوع الدفع الى ،Forceتجد ايضا ً اننا نقوم على قيم واقعية بل سنت ذ قيم نراها مناسبة مع القار .في البداية نتحقق من ان القار
بت زين سرعة القار مقسومة على 100إلعطائنا نسبة اقل من التي موجودة في الماء ونقوم بت زين كالً من هذه القيم في متريرين يمثالن االحتكاك ال طي
والزاوي ونقوم بجعل المترير rbيت ذ هذه القيم في المتريرات التي تمثلها ،ا يرا ً نقوم بنقل السرعة الى السرعة ارجة الماء وافتراضيا ً هي صفر.
وستجد ان االمر يعتمد فقط على ضبط القيم للوصول الى النتيجة التي تريدها ،هذه الصورة تمثل اعدادات هذا القار أ يرا ً تأكد من وضع الملف في القار
الذي يمتلك مراوح:
الحو توزيع نقاط العوم وتجد انها على شكل مستطيل في القار بحيث تجعله ثابت في مكانك أي انه ليس هناك مسافة أكبر من أ رى بحيث ان القار يميل
الى جان معين لهذا يج عليك التركيز على نقاط العوم بشكل كبير بحيث توزع النقاط بانتوام ،المشروع المرفق مع الكتا يوجد به مشهد آللية عمل
القوار وستجد فيه كل شيء قمنا بعمله باإلضافة الى اعدادات أكثر من قار بحيث تالحو ان لكل قار توزيع نقاط عوم م تلفة بسب حجم القار
باإلضافة الى نسبة االحتكاكات والسرعة والكتلة.
اود ان اشكر جميع االصدقاء الذين ساعدوني ودعموني معنويا ً إلكمال العمل على هذه الكتا وان هذه النقطة لن تكون نهاية المطاف انما البداية إلنشاء أشياء
أكبر والتوصل الى نتائج قوية.
يريد الد ول الى هذا المجال ،ويساعد على نشر المحتوى العربي وتوسيع أتمنى من كل مح لتطوير األلعا ان يقوم بنشر هذا الكتا ليستفيد كل ش
نطاقه .هذا الكتا ال يعيد أي أرباح مادية انما قمت بأنشائه في سبيل نشر العلم النافع والى هنا نلتقي في ورشة أ رى بأذن هللا والسالم عليكم ورحمه هللا
وبركاته.
صلى هللا وسلم على سيدنا محمد وعلى آله وصحبه اجمعين.