敌人AI:在没有Navigation2D的情况下追逐玩家并找到路径A *

创建一个敌人必须追逐玩家的游戏?一切都从简单的开始-让敌人跑向玩家。但是,如果它在树后或墙角附近会怎样?好吧,现在敌人看上去会很愚蠢-他会碰到一个物体,指尖就位。不是很好!

要解决此问题,可以使用Godot内置的Navigation2D或AStar节点(这是两个节点上的GDQuest教程)。但是在《愤怒的头盔》中,我们使用了一种对我们的游戏非常有用的方法,我们希望在本教程中分享它。看起来是这样的:


开始工作


我们假设您将敌人创建为KinematicBody2D对象,并使用状态机控制其状态。不确定什么是状态机?我喜欢这篇文章,解释状态机及其使用方法。这是另一篇关于在Godot中实现简单状态机的文章

让我们从一个简单的Chase状态开始,该状态是一个愚蠢的敌人,他刚好跑到目标并被困在某个地方:

# 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)

气味的痕迹


为了改善状态,我们将迫使玩家在移动时离开其先前的位置。因此,当敌人看不到玩家时,他将检查是否可以看到他过去的任何位置,然后将其跟随玩家。由于这类似于狗的足迹,因此我们将其称为气味足迹。

为了使气味跟踪正常工作,我们需要将Timer节点添加到播放器,使其能够自动启动并设置wait_time(我们使用0.1s),然后添加代码,以便在倒计时结束时播放器留下气味。

# 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中设置物理层,您需要单击顶部菜单中的“项目”,然后单击“项目设置”,然后转到左下角的“层名称”部分,然后选择“ 2D物理”。


给他们任何合适的名字。之后,转到游戏中的对象,并在“属性”检查器侧栏中展开“碰撞”,然后单击··进行分配。对于对象,必须将它们指定为“层”。


将物理层分配给对象后,您需要更改敌人的RayCast2D,以便它检查敌人无法移动通过的那些层(在我们的示例中,这是实体,对象,板条箱,孔,gate_closed)。

设置物理层之后,最后一步是更改Chase的状态。

# 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)

现在,当敌人进入追逐状态时,他开始尝试重新铸造玩家,如果他的前进没有任何阻碍,他将跟随他!如果途中有东西,那么他会去闻到气味的痕迹,并尝试重新铸造它们,直到找到其中之一,然后-追赶他!


现在一切正常,敌人变成了猎犬。为了改进系统,可以实现一个避免敌人之间碰撞的系统,但这是另一篇文章的主题。

All Articles