游戏邦在:
杂志专栏:
gamerboom.com订阅到鲜果订阅到抓虾google reader订阅到有道订阅到QQ邮箱订阅到帮看

如何使用行为树去创造游戏中boss的AI

发布时间:2015-11-23 11:32:32 Tags:,,,,

作者:Cody Olivier

在本文中,我将谈论我们的boss是如何决定发动怎样的攻击,我是如何想出我们现在所使用的方法,以及为何我会选择该方法。让我们先说说《FireFight》的背景。这是一款自上而下的2D行动射击游戏,在这里玩家需要击败一波又一波的敌人。同时玩家也拥有一个同伴将通过发出叫声(就像雷达那样),在艰难的情况下提供保护盾牌去帮助你,并协助你攻击其他敌人。关于boss行为的整体感觉是受到早前/复古行动游戏以及《塞尔达》系列游戏的启发。这便是我们想要呈现的内容:攻击将以一种可预测的频率发生,并且每次攻击的发生都有特定的标准(如玩家的健康状况或敌人与玩家的距离),在决定攻击时,如果两个攻击都满足标准,那便存在一些随机性。

游戏中的所有AI都使用了行为树(你们可以从这篇文章中获得更多了解:http://www.gamasutra.com/blogs/ChrisSimpson/20140717/221339/Behavior_trees_for_AI_How_they_work.php)。行为树真的很棒,从视觉上看它很容易辨别与创造,并且也具有强大的灵活性。而根据行为的复杂性,行为树也可以变得非常巨大。

在为第一个boss设计AI时,我和团队成员讨论了该boss将发动怎样的攻击与何时发动攻击。我们决定了三个主要攻击,并且其中一个攻击拥有一个较弱的变体。这便是行为树的基础,如下所示:

grassbosssm(from gamecareerguide)

grassbosssm(from gamecareerguide)

这看起来还不错。行为树的左半部分将决定攻击类型,而右半部分则是围绕着地图的移动。攻击分支同时也包含了一个定时器装饰节点,如此便能有效间隔成功的攻击。这样的设置非常合理。不过有一个主要的缺陷便是如果我想要调整一个特定攻击的条件,我便必须改变管理该攻击的子树的结构。除此之外,如果我想要测试一次攻击,除了将我自己置于具体条件下去触发攻击外,我将很难避免其它攻击的发生。不过从整体看来它还是运行得很好。

当我开始执行下个boss的AI时,问题便出现了。尽管遵循了一个类似的AI风格(游戏邦注:根据不同条件间隔性地发动攻击),但是攻击次数和风格却是不同的,因此我们只能复制一半的行为树,而剩下的一半将是不同的。我觉得比起考虑保留同样的风格,这样的内容需要投入更多精力。我不仅需要编写一个全新的攻击分支,同时我还要在最初分支完成后调试逻辑,而这是一项较困难的工作(在不改变行为树的前提下测试特定攻击并不简单)。如果不存在一个明显的解决方法,我便会在表格中记下每次攻击的距离条件,如下图:

waterboss(from gamecareerguide)

waterboss(from gamecareerguide)

为了让内容更加复杂,Heal拥有一些不是基于距离的其它条件。

首先我想到了能够评估每次攻击标准的执行,并确保每次攻击能够满足它们的标准,然后随机选择攻击。这不仅能够保留随机性,同时还能修改攻击标准,从而确保只能选择一种攻击。这能够帮助我轻松且分开调试每一次攻击。如果说这里所存在一个缺陷的话便是我需要在每一次攻击时检查所有的标准(如果攻击计时器结束了而boss又还未选择攻击的话)并且我也需要保存任何可能的攻击选择(这并不是什么大事,但我还是需要考虑这点)。

这一执行最终变得非常简单,并伴随着行为树上的一个“随机选择器”节点。该节点将筛选可能的攻击,并观察每个检查是否满足标准,并以此做出选择。因为这一系统,我从不需要确保一个包含所有可能攻击的列表,我只需要在满足攻击标准时检查它便可。最糟糕的情况是,所有攻击都未能满足标准,所以将执行所有攻击(但这也也比之前提到的一帧一帧执行它们好多了)。以下是带有全新系统的行为树攻击选择图表:

waterboss(from gamecareerguide)

waterboss(from gamecareerguide)

这看起来更简单,更灵活且更容易浏览了。而唯一添加的复杂性,也就是人类在理解这棵行为树时需要清楚的是,“选择行动”分支“上的每片叶子都拥有如下形式:

如果攻击未能满足条件

回到失败

执行攻击

回到成功

我这么做只是为了保持行为树足够简单,就像在行为树上执行这些行动能够取代树上的每片叶子一样:

waterboss(from gamecareerguide)

waterboss(from gamecareerguide)

这不仅能让调试/测试特定攻击变得更轻松,同时这也能够容纳更多新加入的攻击。这一整体结构并不是只针对于一个boss,也就是说我还可以将其用于其它boss上。我仍然需要决定何时以及如何执行攻击,而这也是我不得不做的事。

尽管行为树拥有许多灵活性,但我敢保证这绝对不是我们能够想出的唯一解决方法。

本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转发,如需转载请联系:游戏邦

Using Behavior Trees to Create Retro Boss AI

Cody Olivier

In this post, I am going to talk about how our bosses determine which attack they will use, how I got to our current method, and why I choose this method. A little background on FireFight. FireFight is a top-down / pokemon-isometric 2D action shooter where you have to defeat waves of enemies. You also have a companion that helps you out by barking ( which acts like radar ), providing a shield to protect you in tough situations, and helps attack other enemies. Each level has unique enemies and a boss. The overall feel for the boss behavior was inspired by old-school / retro action games and retro Legend of Zelda games. Here is the outline of what we wanted: attacks happen in a predictable frequency, each attack has a very specific criteria for when it happens (such as low health or distance from player), there is some randomness if two attacks meet their criteria when determining to attack.

All of the AI in the game uses Behavior Trees (recommended but not required reading:
http://www.gamasutra.com/blogs/ChrisSimpson/20140717/221339/Behavior_trees_for_AI_How_they_work.php). Behavior trees are very nice because they are visually easy to follow and construct and are incredible flexible. Depending on the complexity of behavior however, they can become overwhelming and big.

When designed the AI for the first boss, my teammate and I talked about what attacks the boss would do and when they would happen. We settles on 3 main attacks, and one of them has a weaker variant. This was the basis for its behavior tree, which look like this:

It doesn’t seem too bad. The left half of the tree is for determining what type of attack to use and the right half is for moving around the map. The attack branch is also rooted with a timer decorator node so that successful attacks are spaced out. This worked fairly well. The main downside was that if I wanted to tweak the criteria for a particular attack, I would have to change the structure of the subtree managing that attack. On top of this, if I wanted to test one attack, there was no easy way to prevent the other attacks from happening aside from putting myself in the exact conditions to trigger the attack ( which isn’t always easy ). With all that said, it did work out well.

The issue came when I went to go implement the next boss’ AI. Although following a similar AI style ( spaced out attacks and attacks based on conditions ), the number and type of attacks were different, thus the right half of the tree could be copied, but the left half would need to be completely different. I felt like this was going to lead to more work than necessary considering the same style was going to be retained. Not only did I need to write a new attack branch, but also debugging the logic once the initial branch is done is not something that is easy to do ( remember testing one specific attack isn’t easy without changing the tree ).
Without an obvious solution, I started to write down a chart for the distance conditions for each attack in the hopes that something would spark. Here is the chart:

To complicate things, Heal had some other conditions that weren’t distance based.

One thing that I thought about was an implementation that would evaluate the criteria for each attack, keep ones that met their criteria, and randomly choose between those. This would not only keep the randomness, but I could also modify attack criteria so that only one attack was ever possible to be chosen. This would help debugging each attack easy as I could do it in isolation ( so to speak ). The downside here is that I would need to check all criteria every time I want to attack ( which could be every frame if the attack timer is up and the boss hasn’t chosen an attack ) and I would need to store attack choices that were possible ( not a big deal but I didn’t end up having to worry about this ).

This implementation ended up being very simple with a ‘Random Selector’ node in the behavior tree. This node would shuffle the possible attacks, and for each one check if its criteria were met and if so choose that one. Because of this system, I never need to keep a list of all possible attacks and I only check attack criteria until one attack’s criteria is met. In the worst case, no criteria is met so all are executed ( but still better than executed all of them every frame as stated in the previous paragraph ). Here is a diagram of the attack-selection part of the behavior tree with the new system:

It is a lot simpler, flexible, and readable. The only added complexity that human interpreters of this tree need to know is that each leaf in the “Choose Action” branch has the following form:

if attack doesn’t meet conditions

return fail

perform attack

return success

I did this purely to keep the tree simple as trying to implement this purely on behavior trees would look like this in place of each leaf:

Not only does this make debugging / testing specific attacks easy, it easily holds up to adding more attacks. The overall structure is also not boss specific which means I can ( and will ) use it for additional bosses. I still have to determine when and how the attacks should be performed, but I was going to have to do that anyways.

Although there is a lot of flexiblity with Behavior Trees, I’m sure this isn’t the only solution others have come up with. I would love to hear if and how others approach boss AI design.(source:gamecareerguide

 


上一篇:

下一篇: