<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>AI分享站</title>
	<atom:link href="http://www.aisharing.com/feed" rel="self" type="application/rss+xml" />
	<link>http://www.aisharing.com</link>
	<description>学习，思考，领悟，分享，收获，我是游戏AI程序员</description>
	<lastBuildDate>Sat, 21 Apr 2012 14:56:09 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.2</generator>
		<item>
		<title>GDC2012讲座系列：《杀手：赦免》中的人群系统（Crowds in Hitman:Absolution）</title>
		<link>http://www.aisharing.com/archives/554</link>
		<comments>http://www.aisharing.com/archives/554#comments</comments>
		<pubDate>Sat, 21 Apr 2012 14:53:42 +0000</pubDate>
		<dc:creator>Finney</dc:creator>
				<category><![CDATA[GDC2012]]></category>
		<category><![CDATA[GDC]]></category>
		<category><![CDATA[人工智能]]></category>
		<category><![CDATA[效率]]></category>
		<category><![CDATA[运动系统]]></category>

		<guid isPermaLink="false">http://www.aisharing.com/?p=554</guid>
		<description><![CDATA[<blockquote>从GDC2012回来有一段时间了，一直很忙，没有时间整理在GDC上听到和学到的内容。总的来说，GDC上的讲座很多，在5天的会议时间里，有超过将近340场的各种讲座，作为AI程序员，当然比较关心AI方面的技术内容，在GDC上也确实听到了一些比较有意思的东西，也和世界各地的游戏开发者讨论和分享了一些信息，我会慢慢整理后，在博客上做个记录，分享给大家。另外，GDC比较厚道的是，很多演讲的PPT是可以免费查看和下载的，所有在每篇博客的最后，我会附上该PPT的链接，供大家下载学习。由于讲座和资料都是英文的，有些名词我能理解，但不一定能翻译的很好，我会在一些关键词后面标注英文原文。</blockquote>
今天为大家介绍的是由来自IO Interactive的主程Kasper Fauerby带来的关于一个人群系统的介绍，这个系统用于《杀手：赦免》这个游戏中，用这个系统试图去营造一种在人群中暗杀的气氛。显然，这样一个系统的难点一个是AI的计算如何保证效率，因为同屏会存在大量的NPC，另一个是如何来模拟真实人群行为，特别是在“暗杀者”作出一些动作（比如，开枪）的时候，人群如何去对这种行为作为反应。

在设计这样的系统时，我们都会有多种的考量，在效率上，在实现的难度上，在和关卡设计师的配合上等等，Kasper在考虑了他们的设计目标后，决定采用一种比较轻量化，比较传统的AI设计来做这个人群系统。他通过以下几个部分来介绍了他所设计的人群系统：
<ul>
	<li>框架（Framework）：单元化的地图信息（Cell Map），智能体的建模，以及一些工具</li>
	<li>AI：主要是关于导向行为（Steering Behavior）和行为的选择（Behavior Selector）（这也是这个系统中比较重要的部分）</li>
	<li>画面表现（Visual）：如何用动画和模型来表现</li>
	<li>可信度（Believability）：因为这个系统是和游戏性息息相关的，所以它需要很好的和核心的游戏系统进行整合</li>
</ul>
对于一个大场景的游戏，对于地图的抽象化是非常必要的，我们在平时的开发中，也会来把地图分成一系列的单元格，一方面可以方便计算，也比较高效（<a href="http://www.aisharing.com/archives/410">参考这里</a>），Kasper也用了这样一个方法，把地图用2.5D的网格来表示，在每个单元格中，存入一些参考信息以供AI使用，比如是否可以到达，被哪些智能体占据，是否是出口，是否是收到惊吓时才能去的，等等，这些信息的种类完全取决与AI的需要。

Kasper把人群中每一个智能体比喻成“粒子”（Particle），做过或者用过粒子系统的同学都知道，粒子系统中的每一个粒子都有一系列的属性，比如发射时间，发射速度，旋转，大小，生命期等等，在人群系统的中每一个智能体也可以看成这样一个“粒子”，赋予它一系列的控制属性，来描述智能体的状态。Kasper列出了6种用到的智能体状态：位置（Position），半径（Radius），朝向（Forward Vector），速度（Speed），导向的输入（Steering Input）。这些就是智能体的基本模型。

人群系统的中对于每一个智能体的AI部分其实非常的简单，它基于一个有限状态机，有限状态机分为两个部分，一个称为基本的导向状态机（Navigation States)，一个称为游戏状态机（Gameplay States）。因为在人群系统中，智能体很重要的一个行为就是如何在人群中移动，它需要选择方向，选择移动的时间，还要避让障碍物，避让其他智能体等等，所以第一个状态机就负责这个行为，它会在三个状态间切换
<ul>
	<li>空闲（Idle）：无所事事的状态</li>
	<li>准备移动（Pending Walk）：选择一个合适的移动方向和移动的开始时间，所以它的行为会分成两个阶段，一个是选择阶段（确定方向），一个是等待阶段（等待直至可以开始移动）。</li>
	<li>移动（Walk）：真正的移动，在这个状态中要负责避让障碍等实际的工作</li>
</ul>
所以我们看到，在这个导向行为时，每一个智能体是独立思考，它会根据当前的情况来决定如何和怎样去移动，还有一种我想到的做法是，由外部来统一计算，并给与智能体指令，而智能体只负责完成移动指令即可。

在《杀手》游戏中，人群的移动有一些特殊的状态，比如在受到惊吓时（因为有杀手开枪了嘛），会进入一种特殊的“惊慌”（Panic）状态，这时智能体的移动方式会产生一些变化，虽然状态机还是那样，但行为会被调整，会产生一个移动的“流”（Flow），这也是很好理解的，因为我们可以想像，在惊慌的时候，人们都会统一的远离危险点，往出口散开。为了模拟这样的情况，在计算移动方向的时候，用到了在地图信息上预先计算和处理好的参数，比如指向出口的方向，到出口的代价等一系列值等等，把这些值带入到决策的话，就可以很好的模拟这样的行为，Kasper放了一小段视频来描述这样的行为，我们看到当“杀手”在人群中开枪的时候，人就自然的往后散开，并逃离，效果还是很不错的。

第二个状态机用来处理智能体对于外部情况的一个反应，主要是玩家行为的一个反应，这个状态机由一个称之为“刺激（Pulse）”的方式来驱动，其实也就可以认为是事件驱动的。比如当玩家瞄准人群的时候，智能体就会收到这样一个“刺激”，从而表现出惊慌的行为状态。由于玩家是置身于人群中，处在不同位置的智能体对于玩家的行为应该有不同的表现，比如就算玩家拿枪指着人群，但离玩家比较远，或者在玩家背后的人，应该会有不同的行为，所以，Kasper为玩家设定了一个“区域刺激”（Zone Pulses）的概念，说白了，就是玩家有几个不同半径的圈，在不同的区域里的智能体就会有不同的行为，如下图所示：

<img title="屏幕快照 2012-04-21 下午10.16.44.png" src="http://aisharing.com/wp/wp-content/uploads/2012/04/屏幕快照-2012-04-21-下午10.16.44.png" alt="屏幕快照 2012 04 21 下午10 16 44" width="385" height="258" border="0" />

如果智能体在绿圈的范围内，则会表现为警觉（Alert）的状态，在蓝圈和红的三角区域时，就会抱头扑倒（Prone），很好理解的概念。

我们可以看到，从主要的概念上来看，人群系统的AI部分相对是比较好理解的，也比较简单，但在实现的过程中，可能会有遇到很多实际的问题，比如如何合理的选择路径，如何来抽象地图信息，如何来处理效率和真实之间的平衡，如何处理内存的问题，据Kasper说，他们需要处理1200多个智能体，同屏要超过500个智能体，所以这些都是需要考虑的。当然一个好的调试工具也必不可少，下面这张图就是《杀手》中人群系统的调试工具，和编辑工具的截屏：

<img title="屏幕快照 2012-04-21 下午10.28.44.png" src="http://aisharing.com/wp/wp-content/uploads/2012/04/屏幕快照-2012-04-21-下午10.28.44.png" alt="屏幕快照 2012 04 21 下午10 28 44" width="547" height="357" border="0" />

<img title="屏幕快照 2012-04-21 下午10.28.28.png" src="http://aisharing.com/wp/wp-content/uploads/2012/04/屏幕快照-2012-04-21-下午10.28.28.png" alt="屏幕快照 2012 04 21 下午10 28 28" width="600" height="333" border="0" />

为了更好的表现人群中不同智能体行为的多样性，动画系统的支持，也是必不可少的。我以前提到过动画的表现有两种的方式（<a href="http://www.aisharing.com/archives/458">参考这里</a>），一种是动画配合运动，一种是运动配合动画，前者可以有比较良好的反应（对于方向改变的响应更快），比较简单，但缺点是会有滑步，而且不够真实，后者的优缺点正好和前者相反，它用弥补了真实性，但响应变慢，运行开销也变大了。《杀手》中对于每个智能体的动画系统选择也遇到了这样的问题，他们对于这两种方式都做了尝试，最后如果我理解的没有错的话，貌似还是选择了反应较快的方式，也就是他提到的First Try。因为对于方向改变的相应变慢直接导致了智能体无法避开障碍物，这个是比较不能忍的….。

Kasper在整个演讲的中，还提到了一些关于如何和关卡设计师合作，如何优化，如何组织数据等细节部分等等，大家可以参看他的PPT，有问题，可以留言讨论，我这边就不作为重点介绍了。

我听了这个讲座后，感觉模拟一个人群系统比较重要的一点就是我在整个介绍过程中反复提到的平衡效率和真实的问题，由于每个智能体都是单独思考，单独表现，光是每一个循环一遍就够费了，所以，如何用最简单的方式来作出一个相对真实和多变的人群，是值得在设计系统前就好好思考的，Kasper给了一个很好的设计思路，虽然有一些地方还是不够完美，但这也是在取舍时候的一些妥协，毕竟帧数一低，再好的系统也一无是处了。

最后，给出本文的PPT地址，<a href="http://www.gdcvault.com/play/1015315/Crowds-in-Hitman" target="_blank">点击这里</a>

<span style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; display: inline !important; float: none;">————————————————————————</span><br style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; padding: 0px; margin: 0px;" /><span style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; display: inline !important; float: none;">作者：Finney</span><br style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; padding: 0px; margin: 0px;" /><span style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; display: inline !important; float: none;">Blog：AI分享站(</span><a style="text-decoration: none; color: #3b5998; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; padding: 0px; margin: 0px;" href="http://www.aisharing.com/">http://www.aisharing.com/</a><span style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; display: inline !important; float: none;">)</span><br style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; padding: 0px; margin: 0px;" /><span style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; display: inline !important; float: none;">Email：finneytang@gmail.com</span><br style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; padding: 0px; margin: 0px;" /><span style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; display: inline !important; float: none;">本文欢迎转载和引用，请保留本说明并注明出处</span><br style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; padding: 0px; margin: 0px;" /><span style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; display: inline !important; float: none;">————————————————————————</span>

&#160;]]></description>
			<content:encoded><![CDATA[<blockquote><p>从GDC2012回来有一段时间了，一直很忙，没有时间整理在GDC上听到和学到的内容。总的来说，GDC上的讲座很多，在5天的会议时间里，有超过将近340场的各种讲座，作为AI程序员，当然比较关心AI方面的技术内容，在GDC上也确实听到了一些比较有意思的东西，也和世界各地的游戏开发者讨论和分享了一些信息，我会慢慢整理后，在博客上做个记录，分享给大家。另外，GDC比较厚道的是，很多演讲的PPT是可以免费查看和下载的，所有在每篇博客的最后，我会附上该PPT的链接，供大家下载学习。由于讲座和资料都是英文的，有些名词我能理解，但不一定能翻译的很好，我会在一些关键词后面标注英文原文。</p></blockquote>
<p>今天为大家介绍的是由来自IO Interactive的主程Kasper Fauerby带来的关于一个人群系统的介绍，这个系统用于《杀手：赦免》这个游戏中，用这个系统试图去营造一种在人群中暗杀的气氛。显然，这样一个系统的难点一个是AI的计算如何保证效率，因为同屏会存在大量的NPC，另一个是如何来模拟真实人群行为，特别是在“暗杀者”作出一些动作（比如，开枪）的时候，人群如何去对这种行为作为反应。</p>
<p>在设计这样的系统时，我们都会有多种的考量，在效率上，在实现的难度上，在和关卡设计师的配合上等等，Kasper在考虑了他们的设计目标后，决定采用一种比较轻量化，比较传统的AI设计来做这个人群系统。他通过以下几个部分来介绍了他所设计的人群系统：</p>
<ul>
<li>框架（Framework）：单元化的地图信息（Cell Map），智能体的建模，以及一些工具</li>
<li>AI：主要是关于导向行为（Steering Behavior）和行为的选择（Behavior Selector）（这也是这个系统中比较重要的部分）</li>
<li>画面表现（Visual）：如何用动画和模型来表现</li>
<li>可信度（Believability）：因为这个系统是和游戏性息息相关的，所以它需要很好的和核心的游戏系统进行整合</li>
</ul>
<p>对于一个大场景的游戏，对于地图的抽象化是非常必要的，我们在平时的开发中，也会来把地图分成一系列的单元格，一方面可以方便计算，也比较高效（<a href="http://www.aisharing.com/archives/410">参考这里</a>），Kasper也用了这样一个方法，把地图用2.5D的网格来表示，在每个单元格中，存入一些参考信息以供AI使用，比如是否可以到达，被哪些智能体占据，是否是出口，是否是收到惊吓时才能去的，等等，这些信息的种类完全取决与AI的需要。</p>
<p>Kasper把人群中每一个智能体比喻成“粒子”（Particle），做过或者用过粒子系统的同学都知道，粒子系统中的每一个粒子都有一系列的属性，比如发射时间，发射速度，旋转，大小，生命期等等，在人群系统的中每一个智能体也可以看成这样一个“粒子”，赋予它一系列的控制属性，来描述智能体的状态。Kasper列出了6种用到的智能体状态：位置（Position），半径（Radius），朝向（Forward Vector），速度（Speed），导向的输入（Steering Input）。这些就是智能体的基本模型。</p>
<p>人群系统的中对于每一个智能体的AI部分其实非常的简单，它基于一个有限状态机，有限状态机分为两个部分，一个称为基本的导向状态机（Navigation States)，一个称为游戏状态机（Gameplay States）。因为在人群系统中，智能体很重要的一个行为就是如何在人群中移动，它需要选择方向，选择移动的时间，还要避让障碍物，避让其他智能体等等，所以第一个状态机就负责这个行为，它会在三个状态间切换</p>
<ul>
<li>空闲（Idle）：无所事事的状态</li>
<li>准备移动（Pending Walk）：选择一个合适的移动方向和移动的开始时间，所以它的行为会分成两个阶段，一个是选择阶段（确定方向），一个是等待阶段（等待直至可以开始移动）。</li>
<li>移动（Walk）：真正的移动，在这个状态中要负责避让障碍等实际的工作</li>
</ul>
<p>所以我们看到，在这个导向行为时，每一个智能体是独立思考，它会根据当前的情况来决定如何和怎样去移动，还有一种我想到的做法是，由外部来统一计算，并给与智能体指令，而智能体只负责完成移动指令即可。</p>
<p>在《杀手》游戏中，人群的移动有一些特殊的状态，比如在受到惊吓时（因为有杀手开枪了嘛），会进入一种特殊的“惊慌”（Panic）状态，这时智能体的移动方式会产生一些变化，虽然状态机还是那样，但行为会被调整，会产生一个移动的“流”（Flow），这也是很好理解的，因为我们可以想像，在惊慌的时候，人们都会统一的远离危险点，往出口散开。为了模拟这样的情况，在计算移动方向的时候，用到了在地图信息上预先计算和处理好的参数，比如指向出口的方向，到出口的代价等一系列值等等，把这些值带入到决策的话，就可以很好的模拟这样的行为，Kasper放了一小段视频来描述这样的行为，我们看到当“杀手”在人群中开枪的时候，人就自然的往后散开，并逃离，效果还是很不错的。</p>
<p>第二个状态机用来处理智能体对于外部情况的一个反应，主要是玩家行为的一个反应，这个状态机由一个称之为“刺激（Pulse）”的方式来驱动，其实也就可以认为是事件驱动的。比如当玩家瞄准人群的时候，智能体就会收到这样一个“刺激”，从而表现出惊慌的行为状态。由于玩家是置身于人群中，处在不同位置的智能体对于玩家的行为应该有不同的表现，比如就算玩家拿枪指着人群，但离玩家比较远，或者在玩家背后的人，应该会有不同的行为，所以，Kasper为玩家设定了一个“区域刺激”（Zone Pulses）的概念，说白了，就是玩家有几个不同半径的圈，在不同的区域里的智能体就会有不同的行为，如下图所示：</p>
<p><img title="屏幕快照 2012-04-21 下午10.16.44.png" src="http://aisharing.com/wp/wp-content/uploads/2012/04/屏幕快照-2012-04-21-下午10.16.44.png" alt="屏幕快照 2012 04 21 下午10 16 44" width="385" height="258" border="0" /></p>
<p>如果智能体在绿圈的范围内，则会表现为警觉（Alert）的状态，在蓝圈和红的三角区域时，就会抱头扑倒（Prone），很好理解的概念。</p>
<p>我们可以看到，从主要的概念上来看，人群系统的AI部分相对是比较好理解的，也比较简单，但在实现的过程中，可能会有遇到很多实际的问题，比如如何合理的选择路径，如何来抽象地图信息，如何来处理效率和真实之间的平衡，如何处理内存的问题，据Kasper说，他们需要处理1200多个智能体，同屏要超过500个智能体，所以这些都是需要考虑的。当然一个好的调试工具也必不可少，下面这张图就是《杀手》中人群系统的调试工具，和编辑工具的截屏：</p>
<p><img title="屏幕快照 2012-04-21 下午10.28.44.png" src="http://aisharing.com/wp/wp-content/uploads/2012/04/屏幕快照-2012-04-21-下午10.28.44.png" alt="屏幕快照 2012 04 21 下午10 28 44" width="547" height="357" border="0" /></p>
<p><img title="屏幕快照 2012-04-21 下午10.28.28.png" src="http://aisharing.com/wp/wp-content/uploads/2012/04/屏幕快照-2012-04-21-下午10.28.28.png" alt="屏幕快照 2012 04 21 下午10 28 28" width="600" height="333" border="0" /></p>
<p>为了更好的表现人群中不同智能体行为的多样性，动画系统的支持，也是必不可少的。我以前提到过动画的表现有两种的方式（<a href="http://www.aisharing.com/archives/458">参考这里</a>），一种是动画配合运动，一种是运动配合动画，前者可以有比较良好的反应（对于方向改变的响应更快），比较简单，但缺点是会有滑步，而且不够真实，后者的优缺点正好和前者相反，它用弥补了真实性，但响应变慢，运行开销也变大了。《杀手》中对于每个智能体的动画系统选择也遇到了这样的问题，他们对于这两种方式都做了尝试，最后如果我理解的没有错的话，貌似还是选择了反应较快的方式，也就是他提到的First Try。因为对于方向改变的相应变慢直接导致了智能体无法避开障碍物，这个是比较不能忍的….。</p>
<p>Kasper在整个演讲的中，还提到了一些关于如何和关卡设计师合作，如何优化，如何组织数据等细节部分等等，大家可以参看他的PPT，有问题，可以留言讨论，我这边就不作为重点介绍了。</p>
<p>我听了这个讲座后，感觉模拟一个人群系统比较重要的一点就是我在整个介绍过程中反复提到的平衡效率和真实的问题，由于每个智能体都是单独思考，单独表现，光是每一个循环一遍就够费了，所以，如何用最简单的方式来作出一个相对真实和多变的人群，是值得在设计系统前就好好思考的，Kasper给了一个很好的设计思路，虽然有一些地方还是不够完美，但这也是在取舍时候的一些妥协，毕竟帧数一低，再好的系统也一无是处了。</p>
<p>最后，给出本文的PPT地址，<a href="http://www.gdcvault.com/play/1015315/Crowds-in-Hitman" target="_blank">点击这里</a></p>
<p><span style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; display: inline !important; float: none;">————————————————————————</span><br style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; padding: 0px; margin: 0px;" /><span style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; display: inline !important; float: none;">作者：Finney</span><br style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; padding: 0px; margin: 0px;" /><span style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; display: inline !important; float: none;">Blog：AI分享站(</span><a style="text-decoration: none; color: #3b5998; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; padding: 0px; margin: 0px;" href="http://www.aisharing.com/">http://www.aisharing.com/</a><span style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; display: inline !important; float: none;">)</span><br style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; padding: 0px; margin: 0px;" /><span style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; display: inline !important; float: none;">Email：finneytang@gmail.com</span><br style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; padding: 0px; margin: 0px;" /><span style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; display: inline !important; float: none;">本文欢迎转载和引用，请保留本说明并注明出处</span><br style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; padding: 0px; margin: 0px;" /><span style="color: #333333; font-family: Verdana, Tahoma, 'BitStream vera Sans', Arial, Helvetica, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 17px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: #ffffff; display: inline !important; float: none;">————————————————————————</span></p>
<p>&nbsp;</p>
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/2.5/cn/"><img alt="知识共享许可协议" style="border-width:0" src="http://i.creativecommons.org/l/by-nc-nd/2.5/cn/88x31.png" /></a><br />本博客作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/2.5/cn/">知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议</a>进行许可。<hr /><a href="http://www.aisharing.com/donation" target="_blank"><img src="http://aisharing.com/wp/wp-content/uploads/2011/08/donate.png" alt="捐赠本站" /></a> <table class="wumii-related-items" cellspacing="0" cellpadding="2" border="0" width="100%" style="clear: both;">
    
    <tr>
        <td ><b><font size="-1"  style="display: block !important; padding: 20px 0 5px !important;">您可能也喜欢：</font></b></td>
    </tr>
    
            <tr>
                <td style="margin: 0 !important; padding: 0 !important; line-height: 20px !important;">
                    <img border="0" src="http://static.wumii.com/images/widget/widget_solidPoint.gif">
                    <a target="_blank" style="text-decoration: none !important;" href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fwww.aisharing.com%2Farchives%2F540&from=http%3A%2F%2Fwww.aisharing.com%2Farchives%2F554">
                        <font size="-1" color="#333333" style="line-height: 1.65em; font-size: 12px !important;">2012年游戏开发者大会（GDC 2012）预告</font>
                    </a>
                </td>
            </tr>
            <tr>
                <td style="margin: 0 !important; padding: 0 !important; line-height: 20px !important;">
                    <img border="0" src="http://static.wumii.com/images/widget/widget_solidPoint.gif">
                    <a target="_blank" style="text-decoration: none !important;" href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fwww.aisharing.com%2Farchives%2F462&from=http%3A%2F%2Fwww.aisharing.com%2Farchives%2F554">
                        <font size="-1" color="#333333" style="line-height: 1.65em; font-size: 12px !important;">可预测的运动系统设计（2）</font>
                    </a>
                </td>
            </tr>
            <tr>
                <td style="margin: 0 !important; padding: 0 !important; line-height: 20px !important;">
                    <img border="0" src="http://static.wumii.com/images/widget/widget_solidPoint.gif">
                    <a target="_blank" style="text-decoration: none !important;" href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fwww.aisharing.com%2Farchives%2F458&from=http%3A%2F%2Fwww.aisharing.com%2Farchives%2F554">
                        <font size="-1" color="#333333" style="line-height: 1.65em; font-size: 12px !important;">可预测的运动系统设计（1）</font>
                    </a>
                </td>
            </tr>
            <tr>
                <td style="margin: 0 !important; padding: 0 !important; line-height: 20px !important;">
                    <img border="0" src="http://static.wumii.com/images/widget/widget_solidPoint.gif">
                    <a target="_blank" style="text-decoration: none !important;" href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fwww.aisharing.com%2Farchives%2F47&from=http%3A%2F%2Fwww.aisharing.com%2Farchives%2F554">
                        <font size="-1" color="#333333" style="line-height: 1.65em; font-size: 12px !important;">基于分数系统(Scoring System)的AI设计(2)</font>
                    </a>
                </td>
            </tr>
            <tr>
                <td style="margin: 0 !important; padding: 0 !important; line-height: 20px !important;">
                    <img border="0" src="http://static.wumii.com/images/widget/widget_solidPoint.gif">
                    <a target="_blank" style="text-decoration: none !important;" href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fwww.aisharing.com%2Farchives%2F42&from=http%3A%2F%2Fwww.aisharing.com%2Farchives%2F554">
                        <font size="-1" color="#333333" style="line-height: 1.65em; font-size: 12px !important;">基于分数系统(Scoring System)的AI设计(1)</font>
                    </a>
                </td>
            </tr>
    
    <tr>
        <td  align="right">
            <a style="text-decoration: none !important;" href="http://www.wumii.com/widget/relatedItems" target="_blank" title="无觅相关文章插件">
                <font size="-1" color="#bbbbbb" style="display: block !important; font-family: arial !important; padding: 5px 0 !important; font-size: 12px !important; color: #bbb !important;">无觅</font>
            </a>
        </td>
    </tr>
</table>]]></content:encoded>
			<wfw:commentRss>http://www.aisharing.com/archives/554/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>2012年游戏开发者大会（GDC 2012）预告</title>
		<link>http://www.aisharing.com/archives/540</link>
		<comments>http://www.aisharing.com/archives/540#comments</comments>
		<pubDate>Tue, 21 Feb 2012 15:07:27 +0000</pubDate>
		<dc:creator>Finney</dc:creator>
				<category><![CDATA[GDC2012]]></category>
		<category><![CDATA[GDC]]></category>
		<category><![CDATA[next-gen AI]]></category>
		<category><![CDATA[人工智能]]></category>
		<category><![CDATA[游戏开发者大会]]></category>

		<guid isPermaLink="false">http://www.aisharing.com/?p=540</guid>
		<description><![CDATA[<a href="http://aisharing.com/wp/wp-content/uploads/2012/02/GDC2012.png"><img class="alignnone  wp-image-544" title="GDC2012" src="http://aisharing.com/wp/wp-content/uploads/2012/02/GDC2012.png" alt="GDC2012" width="330" height="143" /></a>

2012年游戏开发者大会（GDC 2012）将于2012年3月5日在美国旧金山举行，我有幸届时能够亲临现场，感受下全球游戏开发者的第一盛会。公司很厚道的帮我买的是All Access的票，这意味着，我可以没有任何限制的参加所有我感兴趣的会议，讲座等等，这里不得不赞一下。在GDC的官网上，可以查到所有的讲座会议日程表，除了一些必去的大型会议外（比如，Google开发者日和Microsoft开发者日），作为AI程序员，我的选择更偏向AI方面为主，因为平时工作还涉及一些3D方面的内容，所以3D技术讲座也是备选的内容之一。作为预告，我列出我初步的一个意向，回来后，在博客上会写些心得体会，与大家分享。

<span style="color: #993366;"><strong>Google开发者日（Google Developer Day I ）</strong></span>

介绍些Google的在游戏上的最新技术，包括可扩展的服务器，在浏览器上的高性能的代码和绘图，还有把平台游戏移植到网页上等等（原文：Get a peek at the latest technologies Google is developing for games. From scalable servers, to high-performance code and graphics in web browsers, to porting console games to the web, Google’s online technologies can help you better create, distribute, promote and monetize your games.）

<span style="color: #993366;"><strong>微软的开发者日(Microsoft Developer Day: Entertainment Reimagined)</strong></span>

会深入的介绍些Live，Windows8的东西，还有Xbox Live针对多个平台间的强劲的能力（原文：At this event, Microsoft introduces developers to the future of games and entertainment. With technology deep dives focusing on Xbox LIVE and Windows 8, learn about how you can build great experiences by capitalizing on Microsoft’s vision for connected entertainment. Join us, and seize the opportunity to be in the first wave of games and entertainment applications that unleash the power of Xbox LIVE services across Xbox 360, Windows 8, Windows Phone, and the web.)

<span style="color: #993366;"><strong>用Simplygon做成的高质量的3D资源来提高渲染的效率（Boosting Rendering Performance with Automated High Quality 3D Asset Optimization Using Simplygon）</strong></span>

应该是个广告类的讲座，不过这个产品倒是可以了解下，话说在GDC上做技术产品广告的厂商还真是不少啊

<span style="color: #993366;"><strong>游戏中避免相互碰撞和运动导航（Reciprocal Collision Avoidance and Navigation for Video Games）</strong></span>

<span style="color: #993366;"><strong>在战争机器（<strong>Gears of War 3）</strong>中用到的可以扩展到支持百万人的专用服务器（Dedicated Servers In Gears of War 3: Scaling to Millions of Players）</strong></span>

听上去很强大，去学习下

<span style="color: #993366;"><strong>在杀手5赦免（Hitman: Absolution）中的人群系统（Crowds in Hitman: Absolution）</strong></span>

就是如何处理很多人的情况，这个游戏没玩过，不过这个话题我比较感兴趣。

<span style="color: #993366;"><strong>把AAA级画质带到移动平台来（Bringing AAA Graphics to Mobile Platforms）</strong></span>

移动平台现在很火热，听听如何这这样的平台上做出顶级的游戏画面

<span style="color: #993366;"><strong>神秘海域3（<strong>Uncharted 3: Drake's Deception）</strong>的特效技术（Effects Techniques Used in Uncharted 3: Drake's Deception）</strong></span>

这个系列的画面一致是很不错的，值得一听

<span style="color: #993366;"><strong>用DX11做高级渲染（Advanced Procedural Rendering with DirectX 11）</strong></span>

<span style="color: #993366;"><strong>游戏中的人工智能圆桌会议（Artificial Intelligence in Computer Games Roundtable）</strong></span>

<span style="color: #993366;"><strong>通过模糊模式匹配由AI驱动的动态对话系统（AI-driven Dynamic Dialog through Fuzzy Pattern Matching. Empower Your Writers!）</strong></span>

听上去很炫的名字，应该就是介绍如何让NPC的对话更智能化，不错的讲座

总的来说，GDC上专门关于AI的东西并不是很多，因为游戏开发所涵盖的内容实在太大，以上是我一些初步的选择，到了那以后，再看情况，实时调整，希望此行能够顺利，对游戏界新的技术方向有更多的了解和学习。

————————————————————————
作者：Finney
Blog：AI分享站(<a href="http://www.aisharing.com/">http://www.aisharing.com/</a>)
Email：finneytang@gmail.com
本文欢迎转载和引用，请保留本说明并注明出处
————————————————————————]]></description>
			<content:encoded><![CDATA[<p><a href="http://aisharing.com/wp/wp-content/uploads/2012/02/GDC2012.png"><img class="alignnone  wp-image-544" title="GDC2012" src="http://aisharing.com/wp/wp-content/uploads/2012/02/GDC2012.png" alt="GDC2012" width="330" height="143" /></a></p>
<p>2012年游戏开发者大会（GDC 2012）将于2012年3月5日在美国旧金山举行，我有幸届时能够亲临现场，感受下全球游戏开发者的第一盛会。公司很厚道的帮我买的是All Access的票，这意味着，我可以没有任何限制的参加所有我感兴趣的会议，讲座等等，这里不得不赞一下。在GDC的官网上，可以查到所有的讲座会议日程表，除了一些必去的大型会议外（比如，Google开发者日和Microsoft开发者日），作为AI程序员，我的选择更偏向AI方面为主，因为平时工作还涉及一些3D方面的内容，所以3D技术讲座也是备选的内容之一。作为预告，我列出我初步的一个意向，回来后，在博客上会写些心得体会，与大家分享。</p>
<p><span style="color: #993366;"><strong>Google开发者日（Google Developer Day I ）</strong></span></p>
<p>介绍些Google的在游戏上的最新技术，包括可扩展的服务器，在浏览器上的高性能的代码和绘图，还有把平台游戏移植到网页上等等（原文：Get a peek at the latest technologies Google is developing for games. From scalable servers, to high-performance code and graphics in web browsers, to porting console games to the web, Google’s online technologies can help you better create, distribute, promote and monetize your games.）</p>
<p><span style="color: #993366;"><strong>微软的开发者日(Microsoft Developer Day: Entertainment Reimagined)</strong></span></p>
<p>会深入的介绍些Live，Windows8的东西，还有Xbox Live针对多个平台间的强劲的能力（原文：At this event, Microsoft introduces developers to the future of games and entertainment. With technology deep dives focusing on Xbox LIVE and Windows 8, learn about how you can build great experiences by capitalizing on Microsoft’s vision for connected entertainment. Join us, and seize the opportunity to be in the first wave of games and entertainment applications that unleash the power of Xbox LIVE services across Xbox 360, Windows 8, Windows Phone, and the web.)</p>
<p><span style="color: #993366;"><strong>用Simplygon做成的高质量的3D资源来提高渲染的效率（Boosting Rendering Performance with Automated High Quality 3D Asset Optimization Using Simplygon）</strong></span></p>
<p>应该是个广告类的讲座，不过这个产品倒是可以了解下，话说在GDC上做技术产品广告的厂商还真是不少啊</p>
<p><span style="color: #993366;"><strong>游戏中避免相互碰撞和运动导航（Reciprocal Collision Avoidance and Navigation for Video Games）</strong></span></p>
<p><span style="color: #993366;"><strong>在战争机器（<strong>Gears of War 3）</strong>中用到的可以扩展到支持百万人的专用服务器（Dedicated Servers In Gears of War 3: Scaling to Millions of Players）</strong></span></p>
<p>听上去很强大，去学习下</p>
<p><span style="color: #993366;"><strong>在杀手5赦免（Hitman: Absolution）中的人群系统（Crowds in Hitman: Absolution）</strong></span></p>
<p>就是如何处理很多人的情况，这个游戏没玩过，不过这个话题我比较感兴趣。</p>
<p><span style="color: #993366;"><strong>把AAA级画质带到移动平台来（Bringing AAA Graphics to Mobile Platforms）</strong></span></p>
<p>移动平台现在很火热，听听如何这这样的平台上做出顶级的游戏画面</p>
<p><span style="color: #993366;"><strong>神秘海域3（<strong>Uncharted 3: Drake&#8217;s Deception）</strong>的特效技术（Effects Techniques Used in Uncharted 3: Drake&#8217;s Deception）</strong></span></p>
<p>这个系列的画面一致是很不错的，值得一听</p>
<p><span style="color: #993366;"><strong>用DX11做高级渲染（Advanced Procedural Rendering with DirectX 11）</strong></span></p>
<p><span style="color: #993366;"><strong>游戏中的人工智能圆桌会议（Artificial Intelligence in Computer Games Roundtable）</strong></span></p>
<p><span style="color: #993366;"><strong>通过模糊模式匹配由AI驱动的动态对话系统（AI-driven Dynamic Dialog through Fuzzy Pattern Matching. Empower Your Writers!）</strong></span></p>
<p>听上去很炫的名字，应该就是介绍如何让NPC的对话更智能化，不错的讲座</p>
<p>总的来说，GDC上专门关于AI的东西并不是很多，因为游戏开发所涵盖的内容实在太大，以上是我一些初步的选择，到了那以后，再看情况，实时调整，希望此行能够顺利，对游戏界新的技术方向有更多的了解和学习。</p>
<p>————————————————————————<br />
作者：Finney<br />
Blog：AI分享站(<a href="http://www.aisharing.com/">http://www.aisharing.com/</a>)<br />
Email：finneytang@gmail.com<br />
本文欢迎转载和引用，请保留本说明并注明出处<br />
————————————————————————</p>
<hr /><h2>评论</h2><ul><li><a href="http://www.aisharing.com/archives/540">2012 年 2 月 22 日</a>, vincent 评论到: 赞啊，每年都可以出国哈：）</li><li><a href="http://www.aisharing.com/archives/540">2012 年 2 月 22 日</a>, <a href='http://www.aisharing.com' rel='external nofollow' class='url'>Finney</a> 评论到: 呵呵，这种大会去参加一下，相当不错，很期待</li><li><a href="http://www.aisharing.com/archives/540">2012 年 2 月 23 日</a>, Lucidniht 评论到: 无比羡慕中啊。
希望有一天我也能亲临GDC</li><li><a href="http://www.aisharing.com/archives/540">2012 年 2 月 28 日</a>, <a href='http://www.3rbang.com' rel='external nofollow' class='url'>代码三人帮</a> 评论到: ai程序员是指专门做游戏  引擎的？</li><li><a href="http://www.aisharing.com/archives/540">2012 年 2 月 28 日</a>, <a href='http://www.aisharing.com' rel='external nofollow' class='url'>Finney</a> 评论到: AI程序员一般不太涉及底层的引擎，主要负责高层的游戏逻辑，这是从工作的侧重点上来说，不过对于游戏程序员来说，游戏引擎的结构都要有所了解。</li><li><a href="http://www.aisharing.com/archives/540">2012 年 3 月 2 日</a>, haha 评论到: Finney搞个twitter到时直播吧。</li><li><a href="http://www.aisharing.com/archives/540">2012 年 4 月 21 日</a>, trout 评论到: 羡慕，Ubi这么好？</li></ul><a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/2.5/cn/"><img alt="知识共享许可协议" style="border-width:0" src="http://i.creativecommons.org/l/by-nc-nd/2.5/cn/88x31.png" /></a><br />本博客作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/2.5/cn/">知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议</a>进行许可。<hr /><a href="http://www.aisharing.com/donation" target="_blank"><img src="http://aisharing.com/wp/wp-content/uploads/2011/08/donate.png" alt="捐赠本站" /></a> ]]></content:encoded>
			<wfw:commentRss>http://www.aisharing.com/archives/540/feed</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>用800行代码做个行为树（Behavior Tree）的库（3）</title>
		<link>http://www.aisharing.com/archives/530</link>
		<comments>http://www.aisharing.com/archives/530#comments</comments>
		<pubDate>Tue, 31 Jan 2012 10:36:52 +0000</pubDate>
		<dc:creator>Finney</dc:creator>
				<category><![CDATA[代码分享]]></category>
		<category><![CDATA[Behavior]]></category>
		<category><![CDATA[Behavior Tree]]></category>
		<category><![CDATA[代码]]></category>
		<category><![CDATA[节点]]></category>
		<category><![CDATA[行为]]></category>
		<category><![CDATA[行为树]]></category>

		<guid isPermaLink="false">http://www.aisharing.com/?p=530</guid>
		<description><![CDATA[<blockquote><a href="http://www.aisharing.com/archives/517" target="_blank">第一部分</a> <a href="http://www.aisharing.com/archives/522" target="_blank">第二部分</a></blockquote>
行为树最后一个要讲的地方，是关于前提（Precondition），在第一部分里，我略微提到了一下，这次我们来仔细看看，再来看看关于前提的纯虚基类的定义：
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> BevNodePrecondition</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">public</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span>     <span style="color: #0000ff;">virtual</span> <span style="color: #0000ff;">bool</span> ExternalCondition(<span style="color: #0000ff;">const</span> BevNodeInputParam&#38; input) <span style="color: #0000ff;">const</span> = 0;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span> };</pre>
</div>
</div>
每一个前提类，都需要实现这个判断的虚函数。我在<a href="http://www.aisharing.com/archives/359" target="_blank">《用类来表示逻辑运算–关于行为树前提的一种实现方式》</a>提到，我们可以用类来表示逻辑运算，这样的好处是可以做到模块化，同样的判断条件可以复用，所以在库中，我也实现了这种逻辑的表达方式，定义了基本的逻辑运算类
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> BevNodePreconditionTRUE{};</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> <span style="color: #0000ff;">class</span> BevNodePreconditionFALSE{};</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">class</span> BevNodePreconditionNOT{};</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span> <span style="color: #0000ff;">class</span> BevNodePreconditionAND{};</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span> <span style="color: #0000ff;">class</span> BevNodePreconditionOR{};</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span> <span style="color: #0000ff;">class</span> BevNodePreconditionXOR{};</pre>
</div>
</div>
从这些类的名字应该就可以明显的看出这些类的含义了，和逻辑操作符一样，有些类的构造函数需要两个参数，以此来表示二元的逻辑运算（AND，OR，XOR），有些只需要一个参数，以此来表示一元的逻辑运算（NOT）。前提类被用来附在行为树的节点上（每一个节点都可以附加），默认情况下，节点上是没有前提类的，也就是不存在“外在前提”，而只有“内在前提”，这和附了一个BevNodePreconditionTRUE（永远返回True）的“外在前提”的节点是等价的。

好了，行为树库的内容基本就是这些了。接下去我们来看看例子程序，介绍如何用库来创建行为树，例子的代码在BevTreeTest这个工程中，编译后可直接运行，这个例子分别演示了三个行为树，从简单到复杂，单击鼠标可以在这三个例子间切换。这个程序实现了这样一个功能，“在场景地图上，定时会产生一个目标点，智能体就会根据行为树的定义，用不同的行为模式移动到目标点”。

在这个程序中，我为智能体一共定义了4个行为：
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> NOD_Idle{};      <span style="color: #008000;">//空闲，表现是颜色不停变化</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> <span style="color: #0000ff;">class</span> NOD_Breathe{};   <span style="color: #008000;">//呼吸，表现是大小规律性变化</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">class</span> NOD_MoveTo{};    <span style="color: #008000;">//移动，平移到某目标点</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span> <span style="color: #0000ff;">class</span> NOD_FaceTo{};    <span style="color: #008000;">//转向，转向到某方向</span></pre>
</div>
</div>
再定义了2个“外在前提”：
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> CON_HasReachedTarget{};    <span style="color: #008000;">//是否到达目标点</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> <span style="color: #0000ff;">class</span> CON_HasFacedToTarget{};    <span style="color: #008000;">//是否朝向目标点</span></pre>
</div>
</div>
我就用第一个例子来说，第一例子的行为树图如下：

<a href="http://aisharing.com/wp/wp-content/uploads/2012/01/BevTreeTest1.png"><img style="display: inline; border-width: 0px;" title="BevTreeTest1" src="http://aisharing.com/wp/wp-content/uploads/2012/01/BevTreeTest1_thumb.png" alt="BevTreeTest1" width="378" height="208" border="0" /></a>

这是一个很简单的行为树，根节点是一个带优先级的选择节点，所以MoveTo比Idle的优先级高，MoveTo带有一个“外在前提”，“当没有到达目标点”时，会选在MoveTo的行为，反之，则选Idle的行为。

在代码中，可以这样来定义这棵行为树
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> BevNode&#38; ret =</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span>     BevNodeFactory::oCreatePrioritySelectorNode(NULL, <span style="color: #006080;">"root"</span>);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span>         BevNodeFactory::oCreateTeminalNode&#60;NOD_MoveTo&#62;(&#38;ret, <span style="color: #006080;">"move to"</span>)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span>             .SetNodePrecondition(<span style="color: #0000ff;">new</span> BevNodePreconditionNOT(<span style="color: #0000ff;">new</span> CON_HasReachedTarget()));</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span>         BevNodeFactory::oCreateTeminalNode&#60;NOD_Idle&#62;(&#38;ret, <span style="color: #006080;">"idle"</span>)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span>             .SetNodePrecondition(<span style="color: #0000ff;">new</span> BevNodePreconditionTRUE());</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum7" style="color: #606060;"> 7:</span> m_BevTreeRoot = &#38;ret;</pre>
</div>
</div>
我在库中定义了一些工厂方法，帮助创建相关的节点。值得注意的是，我在这里演示了用类表示逻辑的用法。我在定义行为树的时候，会用一些格式上的缩进，来表示相应的父子结构，这仅仅是为了视觉上比较明了。当然，以后可以改进行为树的定义接口，更可以用数据文件来定义行为树。

这样定义完毕后，我们就可以用行为树来决策我们的行为了，代码相当简单
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> BevNodeInputParam input(&#38;m_BevTreeInputData);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> BevNodeOutputParam output(&#38;m_BevTreeOutputdata);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">if</span>(m_BevTreeRoot-&#62;Evaluate(input))</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span>     m_BevTreeRoot-&#62;Tick(input, output);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span> }</pre>
</div>
</div>
在例子中，我尽量把行为树中要输出的变量写到BevNodeOutputParam结构中（而不是直接修改智能体的信息），这样做的好处是可以让行为树的输入和输出的接口相当清晰，做成黑盒，可以参考我在<a href="http://www.aisharing.com/archives/280" target="_blank">这里</a>的讨论。

第二个例子演示了并行节点的用法，第三个例子演示了序列节点的用法，就不多说了，大家可以自行看代码。

所有的代码可以通过以下方式获得：

<strong>下载地址：</strong>

<a href="http://tsiu.googlecode.com/files/tsiublogver.zip" target="_blank">GoogleCode下载点</a>（exe文件夹中已包含可执行文件）

也可用svn通过以下地址来得：

<strong><em>http</em></strong>://tsiu.googlecode.com/svn/branches/blogver/

<strong>编译方法：</strong>

用VS2005以上打开，选择Debug NoDx或者Release NoDx，编译后，运行BevTreeTest.

<strong>相关代码：</strong>
<blockquote>TAI_BevTree.h

TAI_BevTree.cpp</blockquote>
<strong>关于TsiU</strong>

TsiU是我一直在维护的一个自己用的小型的框架，我平时做的一些AI的sample，或者一些工具，都会基于这个框架，TsiU有一些基本的UI控件库，网络模块库，GDI绘图模块，D3D绘图模块等等，可以快速的做成一个小型的示例程序，很方便（具体可参考SampleAppｓ里的例子程序），并且整个架构是用Object的方式来组织，非常容易理解和扩展。整个框架很轻量化，基本就是做了一些底层的基本的功能，这样我在平时做东西的时候，就不需要重新写底层了，把精力都放在高层的实现了。以后分享代码都会基于这个框架，大家也可以通过svn来随时update到我最新的改动。下图就是TsiU里的几个工程介绍，代码不多，大家想看的也可以自己看一下:)

<img src="http://aisharing.com/wp/wp-content/uploads/2011/07/wKgKCk4gA6YAAAAAAABeOQSvCeE741.jpg" alt="" />

————————————————————————
作者：Finney
Blog：AI分享站(<a href="http://www.aisharing.com/">http://www.aisharing.com/</a>)
Email：finneytang@gmail.com
本文欢迎转载和引用，请保留本说明并注明出处
————————————————————————]]></description>
			<content:encoded><![CDATA[<blockquote><p><a href="http://www.aisharing.com/archives/517" target="_blank">第一部分</a> <a href="http://www.aisharing.com/archives/522" target="_blank">第二部分</a></p></blockquote>
<p>行为树最后一个要讲的地方，是关于前提（Precondition），在第一部分里，我略微提到了一下，这次我们来仔细看看，再来看看关于前提的纯虚基类的定义：</p>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> BevNodePrecondition</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">public</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span>     <span style="color: #0000ff;">virtual</span> <span style="color: #0000ff;">bool</span> ExternalCondition(<span style="color: #0000ff;">const</span> BevNodeInputParam&amp; input) <span style="color: #0000ff;">const</span> = 0;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span> };</pre>
</div>
</div>
<p>每一个前提类，都需要实现这个判断的虚函数。我在<a href="http://www.aisharing.com/archives/359" target="_blank">《用类来表示逻辑运算–关于行为树前提的一种实现方式》</a>提到，我们可以用类来表示逻辑运算，这样的好处是可以做到模块化，同样的判断条件可以复用，所以在库中，我也实现了这种逻辑的表达方式，定义了基本的逻辑运算类</p>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> BevNodePreconditionTRUE{};</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> <span style="color: #0000ff;">class</span> BevNodePreconditionFALSE{};</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">class</span> BevNodePreconditionNOT{};</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span> <span style="color: #0000ff;">class</span> BevNodePreconditionAND{};</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span> <span style="color: #0000ff;">class</span> BevNodePreconditionOR{};</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span> <span style="color: #0000ff;">class</span> BevNodePreconditionXOR{};</pre>
</div>
</div>
<p>从这些类的名字应该就可以明显的看出这些类的含义了，和逻辑操作符一样，有些类的构造函数需要两个参数，以此来表示二元的逻辑运算（AND，OR，XOR），有些只需要一个参数，以此来表示一元的逻辑运算（NOT）。前提类被用来附在行为树的节点上（每一个节点都可以附加），默认情况下，节点上是没有前提类的，也就是不存在“外在前提”，而只有“内在前提”，这和附了一个BevNodePreconditionTRUE（永远返回True）的“外在前提”的节点是等价的。</p>
<p>好了，行为树库的内容基本就是这些了。接下去我们来看看例子程序，介绍如何用库来创建行为树，例子的代码在BevTreeTest这个工程中，编译后可直接运行，这个例子分别演示了三个行为树，从简单到复杂，单击鼠标可以在这三个例子间切换。这个程序实现了这样一个功能，“在场景地图上，定时会产生一个目标点，智能体就会根据行为树的定义，用不同的行为模式移动到目标点”。</p>
<p>在这个程序中，我为智能体一共定义了4个行为：</p>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> NOD_Idle{};      <span style="color: #008000;">//空闲，表现是颜色不停变化</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> <span style="color: #0000ff;">class</span> NOD_Breathe{};   <span style="color: #008000;">//呼吸，表现是大小规律性变化</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">class</span> NOD_MoveTo{};    <span style="color: #008000;">//移动，平移到某目标点</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span> <span style="color: #0000ff;">class</span> NOD_FaceTo{};    <span style="color: #008000;">//转向，转向到某方向</span></pre>
</div>
</div>
<p>再定义了2个“外在前提”：</p>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> CON_HasReachedTarget{};    <span style="color: #008000;">//是否到达目标点</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> <span style="color: #0000ff;">class</span> CON_HasFacedToTarget{};    <span style="color: #008000;">//是否朝向目标点</span></pre>
</div>
</div>
<p>我就用第一个例子来说，第一例子的行为树图如下：</p>
<p><a href="http://aisharing.com/wp/wp-content/uploads/2012/01/BevTreeTest1.png"><img style="display: inline; border-width: 0px;" title="BevTreeTest1" src="http://aisharing.com/wp/wp-content/uploads/2012/01/BevTreeTest1_thumb.png" alt="BevTreeTest1" width="378" height="208" border="0" /></a></p>
<p>这是一个很简单的行为树，根节点是一个带优先级的选择节点，所以MoveTo比Idle的优先级高，MoveTo带有一个“外在前提”，“当没有到达目标点”时，会选在MoveTo的行为，反之，则选Idle的行为。</p>
<p>在代码中，可以这样来定义这棵行为树</p>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> BevNode&amp; ret =</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span>     BevNodeFactory::oCreatePrioritySelectorNode(NULL, <span style="color: #006080;">"root"</span>);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span>         BevNodeFactory::oCreateTeminalNode&lt;NOD_MoveTo&gt;(&amp;ret, <span style="color: #006080;">"move to"</span>)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span>             .SetNodePrecondition(<span style="color: #0000ff;">new</span> BevNodePreconditionNOT(<span style="color: #0000ff;">new</span> CON_HasReachedTarget()));</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span>         BevNodeFactory::oCreateTeminalNode&lt;NOD_Idle&gt;(&amp;ret, <span style="color: #006080;">"idle"</span>)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span>             .SetNodePrecondition(<span style="color: #0000ff;">new</span> BevNodePreconditionTRUE());</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum7" style="color: #606060;"> 7:</span> m_BevTreeRoot = &amp;ret;</pre>
</div>
</div>
<p>我在库中定义了一些工厂方法，帮助创建相关的节点。值得注意的是，我在这里演示了用类表示逻辑的用法。我在定义行为树的时候，会用一些格式上的缩进，来表示相应的父子结构，这仅仅是为了视觉上比较明了。当然，以后可以改进行为树的定义接口，更可以用数据文件来定义行为树。</p>
<p>这样定义完毕后，我们就可以用行为树来决策我们的行为了，代码相当简单</p>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> BevNodeInputParam input(&amp;m_BevTreeInputData);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> BevNodeOutputParam output(&amp;m_BevTreeOutputdata);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">if</span>(m_BevTreeRoot-&gt;Evaluate(input))</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span>     m_BevTreeRoot-&gt;Tick(input, output);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span> }</pre>
</div>
</div>
<p>在例子中，我尽量把行为树中要输出的变量写到BevNodeOutputParam结构中（而不是直接修改智能体的信息），这样做的好处是可以让行为树的输入和输出的接口相当清晰，做成黑盒，可以参考我在<a href="http://www.aisharing.com/archives/280" target="_blank">这里</a>的讨论。</p>
<p>第二个例子演示了并行节点的用法，第三个例子演示了序列节点的用法，就不多说了，大家可以自行看代码。</p>
<p>所有的代码可以通过以下方式获得：</p>
<p><strong>下载地址：</strong></p>
<p><a href="http://tsiu.googlecode.com/files/tsiublogver.zip" target="_blank">GoogleCode下载点</a>（exe文件夹中已包含可执行文件）</p>
<p>也可用svn通过以下地址来得：</p>
<p><strong><em>http</em></strong>://tsiu.googlecode.com/svn/branches/blogver/</p>
<p><strong>编译方法：</strong></p>
<p>用VS2005以上打开，选择Debug NoDx或者Release NoDx，编译后，运行BevTreeTest.</p>
<p><strong>相关代码：</strong></p>
<blockquote><p>TAI_BevTree.h</p>
<p>TAI_BevTree.cpp</p></blockquote>
<p><strong>关于TsiU</strong></p>
<p>TsiU是我一直在维护的一个自己用的小型的框架，我平时做的一些AI的sample，或者一些工具，都会基于这个框架，TsiU有一些基本的UI控件库，网络模块库，GDI绘图模块，D3D绘图模块等等，可以快速的做成一个小型的示例程序，很方便（具体可参考SampleAppｓ里的例子程序），并且整个架构是用Object的方式来组织，非常容易理解和扩展。整个框架很轻量化，基本就是做了一些底层的基本的功能，这样我在平时做东西的时候，就不需要重新写底层了，把精力都放在高层的实现了。以后分享代码都会基于这个框架，大家也可以通过svn来随时update到我最新的改动。下图就是TsiU里的几个工程介绍，代码不多，大家想看的也可以自己看一下:)</p>
<p><img src="http://aisharing.com/wp/wp-content/uploads/2011/07/wKgKCk4gA6YAAAAAAABeOQSvCeE741.jpg" alt="" /></p>
<p>————————————————————————<br />
作者：Finney<br />
Blog：AI分享站(<a href="http://www.aisharing.com/">http://www.aisharing.com/</a>)<br />
Email：finneytang@gmail.com<br />
本文欢迎转载和引用，请保留本说明并注明出处<br />
————————————————————————</p>
<hr /><h2>评论</h2><ul><li><a href="http://www.aisharing.com/archives/530">2012 年 2 月 3 日</a>, donglee 评论到: 赞！谢谢分享</li><li><a href="http://www.aisharing.com/archives/530">2012 年 2 月 3 日</a>, dulm 评论到: 有讨论AI的QQ群么~</li><li><a href="http://www.aisharing.com/archives/530">2012 年 2 月 3 日</a>, <a href='http://www.aisharing.com' rel='external nofollow' class='url'>Finney</a> 评论到: 鉴于很少用qq，所以目前暂时没有，可以发信给我，也可以在这里留言。</li><li><a href="http://www.aisharing.com/archives/530">2012 年 2 月 3 日</a>, 小N_大进行曲 评论到: 三篇都看完了，非常感谢您提供的源代码，不过由于现在电脑里没有vs，可能要过些日子才能看，由于平时是用unity3D开发的，C++还得适应适应。曾经看过http://angryant.com/上面的行为树，发现在一些节点的用法上还是有些不同的，目前还没有想清楚为什么不同，比如说他没有内部前提的概念，我继续研究研究。

目前有几个问题：

第一个是“二”中的表格中，对优先级的选择节点的Evaluate描述，文章说的是从头检查，为什么不是：“如果当前节点为false，则检查当前节点下一个节点。”

第二个问题，有关于内部前提，这个东西是否应该看作“action节点的一部分”。

第三个问题，这里面没有“Decorator”的概念吗？</li><li><a href="http://www.aisharing.com/archives/530">2012 年 2 月 4 日</a>, <a href='http://www.aisharing.com' rel='external nofollow' class='url'>Finney</a> 评论到: 每个人对行为树的实现会有些不同，用法也就不同了，不过基本概念是差不多的。优先级节点的优先级是从头开始排列的呀，也就是说第一个子节点的优先级最高，所以每次都必须从头检查，要不就体现不出优先级了。内部前提就是作为这个行为节点的固有前提，是它的一部分，也就是不管这个节点被复用在行为树的什么地方，都存在这个前提。Decorator只是一种设计模式，其实像循环节点，就可以看成是使用了这种设计模式。</li><li><a href="http://www.aisharing.com/archives/530">2012 年 2 月 27 日</a>, 小N_大进行曲 评论到: 您好，刚在aiDev上看了一偏文章。
http://aigamedev.com/open/tutorial/second-generation-bt/
但是由于英语水平实在是太悲剧了……隐约明白他的意思好像是，更新了一些bt的实现方式，比如事件驱动什么的。
如果博主也看了这个，可否简单讲一讲他的意思呢。
谢谢。</li><li><a href="http://www.aisharing.com/archives/530">2012 年 2 月 27 日</a>, <a href='http://www.aisharing.com' rel='external nofollow' class='url'>Finney</a> 评论到: 我也看到了这篇文章，他是想解决这样一个问题，当行为树的分支太多后，每次都要遍历一遍整个树寻找可以运行的节点，效率比较低，所以做了些改进，具体的内容，我会写篇博文来讨论，最近在准备去GDC的资料，还没时间研究。有好的想法，可以留言，或者发信给我。</li></ul><a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/2.5/cn/"><img alt="知识共享许可协议" style="border-width:0" src="http://i.creativecommons.org/l/by-nc-nd/2.5/cn/88x31.png" /></a><br />本博客作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/2.5/cn/">知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议</a>进行许可。<hr /><a href="http://www.aisharing.com/donation" target="_blank"><img src="http://aisharing.com/wp/wp-content/uploads/2011/08/donate.png" alt="捐赠本站" /></a> ]]></content:encoded>
			<wfw:commentRss>http://www.aisharing.com/archives/530/feed</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>用800行代码做个行为树（Behavior Tree）的库（2）</title>
		<link>http://www.aisharing.com/archives/522</link>
		<comments>http://www.aisharing.com/archives/522#comments</comments>
		<pubDate>Sun, 29 Jan 2012 05:59:38 +0000</pubDate>
		<dc:creator>Finney</dc:creator>
				<category><![CDATA[代码分享]]></category>
		<category><![CDATA[Behavior]]></category>
		<category><![CDATA[Behavior Tree]]></category>
		<category><![CDATA[代码]]></category>
		<category><![CDATA[节点]]></category>
		<category><![CDATA[行为]]></category>
		<category><![CDATA[行为树]]></category>

		<guid isPermaLink="false">http://www.aisharing.com/?p=522</guid>
		<description><![CDATA[<blockquote><a href="http://www.aisharing.com/archives/517" target="_blank">第一部分</a></blockquote>
上一次说到了节点的基类，它描述了在行为树上一个节点的基本结构。我们知道，在行为树上有两大类的节点，一种我称之为“控制节点”，像“选择节点”，“并行节点”，“序列节点”都属于此类，这类节点负责行为树逻辑的控制，是和具体的游戏逻辑无关的，属于行为树库的一部分，并且这类节点一般不会作为叶节点。还有一类称为“行为节点”，也就是行为树上挂载的具体行为，是和游戏逻辑相关的，不属于行为树库的一部分，需要自己去继承和实现，这类节点一般都作为叶节点出现。

先来看看“行为节点”的代码，我先从节点的基类继承了一个所有“行为节点”的基类
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> BevNodeTerminal : <span style="color: #0000ff;">public</span> BevNode</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {}</pre>
</div>
</div>
在它的Tick方法中，我做了一个简单的状态机（可以自行看代码），负责处理进入行为（Enter），更新行为（Execute），退出行为（Exit），所有的行为节点应该继承自BevNodeTerminal类，并且重写这些虚函数，在进入和退出行为里，可以做一个初始化和清理的工作：
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> BevNodeTerminal : <span style="color: #0000ff;">public</span> BevNode</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">protected</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span>     <span style="color: #0000ff;">virtual</span> <span style="color: #0000ff;">void</span>                _DoEnter(<span style="color: #0000ff;">const</span> BevNodeInputParam&#38; input)                                {}</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span>     <span style="color: #0000ff;">virtual</span> BevRunningStatus    _DoExecute(<span style="color: #0000ff;">const</span> BevNodeInputParam&#38; input, BevNodeOutputParam&#38; output)  { <span style="color: #0000ff;">return</span> k_BRS_Finish;}</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span>     <span style="color: #0000ff;">virtual</span> <span style="color: #0000ff;">void</span>                _DoExit(<span style="color: #0000ff;">const</span> BevNodeInputParam&#38; input, BevRunningStatus _ui_ExitID)    {}</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum7" style="color: #606060;"> 7:</span> }</pre>
</div>
</div>
值得注意的是，在Tick方法中，它有一个返回值，表示当前节点是否处理完毕，在库中，我定义了一个enum来表示节点的运行状态：
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">enum</span> BevRunningStatus</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span>     k_BRS_Executing                 = 0,</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span>     k_BRS_Finish                    = 1,</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span>     ...</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span> };</pre>
</div>
</div>
当返回k_BRS_Finish的时候，就表示当前节点已经处理完毕了，如果再次进入该节点，就认为是重新进入了。用上面描述的那个状态机的来说的话就是，如果是重新进入，会先调用_DoEnter方法，然后调用_DoExecute方法，如果_DoExecute返回正在运行（k_BRS_Executing），那么以后再进入这个节点就会直接调用_DoExectue，如果返回已经结束（k_BRS_Finish），则会调用_DoExit，以后再进入这个节点就会重新调用_DoEnter方法了。

对于控制节点来说，它的运行状态和子节点的运行状态是息息相关的，比如，选择节点的运行状态，就是它当前选择的这个节点的运行状态，并且，有时控制节点的控制逻辑也和子节点的运行状态有关，比如序列节点，当它前一个子节点运行结束，序列节点就会自动的切换到下一个子节点运行。所以在实现具体的行为类时，我们应该要正确的返回节点的运行状态。在例子程序中，我做的一个“空闲”（idle）的行为节点，就能很好的说明问题：
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> NOD_Idle : <span style="color: #0000ff;">public</span> BevNodeTerminal</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">public</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span>     NOD_Idle(BevNode* _o_ParentNode)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span>         :BevNodeTerminal(_o_ParentNode)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span>     {}</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum7" style="color: #606060;"> 7:</span> <span style="color: #0000ff;">protected</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum8" style="color: #606060;"> 8:</span>     <span style="color: #0000ff;">virtual</span> <span style="color: #0000ff;">void</span> _DoEnter(<span style="color: #0000ff;">const</span> BevNodeInputParam&#38; input)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum9" style="color: #606060;"> 9:</span>     {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum10" style="color: #606060;"> 10:</span>         m_WaitingTime = 0.5f;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum11" style="color: #606060;"> 11:</span>     }</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum12" style="color: #606060;"> 12:</span>     <span style="color: #0000ff;">virtual</span> BevRunningStatus _DoExecute(<span style="color: #0000ff;">const</span> BevNodeInputParam&#38; input, BevNodeOutputParam&#38; output)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum13" style="color: #606060;"> 13:</span>     {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum14" style="color: #606060;"> 14:</span>         <span style="color: #0000ff;">const</span> BevInputData&#38; inputData = input.GetRealDataType&#60;BevInputData&#62;();</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum15" style="color: #606060;"> 15:</span>         BevOutputData&#38; outputData = output.GetRealDataType&#60;BevOutputData&#62;();</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum16" style="color: #606060;"> 16:</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum17" style="color: #606060;"> 17:</span>         f32 timeStep = inputData.m_TimeStep;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum18" style="color: #606060;"> 18:</span>         m_WaitingTime -= timeStep;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum19" style="color: #606060;"> 19:</span>         <span style="color: #0000ff;">if</span>(m_WaitingTime &#60; 0)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum20" style="color: #606060;"> 20:</span>         {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum21" style="color: #606060;"> 21:</span>             outputData.m_BodyColor = D_Color(rand() % 256, rand() % 256, rand() % 256);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum22" style="color: #606060;"> 22:</span>             <span style="color: #0000ff;">return</span> k_BRS_Finish;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum23" style="color: #606060;"> 23:</span>         }</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum24" style="color: #606060;"> 24:</span>         <span style="color: #0000ff;">return</span> k_BRS_Executing;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum25" style="color: #606060;"> 25:</span>     }</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum26" style="color: #606060;"> 26:</span> <span style="color: #0000ff;">private</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum27" style="color: #606060;"> 27:</span>     <span style="color: #0000ff;">float</span> m_WaitingTime;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum28" style="color: #606060;"> 28:</span> };</pre>
</div>
</div>
这段代码中的某些内容不明白也没有关系，我们主要关注的是关于节点运行状态的部分。这个Idle行为做了一件这样的事，就是不停的变换自己的颜色，间隔是0.5秒，当时间一到，就会返回运行结束（k_BRS_Finish），并输出当前的颜色，当时间还没到，则返回运行中（k_BRS_Executing），并且维持当前颜色。可以看到，我们用运行状态控制了计时器的重置，选择在_DoEnter方法中重置了计时器，当然，更合理的做法是在时间一到的时候，就重置计时器，并且永远返回运行中，不过这个例子里，我主要就是想用来演示运行状态，和_DoEnter的相关用法。

接下去再来看看控制节点，我一共写了5种控制节点，带优先级的选择节点（BevNodePrioritySelector），不带优先级的选择节点（BevNodeNonePrioritySelector），序列节点（BevNodeSequence），并行节点（BevNodeParallel），循环节点（BevNodeLoop），这些节点的进入条件和选择逻辑都是按照在行为树中改节点的定义来做的，我想用一张表格来说明：
<table width="646" border="1" cellspacing="0" cellpadding="1">
<tbody>
<tr>
<td valign="top" width="150"></td>
<td valign="top" width="227">测试（Evaluate）</td>
<td valign="top" width="267">更新（Tick）</td>
</tr>
<tr>
<td valign="top" width="150">带优先级的选择节点（BevNodePrioritySelector）</td>
<td valign="top" width="227">从第一个子节点开始依次遍历所有的子节点，调用其Evaluate方法，当发现存在可以运行的子节点时，记录子节点索引，停止遍历，返回True。</td>
<td valign="top" width="267">调用可以运行的子节点的Tick方法，用它所返回的运行状态作为自身的运行状态返回</td>
</tr>
<tr>
<td valign="top" width="150">不带优先级的选择节点（BevNodeNonePrioritySelector）</td>
<td valign="top" width="227">先调用上一个运行的子节点（若存在）的Evaluate方法，如果可以运行，则继续运保存该节点的索引，返回True，如果不能运行，则重新选择（同带优先级的选择节点的选择方式）</td>
<td valign="top" width="267">调用可以运行的子节点的Tick方法，用它所返回的运行状态作为自身的运行状态返回</td>
</tr>
<tr>
<td valign="top" width="150">序列节点（BevNodeSequence）</td>
<td valign="top" width="227">若是从头开始的，则调用第一个子节点的Evaluate方法，将其返回值作为自身的返回值返回。否则，调用当前运行节点的Evaluate方法，将其返回值作为自身的返回值返回。</td>
<td valign="top" width="267">调用可以运行的子节点的Tick方法，若返回运行结束，则将下一个子节点作为当前运行节点，若当前已是最后一个子节点，表示该序列已经运行结束，则自身返回运行结束。若子节点返回运行中，则用它所返回的运行状态作为自身的运行状态返回</td>
</tr>
<tr>
<td valign="top" width="150">并行节点（BevNodeParallel）</td>
<td valign="top" width="227">依次调用所有的子节点的Evaluate方法，若所有的子节点都返回True，则自身也返回True，否则，返回False</td>
<td valign="top" width="267">调用所有子节点的Tick方法，若并行节点是“或者”的关系，则只要有一个子节点返回运行结束，那自身就返回运行结束。若并行节点是“并且”的关系，则只有所有的子节点返回结束，自身才返回运行结束</td>
</tr>
<tr>
<td valign="top" width="150">循环节点（BevNodeLoop）</td>
<td valign="top" width="227">预设的循环次数到了就返回False，否则，只调用第一个子节点的Evaluate方法，用它所返回的值作为自身的值返回</td>
<td valign="top" width="267">只调用第一个节点的Tick方法，若返回运行结束，则看是否需要重复运行，若循环次数没到，则自身返回运行中，若循环次数已到，则返回运行结束</td>
</tr>
</tbody>
</table>
可能看表格内的描述会感觉有点拗口，可以结合代码一起看，会理解的更好。特别要提一点的是，在某些控制节点的Evaluate方法中，我会修改和记录可以运行的节点索引，当调用Tick的时候，就可以用这个索引来找到可以运行的节点了。这种模式和我以前提到的行为树更新模式有点不太一样，不过本质上是相同的。

（待续…）

————————————————————————
作者：Finney
Blog：AI分享站(<a href="http://www.aisharing.com/">http://www.aisharing.com/</a>)
Email：finneytang@gmail.com
本文欢迎转载和引用，请保留本说明并注明出处
————————————————————————]]></description>
			<content:encoded><![CDATA[<blockquote><p><a href="http://www.aisharing.com/archives/517" target="_blank">第一部分</a></p></blockquote>
<p>上一次说到了节点的基类，它描述了在行为树上一个节点的基本结构。我们知道，在行为树上有两大类的节点，一种我称之为“控制节点”，像“选择节点”，“并行节点”，“序列节点”都属于此类，这类节点负责行为树逻辑的控制，是和具体的游戏逻辑无关的，属于行为树库的一部分，并且这类节点一般不会作为叶节点。还有一类称为“行为节点”，也就是行为树上挂载的具体行为，是和游戏逻辑相关的，不属于行为树库的一部分，需要自己去继承和实现，这类节点一般都作为叶节点出现。</p>
<p>先来看看“行为节点”的代码，我先从节点的基类继承了一个所有“行为节点”的基类</p>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> BevNodeTerminal : <span style="color: #0000ff;">public</span> BevNode</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {}</pre>
</div>
</div>
<p>在它的Tick方法中，我做了一个简单的状态机（可以自行看代码），负责处理进入行为（Enter），更新行为（Execute），退出行为（Exit），所有的行为节点应该继承自BevNodeTerminal类，并且重写这些虚函数，在进入和退出行为里，可以做一个初始化和清理的工作：</p>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> BevNodeTerminal : <span style="color: #0000ff;">public</span> BevNode</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">protected</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span>     <span style="color: #0000ff;">virtual</span> <span style="color: #0000ff;">void</span>                _DoEnter(<span style="color: #0000ff;">const</span> BevNodeInputParam&amp; input)                                {}</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span>     <span style="color: #0000ff;">virtual</span> BevRunningStatus    _DoExecute(<span style="color: #0000ff;">const</span> BevNodeInputParam&amp; input, BevNodeOutputParam&amp; output)  { <span style="color: #0000ff;">return</span> k_BRS_Finish;}</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span>     <span style="color: #0000ff;">virtual</span> <span style="color: #0000ff;">void</span>                _DoExit(<span style="color: #0000ff;">const</span> BevNodeInputParam&amp; input, BevRunningStatus _ui_ExitID)    {}</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum7" style="color: #606060;"> 7:</span> }</pre>
</div>
</div>
<p>值得注意的是，在Tick方法中，它有一个返回值，表示当前节点是否处理完毕，在库中，我定义了一个enum来表示节点的运行状态：</p>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">enum</span> BevRunningStatus</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span>     k_BRS_Executing                 = 0,</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span>     k_BRS_Finish                    = 1,</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span>     ...</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span> };</pre>
</div>
</div>
<p>当返回k_BRS_Finish的时候，就表示当前节点已经处理完毕了，如果再次进入该节点，就认为是重新进入了。用上面描述的那个状态机的来说的话就是，如果是重新进入，会先调用_DoEnter方法，然后调用_DoExecute方法，如果_DoExecute返回正在运行（k_BRS_Executing），那么以后再进入这个节点就会直接调用_DoExectue，如果返回已经结束（k_BRS_Finish），则会调用_DoExit，以后再进入这个节点就会重新调用_DoEnter方法了。</p>
<p>对于控制节点来说，它的运行状态和子节点的运行状态是息息相关的，比如，选择节点的运行状态，就是它当前选择的这个节点的运行状态，并且，有时控制节点的控制逻辑也和子节点的运行状态有关，比如序列节点，当它前一个子节点运行结束，序列节点就会自动的切换到下一个子节点运行。所以在实现具体的行为类时，我们应该要正确的返回节点的运行状态。在例子程序中，我做的一个“空闲”（idle）的行为节点，就能很好的说明问题：</p>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> NOD_Idle : <span style="color: #0000ff;">public</span> BevNodeTerminal</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">public</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span>     NOD_Idle(BevNode* _o_ParentNode)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span>         :BevNodeTerminal(_o_ParentNode)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span>     {}</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum7" style="color: #606060;"> 7:</span> <span style="color: #0000ff;">protected</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum8" style="color: #606060;"> 8:</span>     <span style="color: #0000ff;">virtual</span> <span style="color: #0000ff;">void</span> _DoEnter(<span style="color: #0000ff;">const</span> BevNodeInputParam&amp; input)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum9" style="color: #606060;"> 9:</span>     {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum10" style="color: #606060;"> 10:</span>         m_WaitingTime = 0.5f;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum11" style="color: #606060;"> 11:</span>     }</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum12" style="color: #606060;"> 12:</span>     <span style="color: #0000ff;">virtual</span> BevRunningStatus _DoExecute(<span style="color: #0000ff;">const</span> BevNodeInputParam&amp; input, BevNodeOutputParam&amp; output)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum13" style="color: #606060;"> 13:</span>     {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum14" style="color: #606060;"> 14:</span>         <span style="color: #0000ff;">const</span> BevInputData&amp; inputData = input.GetRealDataType&lt;BevInputData&gt;();</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum15" style="color: #606060;"> 15:</span>         BevOutputData&amp; outputData = output.GetRealDataType&lt;BevOutputData&gt;();</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum16" style="color: #606060;"> 16:</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum17" style="color: #606060;"> 17:</span>         f32 timeStep = inputData.m_TimeStep;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum18" style="color: #606060;"> 18:</span>         m_WaitingTime -= timeStep;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum19" style="color: #606060;"> 19:</span>         <span style="color: #0000ff;">if</span>(m_WaitingTime &lt; 0)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum20" style="color: #606060;"> 20:</span>         {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum21" style="color: #606060;"> 21:</span>             outputData.m_BodyColor = D_Color(rand() % 256, rand() % 256, rand() % 256);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum22" style="color: #606060;"> 22:</span>             <span style="color: #0000ff;">return</span> k_BRS_Finish;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum23" style="color: #606060;"> 23:</span>         }</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum24" style="color: #606060;"> 24:</span>         <span style="color: #0000ff;">return</span> k_BRS_Executing;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum25" style="color: #606060;"> 25:</span>     }</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum26" style="color: #606060;"> 26:</span> <span style="color: #0000ff;">private</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum27" style="color: #606060;"> 27:</span>     <span style="color: #0000ff;">float</span> m_WaitingTime;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum28" style="color: #606060;"> 28:</span> };</pre>
</div>
</div>
<p>这段代码中的某些内容不明白也没有关系，我们主要关注的是关于节点运行状态的部分。这个Idle行为做了一件这样的事，就是不停的变换自己的颜色，间隔是0.5秒，当时间一到，就会返回运行结束（k_BRS_Finish），并输出当前的颜色，当时间还没到，则返回运行中（k_BRS_Executing），并且维持当前颜色。可以看到，我们用运行状态控制了计时器的重置，选择在_DoEnter方法中重置了计时器，当然，更合理的做法是在时间一到的时候，就重置计时器，并且永远返回运行中，不过这个例子里，我主要就是想用来演示运行状态，和_DoEnter的相关用法。</p>
<p>接下去再来看看控制节点，我一共写了5种控制节点，带优先级的选择节点（BevNodePrioritySelector），不带优先级的选择节点（BevNodeNonePrioritySelector），序列节点（BevNodeSequence），并行节点（BevNodeParallel），循环节点（BevNodeLoop），这些节点的进入条件和选择逻辑都是按照在行为树中改节点的定义来做的，我想用一张表格来说明：</p>
<table width="646" border="1" cellspacing="0" cellpadding="1">
<tbody>
<tr>
<td valign="top" width="150"></td>
<td valign="top" width="227">测试（Evaluate）</td>
<td valign="top" width="267">更新（Tick）</td>
</tr>
<tr>
<td valign="top" width="150">带优先级的选择节点（BevNodePrioritySelector）</td>
<td valign="top" width="227">从第一个子节点开始依次遍历所有的子节点，调用其Evaluate方法，当发现存在可以运行的子节点时，记录子节点索引，停止遍历，返回True。</td>
<td valign="top" width="267">调用可以运行的子节点的Tick方法，用它所返回的运行状态作为自身的运行状态返回</td>
</tr>
<tr>
<td valign="top" width="150">不带优先级的选择节点（BevNodeNonePrioritySelector）</td>
<td valign="top" width="227">先调用上一个运行的子节点（若存在）的Evaluate方法，如果可以运行，则继续运保存该节点的索引，返回True，如果不能运行，则重新选择（同带优先级的选择节点的选择方式）</td>
<td valign="top" width="267">调用可以运行的子节点的Tick方法，用它所返回的运行状态作为自身的运行状态返回</td>
</tr>
<tr>
<td valign="top" width="150">序列节点（BevNodeSequence）</td>
<td valign="top" width="227">若是从头开始的，则调用第一个子节点的Evaluate方法，将其返回值作为自身的返回值返回。否则，调用当前运行节点的Evaluate方法，将其返回值作为自身的返回值返回。</td>
<td valign="top" width="267">调用可以运行的子节点的Tick方法，若返回运行结束，则将下一个子节点作为当前运行节点，若当前已是最后一个子节点，表示该序列已经运行结束，则自身返回运行结束。若子节点返回运行中，则用它所返回的运行状态作为自身的运行状态返回</td>
</tr>
<tr>
<td valign="top" width="150">并行节点（BevNodeParallel）</td>
<td valign="top" width="227">依次调用所有的子节点的Evaluate方法，若所有的子节点都返回True，则自身也返回True，否则，返回False</td>
<td valign="top" width="267">调用所有子节点的Tick方法，若并行节点是“或者”的关系，则只要有一个子节点返回运行结束，那自身就返回运行结束。若并行节点是“并且”的关系，则只有所有的子节点返回结束，自身才返回运行结束</td>
</tr>
<tr>
<td valign="top" width="150">循环节点（BevNodeLoop）</td>
<td valign="top" width="227">预设的循环次数到了就返回False，否则，只调用第一个子节点的Evaluate方法，用它所返回的值作为自身的值返回</td>
<td valign="top" width="267">只调用第一个节点的Tick方法，若返回运行结束，则看是否需要重复运行，若循环次数没到，则自身返回运行中，若循环次数已到，则返回运行结束</td>
</tr>
</tbody>
</table>
<p>可能看表格内的描述会感觉有点拗口，可以结合代码一起看，会理解的更好。特别要提一点的是，在某些控制节点的Evaluate方法中，我会修改和记录可以运行的节点索引，当调用Tick的时候，就可以用这个索引来找到可以运行的节点了。这种模式和我以前提到的行为树更新模式有点不太一样，不过本质上是相同的。</p>
<p>（待续…）</p>
<p>————————————————————————<br />
作者：Finney<br />
Blog：AI分享站(<a href="http://www.aisharing.com/">http://www.aisharing.com/</a>)<br />
Email：finneytang@gmail.com<br />
本文欢迎转载和引用，请保留本说明并注明出处<br />
————————————————————————</p>
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/2.5/cn/"><img alt="知识共享许可协议" style="border-width:0" src="http://i.creativecommons.org/l/by-nc-nd/2.5/cn/88x31.png" /></a><br />本博客作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/2.5/cn/">知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议</a>进行许可。<hr /><a href="http://www.aisharing.com/donation" target="_blank"><img src="http://aisharing.com/wp/wp-content/uploads/2011/08/donate.png" alt="捐赠本站" /></a> ]]></content:encoded>
			<wfw:commentRss>http://www.aisharing.com/archives/522/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>用800行代码做个行为树（Behavior Tree）的库（1）</title>
		<link>http://www.aisharing.com/archives/517</link>
		<comments>http://www.aisharing.com/archives/517#comments</comments>
		<pubDate>Fri, 27 Jan 2012 10:08:55 +0000</pubDate>
		<dc:creator>Finney</dc:creator>
				<category><![CDATA[代码分享]]></category>
		<category><![CDATA[Behavior]]></category>
		<category><![CDATA[Behavior Tree]]></category>
		<category><![CDATA[代码]]></category>
		<category><![CDATA[节点]]></category>
		<category><![CDATA[行为]]></category>
		<category><![CDATA[行为树]]></category>

		<guid isPermaLink="false">http://www.aisharing.com/?p=517</guid>
		<description><![CDATA[最近一直在忙新项目的准备，甚少涉及AI的东西，所以博客也疏于更新。春节前，收到一个网友的邮件，说看了行为树的一些东西，但还是不知道如何去入手实现，我就乘着春节假期，动手写了一个简单的行为树的库，和大家一起边分析代码，边说说行为树的具体实现方法。这个库很简单，一共也就800行的代码左右，不过麻雀虽小，五脏俱全，行为树中的主要部分基本都有涵盖，包括前提（Precondition），选择节点（Selector），并行节点（Parallel），序列节点（Sequence）等等。在分析代码前，如果有朋友对行为树的相关概念还不是很了解，建议先阅读本站上对于行为树介绍的相关文章。

这次的代码以及示例程序，还是基于我自己维护的一个框架<a href="http://code.google.com/p/tsiu/" target="_blank">TsiU</a>，在系列文章的最后会给出下载链接。

行为树，由名字就可以看到，它是一个树结构，通过各个节点相互连接，所以我先定义了节点的基类：
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; max-height: 200px; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> BevNode{}</pre>
</div>
</div>
要把树链接起来，需要在这个类中保留父节点指针，和子节点指针，我用了一个固定的数组来保存子节点指针，它的大小是16，也就是说，一个节点最多可以有16个子节点
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; max-height: 200px; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> BevNode</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">protected</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span>     BevNode*    mao_ChildNodeList[k_BLimited_MaxChildNodeCnt];</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span>     ...</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span>     BevNode*    mo_ParentNode;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum7" style="color: #606060;"> 7:</span> }</pre>
</div>
</div>
有了这些变量的定义，我们就可以串联起一颗树了。到目前为止，这个节点类还仅仅是一个树的节点，作为行为树的节点还差了些东西，在以前的介绍中，我们知道行为树的每一个节点都可以绑定一个称为前提（Precondition）的部分，用来作为是否进入这个节点的条件，在我的实现中，我把这个前提拆分成了两个部分，一个称为“内在前提”，一个称为“外在前提”。“内在前提”是和节点类静态绑定的（也就是说，这个节点的固有前提），而“外在前提”是可以和节点做动态绑定的。这样做的原因是，由于在行为树上，节点是可以被复用的，在不同的子树上他的进入条件往往是不同的。比如，“移动”，这是一个常见的行为节点，逃跑的时候，可能需要“移动”，追击的时候也需要“移动”，但进入这个节点需要不同的“外在前提”，所以这里就需要让节点支持动态绑定的前提。“内在前提”，我用继承的方式来实现，而“外在前提”，我用了另一个类来实现
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; max-height: 200px; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> BevNode</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">public</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span>     <span style="color: #0000ff;">bool</span> Evaluate(<span style="color: #0000ff;">const</span> BevNodeInputParam&#38; input)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span>     {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span>         <span style="color: #0000ff;">return</span> (mo_NodePrecondition == NULL &#124;&#124; mo_NodePrecondition-&#62;ExternalCondition(input)) &#38;&#38; _DoEvaluate(input);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum7" style="color: #606060;"> 7:</span>     }</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum8" style="color: #606060;"> 8:</span> <span style="color: #0000ff;">protected</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum9" style="color: #606060;"> 9:</span>     <span style="color: #0000ff;">virtual</span> <span style="color: #0000ff;">bool</span> _DoEvaluate(<span style="color: #0000ff;">const</span> BevNodeInputParam&#38; input)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum10" style="color: #606060;"> 10:</span>     {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum11" style="color: #606060;"> 11:</span>         <span style="color: #0000ff;">return</span> true;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum12" style="color: #606060;"> 12:</span>     }</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum13" style="color: #606060;"> 13:</span> <span style="color: #0000ff;">protected</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum14" style="color: #606060;"> 14:</span>     BevNodePrecondition* mo_NodePrecondition;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum15" style="color: #606060;"> 15:</span> }</pre>
</div>
</div>
可以看到这里用到了一个叫做BevNodePrecondition的类，用来表示“外在前提”，他是一个纯虚函数，只有一个方法，先看一下它的定义，后面会有详细的讨论。
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; max-height: 200px; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> BevNodePrecondition</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">public</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span>     <span style="color: #0000ff;">virtual</span> <span style="color: #0000ff;">bool</span> ExternalCondition(<span style="color: #0000ff;">const</span> BevNodeInputParam&#38; input) <span style="color: #0000ff;">const</span> = 0;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span> };</pre>
</div>
</div>
_DoEvaluate虚方法就是需要被子类继承并实现的“内在前提”，这两种前提在Evaluate方法中被结合了起来，用来检测进入条件，当返回True时，就表示当前节点可以被运行。返回False时，就表示当前节点进入条件不满足，不能被运行。

在节点基类的中，还有两个重要的方法是:
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; max-height: 200px; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> BevNode</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">public</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span>     <span style="color: #0000ff;">void</span> Transition(<span style="color: #0000ff;">const</span> BevNodeInputParam&#38; input)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span>     {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span>         _DoTransition(input);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum7" style="color: #606060;"> 7:</span>     }</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum8" style="color: #606060;"> 8:</span>     BevRunningStatus Tick(<span style="color: #0000ff;">const</span> BevNodeInputParam&#38; input, BevNodeOutputParam&#38; output)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum9" style="color: #606060;"> 9:</span>     {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum10" style="color: #606060;"> 10:</span>         <span style="color: #0000ff;">return</span> _DoTick(input, output);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum11" style="color: #606060;"> 11:</span>     }</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum12" style="color: #606060;"> 12:</span> }</pre>
</div>
</div>
转移（Transition）的概念是第一次出现，转移（Transition）指从上一个可运行的节点切换到另一个节点的行为。这个方法会被在节点切换的时候调用，比如，在一个带优先级的选择节点下有节点A，和节点B，节点A的优先级高于节点B，当前运行的节点是B，然后发现节点A可以运行了，但带优先级的选择节点就会选择去运行节点A，这时就会调用节点B的Transition方法，所以在这个方法中，一般可以用来做一些清理的工作。Tick方法就是通常的更新方法，就不多说了。

再来看一下这三个重要方法的参数，一共有两种类型的参数，BevNodeInputParam和BevNodeOutputParam，前者是传入参数，可以认为是行为树的输入，用const作为限定符，表示只读，后者是传出参数，可以认为是行为树的输出，可以修改。其实，从代码中可以看到，这两种类型的本质都是一样的，都是一个名为AnyData的类
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; max-height: 200px; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">typedef</span> AnyData BevNodeInputParam;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> <span style="color: #0000ff;">typedef</span> AnyData BevNodeOutputParam;</pre>
</div>
</div>
由于输入和输出参数是游戏相关的，所以这里用AnyData这个类来表示，这个类可以存放任意的数据结构，所以，这个类中真正的内容是需要玩家自己定义的。

最后来看看行为树是如何被定义和更新的（可以在示例程序中找到相关代码）
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; max-height: 200px; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #008000;">//define input &#38; output data</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> <span style="color: #0000ff;">struct</span> BevInputData{...}</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">struct</span> BevOutputData{...}</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span> BevInputData    m_BevTreeInputData;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span> BevOutputData   m_BevTreeOutputdata;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span> ....</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum7" style="color: #606060;"> 7:</span> <span style="color: #008000;">//create tree</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum8" style="color: #606060;"> 8:</span> m_BevTreeRoot = CreateTree();</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum9" style="color: #606060;"> 9:</span> ...</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum10" style="color: #606060;"> 10:</span> <span style="color: #008000;">//update</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum11" style="color: #606060;"> 11:</span> BevNodeInputParam input(&#38;m_BevTreeInputData);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum12" style="color: #606060;"> 12:</span> BevNodeOutputParam output(&#38;m_BevTreeOutputdata);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum13" style="color: #606060;"> 13:</span> <span style="color: #0000ff;">if</span>(m_BevTreeRoot-&#62;Evaluate(input))</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum14" style="color: #606060;"> 14:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum15" style="color: #606060;"> 15:</span>     m_BevTreeRoot-&#62;Tick(input, output);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum16" style="color: #606060;"> 16:</span> }</pre>
</div>
</div>
<ol>
	<li>定义自己的输入和输出参数（BevInputData，BevOutputData）</li>
	<li>创建行为树，保存根节点指针（m_BevTreeRoot）</li>
	<li>测试是否有可以运行的节点，如有则更新</li>
</ol>
（待续…）

————————————————————————
作者：Finney
Blog：AI分享站(<a href="http://www.aisharing.com/">http://www.aisharing.com/</a>)
Email：finneytang@gmail.com
本文欢迎转载和引用，请保留本说明并注明出处
————————————————————————]]></description>
			<content:encoded><![CDATA[<p>最近一直在忙新项目的准备，甚少涉及AI的东西，所以博客也疏于更新。春节前，收到一个网友的邮件，说看了行为树的一些东西，但还是不知道如何去入手实现，我就乘着春节假期，动手写了一个简单的行为树的库，和大家一起边分析代码，边说说行为树的具体实现方法。这个库很简单，一共也就800行的代码左右，不过麻雀虽小，五脏俱全，行为树中的主要部分基本都有涵盖，包括前提（Precondition），选择节点（Selector），并行节点（Parallel），序列节点（Sequence）等等。在分析代码前，如果有朋友对行为树的相关概念还不是很了解，建议先阅读本站上对于行为树介绍的相关文章。</p>
<p>这次的代码以及示例程序，还是基于我自己维护的一个框架<a href="http://code.google.com/p/tsiu/" target="_blank">TsiU</a>，在系列文章的最后会给出下载链接。</p>
<p>行为树，由名字就可以看到，它是一个树结构，通过各个节点相互连接，所以我先定义了节点的基类：</p>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; max-height: 200px; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> BevNode{}</pre>
</div>
</div>
<p>要把树链接起来，需要在这个类中保留父节点指针，和子节点指针，我用了一个固定的数组来保存子节点指针，它的大小是16，也就是说，一个节点最多可以有16个子节点</p>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; max-height: 200px; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> BevNode</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">protected</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span>     BevNode*    mao_ChildNodeList[k_BLimited_MaxChildNodeCnt];</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span>     ...</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span>     BevNode*    mo_ParentNode;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum7" style="color: #606060;"> 7:</span> }</pre>
</div>
</div>
<p>有了这些变量的定义，我们就可以串联起一颗树了。到目前为止，这个节点类还仅仅是一个树的节点，作为行为树的节点还差了些东西，在以前的介绍中，我们知道行为树的每一个节点都可以绑定一个称为前提（Precondition）的部分，用来作为是否进入这个节点的条件，在我的实现中，我把这个前提拆分成了两个部分，一个称为“内在前提”，一个称为“外在前提”。“内在前提”是和节点类静态绑定的（也就是说，这个节点的固有前提），而“外在前提”是可以和节点做动态绑定的。这样做的原因是，由于在行为树上，节点是可以被复用的，在不同的子树上他的进入条件往往是不同的。比如，“移动”，这是一个常见的行为节点，逃跑的时候，可能需要“移动”，追击的时候也需要“移动”，但进入这个节点需要不同的“外在前提”，所以这里就需要让节点支持动态绑定的前提。“内在前提”，我用继承的方式来实现，而“外在前提”，我用了另一个类来实现</p>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; max-height: 200px; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> BevNode</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">public</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span>     <span style="color: #0000ff;">bool</span> Evaluate(<span style="color: #0000ff;">const</span> BevNodeInputParam&amp; input)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span>     {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span>         <span style="color: #0000ff;">return</span> (mo_NodePrecondition == NULL || mo_NodePrecondition-&gt;ExternalCondition(input)) &amp;&amp; _DoEvaluate(input);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum7" style="color: #606060;"> 7:</span>     }</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum8" style="color: #606060;"> 8:</span> <span style="color: #0000ff;">protected</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum9" style="color: #606060;"> 9:</span>     <span style="color: #0000ff;">virtual</span> <span style="color: #0000ff;">bool</span> _DoEvaluate(<span style="color: #0000ff;">const</span> BevNodeInputParam&amp; input)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum10" style="color: #606060;"> 10:</span>     {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum11" style="color: #606060;"> 11:</span>         <span style="color: #0000ff;">return</span> true;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum12" style="color: #606060;"> 12:</span>     }</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum13" style="color: #606060;"> 13:</span> <span style="color: #0000ff;">protected</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum14" style="color: #606060;"> 14:</span>     BevNodePrecondition* mo_NodePrecondition;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum15" style="color: #606060;"> 15:</span> }</pre>
</div>
</div>
<p>可以看到这里用到了一个叫做BevNodePrecondition的类，用来表示“外在前提”，他是一个纯虚函数，只有一个方法，先看一下它的定义，后面会有详细的讨论。</p>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; max-height: 200px; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> BevNodePrecondition</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">public</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span>     <span style="color: #0000ff;">virtual</span> <span style="color: #0000ff;">bool</span> ExternalCondition(<span style="color: #0000ff;">const</span> BevNodeInputParam&amp; input) <span style="color: #0000ff;">const</span> = 0;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span> };</pre>
</div>
</div>
<p>_DoEvaluate虚方法就是需要被子类继承并实现的“内在前提”，这两种前提在Evaluate方法中被结合了起来，用来检测进入条件，当返回True时，就表示当前节点可以被运行。返回False时，就表示当前节点进入条件不满足，不能被运行。</p>
<p>在节点基类的中，还有两个重要的方法是:</p>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; max-height: 200px; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> BevNode</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">public</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span>     <span style="color: #0000ff;">void</span> Transition(<span style="color: #0000ff;">const</span> BevNodeInputParam&amp; input)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span>     {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span>         _DoTransition(input);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum7" style="color: #606060;"> 7:</span>     }</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum8" style="color: #606060;"> 8:</span>     BevRunningStatus Tick(<span style="color: #0000ff;">const</span> BevNodeInputParam&amp; input, BevNodeOutputParam&amp; output)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum9" style="color: #606060;"> 9:</span>     {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum10" style="color: #606060;"> 10:</span>         <span style="color: #0000ff;">return</span> _DoTick(input, output);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum11" style="color: #606060;"> 11:</span>     }</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum12" style="color: #606060;"> 12:</span> }</pre>
</div>
</div>
<p>转移（Transition）的概念是第一次出现，转移（Transition）指从上一个可运行的节点切换到另一个节点的行为。这个方法会被在节点切换的时候调用，比如，在一个带优先级的选择节点下有节点A，和节点B，节点A的优先级高于节点B，当前运行的节点是B，然后发现节点A可以运行了，但带优先级的选择节点就会选择去运行节点A，这时就会调用节点B的Transition方法，所以在这个方法中，一般可以用来做一些清理的工作。Tick方法就是通常的更新方法，就不多说了。</p>
<p>再来看一下这三个重要方法的参数，一共有两种类型的参数，BevNodeInputParam和BevNodeOutputParam，前者是传入参数，可以认为是行为树的输入，用const作为限定符，表示只读，后者是传出参数，可以认为是行为树的输出，可以修改。其实，从代码中可以看到，这两种类型的本质都是一样的，都是一个名为AnyData的类</p>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; max-height: 200px; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">typedef</span> AnyData BevNodeInputParam;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> <span style="color: #0000ff;">typedef</span> AnyData BevNodeOutputParam;</pre>
</div>
</div>
<p>由于输入和输出参数是游戏相关的，所以这里用AnyData这个类来表示，这个类可以存放任意的数据结构，所以，这个类中真正的内容是需要玩家自己定义的。</p>
<p>最后来看看行为树是如何被定义和更新的（可以在示例程序中找到相关代码）</p>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; max-height: 200px; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #008000;">//define input &amp; output data</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> <span style="color: #0000ff;">struct</span> BevInputData{...}</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">struct</span> BevOutputData{...}</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span> BevInputData    m_BevTreeInputData;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span> BevOutputData   m_BevTreeOutputdata;</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span> ....</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum7" style="color: #606060;"> 7:</span> <span style="color: #008000;">//create tree</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum8" style="color: #606060;"> 8:</span> m_BevTreeRoot = CreateTree();</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum9" style="color: #606060;"> 9:</span> ...</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum10" style="color: #606060;"> 10:</span> <span style="color: #008000;">//update</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum11" style="color: #606060;"> 11:</span> BevNodeInputParam input(&amp;m_BevTreeInputData);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum12" style="color: #606060;"> 12:</span> BevNodeOutputParam output(&amp;m_BevTreeOutputdata);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum13" style="color: #606060;"> 13:</span> <span style="color: #0000ff;">if</span>(m_BevTreeRoot-&gt;Evaluate(input))</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum14" style="color: #606060;"> 14:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: white; text-align: left; border-style: none; padding: 0px;"><span id="lnum15" style="color: #606060;"> 15:</span>     m_BevTreeRoot-&gt;Tick(input, output);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum16" style="color: #606060;"> 16:</span> }</pre>
</div>
</div>
<ol>
<li>定义自己的输入和输出参数（BevInputData，BevOutputData）</li>
<li>创建行为树，保存根节点指针（m_BevTreeRoot）</li>
<li>测试是否有可以运行的节点，如有则更新</li>
</ol>
<p>（待续…）</p>
<p>————————————————————————<br />
作者：Finney<br />
Blog：AI分享站(<a href="http://www.aisharing.com/">http://www.aisharing.com/</a>)<br />
Email：finneytang@gmail.com<br />
本文欢迎转载和引用，请保留本说明并注明出处<br />
————————————————————————</p>
<hr /><h2>评论</h2><ul><li><a href="http://www.aisharing.com/archives/517">2012 年 2 月 28 日</a>, 震荡电路 评论到: 请问有没有考虑过把行为树再进一步抽象，分离成树的逻辑判断以及数的节点数据，因为我看到过一张图，感觉很这个树结构很适合用xml格式存储。如果能实现的话，就相当于一个小型的AI引擎了，每个AI对象都由一个xml表来定义，其中每个节点的前提和行动另行存储。</li><li><a href="http://www.aisharing.com/archives/517">2012 年 2 月 28 日</a>, <a href='http://www.aisharing.com' rel='external nofollow' class='url'>Finney</a> 评论到: 嗯，行为树很适合用配置文件来配置，我们用过lua来作为AI的配置文件，lua较之xml的优势是，可以包含一定的逻辑。我一直很想做一个行为树的编辑器和调试器，把AI数据化，不过一直没时间做，有兴趣，可以一起讨论。</li><li><a href="http://www.aisharing.com/archives/517">2012 年 2 月 29 日</a>, 震荡电路 评论到: 想问个问题，对于Parallel Node，应该理解成每个子节点都执行一遍；那么对于我想实现同时作几件事情，比如同时刮风、下雨，感觉这个类型的节点并不合适，因为我觉得不是间隔执行刮风、下雨两个动作就可以的。这种情况我认为更需要一个新的刮风下雨的action node或者让Parallel Node可以实现多线程同步。但是这样就破坏了算法整体的设定了。请问有什么好办法么？</li><li><a href="http://www.aisharing.com/archives/517">2012 年 3 月 2 日</a>, <a href='http://www.aisharing.com' rel='external nofollow' class='url'>Finney</a> 评论到: 我的理解其实是怎么来定义“同时”这样一个概念，在单线程的逻辑里，游戏是以帧为单位更新的，画面也是以帧为单位来呈现的，所以可以认为在一帧里做的事情，都是“同时”的。你说的和我想的一样，我也觉得行为树的多线程不是很可行，而且没什么必要。</li></ul><a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/2.5/cn/"><img alt="知识共享许可协议" style="border-width:0" src="http://i.creativecommons.org/l/by-nc-nd/2.5/cn/88x31.png" /></a><br />本博客作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/2.5/cn/">知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议</a>进行许可。<hr /><a href="http://www.aisharing.com/donation" target="_blank"><img src="http://aisharing.com/wp/wp-content/uploads/2011/08/donate.png" alt="捐赠本站" /></a> ]]></content:encoded>
			<wfw:commentRss>http://www.aisharing.com/archives/517/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>关于博客上AI技术文章的问与答 &#8211; 第一回</title>
		<link>http://www.aisharing.com/archives/510</link>
		<comments>http://www.aisharing.com/archives/510#comments</comments>
		<pubDate>Mon, 19 Dec 2011 09:09:13 +0000</pubDate>
		<dc:creator>Finney</dc:creator>
				<category><![CDATA[AI分享]]></category>
		<category><![CDATA[Behavior]]></category>
		<category><![CDATA[Behavior Tree]]></category>
		<category><![CDATA[Reflection]]></category>
		<category><![CDATA[反射]]></category>
		<category><![CDATA[状态机]]></category>
		<category><![CDATA[节点]]></category>
		<category><![CDATA[行为]]></category>
		<category><![CDATA[行为树]]></category>
		<category><![CDATA[请求]]></category>

		<guid isPermaLink="false">http://www.aisharing.com/?p=510</guid>
		<description><![CDATA[自开博以来，经常会有网友发信给我，询问一些关于AI方面的问题，一般我都会尽力一一回答，也希望我的这些经验，对网友有些帮助，我想，有些问题可能是大家都会有的，所以，这一次，我会把一些网友的提问，和我的回答列在这个地方，供更多的同学参考，也欢迎一起讨论，才疏学浅，不吝赐教。

<em><strong>Q：</strong>你好，我在</em><em>你的博客</em><em>上看了你的一些关于行为树的文章，写得很好，让我受益匪浅。我想实践一下怎么用，在网上找了一个相关的库libbehavior(</em><a href="http://www.aisharing.com/"><em>https://code.google.com/p/libbehavior/</em></a><em>),想用这个库做个小例子，它里面有一些演示程序，但牵涉到很多库，不好学习，我就想将一个简单的状态机示例程序源码改成用行为树实现，发现还是很茫然，不知道你是否有空给些提示，谢谢！</em>

<strong>A：</strong>我下载了程序简单看了下，libbehavior好像已经实现了一个可用的behavior tree了，虽然可能还不够完善，但基本的行为树的样子已经有了，你可以考虑基于他这个，然后根据自己的需求添加。 不知道你有没有看到它里面的sample，我从Scenario3.h里摘录了一段建行为树的例子来说明下，我还画了一张示意图作为参考
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #008000;">//opponent-&#62;brain是一个ParallelNode，并行的节点</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> <span style="color: #008000;">//在并行节点上添加第一个子节点，行为是转向目标TurnTowardsTarget</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> opponent-&#62;brain-&#62;addChild(<span style="color: #0000ff;">new</span> TurnTowardsTarget(1));</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span> <span style="color: #008000;">//建立一个随机节点作为第二个子节点，下面三个序列节点应该会随机选一个执行</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span> ProbabilityNode* pNode = <span style="color: #0000ff;">new</span> ProbabilityNode();</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span> <span style="color: #008000;">//在这个随机节点上，建立一个序列节点</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum7" style="color: #606060;"> 7:</span> pNode-&#62;addChild((<span style="color: #0000ff;">new</span> SequentialNode())</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum8" style="color: #606060;"> 8:</span>         <span style="color: #008000;">//在序列节点上建立第一个子节点，感觉这个应该是个判断条件</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum9" style="color: #606060;"> 9:</span>          -&#62;addChild(<span style="color: #0000ff;">new</span> BoolCondition&#60;GameObject&#62;(&#38;GameObject::alignedWithPlayer,true))</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum10" style="color: #606060;"> 10:</span>         <span style="color: #008000;">//在序列节点上建立第二个子节点，行为是射击Fire</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum11" style="color: #606060;"> 11:</span>          -&#62;addChild(<span style="color: #0000ff;">new</span> Fire())</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum12" style="color: #606060;"> 12:</span>         <span style="color: #008000;">//在序列节点上建立第二个子节点，行为是等待CD，防止太频繁</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum13" style="color: #606060;"> 13:</span>          -&#62;addChild(<span style="color: #0000ff;">new</span> Cooldown(500)),5.0);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum14" style="color: #606060;"> 14:</span> <span style="color: #008000;">//在这个随机节点上，再建立一个序列节点，下面的子节点就不说明了</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum15" style="color: #606060;"> 15:</span> pNode-&#62;addChild((<span style="color: #0000ff;">new</span> SequentialNode())</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum16" style="color: #606060;"> 16:</span>          -&#62;addChild(<span style="color: #0000ff;">new</span> FloatCondition&#60;GameObject&#62;(&#38;Ship::getXPosition,LESS_THAN_FP,200,1))</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum17" style="color: #606060;"> 17:</span>          -&#62;addChild(<span style="color: #0000ff;">new</span> GotoPoint(point(300,50),500))</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum18" style="color: #606060;"> 18:</span>          -&#62;addChild(<span style="color: #0000ff;">new</span> Cooldown(200)))</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum19" style="color: #606060;"> 19:</span> <span style="color: #008000;">//在这个随机节点上，再建立一个序列节点，下面的子节点就不说明了</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum20" style="color: #606060;"> 20:</span> pNode-&#62;addChild((<span style="color: #0000ff;">new</span> SequentialNode())</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum21" style="color: #606060;"> 21:</span>          -&#62;addChild(<span style="color: #0000ff;">new</span> FloatCondition&#60;GameObject&#62;(&#38;Ship::getXPosition,GREATER_OR_CLOSE,200,1))</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum22" style="color: #606060;"> 22:</span>          -&#62;addChild(<span style="color: #0000ff;">new</span> GotoPoint(point(100,50),500))</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum23" style="color: #606060;"> 23:</span>          -&#62;addChild(<span style="color: #0000ff;">new</span> Cooldown(200)));</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum24" style="color: #606060;"> 24:</span> <span style="color: #008000;">//在这个随机节点的上面做一个循环节点，把上面的行为无限循环</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum25" style="color: #606060;"> 25:</span> opponent-&#62;brain</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum26" style="color: #606060;"> 26:</span>     -&#62;addChild((<span style="color: #0000ff;">new</span> RepeatNode(-1))</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum27" style="color: #606060;"> 27:</span>         -&#62;addChild(pNode));</pre>
</div>
</div>
<a href="http://aisharing.com/wp/wp-content/uploads/2011/12/Scenario3BevTree.png"><img style="display: inline; border: 0px;" title="Scenario3BevTree" src="http://aisharing.com/wp/wp-content/uploads/2011/12/Scenario3BevTree_thumb.png" alt="Scenario3BevTree" width="448" height="480" border="0" /></a>

&#160;

所以如果你要把状态机改成行为树的话，可以考虑用我说过的Selector节点+Precondition的方式（libbehavior里好像是叫PriorityNode，但它好像没有Preconditon的概念），因为状态机里不是要跳转嘛，你原先每一个state都会有进入条件的，比如下面这个
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">if</span> (pMiner-&#62;PocketsFull())</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span>     pMiner-&#62;ChangeState(VisitBankAndDepositGold::Instance());</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span> }</pre>
</div>
</div>
这样的话，你可以把pMiner-&#62;PocketsFull()作为VisitBankAndDepositGold的Precondition挂在一个Selector节点下面，然后让Selector节点帮助你选择应该进入那个状态。我建议，你可以先看看libbehavior每一个Scenario的行为树创建部分的代码，这样应该会有点感觉，另外，可以边参考我网上的文章，边来看libbehavior\BehaviorTree-src里的源码，会好理解很多:)，希望对你有所帮助！欢迎继续来信交流！（状态机和行为树之间转换可以参看<a href="http://www.aisharing.com/archives/439" target="_blank">这里</a>和<a href="http://www.aisharing.com/archives/452" target="_blank">这里</a>）

<em>Q：看了你写的一系列文章，写的真不错。我也最进在游戏行业，做AI相关的工作。涉及一些游戏AI设计的问题，能否指教一下。目前我们游戏AI设计的部分考虑NPC的AI部分可配置成AI执行脚本文件方式。我考虑是可以设计AI框架，依据游戏对象的配置，框架决定加载相应的脚本引擎。我看 </em><a href="http://www.aisharing.com/archives/86"><em>《浅谈层次化的AI架构》</em></a><em> 不错，你能不能分享一下AI设计中如何分离AI框架和AI脚本内容的呢。分离的话，感觉状态转换是应该在AI脚本中完成的吧？ 暴露AI引擎的API接口的通用设计一般是怎么样的呢？学习中...., 谢谢。</em>

A：如果想在脚本中写很多AI逻辑的话，如何调试是个比较麻烦的事情，需要比较好的调试接口。我用过一种做法，你可以试试看，就是用脚本做AI的配置，逻辑还是在C++端。C++提供一个个AI逻辑模块，然后在脚本端把它配置起来，因为我用的是行为树（可以参看我博客上关于行为树的相关文章），所以比较容易配置。接口的话，首先最好是引擎能支持反射，然后就能方便的导出函数和变量直接给脚本用，至少我用lua做脚本语言，这样是可行的。

<em>Q：您好。今天再搜索层次状态机相关资料的时候进入了您的博客。本人只在游戏中使用过简单的FSM，属于AI初学者，阅读了几篇文章后产生了几个问题:</em>
<ul>
	<li><em>HFSM和行为树到底是什么关系。我对行为树和状态机的区别还是把握不了…… </em></li>
	<li><em>您有一文章说：大部分的AI，都可以分成“决策”和“行为”，二者通过一个双缓冲的“请求层”通信，但是这篇文章您没有举例子，我非常模糊。 </em></li>
	<li><em>对“并行节点”我还是很模糊……您可以给我举个例子吗……麻烦了…。</em></li>
</ul>
A：谢谢，针对每一个问题，我的回答如下：
<ul>
	<li>HFSM和行为树其实没什么关系，是两种不同的AI结构，区别的话，可以看我在这里的评论<a href="http://www.aisharing.com/archives/90#comment-111">http://www.aisharing.com/archives/90#comment-111</a></li>
	<li>有人和你提出了一样的问题，可以参看我这里的回复，<a href="http://www.aisharing.com/archives/86#comment-115">http://www.aisharing.com/archives/86#comment-115</a></li>
	<li>比如一个并行节点下有A和B两个节点，那在一次循环里，如果前提满足的话，既会执行A，又会执行B，举个实际的例子，比如要描述“又吃又喝”，就可以用并行节点：）</li>
</ul>
<em>Q：最近在学习行为树，有幸拜读了您几篇关于行为树的文章，感觉收获颇多。刚开始接触行为树，所知有限，特来请教几个问题：</em>
<ul>
<ul>
	<li><em>关于以下代码，在文章评论中，您回复说 input 是传入行为树的参数，那么 request 是指输入的事件吗？比如移动事件、死亡事件等。 如果是事件，那您实例代码的行为树是事件驱动的，还是Tick驱动？</em></li>
</ul>
</ul>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> action = root.FindNextAction(input);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> <span style="color: #0000ff;">if</span> action is not empty then</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span>     action.Execute(request,  input)  <span style="color: #008000;">//request是输出的请求</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span> <span style="color: #0000ff;">else</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span>     print “no action is available”</pre>
</div>
</div>
<ul>
	<li><em>在FSM中，状态进入和退出时可以做相对应的事情，比如进入某个状态身上挂个循环特效，退出该状态将这个特效移除，在行为树上，怎么实现同样的功能？ </em></li>
	<li><em>按我现在对行为树的理解，感觉行为树就是将FSM中的状态变成了子树，挂在行为树的相应位置，通过行为树的节点完成状态的迁移。但是，在您文章的总结中提到："在AI设计过程中，一般来说，我们并不是先有状态机，再去转化成行为树的，当我们选择用行为树的时候，我们就要充分的理解控制节点，前提，节点等概念，并试着用行为树的逻辑方式去思考和设计。"，看后对自己将状态视为子树的理解产生了很大的怀疑，您能详细地解释下这句话吗？不知是否可以提供一张您平时设计的较复杂些的行为树供我学习之用？</em></li>
</ul>
A：谢谢，针对每一个问题，我的回答如下：
<ul>
	<li>request是输出的请求，也就是行为树的输出，就像你说的，像移动，死亡等，可以看看我博客上关于分层次的AI架构的相关文章，整个行为树的决策还是在Tick中更新的。</li>
	<li>一般我在做行为树的Execute时会分成三个阶段，onEnter() ，onExecute()，onExit()，在第一次进入这个节点的时候，会调用onEnter，在从这个节点切换出去的时候，会调用onExit()。每个行为树的节点，我都会做成这种1P(前提Precondition) + 3E(Enter, Execute, Exit)的结构</li>
	<li>行为树的思考方式是指，对于每一个节点我都需要明确知道它的进入条件是什么，它要做的事情是什么。把跳转逻辑写到控制节点中，用控制节点来控制行为树的选择流程。和状态机不同的是，对于行为树的节点，一般不需要知道它是从哪个节点过来的，它只是需要关心，满足什么条件（前提）可以进入这个节点，这些条件的判断依据都是通过行为树的输入参数传入的，所以行为树和黑板的组合是很好用的，可以看看我博客上关于黑板和行为树结合的文章</li>
	<li>项目里的行为树图不太好给，呵呵，而且需要解释才能明白。复杂的话，一般就会用到序列节点，并行节点等等。行为树的好处就是只要定义了进入前提，就可以加入到现有的行为树中，这样就越来越复杂了～，不过，这也就是行为树的好处了:)</li>
</ul>
<em>Q：Hi，在我们现在的项目里面涉及到AI的一些东西,之前学习了关于你写得行为树,正好可以发挥一下,现在有一个问题,就是关于怪物行走,怪物行走我们这边的做法就是发目标A*路径给客户端通知其他玩家,然后,怪物每秒刷一步就走一步...这样的做法...我想问问...你们那边关于怪物行走是如何实现的呢?巡逻和追击 两个地方行为怎么做互斥?</em>

A：针对你的问题，我在想哦，对于怪物移动的话，可以分两个部分，一般的移动，就是你说的巡逻，可以不需要用A＊，因为毕竟A＊的开销是比较大的，因为怪物一般的移动不会太复杂，而且一般是在一定的区域内移动（当然这是看你们怎么设计了），可以在区域内设置几个路点，然后每过一段时间选择临近的路点就可以了，也就是仅仅做一步的“寻路”。如果是要追击玩家的话，可以尝试用到A＊。

网页游戏我没做过，其他游戏里，我们用到过同步位置信息的方式，也就是每隔一段时间同步一次位置，然后两次同步之间的移动，都是客户端本地模拟。如果模拟后的位置和服务器上的位置有偏差，简单的做法是瞬移到服务器上的位置，好一点的话就是平滑的从客户端现在的位置移动到服务器上的位置。

巡逻和追击我觉得比较大的不同是，巡逻中是没有目标敌人的，而追击是存在目标敌人的，所以这样就可以做到互斥了。

————————————————————————
作者：Finney
Blog：AI分享站(<a href="http://www.aisharing.com/">http://www.aisharing.com/</a>)
Email：finneytang@gmail.com
本文欢迎转载和引用，请保留本说明并注明出处
————————————————————————]]></description>
			<content:encoded><![CDATA[<p>自开博以来，经常会有网友发信给我，询问一些关于AI方面的问题，一般我都会尽力一一回答，也希望我的这些经验，对网友有些帮助，我想，有些问题可能是大家都会有的，所以，这一次，我会把一些网友的提问，和我的回答列在这个地方，供更多的同学参考，也欢迎一起讨论，才疏学浅，不吝赐教。</p>
<p><em><strong>Q：</strong>你好，我在</em><em>你的博客</em><em>上看了你的一些关于行为树的文章，写得很好，让我受益匪浅。我想实践一下怎么用，在网上找了一个相关的库libbehavior(</em><a href="http://www.aisharing.com/"><em>https://code.google.com/p/libbehavior/</em></a><em>),想用这个库做个小例子，它里面有一些演示程序，但牵涉到很多库，不好学习，我就想将一个简单的状态机示例程序源码改成用行为树实现，发现还是很茫然，不知道你是否有空给些提示，谢谢！</em></p>
<p><strong>A：</strong>我下载了程序简单看了下，libbehavior好像已经实现了一个可用的behavior tree了，虽然可能还不够完善，但基本的行为树的样子已经有了，你可以考虑基于他这个，然后根据自己的需求添加。 不知道你有没有看到它里面的sample，我从Scenario3.h里摘录了一段建行为树的例子来说明下，我还画了一张示意图作为参考</p>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #008000;">//opponent-&gt;brain是一个ParallelNode，并行的节点</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> <span style="color: #008000;">//在并行节点上添加第一个子节点，行为是转向目标TurnTowardsTarget</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> opponent-&gt;brain-&gt;addChild(<span style="color: #0000ff;">new</span> TurnTowardsTarget(1));</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span> <span style="color: #008000;">//建立一个随机节点作为第二个子节点，下面三个序列节点应该会随机选一个执行</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span> ProbabilityNode* pNode = <span style="color: #0000ff;">new</span> ProbabilityNode();</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span> <span style="color: #008000;">//在这个随机节点上，建立一个序列节点</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum7" style="color: #606060;"> 7:</span> pNode-&gt;addChild((<span style="color: #0000ff;">new</span> SequentialNode())</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum8" style="color: #606060;"> 8:</span>         <span style="color: #008000;">//在序列节点上建立第一个子节点，感觉这个应该是个判断条件</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum9" style="color: #606060;"> 9:</span>          -&gt;addChild(<span style="color: #0000ff;">new</span> BoolCondition&lt;GameObject&gt;(&amp;GameObject::alignedWithPlayer,true))</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum10" style="color: #606060;"> 10:</span>         <span style="color: #008000;">//在序列节点上建立第二个子节点，行为是射击Fire</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum11" style="color: #606060;"> 11:</span>          -&gt;addChild(<span style="color: #0000ff;">new</span> Fire())</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum12" style="color: #606060;"> 12:</span>         <span style="color: #008000;">//在序列节点上建立第二个子节点，行为是等待CD，防止太频繁</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum13" style="color: #606060;"> 13:</span>          -&gt;addChild(<span style="color: #0000ff;">new</span> Cooldown(500)),5.0);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum14" style="color: #606060;"> 14:</span> <span style="color: #008000;">//在这个随机节点上，再建立一个序列节点，下面的子节点就不说明了</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum15" style="color: #606060;"> 15:</span> pNode-&gt;addChild((<span style="color: #0000ff;">new</span> SequentialNode())</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum16" style="color: #606060;"> 16:</span>          -&gt;addChild(<span style="color: #0000ff;">new</span> FloatCondition&lt;GameObject&gt;(&amp;Ship::getXPosition,LESS_THAN_FP,200,1))</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum17" style="color: #606060;"> 17:</span>          -&gt;addChild(<span style="color: #0000ff;">new</span> GotoPoint(point(300,50),500))</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum18" style="color: #606060;"> 18:</span>          -&gt;addChild(<span style="color: #0000ff;">new</span> Cooldown(200)))</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum19" style="color: #606060;"> 19:</span> <span style="color: #008000;">//在这个随机节点上，再建立一个序列节点，下面的子节点就不说明了</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum20" style="color: #606060;"> 20:</span> pNode-&gt;addChild((<span style="color: #0000ff;">new</span> SequentialNode())</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum21" style="color: #606060;"> 21:</span>          -&gt;addChild(<span style="color: #0000ff;">new</span> FloatCondition&lt;GameObject&gt;(&amp;Ship::getXPosition,GREATER_OR_CLOSE,200,1))</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum22" style="color: #606060;"> 22:</span>          -&gt;addChild(<span style="color: #0000ff;">new</span> GotoPoint(point(100,50),500))</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum23" style="color: #606060;"> 23:</span>          -&gt;addChild(<span style="color: #0000ff;">new</span> Cooldown(200)));</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum24" style="color: #606060;"> 24:</span> <span style="color: #008000;">//在这个随机节点的上面做一个循环节点，把上面的行为无限循环</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum25" style="color: #606060;"> 25:</span> opponent-&gt;brain</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum26" style="color: #606060;"> 26:</span>     -&gt;addChild((<span style="color: #0000ff;">new</span> RepeatNode(-1))</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum27" style="color: #606060;"> 27:</span>         -&gt;addChild(pNode));</pre>
</div>
</div>
<p><a href="http://aisharing.com/wp/wp-content/uploads/2011/12/Scenario3BevTree.png"><img style="display: inline; border: 0px;" title="Scenario3BevTree" src="http://aisharing.com/wp/wp-content/uploads/2011/12/Scenario3BevTree_thumb.png" alt="Scenario3BevTree" width="448" height="480" border="0" /></a></p>
<p>&nbsp;</p>
<p>所以如果你要把状态机改成行为树的话，可以考虑用我说过的Selector节点+Precondition的方式（libbehavior里好像是叫PriorityNode，但它好像没有Preconditon的概念），因为状态机里不是要跳转嘛，你原先每一个state都会有进入条件的，比如下面这个</p>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">if</span> (pMiner-&gt;PocketsFull())</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span>     pMiner-&gt;ChangeState(VisitBankAndDepositGold::Instance());</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span> }</pre>
</div>
</div>
<p>这样的话，你可以把pMiner-&gt;PocketsFull()作为VisitBankAndDepositGold的Precondition挂在一个Selector节点下面，然后让Selector节点帮助你选择应该进入那个状态。我建议，你可以先看看libbehavior每一个Scenario的行为树创建部分的代码，这样应该会有点感觉，另外，可以边参考我网上的文章，边来看libbehavior\BehaviorTree-src里的源码，会好理解很多:)，希望对你有所帮助！欢迎继续来信交流！（状态机和行为树之间转换可以参看<a href="http://www.aisharing.com/archives/439" target="_blank">这里</a>和<a href="http://www.aisharing.com/archives/452" target="_blank">这里</a>）</p>
<p><em>Q：看了你写的一系列文章，写的真不错。我也最进在游戏行业，做AI相关的工作。涉及一些游戏AI设计的问题，能否指教一下。目前我们游戏AI设计的部分考虑NPC的AI部分可配置成AI执行脚本文件方式。我考虑是可以设计AI框架，依据游戏对象的配置，框架决定加载相应的脚本引擎。我看 </em><a href="http://www.aisharing.com/archives/86"><em>《浅谈层次化的AI架构》</em></a><em> 不错，你能不能分享一下AI设计中如何分离AI框架和AI脚本内容的呢。分离的话，感觉状态转换是应该在AI脚本中完成的吧？ 暴露AI引擎的API接口的通用设计一般是怎么样的呢？学习中&#8230;., 谢谢。</em></p>
<p>A：如果想在脚本中写很多AI逻辑的话，如何调试是个比较麻烦的事情，需要比较好的调试接口。我用过一种做法，你可以试试看，就是用脚本做AI的配置，逻辑还是在C++端。C++提供一个个AI逻辑模块，然后在脚本端把它配置起来，因为我用的是行为树（可以参看我博客上关于行为树的相关文章），所以比较容易配置。接口的话，首先最好是引擎能支持反射，然后就能方便的导出函数和变量直接给脚本用，至少我用lua做脚本语言，这样是可行的。</p>
<p><em>Q：您好。今天再搜索层次状态机相关资料的时候进入了您的博客。本人只在游戏中使用过简单的FSM，属于AI初学者，阅读了几篇文章后产生了几个问题:</em></p>
<ul>
<li><em>HFSM和行为树到底是什么关系。我对行为树和状态机的区别还是把握不了…… </em></li>
<li><em>您有一文章说：大部分的AI，都可以分成“决策”和“行为”，二者通过一个双缓冲的“请求层”通信，但是这篇文章您没有举例子，我非常模糊。 </em></li>
<li><em>对“并行节点”我还是很模糊……您可以给我举个例子吗……麻烦了…。</em></li>
</ul>
<p>A：谢谢，针对每一个问题，我的回答如下：</p>
<ul>
<li>HFSM和行为树其实没什么关系，是两种不同的AI结构，区别的话，可以看我在这里的评论<a href="http://www.aisharing.com/archives/90#comment-111">http://www.aisharing.com/archives/90#comment-111</a></li>
<li>有人和你提出了一样的问题，可以参看我这里的回复，<a href="http://www.aisharing.com/archives/86#comment-115">http://www.aisharing.com/archives/86#comment-115</a></li>
<li>比如一个并行节点下有A和B两个节点，那在一次循环里，如果前提满足的话，既会执行A，又会执行B，举个实际的例子，比如要描述“又吃又喝”，就可以用并行节点：）</li>
</ul>
<p><em>Q：最近在学习行为树，有幸拜读了您几篇关于行为树的文章，感觉收获颇多。刚开始接触行为树，所知有限，特来请教几个问题：</em></p>
<ul>
<ul>
<li><em>关于以下代码，在文章评论中，您回复说 input 是传入行为树的参数，那么 request 是指输入的事件吗？比如移动事件、死亡事件等。 如果是事件，那您实例代码的行为树是事件驱动的，还是Tick驱动？</em></li>
</ul>
</ul>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> action = root.FindNextAction(input);</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> <span style="color: #0000ff;">if</span> action is not empty then</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span>     action.Execute(request,  input)  <span style="color: #008000;">//request是输出的请求</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span> <span style="color: #0000ff;">else</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span>     print “no action is available”</pre>
</div>
</div>
<ul>
<li><em>在FSM中，状态进入和退出时可以做相对应的事情，比如进入某个状态身上挂个循环特效，退出该状态将这个特效移除，在行为树上，怎么实现同样的功能？ </em></li>
<li><em>按我现在对行为树的理解，感觉行为树就是将FSM中的状态变成了子树，挂在行为树的相应位置，通过行为树的节点完成状态的迁移。但是，在您文章的总结中提到：”在AI设计过程中，一般来说，我们并不是先有状态机，再去转化成行为树的，当我们选择用行为树的时候，我们就要充分的理解控制节点，前提，节点等概念，并试着用行为树的逻辑方式去思考和设计。”，看后对自己将状态视为子树的理解产生了很大的怀疑，您能详细地解释下这句话吗？不知是否可以提供一张您平时设计的较复杂些的行为树供我学习之用？</em></li>
</ul>
<p>A：谢谢，针对每一个问题，我的回答如下：</p>
<ul>
<li>request是输出的请求，也就是行为树的输出，就像你说的，像移动，死亡等，可以看看我博客上关于分层次的AI架构的相关文章，整个行为树的决策还是在Tick中更新的。</li>
<li>一般我在做行为树的Execute时会分成三个阶段，onEnter() ，onExecute()，onExit()，在第一次进入这个节点的时候，会调用onEnter，在从这个节点切换出去的时候，会调用onExit()。每个行为树的节点，我都会做成这种1P(前提Precondition) + 3E(Enter, Execute, Exit)的结构</li>
<li>行为树的思考方式是指，对于每一个节点我都需要明确知道它的进入条件是什么，它要做的事情是什么。把跳转逻辑写到控制节点中，用控制节点来控制行为树的选择流程。和状态机不同的是，对于行为树的节点，一般不需要知道它是从哪个节点过来的，它只是需要关心，满足什么条件（前提）可以进入这个节点，这些条件的判断依据都是通过行为树的输入参数传入的，所以行为树和黑板的组合是很好用的，可以看看我博客上关于黑板和行为树结合的文章</li>
<li>项目里的行为树图不太好给，呵呵，而且需要解释才能明白。复杂的话，一般就会用到序列节点，并行节点等等。行为树的好处就是只要定义了进入前提，就可以加入到现有的行为树中，这样就越来越复杂了～，不过，这也就是行为树的好处了:)</li>
</ul>
<p><em>Q：Hi，在我们现在的项目里面涉及到AI的一些东西,之前学习了关于你写得行为树,正好可以发挥一下,现在有一个问题,就是关于怪物行走,怪物行走我们这边的做法就是发目标A*路径给客户端通知其他玩家,然后,怪物每秒刷一步就走一步&#8230;这样的做法&#8230;我想问问&#8230;你们那边关于怪物行走是如何实现的呢?巡逻和追击 两个地方行为怎么做互斥?</em></p>
<p>A：针对你的问题，我在想哦，对于怪物移动的话，可以分两个部分，一般的移动，就是你说的巡逻，可以不需要用A＊，因为毕竟A＊的开销是比较大的，因为怪物一般的移动不会太复杂，而且一般是在一定的区域内移动（当然这是看你们怎么设计了），可以在区域内设置几个路点，然后每过一段时间选择临近的路点就可以了，也就是仅仅做一步的“寻路”。如果是要追击玩家的话，可以尝试用到A＊。</p>
<p>网页游戏我没做过，其他游戏里，我们用到过同步位置信息的方式，也就是每隔一段时间同步一次位置，然后两次同步之间的移动，都是客户端本地模拟。如果模拟后的位置和服务器上的位置有偏差，简单的做法是瞬移到服务器上的位置，好一点的话就是平滑的从客户端现在的位置移动到服务器上的位置。</p>
<p>巡逻和追击我觉得比较大的不同是，巡逻中是没有目标敌人的，而追击是存在目标敌人的，所以这样就可以做到互斥了。</p>
<p>————————————————————————<br />
作者：Finney<br />
Blog：AI分享站(<a href="http://www.aisharing.com/">http://www.aisharing.com/</a>)<br />
Email：finneytang@gmail.com<br />
本文欢迎转载和引用，请保留本说明并注明出处<br />
————————————————————————</p>
<hr /><h2>评论</h2><ul><li><a href="http://www.aisharing.com/archives/510">2011 年 12 月 21 日</a>, mingteng 评论到: Finney,您文章里的行为树使用什么工具画的？</li><li><a href="http://www.aisharing.com/archives/510">2011 年 12 月 21 日</a>, <a href='http://www.aisharing.com' rel='external nofollow' class='url'>Finney</a> 评论到: 一个在线的画图工具，https://www.lucidchart.com/，挺好用的</li><li><a href="http://www.aisharing.com/archives/510">2011 年 12 月 27 日</a>, davylew 评论到: Finney：您好~
 非常喜欢您博客中讨论的内容，获益良多。刚开始接触BT，目前感觉GOAP对于worldState的传递以及修改颇为麻烦，以及需要对Action和GOAPGOAL都需要定义优先级以及COST，还有A*用来进行Plan可能有点影响性能等。
您能评价一下GOAP与BT的优劣吗？以及在什么情况下如何进行选择.谢谢。

davylew</li><li><a href="http://www.aisharing.com/archives/510">2011 年 12 月 27 日</a>, <a href='http://www.aisharing.com' rel='external nofollow' class='url'>Finney</a> 评论到: 关于正统的GOAP，我在实际的项目里还没有用过，仅看过几篇关于GOAP的论文，不过这种基于“计划器（Planner）”架构的AI，我在项目里看到过一种，感觉开销有点大，我写过一片博文，谈了下我对这种架构的简要理解，<a href="http://www.aisharing.com/archives/408" title="目标导向（Goal-Oriented）的AI架构" rel="nofollow">这里</a>，可以一起探讨下</li></ul><a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/2.5/cn/"><img alt="知识共享许可协议" style="border-width:0" src="http://i.creativecommons.org/l/by-nc-nd/2.5/cn/88x31.png" /></a><br />本博客作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/2.5/cn/">知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议</a>进行许可。<hr /><a href="http://www.aisharing.com/donation" target="_blank"><img src="http://aisharing.com/wp/wp-content/uploads/2011/08/donate.png" alt="捐赠本站" /></a> ]]></content:encoded>
			<wfw:commentRss>http://www.aisharing.com/archives/510/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>AI中的参数系统设计</title>
		<link>http://www.aisharing.com/archives/500</link>
		<comments>http://www.aisharing.com/archives/500#comments</comments>
		<pubDate>Sat, 03 Dec 2011 01:17:07 +0000</pubDate>
		<dc:creator>Finney</dc:creator>
				<category><![CDATA[AI分享]]></category>
		<category><![CDATA[模块化]]></category>
		<category><![CDATA[编辑器]]></category>
		<category><![CDATA[脚本]]></category>
		<category><![CDATA[设计]]></category>
		<category><![CDATA[调试]]></category>
		<category><![CDATA[逻辑]]></category>
		<category><![CDATA[难易度]]></category>

		<guid isPermaLink="false">http://www.aisharing.com/archives/500</guid>
		<description><![CDATA[（ecto真不好用，居然写得东西都没了，害我只能把这篇文章再写了一遍，郁闷啊，跪求一个mac下的支持wordpress的离线博客编辑软件）

提到参数，在我们写程序的时候，作为一个常用的提醒是，一般都会避免去使用“魔数”（Magic Number），因为它含义不清，比如if(i&#62;5){...}，如果没有任何注释的话，很难有人会理解这个“5”是什么意思。在AI中，这样静态的参数的使用更频繁，有时为了定义一些阈值，有时为了可以给游戏设计人员调整AI行为等等，当这些参数慢慢多起来的时候，就需要更好的来管理，修改和查看，所以，如何设计一个好的参数系统就很有必要了。这个想法也源自我上一个项目的一些教训，在上个项目中，我们最后一共定义了超过1600个参数，由于历史原因，由一个简单的参数系统来维护，但实际操作中，这样的简单系统存在很多问题，首先参数没有很好的文档化，导致游戏设计人员很难搞清楚这个参数到底是干什么的，最后需要程序员的不停参与，工作的粘合度就提高了，另外也没有做到逻辑的关联，无法了解这些参数之间的相互关系。总之，这样那样的问题促成了我对于设计一个新的参数系统的想法。

相较与原来那个参数系统的种种问题，对于一个新的参数系统，我希望它能有如下的特性：
<ul>
	<li>仅支持静态的参数：对于游戏来说是只读的，也就是游戏在运行时不允许修改这些参数，但可以通过外部工具进行调试</li>
	<li>支持文档化</li>
	<li>支持实时的修改：不需要重新启动游戏</li>
	<li>支持序列化到文件：可以将调整好的参数存成文件，以便下一次启动游戏时生效</li>
	<li>支持逻辑关联</li>
	<li>支持自动的合法性检查</li>
	<li>高效和便捷的定义和调用</li>
</ul>
以上列出的大部分特性还是比较容易理解的，对于第5点，我特别的说明一下，假设有一个参数A，当A的值发生变化的时候，逻辑上，参数（B1, B2, B3, … , Bn）也会相应发生变化，那我们就称A和（B1, B2, B3, … , Bn）有“逻辑关联”，也就是A的变化会影响到（B1, B2, B3, … , Bn）的值，反之不成立，也就是说，逻辑关联是单向的。举个例子，比如我们有一个可以调整游戏难度的参数，它关联到敌人的血量，攻击值，防御值，当把游戏难度调成“高”时，血量，攻击值，防御值会加倍，当把游戏的难度调成“低”时，血量，攻击值，防御值会减半。在我们原来的参数系统中，这种逻辑关联是隐含的，也就是说所有的参数并没有直接的相互关联，他们的结构是“平面化”的，彼此相互独立，所以当游戏设计人员想调高游戏难度的时候，他就需要记得相应的要去调整血量，攻击值，防御值。这样很容易造成错误和疏漏，而且这种隐含的逻辑关联无处可循，只能存与程序员活或者游戏设计人员的脑海中。

针对我们的需求，我定义了一个参数P所需要包含的各个部分
<ul>
	<li>可识别的名称</li>
	<li>当前值：该参数当前的值（一定是合法值），供游戏读取</li>
	<li>参数类型：表示这个参数是离散型的，还是连续型的</li>
	<li>上下限数组：给连续型参数检查合法性使用，如果值不在取值区间中，则为不合法的参数值，用数组的原因，主要是可能有多个取值区间，[Vmin1, Vmax1], [Vmin2, Vmax2], … , [Vminn, Vmaxn]</li>
	<li>候选值数组：给离散型参数检查合法性使用，如果值不在候选值数组中，则为不合法的参数值。</li>
	<li>参数描述：对于该参数的简要描述</li>
	<li>趋势描述：对于该参数的变化所带来的影响的简要描述</li>
	<li>逻辑关联项数组：该参数所影响的参数项列表(P1, P2, … , Pn)</li>
	<li>被逻辑关联项数组：能影响该参数的参数项列表(P1, P2, … , Pn)</li>
</ul>
作为补充说明，对于上下限的定义，还需要有一个标志来表示是开区间还是闭区间。在逻辑关联项的数组中，和每个参数项还需要绑定一个非常重要的“关联方式”的实现。关联方式是非常灵活的，可能非常简单，就像我们前面举的那个例子，仅仅是加倍和减半，但有时可能非常复杂，有更多的逻辑计算，所以，我想可以调用一段代码来计算关联方式，可以是虚函数，也可以是一段脚本。更新关联项的方式是递归的，直到所有的关联项都被更新为止。文档化有几个部分组成，一个是名称，要求要可以识别，一个是两段描述，这两个部分很难做到要写成什么格式（文档的东西一般都是自觉自愿），但作为规范是要求定义参数的人能详细填写的，另一个部分就是关联项和合法性提示，这个可以用代码来生成文档信息（试想一下开发环境中的代码提示功能）。

我想，有了这样一个参数结构的定义，要实现一个参数系统来满足我上面的要求，并不是一件很难的事情，当然现在的这些只是我一个粗略的想法，还没有实现，有兴趣的同学可以实现一个，我有时间的话，也会做一个出来，因为它很独立，可以作为第三方库用在任何的引擎中，实现技巧的话，我想用“反射”（Reflection）做的话，对于序列化，对于做一个可视化的编辑器，对于文档化，都是挺有帮助的，值得一试。

————————————————————————
作者：Finney
Blog：AI分享站(<a href="http://www.aisharing.com/">http://www.aisharing.com/</a>)
Email：finneytang@gmail.com
本文欢迎转载和引用，请保留本说明并注明出处
————————————————————————]]></description>
			<content:encoded><![CDATA[<p>（ecto真不好用，居然写得东西都没了，害我只能把这篇文章再写了一遍，郁闷啊，跪求一个mac下的支持wordpress的离线博客编辑软件）</p>
<p>提到参数，在我们写程序的时候，作为一个常用的提醒是，一般都会避免去使用“魔数”（Magic Number），因为它含义不清，比如if(i&gt;5){&#8230;}，如果没有任何注释的话，很难有人会理解这个“5”是什么意思。在AI中，这样静态的参数的使用更频繁，有时为了定义一些阈值，有时为了可以给游戏设计人员调整AI行为等等，当这些参数慢慢多起来的时候，就需要更好的来管理，修改和查看，所以，如何设计一个好的参数系统就很有必要了。这个想法也源自我上一个项目的一些教训，在上个项目中，我们最后一共定义了超过1600个参数，由于历史原因，由一个简单的参数系统来维护，但实际操作中，这样的简单系统存在很多问题，首先参数没有很好的文档化，导致游戏设计人员很难搞清楚这个参数到底是干什么的，最后需要程序员的不停参与，工作的粘合度就提高了，另外也没有做到逻辑的关联，无法了解这些参数之间的相互关系。总之，这样那样的问题促成了我对于设计一个新的参数系统的想法。</p>
<p>相较与原来那个参数系统的种种问题，对于一个新的参数系统，我希望它能有如下的特性：</p>
<ul>
<li>仅支持静态的参数：对于游戏来说是只读的，也就是游戏在运行时不允许修改这些参数，但可以通过外部工具进行调试</li>
<li>支持文档化</li>
<li>支持实时的修改：不需要重新启动游戏</li>
<li>支持序列化到文件：可以将调整好的参数存成文件，以便下一次启动游戏时生效</li>
<li>支持逻辑关联</li>
<li>支持自动的合法性检查</li>
<li>高效和便捷的定义和调用</li>
</ul>
<p>以上列出的大部分特性还是比较容易理解的，对于第5点，我特别的说明一下，假设有一个参数A，当A的值发生变化的时候，逻辑上，参数（B1, B2, B3, … , Bn）也会相应发生变化，那我们就称A和（B1, B2, B3, … , Bn）有“逻辑关联”，也就是A的变化会影响到（B1, B2, B3, … , Bn）的值，反之不成立，也就是说，逻辑关联是单向的。举个例子，比如我们有一个可以调整游戏难度的参数，它关联到敌人的血量，攻击值，防御值，当把游戏难度调成“高”时，血量，攻击值，防御值会加倍，当把游戏的难度调成“低”时，血量，攻击值，防御值会减半。在我们原来的参数系统中，这种逻辑关联是隐含的，也就是说所有的参数并没有直接的相互关联，他们的结构是“平面化”的，彼此相互独立，所以当游戏设计人员想调高游戏难度的时候，他就需要记得相应的要去调整血量，攻击值，防御值。这样很容易造成错误和疏漏，而且这种隐含的逻辑关联无处可循，只能存与程序员活或者游戏设计人员的脑海中。</p>
<p>针对我们的需求，我定义了一个参数P所需要包含的各个部分</p>
<ul>
<li>可识别的名称</li>
<li>当前值：该参数当前的值（一定是合法值），供游戏读取</li>
<li>参数类型：表示这个参数是离散型的，还是连续型的</li>
<li>上下限数组：给连续型参数检查合法性使用，如果值不在取值区间中，则为不合法的参数值，用数组的原因，主要是可能有多个取值区间，[Vmin1, Vmax1], [Vmin2, Vmax2], … , [Vminn, Vmaxn]</li>
<li>候选值数组：给离散型参数检查合法性使用，如果值不在候选值数组中，则为不合法的参数值。</li>
<li>参数描述：对于该参数的简要描述</li>
<li>趋势描述：对于该参数的变化所带来的影响的简要描述</li>
<li>逻辑关联项数组：该参数所影响的参数项列表(P1, P2, … , Pn)</li>
<li>被逻辑关联项数组：能影响该参数的参数项列表(P1, P2, … , Pn)</li>
</ul>
<p>作为补充说明，对于上下限的定义，还需要有一个标志来表示是开区间还是闭区间。在逻辑关联项的数组中，和每个参数项还需要绑定一个非常重要的“关联方式”的实现。关联方式是非常灵活的，可能非常简单，就像我们前面举的那个例子，仅仅是加倍和减半，但有时可能非常复杂，有更多的逻辑计算，所以，我想可以调用一段代码来计算关联方式，可以是虚函数，也可以是一段脚本。更新关联项的方式是递归的，直到所有的关联项都被更新为止。文档化有几个部分组成，一个是名称，要求要可以识别，一个是两段描述，这两个部分很难做到要写成什么格式（文档的东西一般都是自觉自愿），但作为规范是要求定义参数的人能详细填写的，另一个部分就是关联项和合法性提示，这个可以用代码来生成文档信息（试想一下开发环境中的代码提示功能）。</p>
<p>我想，有了这样一个参数结构的定义，要实现一个参数系统来满足我上面的要求，并不是一件很难的事情，当然现在的这些只是我一个粗略的想法，还没有实现，有兴趣的同学可以实现一个，我有时间的话，也会做一个出来，因为它很独立，可以作为第三方库用在任何的引擎中，实现技巧的话，我想用“反射”（Reflection）做的话，对于序列化，对于做一个可视化的编辑器，对于文档化，都是挺有帮助的，值得一试。</p>
<p>————————————————————————<br />
作者：Finney<br />
Blog：AI分享站(<a href="http://www.aisharing.com/">http://www.aisharing.com/</a>)<br />
Email：finneytang@gmail.com<br />
本文欢迎转载和引用，请保留本说明并注明出处<br />
————————————————————————</p>
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/2.5/cn/"><img alt="知识共享许可协议" style="border-width:0" src="http://i.creativecommons.org/l/by-nc-nd/2.5/cn/88x31.png" /></a><br />本博客作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/2.5/cn/">知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议</a>进行许可。<hr /><a href="http://www.aisharing.com/donation" target="_blank"><img src="http://aisharing.com/wp/wp-content/uploads/2011/08/donate.png" alt="捐赠本站" /></a> ]]></content:encoded>
			<wfw:commentRss>http://www.aisharing.com/archives/500/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>如何处理被动式的行为请求</title>
		<link>http://www.aisharing.com/archives/495</link>
		<comments>http://www.aisharing.com/archives/495#comments</comments>
		<pubDate>Sat, 05 Nov 2011 05:45:53 +0000</pubDate>
		<dc:creator>Finney</dc:creator>
				<category><![CDATA[AI分享]]></category>
		<category><![CDATA[Behavior]]></category>
		<category><![CDATA[Behavior Tree]]></category>
		<category><![CDATA[Decision]]></category>
		<category><![CDATA[信息]]></category>
		<category><![CDATA[决策]]></category>
		<category><![CDATA[架构]]></category>
		<category><![CDATA[游戏世界]]></category>
		<category><![CDATA[行为]]></category>
		<category><![CDATA[行为树]]></category>
		<category><![CDATA[请求]]></category>

		<guid isPermaLink="false">http://www.aisharing.com/archives/495</guid>
		<description><![CDATA[记得以前我在博客中，提到过一种<a href="http://www.aisharing.com/archives/86" target="_blank">层次化的AI架构</a>，这种架构的核心就是定义了“请求层”的概念，用来分隔决策和行为，并通过行为请求来清晰的定义了决策和行为之间的输入输出关系，不过，当我们仔细审视这个结构的时候，发现其中貌似缺失了对于某种情况的处理，这就是我今天要谈到，如何处理“被动式的行为请求”

一般来说，我们通常所认为的决策是一种“主动式（Active）的行为请求”，比如，我按了个键，玩家所控制的角色就是做出某些行为，或者AI通过对于当前情况的判断，做出了下一步的行为决策，所以说，主动式的行为请求，就是表达了一种“我想要去做什么”的语义。在游戏中，大部分的行为都是输入主动式的行为，那FPS（第一人称射击）类游戏来说，它可能包含的主动式行为有，移动，跳跃，射击，与游戏场景中物体的交互（拾取物品，开门）等等，当玩家和NPC在行动的时候，就是在这些已经定义的主动式行为中，做出决策。在我们以前提到的那种层次化的AI架构中，可以很好的满足这些需求。

但在游戏中，还是一些行为并不在主动式的行为中，比如死亡，当角色生命值等于0的时候，游戏系统就会触发这个行为请求，它不是由玩家或者NPC所发起的，而是有游戏系统判断出某些条件满足的时候，自动触发并赋予角色的行为请求。这一类的请求，我们就可以称之为“被动式（Passive）的行为请求”，在谈如何处理前，我们可以先看看这种请求有什么样的特点。

首先它<span style="color: #800080;"><strong>可能会覆盖原本的主动式的行为请求</strong></span>。比如玩家想要快跑向前走，那他就会发出一个“快速移动”的行为请求，但就在这个时候，角色正好被横在路中间的一根树干绊了一下，当游戏系统检测到这个事件的时候，就会触发一个“绊倒”的行为请求，期望做一个摔倒的行为。显然，“绊倒”这个行为不是玩家主动做出的，是属于被动式的请求，而且这个请求应该被立刻响应（要不就不真实了），所以，当比较这两个请求的时候，我们就会用“绊倒”覆盖掉原先的“快速移动”，将其作为当前请求传给行为层。那我这里为什么说是“可能会覆盖”呢？因为并不是所有的被动请求都需要被响应的，这取决于当前产生的主动和被动，这两个请求的优先级设定，这种优先级设定一般来说会来自与游戏设计的需要。假设，我们在游戏中有一个行为是“超级移动”，这种行为很强大，效果也很炫，是游戏的一个很大的卖点和可玩点，所以游戏设计者就希望尽量能让玩家触发出这种行为，以提到游戏的游戏性和画面效果，所以当玩家触发“超级移动”的时候，我们可能就会忽略掉同时触发的“绊倒”这个行为请求。

其次，<span style="color: #800080;"><strong>它是触发式的，所以意味着同一时刻它可能会有多个</strong></span>。试想一下，对于主动式请求来说，因为角色不可能分身嘛（除非是超人游戏），所以一般来说，同一时刻主动请求只会有一个，也就说，你不可能既想做这个，又想做那个。而被动式请求，就完全不同，在同一时刻，可能会有多个条件被满足，然后同时触发了多个被动行为请求，比如在被绊倒的那一刻，你中了一枪，然后生命值正好减到0（真倒霉。。），所以同时就产生了“绊倒”，“死亡”两个被动请求。那我们就面对，多个请求需要权衡的情况，在上述的例子里，显然，“死亡”的优先级应该是最高的，所以它会成为最后的请求被送到行为层。

还有一个特点就是关于被动请求产生源，<strong><span style="color: #800080;">在角色与角色交互的时候会非常容易产生被动式的行为请求</span></strong>。举个FIFA的例子（对此我研究了很多:)），在足球游戏中，我们可以看到很多球员间交互的例子，比如碰撞后的摔倒，争头球的挤位，带球过程中的拉扯，躲避铲球的跳跃，等等，这些都是被动式的行为请求，是玩家不可操控的，而且这些请求，不像我前面举到的例子，是单个人身上的，而是属于一种“相互作用（Interaction）”的行为请求。它的触发和处理，都会牵涉到两个，甚至多个人的判断。

谈完了特点，接下去来思考下如何来处理这些请求。当然，处理的方案多种多样，我先抛砖引玉的谈谈我想到的两种结构。还是基于原有的层次化的AI架构模型，做一些小的修改。第一种方案采用的是比较直观的集中化的处理。先定义一个结构表示当前时刻所有产生的行为请求。
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">struct</span> CandidateRequests</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span>     Request        m_ActiveRequest;      <span style="color: #008000;">//主动请求，假设同一时刻只有一个 </span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span>     Array&#60;Request&#62; m_PassiveRequests;    <span style="color: #008000;">//被动请求，同一时刻可以有多个</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span> }</pre>
</div>
</div>
然后，我们定义一个模块来负责处理这个结构，并产生一个最终的行为请求，值得注意的是，我们把当前正在处理的请求也作为考虑的因素之一，传入这个函数中。
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> RequestFinalizedModule</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">public</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span>     <span style="color: #0000ff;">static</span> Request GetFinalRequest(</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span>                 <span style="color: #0000ff;">const</span> Entity&#38; entity,</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span>                 <span style="color: #0000ff;">const</span> Request&#38; curReq,</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum7" style="color: #606060;"> 7:</span>                 <span style="color: #0000ff;">const</span> CandidateRequests&#38; nextReq)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum8" style="color: #606060;"> 8:</span>     {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum9" style="color: #606060;"> 9:</span>         <span style="color: #008000;">//添加选择的规则，根据当前请求和下一时刻的候选请求，抉择出最终应该做的行为</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum10" style="color: #606060;"> 10:</span>     }</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum11" style="color: #606060;"> 11:</span> }</pre>
</div>
</div>
集中式的处理方式将所以处理的可能性放到一个函数中，这样的好处是便于排查可能导致的选择不正确的问题，而且对于原有的结构没有什么更改，仅仅是在决策和请求层之间加了一个处理模块。缺点就是这块的代码，随着优先级关系的复杂，会显的相对比较乱，不过将脏代码堆在一个地方，也是一种不错的设计:)，下面是修改后的架构图

<a href="http://aisharing.com/wp/wp-content/uploads/2011/11/layeredaiarchitecture_modified.png"><img style="display: inline; border: 0px;" title="layered-ai-architecture_modified" src="http://aisharing.com/wp/wp-content/uploads/2011/11/layeredaiarchitecture_modified_thumb.png" alt="layered-ai-architecture_modified" width="448" height="336" border="0" /></a>

另一种方案是让游戏系统不直接触发被动式请求，而是触发一些标记，然后AI在决策的时候将这些标记作为决策参考信息的一部分，最终做出一个合理的行为决策，这种方案延伸了决策部分的定义，使其即能产生主动式请求，也能产生被动式请求。如果用行为树的话，可以非常好的表示出这种优先级的关系，如下图：

<a href="http://aisharing.com/wp/wp-content/uploads/2011/11/btflagpassiveaction.png"><img style="display: inline; border: 0px;" title="bt-flag-passive-action" src="http://aisharing.com/wp/wp-content/uploads/2011/11/btflagpassiveaction_thumb.png" alt="bt-flag-passive-action" width="337" height="280" border="0" /></a>

这种方式的优点是可以用到行为树本身对于优先级处理的优势，而不需要额外的添加模块，只需修改原本行为树的设计即可，作为游戏信息的一部分，也可以沿用原有的收集游戏信息的相关模块，做适当的扩展就可以了。缺点是，由于我们将原有的触发式的模式，变成了轮询式的，所以，可能会降低行为树的些许效率，而且对于行为树的设计也提出了更高的要求。不过从架构上来看，是比较清晰，并且能很好的融入原有的设计。修改的架构图如下：

<a href="http://aisharing.com/wp/wp-content/uploads/2011/11/layeredaiarchitecture_modified_2.png"><img style="display: inline; border: 0px;" title="layered-ai-architecture_modified_2" src="http://aisharing.com/wp/wp-content/uploads/2011/11/layeredaiarchitecture_modified_2_thumb.png" alt="layered-ai-architecture_modified_2" width="456" height="284" border="0" /></a>

好，就聊到这里了，不知道大家有什么想法，欢迎一起留言讨论。

————————————————————————
作者：Finney
Blog：AI分享站(<a href="http://www.aisharing.com/">http://www.aisharing.com/</a>)
Email：finneytang@gmail.com
本文欢迎转载和引用，请保留本说明并注明出处
————————————————————————]]></description>
			<content:encoded><![CDATA[<p>记得以前我在博客中，提到过一种<a href="http://www.aisharing.com/archives/86" target="_blank">层次化的AI架构</a>，这种架构的核心就是定义了“请求层”的概念，用来分隔决策和行为，并通过行为请求来清晰的定义了决策和行为之间的输入输出关系，不过，当我们仔细审视这个结构的时候，发现其中貌似缺失了对于某种情况的处理，这就是我今天要谈到，如何处理“被动式的行为请求”</p>
<p>一般来说，我们通常所认为的决策是一种“主动式（Active）的行为请求”，比如，我按了个键，玩家所控制的角色就是做出某些行为，或者AI通过对于当前情况的判断，做出了下一步的行为决策，所以说，主动式的行为请求，就是表达了一种“我想要去做什么”的语义。在游戏中，大部分的行为都是输入主动式的行为，那FPS（第一人称射击）类游戏来说，它可能包含的主动式行为有，移动，跳跃，射击，与游戏场景中物体的交互（拾取物品，开门）等等，当玩家和NPC在行动的时候，就是在这些已经定义的主动式行为中，做出决策。在我们以前提到的那种层次化的AI架构中，可以很好的满足这些需求。</p>
<p>但在游戏中，还是一些行为并不在主动式的行为中，比如死亡，当角色生命值等于0的时候，游戏系统就会触发这个行为请求，它不是由玩家或者NPC所发起的，而是有游戏系统判断出某些条件满足的时候，自动触发并赋予角色的行为请求。这一类的请求，我们就可以称之为“被动式（Passive）的行为请求”，在谈如何处理前，我们可以先看看这种请求有什么样的特点。</p>
<p>首先它<span style="color: #800080;"><strong>可能会覆盖原本的主动式的行为请求</strong></span>。比如玩家想要快跑向前走，那他就会发出一个“快速移动”的行为请求，但就在这个时候，角色正好被横在路中间的一根树干绊了一下，当游戏系统检测到这个事件的时候，就会触发一个“绊倒”的行为请求，期望做一个摔倒的行为。显然，“绊倒”这个行为不是玩家主动做出的，是属于被动式的请求，而且这个请求应该被立刻响应（要不就不真实了），所以，当比较这两个请求的时候，我们就会用“绊倒”覆盖掉原先的“快速移动”，将其作为当前请求传给行为层。那我这里为什么说是“可能会覆盖”呢？因为并不是所有的被动请求都需要被响应的，这取决于当前产生的主动和被动，这两个请求的优先级设定，这种优先级设定一般来说会来自与游戏设计的需要。假设，我们在游戏中有一个行为是“超级移动”，这种行为很强大，效果也很炫，是游戏的一个很大的卖点和可玩点，所以游戏设计者就希望尽量能让玩家触发出这种行为，以提到游戏的游戏性和画面效果，所以当玩家触发“超级移动”的时候，我们可能就会忽略掉同时触发的“绊倒”这个行为请求。</p>
<p>其次，<span style="color: #800080;"><strong>它是触发式的，所以意味着同一时刻它可能会有多个</strong></span>。试想一下，对于主动式请求来说，因为角色不可能分身嘛（除非是超人游戏），所以一般来说，同一时刻主动请求只会有一个，也就说，你不可能既想做这个，又想做那个。而被动式请求，就完全不同，在同一时刻，可能会有多个条件被满足，然后同时触发了多个被动行为请求，比如在被绊倒的那一刻，你中了一枪，然后生命值正好减到0（真倒霉。。），所以同时就产生了“绊倒”，“死亡”两个被动请求。那我们就面对，多个请求需要权衡的情况，在上述的例子里，显然，“死亡”的优先级应该是最高的，所以它会成为最后的请求被送到行为层。</p>
<p>还有一个特点就是关于被动请求产生源，<strong><span style="color: #800080;">在角色与角色交互的时候会非常容易产生被动式的行为请求</span></strong>。举个FIFA的例子（对此我研究了很多:)），在足球游戏中，我们可以看到很多球员间交互的例子，比如碰撞后的摔倒，争头球的挤位，带球过程中的拉扯，躲避铲球的跳跃，等等，这些都是被动式的行为请求，是玩家不可操控的，而且这些请求，不像我前面举到的例子，是单个人身上的，而是属于一种“相互作用（Interaction）”的行为请求。它的触发和处理，都会牵涉到两个，甚至多个人的判断。</p>
<p>谈完了特点，接下去来思考下如何来处理这些请求。当然，处理的方案多种多样，我先抛砖引玉的谈谈我想到的两种结构。还是基于原有的层次化的AI架构模型，做一些小的修改。第一种方案采用的是比较直观的集中化的处理。先定义一个结构表示当前时刻所有产生的行为请求。</p>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">struct</span> CandidateRequests</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span>     Request        m_ActiveRequest;      <span style="color: #008000;">//主动请求，假设同一时刻只有一个 </span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span>     Array&lt;Request&gt; m_PassiveRequests;    <span style="color: #008000;">//被动请求，同一时刻可以有多个</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span> }</pre>
</div>
</div>
<p>然后，我们定义一个模块来负责处理这个结构，并产生一个最终的行为请求，值得注意的是，我们把当前正在处理的请求也作为考虑的因素之一，传入这个函数中。</p>
<div id="codeSnippetWrapper" style="font-size: 8pt; margin: 20px 0px 10px; overflow: auto; width: 97.5%; cursor: text; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; max-height: 200px; border: silver 1px solid; padding: 4px;">
<div id="codeSnippet" style="font-size: 8pt; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;">
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum1" style="color: #606060;"> 1:</span> <span style="color: #0000ff;">class</span> RequestFinalizedModule</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum2" style="color: #606060;"> 2:</span> {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum3" style="color: #606060;"> 3:</span> <span style="color: #0000ff;">public</span>:</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum4" style="color: #606060;"> 4:</span>     <span style="color: #0000ff;">static</span> Request GetFinalRequest(</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum5" style="color: #606060;"> 5:</span>                 <span style="color: #0000ff;">const</span> Entity&amp; entity,</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum6" style="color: #606060;"> 6:</span>                 <span style="color: #0000ff;">const</span> Request&amp; curReq,</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum7" style="color: #606060;"> 7:</span>                 <span style="color: #0000ff;">const</span> CandidateRequests&amp; nextReq)</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum8" style="color: #606060;"> 8:</span>     {</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum9" style="color: #606060;"> 9:</span>         <span style="color: #008000;">//添加选择的规则，根据当前请求和下一时刻的候选请求，抉择出最终应该做的行为</span></pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum10" style="color: #606060;"> 10:</span>     }</pre>
<pre style="font-size: 8pt; margin: 0em; overflow: visible; width: 100%; color: black; direction: ltr; line-height: 12pt; font-family: 'Courier New', courier, monospace; background-color: #f4f4f4; text-align: left; border-style: none; padding: 0px;"><span id="lnum11" style="color: #606060;"> 11:</span> }</pre>
</div>
</div>
<p>集中式的处理方式将所以处理的可能性放到一个函数中，这样的好处是便于排查可能导致的选择不正确的问题，而且对于原有的结构没有什么更改，仅仅是在决策和请求层之间加了一个处理模块。缺点就是这块的代码，随着优先级关系的复杂，会显的相对比较乱，不过将脏代码堆在一个地方，也是一种不错的设计:)，下面是修改后的架构图</p>
<p><a href="http://aisharing.com/wp/wp-content/uploads/2011/11/layeredaiarchitecture_modified.png"><img style="display: inline; border: 0px;" title="layered-ai-architecture_modified" src="http://aisharing.com/wp/wp-content/uploads/2011/11/layeredaiarchitecture_modified_thumb.png" alt="layered-ai-architecture_modified" width="448" height="336" border="0" /></a></p>
<p>另一种方案是让游戏系统不直接触发被动式请求，而是触发一些标记，然后AI在决策的时候将这些标记作为决策参考信息的一部分，最终做出一个合理的行为决策，这种方案延伸了决策部分的定义，使其即能产生主动式请求，也能产生被动式请求。如果用行为树的话，可以非常好的表示出这种优先级的关系，如下图：</p>
<p><a href="http://aisharing.com/wp/wp-content/uploads/2011/11/btflagpassiveaction.png"><img style="display: inline; border: 0px;" title="bt-flag-passive-action" src="http://aisharing.com/wp/wp-content/uploads/2011/11/btflagpassiveaction_thumb.png" alt="bt-flag-passive-action" width="337" height="280" border="0" /></a></p>
<p>这种方式的优点是可以用到行为树本身对于优先级处理的优势，而不需要额外的添加模块，只需修改原本行为树的设计即可，作为游戏信息的一部分，也可以沿用原有的收集游戏信息的相关模块，做适当的扩展就可以了。缺点是，由于我们将原有的触发式的模式，变成了轮询式的，所以，可能会降低行为树的些许效率，而且对于行为树的设计也提出了更高的要求。不过从架构上来看，是比较清晰，并且能很好的融入原有的设计。修改的架构图如下：</p>
<p><a href="http://aisharing.com/wp/wp-content/uploads/2011/11/layeredaiarchitecture_modified_2.png"><img style="display: inline; border: 0px;" title="layered-ai-architecture_modified_2" src="http://aisharing.com/wp/wp-content/uploads/2011/11/layeredaiarchitecture_modified_2_thumb.png" alt="layered-ai-architecture_modified_2" width="456" height="284" border="0" /></a></p>
<p>好，就聊到这里了，不知道大家有什么想法，欢迎一起留言讨论。</p>
<p>————————————————————————<br />
作者：Finney<br />
Blog：AI分享站(<a href="http://www.aisharing.com/">http://www.aisharing.com/</a>)<br />
Email：finneytang@gmail.com<br />
本文欢迎转载和引用，请保留本说明并注明出处<br />
————————————————————————</p>
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/2.5/cn/"><img alt="知识共享许可协议" style="border-width:0" src="http://i.creativecommons.org/l/by-nc-nd/2.5/cn/88x31.png" /></a><br />本博客作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/2.5/cn/">知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议</a>进行许可。<hr /><a href="http://www.aisharing.com/donation" target="_blank"><img src="http://aisharing.com/wp/wp-content/uploads/2011/08/donate.png" alt="捐赠本站" /></a> ]]></content:encoded>
			<wfw:commentRss>http://www.aisharing.com/archives/495/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>黄婆卖瓜，自卖自夸</title>
		<link>http://www.aisharing.com/archives/481</link>
		<comments>http://www.aisharing.com/archives/481#comments</comments>
		<pubDate>Thu, 27 Oct 2011 15:42:07 +0000</pubDate>
		<dc:creator>Finney</dc:creator>
				<category><![CDATA[程序员心情]]></category>
		<category><![CDATA[游戏世界]]></category>

		<guid isPermaLink="false">http://www.aisharing.com/?p=481</guid>
		<description><![CDATA[&#160;

&#160;

<img class="size-medium wp-image-482 aligncenter" title="football city stars online logo" src="http://aisharing.com/wp/wp-content/uploads/2011/10/logo_final-300x154.png" alt="football city stars online logo" width="300" height="154" />
<p style="text-align: center;"><a href="http://zq.upaidui.com/" target="_blank">官方网站由此进入</a></p>
《全民足球》是育碧开发的首款休闲类网络游戏。游戏以街头足球为主题，基于城市街区的真实地图概念，支持8名玩家进行同场竞技。游戏拥有独一无二的角色扮演加足球竞技的游戏模式，玩家可创建各具特点的角色球员，通过比赛和任务，提升其属性技能和天赋，获取奖励和成就。玩家并可基于所在城市街区创建足球俱乐部，和同城玩家一起创造辉煌。

拥有高品质全物理运算的核心游戏性，支持2对2, 3对3和4对4玩家间比赛，3大游戏模式: 单人训练, 自由对战和组队对战，守门员为AI。

玩家可以按自己喜好创建球员角色，自定义项包括性别、场上位置、惯用脚、身高、肤色、脸型、发型和着装等。

玩家也可以在游戏内商店购买其他服装和形象来装扮角色。

独一无二的角色扮演+足球游戏模式，3个球员职业: 前锋, 中场和后卫，6大球员属性: 力量, 速度, 传球, 射门, 带球和防守，每个只有拥有各具特色的技能和天赋。

所有的角色系统均可按玩家喜好升级。

团队配合可以增加士气能量。当士气槽积满后, 会触发团队高潮时刻，在团队高潮时刻, 球员属性会大幅提升, 部分技能也会触发更强大更炫酷的动作。

任务系统有一系列目标任务让玩家在了解游戏的同时获得经验和游戏币。主线剧情任务帮助玩家从玩家从普通球员成为一个球星。

真实地图系统在游戏中内建了和真实世界一致的地图，让玩家更容易和同城同区的其他玩家进行游戏。

玩家从自己所在的街区开始游戏, 通过角色升级逐步探索所在城市乃至世界。

其他社交功能包括: 好友系统, 聊天, 邮箱, 足球俱乐部系统等。

玩家可以在游戏内商店购买服装, 道具, 技能等物品，在购买服装前可以进行试穿。玩家同样可以把商品作为礼物赠送给其他玩家。

————————————————————————
作者：Finney
Blog：AI分享站(<a href="http://www.aisharing.com/">http://www.aisharing.com/</a>)
Email：finneytang@gmail.com
本文欢迎转载和引用，请保留本说明并注明出处
————————————————————————]]></description>
			<content:encoded><![CDATA[<p>&nbsp;</p>
<p>&nbsp;</p>
<p><img class="size-medium wp-image-482 aligncenter" title="football city stars online logo" src="http://aisharing.com/wp/wp-content/uploads/2011/10/logo_final-300x154.png" alt="football city stars online logo" width="300" height="154" /></p>
<p style="text-align: center;"><a href="http://zq.upaidui.com/" target="_blank">官方网站由此进入</a></p>
<p>《全民足球》是育碧开发的首款休闲类网络游戏。游戏以街头足球为主题，基于城市街区的真实地图概念，支持8名玩家进行同场竞技。游戏拥有独一无二的角色扮演加足球竞技的游戏模式，玩家可创建各具特点的角色球员，通过比赛和任务，提升其属性技能和天赋，获取奖励和成就。玩家并可基于所在城市街区创建足球俱乐部，和同城玩家一起创造辉煌。</p>
<p>拥有高品质全物理运算的核心游戏性，支持2对2, 3对3和4对4玩家间比赛，3大游戏模式: 单人训练, 自由对战和组队对战，守门员为AI。</p>
<p>玩家可以按自己喜好创建球员角色，自定义项包括性别、场上位置、惯用脚、身高、肤色、脸型、发型和着装等。</p>
<p>玩家也可以在游戏内商店购买其他服装和形象来装扮角色。</p>
<p>独一无二的角色扮演+足球游戏模式，3个球员职业: 前锋, 中场和后卫，6大球员属性: 力量, 速度, 传球, 射门, 带球和防守，每个只有拥有各具特色的技能和天赋。</p>
<p>所有的角色系统均可按玩家喜好升级。</p>
<p>团队配合可以增加士气能量。当士气槽积满后, 会触发团队高潮时刻，在团队高潮时刻, 球员属性会大幅提升, 部分技能也会触发更强大更炫酷的动作。</p>
<p>任务系统有一系列目标任务让玩家在了解游戏的同时获得经验和游戏币。主线剧情任务帮助玩家从玩家从普通球员成为一个球星。</p>
<p>真实地图系统在游戏中内建了和真实世界一致的地图，让玩家更容易和同城同区的其他玩家进行游戏。</p>
<p>玩家从自己所在的街区开始游戏, 通过角色升级逐步探索所在城市乃至世界。</p>
<p>其他社交功能包括: 好友系统, 聊天, 邮箱, 足球俱乐部系统等。</p>
<p>玩家可以在游戏内商店购买服装, 道具, 技能等物品，在购买服装前可以进行试穿。玩家同样可以把商品作为礼物赠送给其他玩家。</p>
<p>————————————————————————<br />
作者：Finney<br />
Blog：AI分享站(<a href="http://www.aisharing.com/">http://www.aisharing.com/</a>)<br />
Email：finneytang@gmail.com<br />
本文欢迎转载和引用，请保留本说明并注明出处<br />
————————————————————————</p>
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/2.5/cn/"><img alt="知识共享许可协议" style="border-width:0" src="http://i.creativecommons.org/l/by-nc-nd/2.5/cn/88x31.png" /></a><br />本博客作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/2.5/cn/">知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议</a>进行许可。<hr /><a href="http://www.aisharing.com/donation" target="_blank"><img src="http://aisharing.com/wp/wp-content/uploads/2011/08/donate.png" alt="捐赠本站" /></a> ]]></content:encoded>
			<wfw:commentRss>http://www.aisharing.com/archives/481/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>“组合式”实体（Entity）的实现</title>
		<link>http://www.aisharing.com/archives/475</link>
		<comments>http://www.aisharing.com/archives/475#comments</comments>
		<pubDate>Thu, 27 Oct 2011 15:18:16 +0000</pubDate>
		<dc:creator>Finney</dc:creator>
				<category><![CDATA[AI分享]]></category>
		<category><![CDATA[实体]]></category>
		<category><![CDATA[抽象]]></category>
		<category><![CDATA[架构]]></category>
		<category><![CDATA[组合]]></category>
		<category><![CDATA[继承]]></category>

		<guid isPermaLink="false">http://www.aisharing.com/?p=475</guid>
		<description><![CDATA[最近在做项目前期的一些调研的工作，研究并参考了几个引擎和框架的设计，包括内部引擎，商业引擎，和开源引擎，通过比较和学习后，觉得对于游戏中的实体实现，用“组合”的设计模式会比用“继承”的更为便利，想到我早些时候的一个项目里用到的一个引擎，也是实现了组合式的实体，而且对于AI程序员来说也是和“游戏实体”打交道最多的，一个好的设计可以大大的提高代码的质量和可维护性。所以，我觉得很有必要在这里记录一下，也和大家一起分享一下我的一些心得。

在学习面向对象编程的时候，一个很重要的概念就是“万物皆对象”，我们可以把现实世界的物体抽象成一个个的Object，并且通过继承的方式实现多样化的对象集。这个是面向对象编程的一些很重要的概念。我想学过的同学都应该对此非常的熟悉。

游戏中也是一样的，一般游戏内都会抽象出一个称之为“游戏实体”（Entity）的类，当然也有直接叫Object的，不管名字怎么样，概念上是相通的，总之都是对于游戏中可能的任何物体一种提炼。在引擎中用继承的方式扩展Entity是一种比较常见和直观的方式，可以充分利用到面向对象编程中的多态的优势，虽然可能大家都比较熟悉这种方式，但我想还是用一个比较简单的例子来帮助理解，假设我们仅仅做三个类层次的话，可以从Entity这个基类中先继承出，静态实体（Static Entity）和动态实体（Dynamic Entity），静态实体表示没有运动信息的实体，比如一些静态的建筑，地图的网格等等，动态实体表示带运动信息的实体，比如人物，机械，怪物等等，这个作为第二个层次，最下面的一个层次可以是具体的物件，就像我上面所具的那些例子。

继承的好处是直观，而且分工合作也不错，大家的协作可以互不干扰，但“继承式”的实体结构有一个问题，我想用过此类模式的同学应该也有所体会，就是随着开发的进行，基类的“体积”会日益庞大，因为当我们发现子类间有一些共通的地方时，我们可以选择的办法之一，就是把这个共通的地方写到他们共有的基类里面去，我们用上面的例子举例的话，当“人物”和“怪物”有一些属性是共通的时候，我们就不得不把这些代码移到“动态实体”这个类中，当然这还不是最糟，当“人物”和“建筑”有共通的东西时，我们就只能把代码写到最高层的“Entity”中了，而且这些共通的代码可能“怪物”这个类并不需要，但因为它也继承自Entity，所以也“被迫”的包含了这些代码。所以基类代码的可维护性就变的很差，虽然，好的继承关系的设计可以一定程度上缓解这样的问题，但并不能从根本上解决。

为了解决这样的问题，所以，现在很多的引擎架构里，都提出了“组合式”实体的概念，“组合式”的概念就类似与小孩搭积木，通过用不同部件来“组合”出不同的物体。

比如，对于“静态的建筑”这个实体来说，它可以由“空间属性模块”，“显示模块”组成，“空间属性模块”包括了位置，朝向等信息，“显示模块”包括了这个建筑的模型，贴图等和渲染相关的信息。那对于“人物”这个实体来说，他的模块就更多了，除了“空间属性模块”，“显示模块”，还会包括，“人物AI模块”，“动画模块”，“物理模块”等等。对于“怪物”来说，它可能包含了和“人物”差不多的信息，但我们会给它一个“怪物AI的模块，而不是“人物AI模块”。但这时，也许游戏设计师需要一个“高级的人形怪物”，它更聪明，需要有像人一样的AI，如果按照原来“继承式”的设计方式，我们可能需要从“怪物”这个类里继承，然后再从“人物”这个类里拷贝AI部分的代码，或者将这部分代码移到“动态实体”类中，以便于在两个类中共用，但在“组合式”的设计中，我们要做的，仅仅是将那些模块重新组合，用“人物的AI模块”来替代原来的“怪物AI模块”，这样我们就得到了一个全新的实体。

这就是“组合式”实体概念，这些用来“组合”的元件，称之为“组件（Component）”，或者“轨道（Track）”，这些组件是可继承的，所以从本质上来说，它将基于实体的继承，转移到了“组件”的继承上，将实体和属性分开，达到了简化实体类的作用。明白了它的概念，那从实现上来说就更简单了，我们可以在实体类中，包含一个存有组件基类指针的数组，然后在构建实体的时候，将相关的组件添加进来就可以了。然后在更新实体的时候，将所有在这个实体上的组件一并更新即可，代码我就不在这里写了。

“组件式”实体的概念和实现并不是很复杂，但效果却是惊人的，它用一个简单的设计模式，使得代码更易维护，而且，不同的程序员可以更合理的分工实现各自的组件部分。像AI程序员，就可以专注与对于AI组件的开发了，比如，可以做一系列的AI组件，对应不同的难度的AI，只要将这些组件和实体相连，就可以实现虽然怪物的外观相同（因为“显示组件”是一样的），但AI截然不同的效果了。对于3D程序员也是这样，比如，我可以先做一个真实效果的渲染组件，但可能我还想试试卡通渲染效果，那只要再实现一个这样的组件，将原来那个替换掉就可以了。

当然，“组合式”实体的开发中，也有些问题值得大家思考，最重要的一个问题就是，“如何在组件间传递数据?"，最实际的一个例子就是，“空间组件”中存有实体的位置信息，那其他的组件开发者肯定是需要得到这些信息的，所以在组件间传递数据的功能是必不可少的，我不推荐直接开放诸如GetComponent这种直接得到组件指针的接口，那如何安全而有效的传递数据呢，这个仁者见仁，智者见智问题就留给大家讨论了。

————————————————————————
作者：Finney
Blog：AI分享站(<a href="http://www.aisharing.com/">http://www.aisharing.com/</a>)
Email：finneytang@gmail.com
本文欢迎转载和引用，请保留本说明并注明出处
————————————————————————]]></description>
			<content:encoded><![CDATA[<p>最近在做项目前期的一些调研的工作，研究并参考了几个引擎和框架的设计，包括内部引擎，商业引擎，和开源引擎，通过比较和学习后，觉得对于游戏中的实体实现，用“组合”的设计模式会比用“继承”的更为便利，想到我早些时候的一个项目里用到的一个引擎，也是实现了组合式的实体，而且对于AI程序员来说也是和“游戏实体”打交道最多的，一个好的设计可以大大的提高代码的质量和可维护性。所以，我觉得很有必要在这里记录一下，也和大家一起分享一下我的一些心得。</p>
<p>在学习面向对象编程的时候，一个很重要的概念就是“万物皆对象”，我们可以把现实世界的物体抽象成一个个的Object，并且通过继承的方式实现多样化的对象集。这个是面向对象编程的一些很重要的概念。我想学过的同学都应该对此非常的熟悉。</p>
<p>游戏中也是一样的，一般游戏内都会抽象出一个称之为“游戏实体”（Entity）的类，当然也有直接叫Object的，不管名字怎么样，概念上是相通的，总之都是对于游戏中可能的任何物体一种提炼。在引擎中用继承的方式扩展Entity是一种比较常见和直观的方式，可以充分利用到面向对象编程中的多态的优势，虽然可能大家都比较熟悉这种方式，但我想还是用一个比较简单的例子来帮助理解，假设我们仅仅做三个类层次的话，可以从Entity这个基类中先继承出，静态实体（Static Entity）和动态实体（Dynamic Entity），静态实体表示没有运动信息的实体，比如一些静态的建筑，地图的网格等等，动态实体表示带运动信息的实体，比如人物，机械，怪物等等，这个作为第二个层次，最下面的一个层次可以是具体的物件，就像我上面所具的那些例子。</p>
<p>继承的好处是直观，而且分工合作也不错，大家的协作可以互不干扰，但“继承式”的实体结构有一个问题，我想用过此类模式的同学应该也有所体会，就是随着开发的进行，基类的“体积”会日益庞大，因为当我们发现子类间有一些共通的地方时，我们可以选择的办法之一，就是把这个共通的地方写到他们共有的基类里面去，我们用上面的例子举例的话，当“人物”和“怪物”有一些属性是共通的时候，我们就不得不把这些代码移到“动态实体”这个类中，当然这还不是最糟，当“人物”和“建筑”有共通的东西时，我们就只能把代码写到最高层的“Entity”中了，而且这些共通的代码可能“怪物”这个类并不需要，但因为它也继承自Entity，所以也“被迫”的包含了这些代码。所以基类代码的可维护性就变的很差，虽然，好的继承关系的设计可以一定程度上缓解这样的问题，但并不能从根本上解决。</p>
<p>为了解决这样的问题，所以，现在很多的引擎架构里，都提出了“组合式”实体的概念，“组合式”的概念就类似与小孩搭积木，通过用不同部件来“组合”出不同的物体。</p>
<p>比如，对于“静态的建筑”这个实体来说，它可以由“空间属性模块”，“显示模块”组成，“空间属性模块”包括了位置，朝向等信息，“显示模块”包括了这个建筑的模型，贴图等和渲染相关的信息。那对于“人物”这个实体来说，他的模块就更多了，除了“空间属性模块”，“显示模块”，还会包括，“人物AI模块”，“动画模块”，“物理模块”等等。对于“怪物”来说，它可能包含了和“人物”差不多的信息，但我们会给它一个“怪物AI的模块，而不是“人物AI模块”。但这时，也许游戏设计师需要一个“高级的人形怪物”，它更聪明，需要有像人一样的AI，如果按照原来“继承式”的设计方式，我们可能需要从“怪物”这个类里继承，然后再从“人物”这个类里拷贝AI部分的代码，或者将这部分代码移到“动态实体”类中，以便于在两个类中共用，但在“组合式”的设计中，我们要做的，仅仅是将那些模块重新组合，用“人物的AI模块”来替代原来的“怪物AI模块”，这样我们就得到了一个全新的实体。</p>
<p>这就是“组合式”实体概念，这些用来“组合”的元件，称之为“组件（Component）”，或者“轨道（Track）”，这些组件是可继承的，所以从本质上来说，它将基于实体的继承，转移到了“组件”的继承上，将实体和属性分开，达到了简化实体类的作用。明白了它的概念，那从实现上来说就更简单了，我们可以在实体类中，包含一个存有组件基类指针的数组，然后在构建实体的时候，将相关的组件添加进来就可以了。然后在更新实体的时候，将所有在这个实体上的组件一并更新即可，代码我就不在这里写了。</p>
<p>“组件式”实体的概念和实现并不是很复杂，但效果却是惊人的，它用一个简单的设计模式，使得代码更易维护，而且，不同的程序员可以更合理的分工实现各自的组件部分。像AI程序员，就可以专注与对于AI组件的开发了，比如，可以做一系列的AI组件，对应不同的难度的AI，只要将这些组件和实体相连，就可以实现虽然怪物的外观相同（因为“显示组件”是一样的），但AI截然不同的效果了。对于3D程序员也是这样，比如，我可以先做一个真实效果的渲染组件，但可能我还想试试卡通渲染效果，那只要再实现一个这样的组件，将原来那个替换掉就可以了。</p>
<p>当然，“组合式”实体的开发中，也有些问题值得大家思考，最重要的一个问题就是，“如何在组件间传递数据?”，最实际的一个例子就是，“空间组件”中存有实体的位置信息，那其他的组件开发者肯定是需要得到这些信息的，所以在组件间传递数据的功能是必不可少的，我不推荐直接开放诸如GetComponent这种直接得到组件指针的接口，那如何安全而有效的传递数据呢，这个仁者见仁，智者见智问题就留给大家讨论了。</p>
<p>————————————————————————<br />
作者：Finney<br />
Blog：AI分享站(<a href="http://www.aisharing.com/">http://www.aisharing.com/</a>)<br />
Email：finneytang@gmail.com<br />
本文欢迎转载和引用，请保留本说明并注明出处<br />
————————————————————————</p>
<hr /><h2>评论</h2><ul><li><a href="http://www.aisharing.com/archives/475">2011 年 12 月 27 日</a>, davylew 评论到: 使用Components来组合GameObject，的确很方便和灵活，不知道博主对unity3d有没研究，他的引擎结构很不错，因为GameObject是通过组件拼成的，所以可以实现重用功能，例如Unity 中的Prefab系统，这里有篇好文章介绍 http://altdevblogaday.com/2011/05/23/the-top-6-misconceptions-i-had-about-unity/
，关于组件间传递数据，我引用UNITY3D的一些方法，可以使用在某些特定的物体，组成管理器，使用单件类型，通过管理器间接调用组件指针，以及使用类似C#中event delegate的方式，个个组件注册，通过消息管理器来触发Components内的方法。例如：http://www.unifycommunity.com/wiki/index.php?title=Advanced_CSharp_Messenger</li><li><a href="http://www.aisharing.com/archives/475">2011 年 12 月 27 日</a>, <a href='http://www.aisharing.com' rel='external nofollow' class='url'>Finney</a> 评论到: 还没用过unity，不过现在这个很火呀，有时间可以研究下，谢谢提供文章连接，一会儿去看看:)</li></ul><a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/2.5/cn/"><img alt="知识共享许可协议" style="border-width:0" src="http://i.creativecommons.org/l/by-nc-nd/2.5/cn/88x31.png" /></a><br />本博客作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/2.5/cn/">知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议</a>进行许可。<hr /><a href="http://www.aisharing.com/donation" target="_blank"><img src="http://aisharing.com/wp/wp-content/uploads/2011/08/donate.png" alt="捐赠本站" /></a> ]]></content:encoded>
			<wfw:commentRss>http://www.aisharing.com/archives/475/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>

<!-- Dynamic Page Served (once) in 5.303 seconds -->
<!-- Cached page served by WP-Cache -->

