العدو AI: مطاردة لاعب بدون Navigation2D وإيجاد المسار A *

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

لحل هذه المشكلة ، يمكنك استخدام العقد Navigation2D أو AStar المضمنة في Godot ( هنا هو البرنامج التعليمي GDQuest على كلا العقدتين ). ولكن في Helms of Fury ، استخدمنا طريقة مختلفة عملت بشكل جيد لمباراتنا ، ونريد مشاركتها في هذا البرنامج التعليمي. إليك ما يبدو عليه:


الحصول على العمل


نفترض أنك تقوم بإنشاء أعداء ككائنات KinematicBody2D واستخدام آلة الدولة للتحكم في حالاتهم. لست متأكدا ما هو جهاز الدولة؟ يعجبني هذا المقال الذي يشرح أجهزة الدولة وكيفية استخدامها. هنا مقال آخر حول تنفيذ آلات الحالة البسيطة في Godot .

لنبدأ بدولة مطاردة بسيطة لعدو غبي يركض للتو إلى هدفه ويتعثر في مكان ما على طول الطريق:

# ChaseState.gd

func _init(enemy, params):
  enemy.dir = (enemy.target.position - enemy.position).normalized()

func _physics_process(delta):
  var motion = enemy.dir * enemy.speed
  enemy.move_and_slide(motion)

آثار الشم


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

لكي يعمل أثر الرائحة ، نحتاج إلى إضافة عقدة المؤقت إلى المشغل ، وتمكينه من بدء وتعيين وقت الانتظار تلقائيًا (استخدمنا 0.1 ثانية) ، ثم إضافة الرمز بحيث يترك اللاعب رائحة عند انتهاء العد التنازلي.

# Player.gd
extends KinematicBody2D

const scent_scene = preload("res://Player/Scent.tscn")

var scent_trail = []

func _ready():
  $ScentTimer.connect("timeout", self, "add_scent")

func add_scent():
  var scent      = scent_scene.instance()
  scent.player   = player
  scent.position = player.position

  Game.level.effects.add_child(scent)
  scent_trail.push_front(scent)

ثم تحتاج إلى إضافة Scent.tscn المهجورة نفسها. هذا مشهد Node2D بسيط يحتوي على مؤقت بحيث تنتهي الرائحة.

# Player.gd
extends KinematicBody2D

const scent_scene = preload("res://Player/Scent.tscn")

var scent_trail = []

func _ready():
  $ScentTimer.connect("timeout", self, "add_scent")

func add_scent():
  var scent      = scent_scene.instance()
  scent.player   = player
  scent.position = player.position

  Game.level.effects.add_child(scent)
  scent_trail.push_front(scent)

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

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

طبقات الفيزياء


لإعداد طبقات الفيزياء في Godot ، تحتاج إلى النقر فوق Project في القائمة العلوية ثم على Project Settings ، ثم انتقل إلى قسم Layer Names في الزاوية اليسرى السفلية ثم حدد 2D Physics.


أعطهم أي اسم مناسب. بعد ذلك ، انتقل إلى الكائنات الموجودة في اللعبة وقم بتوسيع Collision في الشريط الجانبي لـ Inspector ، ثم انقر فوق · لتعيينها. بالنسبة للكائنات ، يجب تعيينها كطبقات.


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

بعد إعداد طبقات الفيزياء ، فإن الخطوة الأخيرة هي تغيير حالة المطاردة.

# ChaseState.gd

func _init(enemy, params):
  chase_target()

func chase_target():
  var look     = enemy.get_node("RayCast2D")
  look.cast_to = (enemy.target.position - enemy.position)
  look.force_raycast_update()

  # if we can see the target, chase it
  if !look.is_colliding():
    enemy.dir = look.cast_to.normalized()

  # or chase first scent we can see
  else:
    for scent in enemy.target.scent_trail:
      look.cast_to = (scent.position - enemy.position)
      look.force_raycast_update()

      if !look.is_colliding():
        enemy.dir = look.cast_to.normalized()
        break

func _physics_process(delta):
  var motion = enemy.dir * enemy.speed
  enemy.move_and_slide(motion)

الآن ، عندما يدخل العدو إلى حالة Chase ، يبدأ في محاولة إعادة تمثيل اللاعب ، وإذا لم يكن هناك شيء في طريقه ، فسوف يتبعه! إذا كان هناك شيء على الطريق ، فإنه يذهب إلى آثار الشم ويحاول إعادة صب كل منهم حتى يجد واحدًا منهم ، ثم - يلاحقه!


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

All Articles