游戏开发论坛

 找回密码
 立即注册
搜索
查看: 11009|回复: 0

游戏行为系统设计原理解析:行为树的基本概念及进阶

[复制链接]

1万

主题

1万

帖子

3万

积分

论坛元老

Rank: 8Rank: 8

积分
36572
发表于 2016-6-16 15:35:23 | 显示全部楼层 |阅读模式
QQ截图20160616153145.jpg

  GameRes游资网授权发布 文/jonygli

  前言

  开发游戏AI的目标之一就是要找到一个简单,可扩展的编辑逻辑的方案,从而加速游戏开发的迭代速度。在“行为系统图”中,行为系统(Behavior System)响应游戏中的各种信息,进行决策以挑选接下来将要执行的行动并且监控该行动的执行。

1.png
知识模型(Knowledge Model)是对游戏世界中各种信息的抽象。

  在行为系统中,有限状态机(FSM,Finite State Machine)最为经典,FSM模型的优势之一是简单。但是FSMs需要用转换(Transition)连接状态(State),因此,状态(State)失去了模块性(Modularity)。

2.png

  什么是行为树

  一个例子

3.png

  上图中,3号Sequence节点有3个子节点,分别是:

  • 4号Condition节点
  • 5号Action节点
  • 6号Wait节点

  而3号节点的父节点是2号的Loop节点。

  先补充下各节点类型的执行逻辑(详见节点说明):

  序列(Sequence)节点:顺序执行所有子节点返回成功,如果某个子节点失败返回失败。

  循环(Loop)节点:循环执行子节点到指定次数后返回成功,如果循环次数为-1,则无限循环。

  条件(Condition)节点:根据条件的比较结果,返回成功或失败。

  动作(Action)节点:根据动作结果返回成功,失败,或运行。

  等待(Wait)节点:当指定的时间过去后返回成功。

  执行说明


  • 如果4号条件节点的执行结果是成功,其父节点3号节点则继续执行5号节点,如果5号动作节点返回成功,则执行6号等待节点,如果6号节点返回成功,则3号节点全部执行完毕且会返回成功,那么2号节点继续下个迭代。
  • 如果4号条件节点的执行结果是失败,其父节点3号节点则返回失败不再继续执行子节点,并且2号节点继续下个迭代。

  进阶

  聪明的读者可能会问,上面的例子中只讲了成功或失败的情况,但如果动作要持续一段时间呢?如果5号节点,Fire需要持续一段时间呢?

  • 节点的执行结果可以是“成功”,“失败”,或“运行”。
  • 对于持续运行一段时间的Fire动作,其执行结果持续返回“运行”,结束的时候返回“成功”。
  • 对于持续运行一段时间的Wait动作,其执行结果持续返回“运行”,当等待时间到达的时候返回“成功”。

  当节点持续返回“运行”的时候,BT树的内部“知道”该节点是在持续“运行”的,从而在后续的执行过程中“直接”继续执行该节点,而不需要从头开始执行,直到该运行状态的节点返回“成功”或“失败”,从而继续后续的节点。从外面看,就像“阻塞”在了那个“运行”的节点上,其父节点就像不再管理,要一直等运行的子节点结束的时候,其父节点才再次接管

  (请注意,这一段说明只是从概念上这样讲,概念上可以这样理解,实际上即使运行状态的节点每次执行也是要返回的,只是其返回值是运行,其父节点对于返回值是运行状态的节点,将使其继续,所以看上去好像父节点不再管理。)。

  另一个例子

4.png

  如上图,为了清晰说明运行状态,来看另一个例子。在这个例子中,Condition,Action1,Action3是3个函数。

  • 0号节点是个Loop节点,循环3次。
  • 1号节点是个Sequence节点
  • 2号节点模拟一个条件,直接返回成功。
  • 3号节点Action1是一个动作,直接返回成功。
  • 4号节点Action3同样是一个动作,返回3次运行,然后返回成功。

  其代码如下:

  1. bool CBTPlayer::Condition()
  2. {
  3.     m_Frames = 0;
  4.     cout << "\tCondition\n";
  5.     return true;
  6. }
  7. behaviac::EBTStatus CBTPlayer::Action1()
  8. {
  9.     cout << "\tAction1\n";
  10.     return behaviac::BT_SUCCESS;
  11. }
  12. behaviac::EBTStatus CBTPlayer::Action3()
  13. {
  14.     cout << "\tAction3\n";
  15.     m_Frames++;
  16.     if (m_Frames == 3)
  17.     {
  18.         return behaviac::BT_SUCCESS;
  19.     }
  20.     return behaviac::BT_RUNNING;
  21. }
复制代码

  而执行该BT树的C++代码如下:

  1.   int frames = 0;
  2.     behaviac::EBTStatus status = behaviac::BT_RUNNING;
  3.     while (status == behaviac::BT_RUNNING)
  4.     {
  5.         cout << " frame " << ++frames << std::endl;
  6.         status = g_player->btexec();

  7.         //other codes
  8.     }
复制代码
  上面的执行行为树的代码就如同游戏更新部分。status = g_player->btexec()是在游戏的更新函数(update或tick)里,需要每帧调用。

  特别的,对于运行状态,即使运行状态概念上讲是“阻塞”在节点,但是依然是每帧需要调用btexec,也就是说,其节点依然是每帧都在运行,只是下一帧是继续上一帧,从而表现的是运行状态,在其结束之前,其父节点不会把控制转移给其他后续节点。这里的“阻塞”并非真的被阻塞,并非后续的代码(上面的other codes部分)不会被执行。status = g_player->btexec()后面如果有代码,依然被执行。

  执行结果会是个什么样的输出呢?

5.png

  第1帧:

  2号节点Condition返回“成功”,继续执行3号Action1节点,同样返回“成功”,接续执行4号Action3,返回“运行”。

6.png

  第2帧:

  由于上一帧4号Action3返回“运行”,直接继续执行4号Action3节点。

7.png

  第3帧:

  由于上一帧4号Action3返回“运行”,直接继续执行4号Action3节点。

8.png

  同样需要注意的是,2号Condition节点不再被执行。

  而且,本次Action3返回“成功”,1号Sequence节点返回成功。0号Loop节点结束第1次迭代。
第4帧:

  Loop的第2次迭代开始,就像第1帧的执行。

9.png

  再进阶

  又有聪明的读者要问了,持续返回“运行”状态的节点固然优化了执行,但其结果就像“阻塞”了BT的执行一样,如果发生了其他“重要”的事情需要处理怎么办?

  在behaviac里至少有多种办法。

  使用前置


10.png


  每个节点都可以添加前置附件或后置附件。

  上图的action节点添加了一个前置,两个后置。

  可以添加前置附件,并且“执行时机”设为Update或Both,则在每次执行之前都会先执行前置里配置的条件。

  使用Parallel节点

11.png

  如上图,可以使用Parallel节点来“一边检查条件,一边执行动作”,该条件作为该动作的“Guard”条件。当该条件失败的时候来结束该处于持续运行状态的动作节点。

  使用SelectorMonitor节点

12.png

  • SelectorMonitor是一个动态的选择节点,和Selector相同的是,它选择第一个success的节点,但不同的是,它不是只选择一次,而是每次执行的时候都对其子节点进行选择。如上图所示,假若它选择了下面有True条件的那个节点(节点7)并且下面的1号Sequence节点在运行状态,下一次它执行的时候,它依然会去检查上面的那个8号条件的子树,如果该条件为真,则终止下面的运行节点而执行9号节点。
  • WithPrecondition有precondition子树和action子树。只有precondition子树返回success的时候,action子树才能够被执行。

  使用Event子树

  任何一个BT都可以作为事件子树,作为event附加到任何的一个节点上(用鼠标拖动BT到节点)。当运行该BT的时候,如果发生了某个事件,可以通过Agent::FireEvent来触发该事件,则处于running状态的节点,从下到上都有机会检查是否需要响应该事件,如果有该事件配置,则相应的事件子树就会被触发。请参考behaviac的相关文档获取详细信息。

  总结

  行为树的基本概念:

  • 执行每个节点都会有一个结果(成功,失败或运行)
  • 子节点的执行结果由其父节点控制和管理
  • 返回运行结果的节点被视作处于运行状态,处于运行状态的节点将被持续执行一直到其返回结束(成功或失败)。在其结束前,其父节点不会把控制转移到后续节点。

  其中理解运行状态是理解行为树的关键,也是使用好行为树的关键。

13.png

  其他

  上文另一个例子中“demo_running”的例子在安装包及源码里都有提供。最好查看源码,编译运行,自行尝试体会。

14.png

  请指定demo_running作为参数或不指定任何参数运行demo_running:

15.jpg

  相关阅读:游戏AI设计:利用行为树创建boss AI

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2024-5-20 01:57

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表