في هذا المقال أذكر بعض الملاحظات وأراجع بعض الأخطاء في نظامنا البرمجي التي انتبهت إليها مؤخرًا بعد عودتي لهذا المشروع.
وقد انقطعت عنه لعدة ظروف.
بعدها سأنشر درس إضافة خاصية السباحة للعبة.
المحتوى
تغيير نسخة المحرك
بداية، سنكمل المقالات مع استخدام نسخة أحدث من المحرك وهي 2019.4 LTS، إذ أننا كنا نستخدم 2019.1.
بالطبع المقالات السابقة والقادمة يمكن تطبيقها على النسختين، حيث أنني لم أستخدم آخر نسخة من المحرك لكي لا نضطر للقيام بتعديلات على الأنظمة التي كوّناها.
قمت بالتحديث لأن النسخة 2019.4 مستقرة أكثر ومدعومة بإصلاحات مستمرة.
من الممكن أيضًا استخدام النسخة 2020.3 ولكنني لم أجد حاجة لذلك بما أننا حاليًا لن نقوم باستخدام أي من المميزات الجديدة، وأيضًا لا نريد أن نقع في مشاكل أخرى.
إذا أردتم التحديث، قوموا بتنزيل النسخة من خلال برنامج Unity Hub كما فعلنا في أول مقال من السلسلة،
ثم افتحوا المشروع باستخدام هذه النسخة. ستظهر رسالة تأكيد تخبركم بأهمية أخذ نسخة احتياطية، وهو ليس أمر ضروري في حالتنا هذه.
ثم ستظهر رسالة مفادها أنه سيتم تحديث الملفات. لا تقلقوا منها لأن الملفات ستبقى كما هي.
لا مانع من استعمال أي نسخة فرعية مثل 2019.4.1 أو 2019.4.18 والأفضل استعمال آخر إصدار
إصلاح مشكلة الركض في الهواء
قمنا في المقال الأول بإضافة خاصية الركض التي تسمح للاعب بتحريك الشخصية بسرعة أكبر.
المشكلة أننا لا نقوم بالتأكد ما إذا كانت الشخصية في وضع يسمح لها بالركض قبل أن نقوم بتغيير سرعتها.
حيث أن اللاعب الآن يستطيع مثلا أن يقفز في الهواء ثم يغير سرعته، وذلك لا يبدو واقعيا بالنسبة لي.
بالتأكيد الأمر يعتمد على مصمم اللعبة في النهاية، ولكن من الأفضل اقتراح حل يمكنكم تطبيقه.
الحل:
سنسمح بتغيير السرعة فقط إذا كانت الشخصية ملامسة للأرضية. ولكن بعد تغييرها، يمكن الابقاء عليها حتى وإن تركت الشخصية الأرضية.
وذلك سيمسح للاعب بالركض ثم القفز، وأثناء القفز تبقى السرعة ذاتها دون أن تبطئ، إلا في حالة ترك اللاعب زر الركض.
سأقوم بإضافة متغير bool أسميه مثلا isRunning (أو canRun ربما) تكون قيمته false في البداية.
والآن في كل إطار عندما نسأل عن حالة زر الركض، إذا كان الزر يُضغط الآن، نتأكد إذا كانت الشخصية ملامسة للأرضية..
إذا كانت ملامسة، نجعل قيمته true. وإلا، نتركها كما هي. أما إذا لم يكن الزر مضغوطًا، نقوم بجعل قيمته false.
ثم في جزء تغيير السرعة، نقوم فقط بالتأكد من قيمة هذا المتغير. إذا كانت true، نستخدم سرعة الركض. وإلا، نستخدم السرعة العادية.
في الحقيقة لن نضطر إلى القيام بالكثير من التعديلات لتطبيق هذا الحل.
ببساطة في الملف PlayerMovement.cs، المسؤول عن تحريك الشخصية فيزيائيا، وعند الدالة MoveHorizontally:
bool isRunning; public void MoveHorizontally(){ if (jumpedOffWall) return; float x_vel = x_axis * walkSpeed; if(inputHandler.isHoldingRunInput){ if(playerManager.grounded) isRunning = true; } else isRunning = false; if(isRunning) x_vel = x_axis * runSpeed; ... }
تعديل طريقة الارتداد عن الجدران
في المقال الثاني من السلسلة قمنا ببرمجة نظام للقفز عن الجدران.
ولكي يعمل كما أردنا، احتجنا لحفظ قيمة إدخال لزر القفز وزرالاتجاه (يمين أو يسار)
وفي الحقيقة ليس هناك خطأ برمجي والنظام يعمل بشكل جيد. ولكن الذي لاحظته وأخبرني به بعض الزملاء أن الكثير من الألعاب تستخدم طريقة إدخال معاكسة لطريقتنا.
أولاً تذكروا أنه للارتداد، يجب على اللاعب الضغط على زر القفز وزر الحركة باتجاه الارتداد في الوقت نفسه (ولذلك احتجنا إلى حفظ القيم لعدة إطارات)
ولكن معظم الألعاب تطلب من اللاعب ضغط زر القفز والحركة في الاتجاه المعاكس.
وهذا يعني أن اللاعب في العادة لن يحتاج لعكس زر الحركة..
حيث أنه إذا كان يتحرك لليسار مثلا ويريد الارتداد عن الجدار إلى اليمين، يجب أن يبقى ضاغطًا زر الاتجاه لليسار، ثم يقفز في لحظة ملامسته للجدار، لترتد الشخصية مباشرة.
(ولذلك غالبا لا يكون هناك داع لحفظ القيم السابقة لعناصر الإدخال كما فعلنا)
وقد انتبهت لذلك عندما استعملت النظام ذاته في لعبتي Clones لمسابقة Game Jam.
المهم الآن أنني سأطبق هذه الطريقة، وسأحتاج لتعديل بعض الأوامر في الملف PlayerMovement. مع العلم أن الموضوع مرة أخرى يرجع إلى مصمم اللعبة.
لذلك الآن في الملف وداخل الدالة DoJump:
... public void DoJump() { ... /* الارتداد عن الجدران لم يعد هناك حاجة لاستعمال المتغيرين inputHandler.delayedPressedJump inputHandler.delayedHorizButtonPressed */ if (!jumpedOffWall && inputHandler.pressedJump && Mathf.Abs(x_axis) > 0 && playerManager.foundWall) { //إذا كان اتجاه الحركة معاكس لاتجاه الارتداد if (x_axis < 0 == normalRightDot < 0) { //نضرب في إشارة اتجاه الارتداد لنحصل على الاتجاه الصحيح Vector2 reflectionVector = Vector2.right * Mathf.Sign(normalRightDot) * Vector2.up * (1 + jumpOffWallUpBias); if(reflectionVector.sqrMagnitude != 1) reflectionVector.Normalize(); playerManager.rig2D.velocity = reflectionVector * jumpOffWallImpulse; jumpedOffWall = true; lastJumpOffWallTime = Time.time; //نعتبرها قفزة اعتيادية jumpTimer = 0; hasJumped = true; //انظر الدالة في الأسفل FlipDirection(); playerManager.Jumped(); } } ... }
وقمت بكتابة الدالة FlipDirection:
//حفظ المتجه كمتغير أفضل من إنشاء عنصر جديد كل مرة Vector3 negativeX = new Vector3(-1, 1, 1); private void FlipDirection(){ transform.localScale = Vector3.Scale(transform.localScale, negativeX); isFacingRight = !isFacingRight; }
ثم بداخل الدالة UpdateMovement نتأكد من عدم تغييرها للاتجاه أثناء الارتداد عن الجدار:
public void UpdateMovement(){ ... if (IsDirectionFlipped(x_axis)){ if(!jumpedOffWall){ //يمكن إضافة شروط أخرى هنا، كملامسة الأرضية FlipDirection(); } } ... }
بالطبع يمكن إضافة شروط أخرى في الأعلى قبل تغيير الاتجاه كما فعلنا في آخر مقال القفز.
هذه المشاكل المهمة التي لاحظتها.
أود أن أخبركم بأنني أقوم بتحديث مقال إضافة خاصية السباح وتأثير الماء، وبعده نكون أكملنا خصائص الحركة أو الميكانيكيات للنظام.
مع أنني لا أحب أن أترك شيئًا بدأته دون أن أنهيه، ولهذا عدت لتكملة المقالات بعد طول انقطاع..
ولكن ربما لن أكمل الأمور الأخرى مثل تصميم الخريطة وإضافة الأعداء، إلا إذا كان هناك طلب من المتابعين لذلك.