吃豆人:眼睛如何找到回到怪物洞的路?

我发现在《吃豆人》中有很多关于幽灵AI的参考,但没有一个提到当幽灵被《吃豆人》吃掉后,眼睛是如何找到中央幽灵洞的。

在我的实现中,我实现了一个简单但糟糕的解决方案。我只是在每个角落都用硬编码标明了应该往哪个方向走。

有没有更好的/最好的解决办法?也许是适用于不同关卡设计的通用设计?

23413 次浏览

这是一个寻径问题。有关流行的算法,请参见http://wiki.gamedev.net/index.php/A *

你应该看看寻径算法,比如Dijsktra的算法A *算法。这就是你的问题所在:图/路径问题。

任何简单、可维护、可靠、性能足够好的解决方案都是好的解决方案。听起来你已经找到了一个很好的解决办法。

寻径解决方案可能比当前解决方案更复杂,因此更可能需要调试。它可能也会变慢。

在我看来,如果它没坏,就不要修。

编辑

在我看来,如果迷宫是固定的,那么你目前的解决方案良好/优雅的代码。不要错误地把“好”或“优雅”等同于“聪明”。简单的代码也可以是“好的”和“优雅的”。

如果你有可配置的迷宫级别,那么也许你应该在最初配置迷宫时进行寻路。最简单的方法就是让迷宫设计师亲自动手。如果你有无数个迷宫,我才会费心自动化这个……或者用户可以自行设计。

(另外:如果路线是手工配置的,那么迷宫设计师可以通过使用次优路线来让关卡变得更有趣……)

实际上,我想说你的方法是一个非常棒的解决方案,与任何类型的寻径相比,运行时间成本几乎为零。

如果你需要将其推广到任意地图,你可以使用任何寻径算法——例如,宽度优先搜索很容易实现——并在游戏运行前使用该算法计算在每个角落编码的方向。

编辑(2010年8月11日):我刚刚提到了一个关于吃豆人系统的非常详细的页面:吃豆人档案,既然我在这里有一个公认的答案,我觉得我应该更新它。这篇文章似乎没有明确地涉及回到怪物房子的行为,但它指出了《吃豆人》中的直接寻路是以下情况:

  • 继续向下一个路口移动(尽管这本质上是一种特殊情况,即“当有选择时,选择不涉及反转方向的方向,如下一步所示);
  • 在十字路口,看看相邻的出口方块,除了你刚刚出来的那个;
  • 选一个离目标最近的。如果有多个方向同样接近目标,则按以下顺序选择第一个有效方向:上、左、下、右。

对于更传统的寻路算法的替代方案,您可以看看(适当命名!)吃豆人气味反对象模式

你可以在启动时在迷宫中弥漫怪物洞的气味,然后让眼睛跟着它回家。

气味设置好后,运行成本非常低。


遗憾的是维基百科的文章已经被删除了,所以时空穿越机来拯救我们

我认为你的解决方案是正确的,比这更简单,就是制作一个更“现实”的新版本,鬼魂的眼睛可以穿过墙壁=)

知道吃豆人的路径是非随机的(例如,每个特定的关卡0-255,inky, blinky, pinky和clyde将在该关卡中工作完全相同的路径)。

我会采取这个,然后猜测有一些主路径环绕整个 迷宫是眼球物体的“返回路径”,当pac人吃了鬼的时候它就在哪里

我用这种方法解决了一般关卡的这个问题:在关卡开始前,我从怪物洞中进行某种“洪水填充”;迷宫中除了墙之外的每一块瓦都有一个数字,表示它离洞有多远。所以当眼睛盯着一个距离为68的瓦片时,他们会看哪个相邻的瓦片距离为67;那就这么办吧。

《吃豆人》中的幽灵遵循或多或少可预测的模式,即尝试先匹配X或Y,直到实现目标。我一直认为这对于眼睛寻找回去的路是完全一样的。

下面是ammoQ的洪水填充想法的模拟和伪代码。

queue q
enqueue q, ghost_origin
set visited


while q has squares
p <= dequeue q
for each square s adjacent to p
if ( s not in visited ) then
add s to visited
s.returndirection <= direction from s to p
enqueue q, s
end if
next
next

它的思想是宽度优先搜索,所以每次你遇到一个新的相邻正方形s,最好的路径是经过p。我相信是O(N)。

我不太清楚你是如何执行游戏的,但你可以这么做:

  1. 确定眼睛相对于栅极的位置。它在左上方吗?下面吗?
  2. 然后将眼睛朝两个方向中的一个方向移动(例如,如果它在大门的右边,则让它向左移动,在大门的下面),并检查是否有墙壁阻止你这样做。
  3. 如果有墙壁阻止你这样做,那么让它向相反的方向移动(例如,如果眼睛相对于大头针的坐标是右北,它目前正在向左移动,但有一堵墙阻碍它向南移动。
  4. 记住,每次移动时都要不断检查眼睛相对于门的位置,并检查什么时候没有纬度坐标。也就是说,它只在大门上方。
  5. 在这种情况下,它只是在门上面,如果有一堵墙,向下移动,向左或向右移动,一直这样做1 - 4,直到眼睛在书房。
  6. 我在吃豆人游戏中从未见过死胡同,这段代码不会解释死胡同。
  7. 此外,我还在伪代码中包含了一个解决方案,以解决眼睛何时会在跨越原点的墙壁之间“摆动”的问题。

一些伪代码:

   x = getRelativeOppositeLatitudinalCoord()
y
origX = x
while(eyesNotInPen())
x = getRelativeOppositeLatitudinalCoordofGate()
y = getRelativeOppositeLongitudinalCoordofGate()
if (getRelativeOppositeLatitudinalCoordofGate() == 0 && move(y) == false/*assume zero is neither left or right of the the gate and false means wall is in the way */)
while (move(y) == false)
move(origX)
x = getRelativeOppositeLatitudinalCoordofGate()
else if (move(x) == false) {
move(y)
endWhile

假设你已经有了追逐吃豆人所需的逻辑,为什么不重用它呢?只要改变目标。这似乎比尝试使用完全相同的逻辑创建一个全新的例程要少得多。

在最初的《吃豆人》中,幽灵会通过“气味”在地图上留下痕迹,鬼魂会随机四处游荡,直到它们找到气味,然后它们会沿着气味路径直接找到玩家。吃豆人每移动一次,“气味值”就会减少1。

现在,一个扭转整个过程的简单方法是建立一个“幽灵气味金字塔”,它的最高点在地图的中心,然后鬼魂就会朝着气味的方向移动。

如果每个正方形都有一个到中心的距离值呢?这样,对于每个给定的正方形,你可以在所有可能的方向上得到相邻正方形的值。你选择最小值的正方形,然后移动到那个正方形。

数值将使用任何可用的算法预先计算出来。

我建议幽灵存储他从洞到吃豆人的路径。所以一旦鬼魂死了,他就可以沿着这条存储路径向相反的方向移动。

Dtb23的建议是在每个角落随机选择一个方向,最终你会发现怪物洞听起来非常低效。

然而,你可以利用它低效的“回家”算法,通过在游戏难度中引入更多变化来让游戏变得更有趣。你可以通过应用上面的方法,比如你的路径点或洪水填充来做到这一点,但这样做是非确定性的。所以在每个角落,你都可以生成一个随机数来决定是走最优路线,还是随机方向。

随着玩家不断推进关卡,你将减少玩家选择随机方向的可能性。这将在关卡速度,幽灵速度,吃药丸暂停等之外为整体难度关卡添加另一个杠杆。你有更多的时间放松,而鬼魂只是无害的眼睛,但随着你的进步,时间会越来越短。

这是我能找到的关于它是如何工作的最好的资料。

http://gameai.com/wiki/index.php?title=Pac-Man#Respawn 当幽灵被杀死时,它们脱离实体的眼睛会回到最初的位置。这可以通过将幽灵的目标贴图设置到该位置来实现。导航使用相同的规则

这是有道理的。也许不是世界上最有效的方法但这是一种很好的方法不用担心另一种状态或者沿着这些线你只需要改变目标。

附注:我没有意识到那些吃豆人程序员有多棒,他们基本上是在非常小的空间和非常有限的内存中创建了一个完整的消息系统……太神奇了。

简而言之,不是很好。如果你改变了《吃豆人》迷宫,眼睛就不一定会回来。有些四处游荡的黑客就有这个问题。所以它依赖于一个合作迷宫。

  1. 在游戏开始前保存地图上的节点(交叉点)
  2. 当怪物死亡时,取点(坐标)并找到 节点列表中最近的节点
  3. 计算从该节点到洞的所有路径
  4. 按长度取最短路径
  5. 将该点与最近节点之间的空间长度相加
  6. 绘制并在路径上移动

享受吧!

最初的《吃豆人》并没有使用寻径或花哨的AI。它只是让玩家觉得游戏比实际更有深度,但实际上它是随机的。正如Ian Millington和John Funge在《ai Intelligence for Games》中所述。

不确定这是不是真的,但对我来说很有意义。说实话,我没有看到人们谈论的这些行为。Red/Blinky for ex并不会一直跟着玩家。似乎没有人会一直刻意地跟随玩家。在我看来,他们追随你的可能性是随机的。玩家很容易看到随机行为,特别是当玩家被追赶的几率很高,在一个小空间里有4个敌人和非常有限的转弯选择时。至少在最初的执行阶段,这款游戏非常简单。看看这本书,在前几章里。

对于我的《吃豆人》游戏,我创造了一个有点“shortest multiple path home”的算法,它适用于我所提供的迷宫(在我的规则集内)。它也适用于隧道。

当关卡加载时,所有的path home data in every crossroad都是空的(默认),一旦幽灵开始探索迷宫,它们的crossroad path home information会在每次遇到“新的”十字路口或从不同的路径再次绊倒在已知的十字路口时不断更新。

我的方法有点内存密集型(从《吃豆人》时代的角度来看),但你只需要计算一次,它适用于任何关卡设计(包括跳跃)。

一次标记节点

当你第一次加载一个关卡时,将所有怪物巢穴节点标记为0(代表与巢穴的距离)。继续向外标记已连接的节点1,连接到它们的节点2,依此类推,直到所有节点都被标记。(注意:如果巢穴有多个入口,这也是有效的)

我假设您已经有了表示每个节点和到它们的邻居的连接的对象。伪代码可能看起来像这样:

public void fillMap(List<Node> nodes) { // call passing lairNodes
int i = 0;


while(nodes.count > 0) {
// Label with distance from lair
nodes.labelAll(i++);


// Find connected unlabelled nodes
nodes = nodes
.flatMap(n -> n.neighbours)
.filter(!n.isDistanceAssigned());
}
}

Flood-fill from lair

眼睛移动到距离标签最小的邻居

一旦所有节点都标记好了,路由眼睛就变得很简单了……只需要选择距离标签最小的相邻节点(注意:如果多个节点的距离相等,那么选择哪个节点并不重要)。伪代码:

public Node moveEyes(final Node current) {
return current.neighbours.min((n1, n2) -> n1.distance - n2.distance);
}

全标记示例

完整地图