فهم سلوكيات القيادة: المسار التالي
Arabic (العربية/عربي) translation by Hamdan Veerlax (you can also view the original English article)
المسار التالي مشكلة متكررة في تطوير اللعبة. يغطي هذا البرنامج التعليمي المسار التالي لسلوك التوجيه ، والذي يسمح للأحرف باتباع مسار محدد مسبقًا من النقاط والخطوط.
ملحوظة: على الرغم من كتابة هذا البرنامج التعليمي باستخدام AS3 و Flash ، يجب أن تكون قادرًا على استخدام نفس الأساليب والمفاهيم في أي بيئة تطوير ألعاب تقريبًا. يجب أن يكون لديك فهم أساسي لمتجهات الرياضيات.
مقدمة
يمكن تنفيذ المسار التالي للسلوك بعدة طرق. يستخدم تطبيق Reynolds الأصلي مسارًا مصنوعًا من الخطوط ، حيث تتبعه الأحرف بدقة ، تمامًا مثل القطار على القضبان.
اعتمادا على الوضع ، قد لا تكون هذه الدقة مطلوبة. يمكن أن ينتقل الحرف بطول مسار يتبع الخطوط ، ولكنه يستخدم كمرجع ، وليس كقضبان.
إن تنفيذ المسار التالي للسلوك في هذا البرنامج التعليمي هو تبسيط للسير الأصلي الذي اقترحته Reynolds. لا يزال ينتج نتائج جيدة ، لكنه لا يعتمد على حسابات الرياضيات الثقيلة مثل إسقاطات ناقلات الأمراض.
تحديد مسار
يمكن تعريف المسار على أنه مجموعة من النقاط (العقد) متصلة بواسطة الخطوط. على الرغم من إمكانية استخدام المنحنيات أيضًا لوصف المسار ، إلا أن النقاط والخطوط يسهل التعامل معها وتنتج نفس النتائج تقريبًا.
إذا كنت بحاجة إلى استخدام منحنيات ، فيمكن تقليلها إلى مجموعة من النقاط المتصلة:



المنحنيات والخطوط.
سيتم استخدام Path
الفئة لوصف الطريق. في الأساس ، يحتوي الفصل على متجه للنقاط وبضع طرق لإدارة تلك القائمة:
public class Path { private var nodes :Vector.<Vector3D>; public function Path() { this.nodes = new Vector.<Vector3D>(); } public function addNode(node :Vector3D) :void { nodes.push(node); } public function getNodes() :Vector.<Vector3D> { return nodes; } }
كل نقطة في المسار هي Vector3D
تمثل موقعًا في المساحة ، بنفس الطريقة التي تعمل بها خاصية موضع
الحرف.
الانتقال من عقدة إلى عقدة
من أجل التنقل في الفكر ، سوف ينتقل الحرف من العقدة إلى العقدة حتى يصل إلى نهاية الطريق.
يمكن رؤية كل نقطة في المسار على أنها هدف ، لذلك يمكن استخدام سلوك البحث:



سيسعى الحرف إلى النقطة الحالية حتى يتم الوصول إليه ، ثم تصبح النقطة التالية في المسار هي النقطة الحالية وهكذا. كما هو موضح سابقًا في البرنامج التعليمي لتجنب الاصطدام ، يتم إعادة حساب كل القوى السلوكية في كل تحديث للعبة ، وبالتالي فإن الانتقال من عقدة إلى أخرى يكون سلسًا وسلسًا.
ستحتاج فئة الحرف إلى خاصيتين إضافيتين لإجراء عملية التنقل: العقدة الحالية (التي يبحث عنها الحرف) وإشارة إلى المسار الذي يتم اتباعه. سيبدو الفصل كالتالي:
public class Boid { public var path :Path; public var currentNode :int; (...) private function pathFollowing() :Vector3D { var target :Vector3D = null; if (path != null) { var nodes :Vector.<Vector3D> = path.getNodes(); target = nodes[currentNode]; if (distance(position, target) <= 10) { currentNode += 1; if (currentNode >= nodes.length) { currentNode = nodes.length - 1; } } } return null; } private function distance(a :Object, b :Object) :Number { return Math.sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); } (...) }
تعتبر طريقة pathFollowing()
هي المسؤولة عن توليد المسار التالي للقوة. حاليا لا تنتج أي قوة ، لكنها تحدد الأهداف بشكل صحيح.
path! = null
اختبار فارغ يتحقق ما إذا كان الحرف يتبع أي مسار. إذا كانت هذه هي الحالة ، يتم استخدام الخاصية currentNode
للبحث عن الهدف الحالي (الذي يجب أن يسعى إليه الحرف) في قائمة النقاط.
إذا كانت المسافة بين الهدف الحالي وموضع الحرف أقل من 10
، فهذا يعني أن الحرف قد وصل إلى العقدة الحالية. إذا حدث ذلك ، فسيتم زيادة currentNode
بمقدار واحد ، مما يعني أن الحرف سيطلب النقطة التالية في المسار. يتم تكرار العملية حتى نفاد المسار من النقاط.
حساب وإضافة القوات
القوة المستخدمة لدفع الشخصية باتجاه كل عقدة في المسار هي قوة البحث. تقوم طريقة pathFollowing()
باختيار العقدة المناسبة ، لذلك تحتاج الآن إلى إعادة القوة التي ستدفع الحرف باتجاه تلك العقدة:
private function pathFollowing() :Vector3D { var target :Vector3D = null; if (path != null) { var nodes :Vector.<Vector3D> = path.getNodes(); target = nodes[currentNode]; if (distance(position, target) <= 10) { currentNode += 1; if (currentNode >= nodes.length) { currentNode = nodes.length - 1; } } } return target != null ? seek(target) : new Vector3D(); }
بعد احتساب المسار التالي للقوة ، يجب إضافته إلى متجه السرعة الخاص بالحرف كالمعتاد:
steering = nothing(); // the null vector, meaning "zero force magnitude" steering = steering + pathFollowing(); steering = truncate (steering, max_force) steering = steering / mass velocity = truncate (velocity + steering, max_speed) position = position + velocity
يتشابه المسار الذي يتبع قوة التوجيه إلى حد كبير مع pursuit behavior، حيث تقوم الشخصية باستمرار بتعديل اتجاهها للقبض على الهدف. الفرق يكمن في كيفية السعي إلى تحقيق هدف ثابت ، والذي يتم تجاهله لصالح شخص آخر بمجرد أن تقترب الشخصية.
والنتيجة هي ما يلي:
تنعيم الحركة
يتطلب التطبيق الحالي جميع الأحرف "لمس" النقطة الحالية في المسار من أجل تحديد الهدف التالي. ونتيجة لذلك ، قد تؤدي الشخصية أنماط حركة غير مرغوب فيها ، مثل الانتقال في دوائر حول نقطة حتى يتم الوصول إليها.
في الطبيعة ، كل حركة تميل إلى إطاعة مبدأ أقل جهد. على سبيل المثال ، لن يسير الشخص في منتصف ممر طوال الوقت ؛ إذا كان هناك دوران ، فسوف يسير الشخص عن كثب إلى الجدران بينما يتحول من أجل تقصير المسافة.
يمكن إعادة إنشاء هذا النمط بإضافة نصف قطر إلى المسار. يتم تطبيق نصف القطر على النقاط ويمكن رؤيته على أنه المسار "العرض". وسوف يتحكم في مدى انتقال الشخصية البعيدة من النقاط على طول الطريق:



إذا كانت المسافة بين الحرف والنقطة أقل من أو تساوي نصف القطر ، يتم اعتبار النقطة التي تم الوصول إليها. نتيجة لذلك ، سيتم نقل جميع الأحرف باستخدام الخطوط والنقاط كمرشدين:
كلما زاد قطر الشعاع ، كلما اتسعت المسافة وأكبر المسافة التي ستحتفظ بها الشخصيات من النقاط عند الدوران. يمكن تعديل قيمة نصف القطر لإنتاج أنماط تالية مختلفة.
ذهابا وإيابا
في بعض الأحيان يكون من المفيد للشخص أن يستمر في الحركة بعد وصوله إلى نهاية المسار. في نمط دورية ، على سبيل المثال ، يجب أن تعود الشخصية إلى بداية المسار بعد وصوله إلى النهاية ، بعد نفس النقاط.
يمكن تحقيق ذلك عن طريق إضافة الخاصية pathDir
إلى فئة الحرف؛ هذا هو عدد صحيح يتحكم في الاتجاه الذي يتحرك فيه الحرف بطول المسار. إذا كان pathDir
هو 1
، فهذا يعني أن الحرف يتحرك باتجاه نهاية المسار. -1
يدل على حركة نحو البداية.
يمكن تغيير طريقة pathFollowing()
إلى:
private function pathFollowing() :Vector3D { var target :Vector3D = null; if (path != null) { var nodes :Vector.<Vector3D> = path.getNodes(); target = nodes[currentNode]; if (distance(position, target) <= path.radius) { currentNode += pathDir; if (currentNode >= nodes.length || currentNode < 0) { pathDir *= -1; currentNode += pathDir; } } } return target != null ? seek(target) : new Vector3D(); }
بخلاف الإصدار القديم ، يتم الآن إضافة قيمة pathDir
إلى الخاصية currentNode
(بدلاً من إضافة 1
). يسمح هذا للحرف بتحديد النقطة التالية في المسار بناءً على الاتجاه الحالي.
بعد ذلك ، يتحقق اختبار ما إذا كان الحرف قد وصل إلى نهاية الطريق. إذا كان هذا هو الحال ، يتم ضرب pathDir
في -1
، الذي يغير قيمته ، مما يجعل الحرف عكس اتجاه الحركة أيضًا.
والنتيجة هي نمط حركة ذهاب وإياب:
خاتمة
يسمح مسار السلوك التالي لأي حرف بالتحرك على مسار محدد مسبقًا. يتم توجيه المسار بواسطة النقاط ويمكن تعديله ليكون أوسع أو أضيق ، مما ينتج عنه أنماط حركة أكثر طبيعية.
إن التنفيذ المشمول في هذا البرنامج التعليمي هو تبسيط للمسار الأصلي الذي يتبع السلوك الذي اقترحته رينولدز ، لكنه لا يزال ينتج نتائج مقنعة وجذابة.