Arabic (العربية/عربي) translation by Saadkhaled (you can also view the original English article)
ديناميكيات الجسم اللين هي عن محاكاة واقعية للاهداف القابلة للتشوه. سنستخدمها لمحاكاة ستارة قماش ومجموعة من الدمى المرقعة التي يمكنك التفاعل معها وقذفها حول الشاشة. أنها سوف تكون مستقرة وسريعة وبسيطة بما يكفي لتتناسب مع رياضيات مستوى المدرسة الثانوية.
ملاحظة: على الرغم من أن هذا البرنامج التعليمي مكتوب اثناء المعالجة ومجمع باستخدام جافا، يجب أن تكون قادراً على استخدام نفس الأساليب والمفاهيم في ما يقرب في أي بيئة تطوير لعبة.
معاينة النتيجة النهائية
في هذا العرض، يمكنك أن ترى ستارة كبيرة (تعرض المحاكاة المفبركة)، وعدد قليل من الدمى (تعرض المحاكاة للدمى المرقعة):
يمكنك محاولة العرض بنفسك، أيضا. انقر واسحب للتفاعل، اضغط على 'R' لإعادة تعيين، وضرب 'ز' لتبديل الخطورة.
الخطوة الاولى: نقطة وحركتها
تكون اللبنات الأساسية للعبتنا مع النقطة. من أجل تجنب الغموض، سوف نطلق عليه PointMass
. التفاصيل في الاسم: نقطة في الفضاء، وأنه يمثل كمية من الكتل.
أبسط طريقة لتنفيذ الفيزياء لهذه النقطة هو "توجيه" سرعتها في طريقة ما.
x = x + velX y = y + velY
الخطوة الثانية: خطوات الزمن
لا يمكن أن نفترض أن اللعبة سوف تعمل بنفس السرعة طوال الوقت. يمكن تشغيلها في 15 لقطة في الثانية لبعض المستخدمين، ولكن في 60 للآخرين. فمن الأفضل لحساب معدلات اللقطات لجميع النطاقات، الذي يمكن القيام به باستخدام خطوة الزمن.
x = x + velX * timeElapsed y = y + velY * timeElapsed
بهذه الطريقة، إذا كانت لقطة قد تستغرق وقتاً أطول لتنقضي لشخص واحد عن اخر من جهة أخرى، لا تزال سوف تعمل اللعبة بنفس السرعة. لمحرك فيزياء، ومع ذلك، هذا غير مستقر بشكل لا يصدق.
تخيل إذا كانت اللعبة تتجمد لثانية أو اثنتين. المحرك سيفرط في التعويض عن ذلك، وسينقل الPointMass
للخلف لبعض من الجدران والاهداف التي سيكون قد اكتشف خلاف ذلك تصادم معها. وهكذا، ليس فقط سيتأثر اكتشاف التصادم، ولكن أيضا طريقة حل القيد التي سنقوم باستخدامها.
كيف يمكن أن يكون لدينا استقرار المعادلة الأولى، x = x + velX
، مع اتساق المعادلة الثانية، x = x + velX *timeElapsed
؟ ماذا لو، ربما، يمكن أن نجمع بينهما؟
هذا بالضبط ما نفعله. تخيل ان ال timeElapsed
كان لدينا 30. يمكن أن نفعل نفس الشيء بالضبط كالمعادلة الأخيرة، ولكن بدقة أعلى وحباقة، باستدعاء x = x
+ (فيلكس * 5)
ست مرات.
elapsedTime = lastTime - currentTime lastTime = currentTime // reset lastTime // add time that couldn't be used last frame elapsedTime += leftOverTime // divide it up in chunks of 16 ms timesteps = floor(elapsedTime / 16) // store time we couldn't use for the next frame. leftOverTime = elapsedTime - timesteps * 16 for (i = 0; i < timesteps; i++) { x = x + velX * 16 y = y + velY * 16 // solve constraints, look for collisions, etc. }
تستخدم الخوارزمية هنا خطوة وقت ثابتة أكبر من واحد. تجد الوقت المنقضي، تفصلها إلى "قطع" باحجام ثابتة ، وتدفع الكمية المتبقية من الوقت أكثر إلى الإطار التالي. ونحن ندير المحاكاة شيئا فشيئاً لكل قطعة لدينا الوقت منقضي فيها.
قد اخترت 16 لحجم خطوة الوقت، لمحاكاة الفيزياء كما لو أنها كانت تعمل في حوالي 60 لقطة في الثانية الواحدة. يمكن أن يتم التحويل من elapsedTime
للإطارات في الثانية الواحدة مع بعض الرياضيات: 1 second /elapsedTimeInSeconds.
1s/(16ms/1000s) = 62.5
إطارا في الثانية، حيث خطوة الوقت 16ms ما يعادل 62.5 من الإطارات في الثانية الواحدة.
الخطوة الثالثة : القيود
قيود من الضوابط والقواعد المضافة إلى المحاكاة، التوجيه حيث يمكن لبوينتماسيس اولا يمكن أن يستمر.
يمكن أن تكون بسيطة مثل القيد الحدود هذا، لمنع بوينتماسيس من التحرك للحافة اليسرى من الشاشة:
if (x < 0) { x = 0 if (velX < 0) { velX = velX * -1 } }
إضافة القيد على الحافة اليمنى من الشاشة يقام بالمثل:
if (x > width) { x = width if (velX > 0) { velX = velX * -1 } }
القيام بذلك للمحور ص هو مسألة تغيير كل محور اكس لy.
يمكن أن يؤدي وجود النوع الصحيح من القيود في خلق تفاعلات جميلة جداً وآسره. يمكن أيضا أن تصبح القيود معقدة للغاية. حاول محاكاة تخيل سلة تهتز من الحبوب مع عدم كون أي من الحبوب تتقاطع، أو 100ذراع-روبوتية مفصلية، أو حتى شيء بسيط ككومة من المربعات. تتضمن العملية النموذجية إيجاد نقاط التصادم، وإيجاد الوقت الدقيق للاصطدام، وثم العثور على القوة الصحيحة أوالنبض لتطبقه على كل جسم لمنع ذاك الاصطدام.
فهم مقدار التعقيد الذي يمكن أن يكون لدى مجموعة من القيود يمكن أن يكون صعبومن ثم حل تلك القيود، في الوقت الحقيقي حتى أكثر صعوبة. ما سنقوم به هو تبسيط حل القيد إلى حد كبير.
الخطوة الرابعة:تكامل Verlet
عالم رياضيات ومبرمج يدعى توماس ياكوبسن اكتشف بعض الطرق لمحاكاة فيزياء الشخصيات للألعاب. واقترح أن الدقة ليست تقريبا بنفس أهمية المصداقية والأداء. قلب خوارزميته كلها وأسلوبه يستخدم منذ 60s لوضع موديل للديناميكيات الجزيئية ، يسمى "تكامل فيرليت". قد تكون معتاداً على لعبة القاتل المحترف: الاسم الرمزي 47. أنها كانت واحدة من الالعاب الأولى لاستخدام فيزياء الدمى المرقعة، وفي استخدام الخوارزميات التي طورها جاكوبسن.
تكامل فيرليت هو الأسلوب الذي سوف نستخدمه لتوجيه موقفنا ل PointMass خاصتنا. ما فعلنا سابقا، x = x + velX
، هو أسلوب يسمى "تكامل أويلر"(الذي أيضا استخدمته في ترميز منطقة بكسل ممكنة التدمير).
والفرق الرئيسي بين أويلر وتكامل فيرليت الكيفية التي يتم بها تنفيذ السرعة. استخدام صيغة أويلر، يتم تخزين مع الهدف السرعة وإضافتها إلى مكان الهدف في كل إطار. باستخدام فيرليت،مع ذلك، ينطبق القصور الذاتي باستخدام الموقف السابق والحالي. خذ الاختلاف في موقفين، وأضفه إلى مكان آخر لتطبيق الجمود.

// Inertia: objects in motion stay in motion. velX = x - lastX velY = y - lastY nextX = x + velX + accX * timestepSq nextY = y + velY + accY * timestepSq lastX = x lastY = y x = nextX y = nextY
أضفنا نحن التسارع هناك للجاذبية. خلاف ذلك، accX
و accY
لن تكون ضرورية لحل التصادمات. باستخدام "تكامل فيرليت"،نحن لم نعد بحاجة إلى القيام بأي نوع من الاندفاع أو حل القوى للتصادمات. تغيير الموضع وحده سيكون كافياً لمحاكاة مستقرة وواقعية وسريعة. ما طوّره ياكوبسن هو بديل خطي لشيء غير خطي.
الخطوة الخامسة: وصل القيود
يمكن أن تظهر فوائد "تكامل فيرليت" أفضل من خلال المثال. في محرك نسيج، سيتعين علينا ليس فقط أن نملك بوينتماسيس، ولكن أيضا روابط بينهما. روابطنا ستكون على قيد مسافة بين بوينتماسيس اثنين. ومن الناحية المثالية، نريد بوينتماسيس اثنين مع هذا القيد ليكونا دائماً على مسافة معينة عن بعضها البعض.
كلما حللنا هذا القيد، تكامل Verlet يجب أن ببقى هذه البوينتماسيس في حركة. على سبيل المثال، إذا كانت نهاية واحدة ستتحرك بسرعة إلى أسفل، الطرف الآخر ينبغي ان يتابعه مثل سوط عن طريق الطاقة الكامنة.

سنحتاج إلى رابط واحدة فقط لكل زوج من البوينتماسيس المرتبطة ببعضها البعض. جميع البيانات التي سوف تحتاجها في الرابط هي البوينتماسيس والمسافات الباقية. بشكل اختياري يمكنك أن تعاني من شد ، لأكثر من قيد الزنبرك. كما لنا في عرضنا التجريبي "حساسية التمزق"، وهي المسافة التي سيتم عندها إزالة الرابط.
فقط سأوضح restingDistance
هنا، ولكن مسافة التمزق والشد تنفذ على حد سواء في العرض والتعليمات البرمجية للمصدر.
Link { restingDistance tearDistance stiffness PointMass A PointMass B solve() { math for solving distance } }
يمكنك استخدام الجبر الخطي لحل للقيد. اعثر على المسافات بين الاثنين وتحديد مدى بعد restingDistance
، ثم ترجم هذه على أساس ذلك والاختلافات بينهما.
// calculate the distance diffX = p1.x - p2.x diffY = p1.y - p2.y d = sqrt(diffX * diffX + diffY * diffY) // difference scalar difference = (restingDistance - d) / d // translation for each PointMass. They'll be pushed 1/2 the required distance to match their resting distances. translateX = diffX * 0.5 * difference translateY = diffY * 0.5 * difference p1.x += translateX p1.y += translateY p2.x -= translateX p2.y -= translateY
في العرض، يمكننا حساب للكتلة والشد كذلك. هناك بعض المشاكل في حل هذا القيد. عندما يكون هناك أكثر من اثنين أو ثلاثة من مرتبطة ببعضها البعض، حل بعض هذه القيود قد يخرب قيود أخرى سبق حلها.

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

الخطوة السادسة: جمع كل شيء معا
باختصار، هنا كيف يعمل محركنا في الرمز الوهمي. لمثال أكثر تحديداً، راجع كود المصدر للعرض .
animationLoop { numPhysicsUpdates = however many we can fit in the elapsed time for (each numPhysicsUpdates) { // (with constraintSolve being any number 1 or higher. I typically use 3 for (each constraintSolve) { for (each Link constraint) { solve constraint } // end link constraint solves } // end constraints update physics // (use verlet!) } // end physics update draw points and links }
الخطوة السابعة: إضافة نسيج
الآن يمكن أن نقوم ببناء النسيج نفسه. إنشاء الروابط يجب أن بكون بسيط إلى حد ما: وصلة إلى اليسار عندما لا تكون البوينتماس الأولى في الصف، واربط عندما لا تكون الأولى في عمودها.

العرض التوضيحي يستخدم قائمة أحادية البعد لتخزين بوينتماسيس، ويرى نقاط ربط باستخدام x + y * عرض
.
// we want the y loop to be on the outside, so it scans row-by-row instead of column-by-column for (each y from 0 to height) { for (each x from 0 to width) { new PointMass at x,y // attach to the left if (x != 0) attach PM to last PM in list // attach to the right if (y != 0) attach PM to PM @ ((y - 1) * (width+1) + x) in list if (y == 0) pin PM add PM to list } }
قد تلاحظون في التعليمات البرمجية أن لدينا أيضا "دبوس بي ام" إذا كنا لا نريد الستارة الانخفاض، ونحن يمكننا تأمين الصف العلوي من بوينتماسيس إلى مواقعهما الأولية للبداية. لهذا البرنامج رقم قيد pin، اضف بعض المتغيرات لتعقب موقع رقم pin، وثم انقل البوينتماس لذاك الموقع بعد حل كل القيود.
خطوة الثامنة: إضافة بعض الدمى المرقعة
كانت الدمى المرقعة في النوايا الأصلية لجاكوبسن وراء استخدام "تكامل فيرليت". أولاً سوف نبدأ مع الرؤوس. سنقوم بإنشاء دائرة قيد التي سوف تتعامل فقط مع الحدود.
Circle { PointMass radius solve() { if (y < radius) y = 2*(radius) - y; if (y > height-radius) y = 2 * (height - radius) - y; if (x > width-radius) x = 2 * (width - radius) - x; if (x < radius) x = 2*radius - x; } }
تاليا يمكن أن نخلق الجسم. وأضفت كل جزء من الجسم لمطابقة إلى حد ما بدقة نسب الكتل والاطول لجسم الإنسان العادي. تحقق من Body.pde
في ملفات المصدر للحصول على التفاصيل الكاملة. عند القيام بذلك سوف تقودنا بنا إلى قضية أخرى: الجسم بسهولة سوف يتلوى في اشكال محرجة، وسيبدو غير واقعي جداً.
هناك عدد من الطرق لحل هذه المشكلة. في العرض، نستخدم روابط غير مرئية وغير مشدودة جداً من القدمين إلى الكتفين والحوض للرأس من أجل دفع الجسم بطبيعة الحال في موقف أقل احرج ومريح.

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

A = resting distance from end PointMass to center PointMass B = resting distance from other PointMass to center PointMass length = sqrt(A*A + B*B - 2*A*B*cos(angle)) create link between end PointMasses using length as resting distance
عدل الرابط بحيث أنه سيتم تطبيق هذا القيد فقط عندما يكون المسافة أقل من المسافة الموجودة، أو، إذا كانت اكبر ذلك. هذا سوف يبقى الزاوية في نقطة المركز من أي وقت مضى كانت فبه قريبة جداً أو بعيدة جداً، اعتماداً على ما تحتاج إليه.
الخطوة التاسعة: أبعاد أكثر!
واحدة من الأشياء عظيمة مع وجود محرك فيزياء تماما خطي هو حقيقة أنه يمكن أن يكون بأي بعد تريد. كما فعلت كل ما فعل لاكس فعل لقيمة y، ويمكن أن يكون امتد لذلك إلى أبعاد ثلاث أو حتى أربع (لست متأكداً كيف يمكنك أن تصيرها، حتى!
على سبيل المثال، هنا قيد رابط للمحاكاة في 3D:
// calculate the distance diffX = p1.x - p2.x diffY = p1.y - p2.y diffZ = p1.z - p2.z d = sqrt(diffX * diffX + diffY * diffY + diffZ * diffZ) // difference scalar difference = (restingDistance - d) / d // translation for each PointMass. They'll be pushed 1/2 the required distance to match their resting distances. translateX = diffX * 0.5 * difference translateY = diffY * 0.5 * difference translateZ = diffZ * 0.5 * difference p1.x += translateX p1.y += translateY p1.z += translateZ p2.x -= translateX p2.y -= translateY p2.z -= translateZ
الاستنتاج
شكرا للقراءة! الكثير من المحاكاة بشدة تستند لمادة توماس جاكوبسن "فيزياء الشخصية المتقدمة" من GDC 2001. بذلت قصارى جهدي لتبسيط معظم الأشياء المعقدة، والتبسيط لدرجة تجعل معظم المبرمجين يفهموها. إذا كنت بحاجة إلى مساعدة، أو أي تعليقات، لا تتردد في النشر والتعليق أدناه.
Envato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post