إنشاء خطاف القط في الوحدة. الجزء 2

الصورة

ملاحظة : هذا البرنامج التعليمي مخصص للمستخدمين المتقدمين وذوي الخبرة ، ولا يغطي موضوعات مثل إضافة المكونات ، وإنشاء نصوص GameObject جديدة ، وبناء الجملة C #. إذا كنت بحاجة إلى تحسين مهارات Unity الخاصة بك ، فراجع دروس الشروع في البدء في Unity و مقدمة في Unity Scripting .

في الجزء الأول من البرنامج التعليمي ، تعلمنا كيفية إنشاء خطاف القط بآليات لف حبل حول العوائق. ومع ذلك ، نريد المزيد: يمكن للحبل أن يلتف حول الأشياء على المستوى ، لكنه لا ينفصل عند العودة.

للوصول إلى العمل


افتح المشروع النهائي من الجزء الأول في Unity أو قم بتنزيل المسودة لهذا الجزء من البرنامج التعليمي ، ثم افتح 2DGrapplingHook-Part2-Starter . كما في الجزء الأول ، سنستخدم إصدار الوحدة 2017.1 أو أعلى.

افتح مشهد اللعبة في المحرر من مجلد مشروع Scenes .


قم بتشغيل مشهد اللعبة وحاول ربط خطاف القط على الأحجار فوق الشخصية ، ثم تأرجح لجعل الحبل يلتف حول زوج من الحواف الحجرية.

عندما تعود ، ستلاحظ أن نقاط الحجر التي استخدمها الحبل في الدوران لا تظهر مرة أخرى.


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

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


عندما يعود البزاق مرة أخرى في الاتجاه الآخر ، يجب أن يكون الحبل مجددًا عند نفس النقطة (مظللة باللون الأحمر في الشكل أعلاه):


منطق التواء


لحساب اللحظة التي تحتاج فيها إلى فك الحبل في النقاط التي تم لفها في وقت سابق ، نحتاج إلى معرفة الهندسة. على وجه الخصوص ، سنستخدم مقارنة الزوايا لتحديد متى يجب أن ينفصل الحبل عن الحافة.

قد تبدو هذه المهمة مخيفة بعض الشيء. يمكن أن تثير الرياضيات الرعب واليأس حتى في أكثر الشجعان.

لحسن الحظ ، لدى Unity بعض وظائف مساعد الرياضيات الرائعة التي يمكن أن تجعل حياتنا أسهل قليلاً.

افتح البرنامج النصي RopeSystem في IDE وقم بإنشاء طريقة جديدة تسمى HandleRopeUnwrap() .

 private void HandleRopeUnwrap() { } 

انتقل إلى Update() وأضف في النهاية مكالمة لطريقتنا الجديدة.

 HandleRopeUnwrap(); 

في حين أن HandleRopeUnwrap() لا يفعل شيئًا ، ولكن الآن يمكننا معالجة المنطق المرتبط بعملية الانفصال بالكامل عن الحواف.

كما تتذكر من الجزء الأول من البرنامج التعليمي ، قمنا بتخزين أوضاع التفاف الحبل في مجموعة تسمى ropePositions ، وهي مجموعة List<Vector2> . في كل مرة يلتف حبل حول حافة ما ، نحتفظ بموضع نقطة الالتفاف هذه في هذه المجموعة.

لجعل العملية أكثر كفاءة ، لن نقوم بتنفيذ أي منطق في HandleRopeUnwrap() إذا كان عدد المراكز المخزنة في المجموعة يساوي أو أقل من 1.

بعبارة أخرى ، عندما يتم ربط ropePositions بنقطة البداية ولم يتم لف حبله بعد حول الحواف ، سيكون عدد ropePositions 1 ، ولن نتبع منطق المعالجة غير الملتوية.

أضف return البسيطة هذه إلى الجزء العلوي من HandleRopeUnwrap() لحفظ دورات وحدة المعالجة المركزية القيمة ، لأن هذه الطريقة يتم استدعاؤها من Update() عدة مرات في الثانية.

 if (ropePositions.Count <= 1) { return; } 

إضافة متغيرات جديدة


تحت هذا الاختبار الجديد ، سنضيف بعض الأبعاد والمراجع إلى الزوايا المختلفة اللازمة لتطبيق أساس منطق الفك. أضف التعليمات البرمجية التالية إلى HandleRopeUnwrap() :

 // Hinge =       // Anchor =     Hinge // Hinge Angle =   anchor  hinge // Player Angle =   anchor  player // 1 var anchorIndex = ropePositions.Count - 2; // 2 var hingeIndex = ropePositions.Count - 1; // 3 var anchorPosition = ropePositions[anchorIndex]; // 4 var hingePosition = ropePositions[hingeIndex]; // 5 var hingeDir = hingePosition - anchorPosition; // 6 var hingeAngle = Vector2.Angle(anchorPosition, hingeDir); // 7 var playerDir = playerPosition - anchorPosition; // 8 var playerAngle = Vector2.Angle(anchorPosition, playerDir); 

هناك الكثير من المتغيرات هنا ، لذلك سأشرح كل منها ، بالإضافة إلى إضافة توضيح مناسب يساعد على فهم الغرض منها.

  1. anchorIndex هو الفهرس في مجموعة ropePositions في موقعين من نهاية المجموعة. يمكننا اعتبارها نقطة في موقعين على الحبل من موقع سبيكة. في الشكل أدناه ، هذه هي النقطة الأولى لربط الخطاف بالسطح. عند ملء مجموعة ropePositions بنقاط التفاف جديدة ، ستظل هذه النقطة دائمًا نقطة الالتفاف على مسافة موقعين من سبيكة.
  2. hingeIndex هو فهرس المجموعة التي تخزن نقطة المفصلة الحالية ؛ بمعنى آخر ، الموضع الذي يلتف فيه الحبل حاليًا حول النقطة الأقرب إلى نهاية الحبل من سبيكة. إنه دائمًا على مسافة موقع واحد من سبيكة ، وهذا هو السبب في أننا نستخدم ropePositions.Count - 1 .
  3. anchorPosition حساب anchorPosition خلال الإشارة إلى مكان anchorIndex في مجموعة ropePositions وهي قيمة Vector2 البسيطة لهذا الموقع.
  4. hingePosition حساب hingePosition خلال الرجوع إلى مكان hingeIndex في مجموعة ropePositions وهي قيمة Vector2 البسيطة لهذا الموقف.
  5. hingeDir هو ناقل موجه من anchorPosition إلى hingePosition . يتم استخدامه في المتغير التالي للحصول على الزاوية.
  6. hingeAngle - وظيفة المساعد المفيدة Vector2.Angle() تستخدم هنا لحساب الزاوية بين anchorPosition ونقطة المفصلة.
  7. playerDir هو ناقل موجه من anchorPosition إلى الوضع الحالي للسلخ (playerPosition)
  8. بعد ذلك ، باستخدام الزاوية بين نقطة الربط واللاعب (سبيكة) ، playerAngle حساب playerAngle .


يتم حساب جميع هذه المتغيرات باستخدام المواضع المخزنة كقيم Vector2 في مجموعة ropePositions ومقارنة هذه المواضع مع المواضع الأخرى أو الوضع الحالي للاعب (سبيكة).

المتغيران hingeAngle المستخدمان للمقارنة هما hingeAngle و hingeAngle .

يجب أن تظل القيمة المخزنة في hingeAngle ثابتة لأنها دائمًا ما تكون زاوية ثابتة بين النقطة في "طيات الحبل" من سبيكة و "طية الحبل" الحالية الأقرب إلى سبيكة لا تتحرك حتى يتم فك الحبل أو بعد الطي سيتم إضافة نقطة انحناء جديدة.

عندما playerAngle يتغير playerAngle . من خلال مقارنة هذه الزاوية hingeAngle ، والتحقق أيضًا مما إذا كان hingeAngle على يسار أو يمين هذا الركن ، يمكننا تحديد ما إذا كان يجب فصل نقطة الطي الحالية الأقرب إلى البزاق.

في الجزء الأول من هذا البرنامج التعليمي ، قمنا بحفظ مواضع الطي في قاموس يسمى wrapPointsLookup . في كل مرة نحفظ فيها نقطة الانحناء ، نضيفها إلى القاموس مع وضع المفتاح كمفتاح والقيمة 0 كقيمة. ومع ذلك ، كانت قيمة 0 غامضة إلى حد ما ، أليس كذلك؟

سنستخدم هذه القيمة لتخزين موضع البزاق بالنسبة لزاويته مع نقطة المفصلة (نقطة الطي الحالية الأقرب إلى البزاق).

إذا قمت بتعيين قيمة -1 ، فإن زاوية hingeAngle ( hingeAngle ) أقل من زاوية المفصلة ( hingeAngle ) ، وبقيمة 1 ، فإن زاوية hingeAngle أكبر من hingeAngle .

نظرًا لحقيقة أننا hingeAngle القيم في القاموس ، في كل مرة نقوم فيها بمقارنة hingeAngle مع hingeAngle ، يمكننا أن نفهم ما إذا كان hingeAngle قد تجاوزت الحد الذي يجب أن يتخلى عنه الحبل بعد ذلك.

يمكن تفسيرها بشكل مختلف: إذا تم التحقق من زاوية البزاق للتو وكانت أصغر من زاوية المفصلة ، ولكن في المرة الأخيرة التي تم حفظها في قاموس نقاط الانحناء تم تمييزها بقيمة تشير إلى أنها كانت على الجانب الآخر من هذا الركن ، فيجب إزالة النقطة على الفور !

حبل فاصل


ألق نظرة على لقطة الشاشة أدناه مع الملاحظات. تمسك البزاق إلى الصخرة ، متمايلًا لأعلى ، ملفوفًا حبلًا حول حافة الصخرة في طريقه إلى الأعلى.


قد تلاحظ أنه في أعلى موضع تأرجح ، حيث يكون البزاق معتمًا ، سيتم تخزين أقرب نقطة طية له حاليًا (مميزة بنقطة بيضاء) في قاموس wrapPointsLookup بقيمة 1 .

في الطريق للأسفل ، عندما يصبح hingeAngle أصغر من hingeAngle ( hingeAngle ) ، كما هو موضح بالسهم الأزرق ، يتم إجراء فحص ، وإذا كانت القيمة الأخيرة (الحالية) لنقطة الانحناء هي 1 ، فيجب إزالة نقطة الانحناء.

الآن دعنا نطبق هذا المنطق في التعليمات البرمجية. ولكن قبل أن نبدأ ، دعنا ننشئ فراغًا من الطريقة التي سنستخدمها للاسترخاء. ونتيجة لذلك ، بعد إنشاء المنطق ، لن يؤدي إلى خطأ.

أضف طريقة UnwrapRopePosition(anchorIndex, hingeIndex) جديدة UnwrapRopePosition(anchorIndex, hingeIndex) عن طريق إدراج الأسطر التالية:

 private void UnwrapRopePosition(int anchorIndex, int hingeIndex) { } 

بعد القيام بذلك ، عد إلى HandleRopeUnwrap() . تحت المتغيرات المضافة حديثًا ، أضف المنطق التالي ، الذي سيتعامل مع حالتين: hingeAngle أقل من hingeAngle و hingeAngle أكثر من hingeAngle :

 if (playerAngle < hingeAngle) { // 1 if (wrapPointsLookup[hingePosition] == 1) { UnwrapRopePosition(anchorIndex, hingeIndex); return; } // 2 wrapPointsLookup[hingePosition] = -1; } else { // 3 if (wrapPointsLookup[hingePosition] == -1) { UnwrapRopePosition(anchorIndex, hingeIndex); return; } // 4 wrapPointsLookup[hingePosition] = 1; } 

يجب أن يتوافق هذا الرمز مع شرح المنطق الموصوف أعلاه للحالة الأولى (عندما يكون hingeAngle < hingeAngle ) ، ولكنه يعالج أيضًا الحالة الثانية (عندما hingeAngle > hingeAngle ).

  1. إذا كانت قيمة نقطة الطي الحالية الأقرب إلى slug لها قيمة 1 في النقطة التي يكون فيها hingeAngle < hingeAngle ، فإننا نزيل هذه النقطة ونقوم بإرجاع حتى لا يتم تنفيذ باقي الطريقة.
  2. بخلاف ذلك ، إذا لم يتم وضع علامة على نقطة الانحناء آخر مرة بقيمة 1 ، ولكن hingeAngle أقل من hingeAngle ، فسيتم تعيين -1 .
  3. إذا كانت نقطة الطي الحالية الأقرب إلى سبيكة هي -1 عند النقطة التي تكون فيها hingeAngle > hingeAngle ، hingeAngle بإزالة النقطة والعودة.
  4. خلاف ذلك ، فإننا نعين الإدخالات في قاموس نقاط الانحناء عند موضع المفصلة إلى 1 .

يضمن هذا الرمز wrapPointsLookup قاموس wrapPointsLookup دائمًا ، مما يضمن wrapPointsLookup قيمة نقطة الانحناء الحالية (الأقرب إلى slug) لزاوية slig الحالية بالنسبة إلى نقطة الانحناء.

لا تنس أن القيمة -1 عندما تكون زاوية slug أقل من زاوية المفصلة (بالنسبة إلى النقطة المرجعية) ، و 1 عندما تكون زاوية slug أكبر من زاوية المفصلة.

الآن سنقوم UnwrapRopePosition() في البرنامج النصي RopeSystem برمز من شأنه أن ينخرط بشكل مباشر في فك الارتباط ، ونقل الموضع المرجعي وتعيين قيمة مسافة جديدة إلى قيمة مسافة الحبل DistanceJoint2D. أضف الأسطر التالية إلى قرص الطريقة الذي تم إنشاؤه مسبقًا:

  // 1 var newAnchorPosition = ropePositions[anchorIndex]; wrapPointsLookup.Remove(ropePositions[hingeIndex]); ropePositions.RemoveAt(hingeIndex); // 2 ropeHingeAnchorRb.transform.position = newAnchorPosition; distanceSet = false; // Set new rope distance joint distance for anchor position if not yet set. if (distanceSet) { return; } ropeJoint.distance = Vector2.Distance(transform.position, newAnchorPosition); distanceSet = true; 

  1. يصبح مؤشر نقطة الربط الحالية (الموضع الثاني للحبل من البزاقة) هو الموضع الجديد للمفصلة ، ويتم إزالة الموضع القديم للمفصلة (الوضع الذي كان قريبًا سابقًا من الخيط والذي نقوم الآن "بدوره"). newAnchorPosition تعيين قيمة anchorIndex في قائمة مواقع الحبل. سيتم استخدامه بعد ذلك لوضع الموضع المحدّث لنقطة الربط.
  2. RigidBody2D مفصل الحبل (الذي يعلق عليه حبل DistanceJoint2D) يغير موضعه إلى الموضع الجديد لنقطة الربط. وهذا يضمن حركة سلسة ومستمرة للسلخ على الحبل عندما يكون متصلاً بـ DistanceJoint2D ، ويجب أن يسمح هذا الاتصال له بالاستمرار في التأرجح بالنسبة للموضع الجديد ، الذي أصبح المرجع - بعبارة أخرى ، بالنسبة للنقطة التالية أسفل الحبل من موقعه.
  3. ثم تحتاج إلى تحديث قيمة المسافة المسافة Joint2D لتأخذ في الاعتبار تغيير حاد في المسافة من سبيكة إلى النقطة المرجعية الجديدة. إذا لم يتم ذلك بالفعل ، يتم إجراء فحص سريع لعلم distanceSet ، ويتم تعيين المسافة قيمة المسافة المحسوبة بين سبيكة والموضع الجديد لنقطة الارتساء.

احفظ النص البرمجي وارجع إلى المحرر. ابدأ اللعبة مرة أخرى وشاهد كيف ينفصل الحبل عن الحواف عندما يتجاوز سبيكة عتبة كل نقطة الانحناء!


على الرغم من أن المنطق جاهز ، سنضيف بعض التعليمات البرمجية المساعدة إلى HandleRopeUnwrap() مباشرة قبل مقارنة hingeAngle مع hingeAngle ( if (playerAngle < hingeAngle) ).

 if (!wrapPointsLookup.ContainsKey(hingePosition)) { Debug.LogError("We were not tracking hingePosition (" + hingePosition + ") in the look up dictionary."); return; } 

في الواقع ، لا ينبغي أن يحدث هذا ، لأننا نعيد تعريف ونفصل خطاف القط عندما يلتف حول ضلع واحد مرتين ، ولكن إذا حدث ذلك ، يمكننا الخروج بسهولة من هذه الطريقة return بسيط ورسالة خطأ في وحدة التحكم.

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

إلى أين أذهب بعد ذلك؟


هنا رابط للمشروع النهائي لهذا الجزء الثاني والأخير من البرنامج التعليمي.

تهانينا على إكمال هذه السلسلة التعليمية! عندما يتعلق الأمر بمقارنة الزوايا والمواقف ، أصبح كل شيء معقدًا تمامًا ، لكننا نجونا من هذا والآن لدينا نظام ربط وحبل رائع يمكن أن ينتهي به الأمر على الأشياء في اللعبة.


هل تعلم أن فريق تطوير الوحدة لدينا قد كتب كتابًا؟ إذا لم يكن الأمر كذلك ، فراجع Unity Games By Tutorials . ستعلمك هذه اللعبة كيفية إنشاء أربع ألعاب جاهزة من البداية:

  • مطلق النار بعصا
  • أول شخص مطلق النار
  • لعبة برج الدفاع (مع دعم الواقع الافتراضي!)
  • 2D منهاج

بعد قراءة هذا الكتاب ، ستتعلم كيفية إنشاء ألعابك الخاصة لأنظمة التشغيل Windows و macOS و iOS وغيرها من الأنظمة الأساسية!

هذا الكتاب مخصص للمبتدئين وأولئك الذين يرغبون في الارتقاء بمهارات الوحدة إلى مستوى مهني. لإتقان الكتاب ، تحتاج إلى خبرة في البرمجة (بأي لغة).

Source: https://habr.com/ru/post/ar415217/


All Articles