— AI分享站

Archive
Tag "抽象"

最近在做项目前期的一些调研的工作,研究并参考了几个引擎和框架的设计,包括内部引擎,商业引擎,和开源引擎,通过比较和学习后,觉得对于游戏中的实体实现,用“组合”的设计模式会比用“继承”的更为便利,想到我早些时候的一个项目里用到的一个引擎,也是实现了组合式的实体,而且对于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分享站(http://www.aisharing.com/)
Email:finneytang@gmail.com
本文欢迎转载和引用,请保留本说明并注明出处
————————————————————————

Read More

游戏AI在做决策的时候,最重要的参考依据就是当前游戏世界信息,这其实和人做判断的时候是一样的,我们在做出一个决定的时候,脑中会闪过很多我们已经收集的信息,包括所见,所听,直觉(基于以往的经验)等等。所以,我们如何去抽象游戏世界信息,并收集起来以供AI使用,也是非常值得我们去思考和探讨的问题。

当然,不同的AI决策,对于游戏世界的信息需求是完全不同的,不同的游戏,对于游戏世界的抽象方式也不完全相同,因此,关于游戏信息收集的实现是一种“游戏特定”(Game specific)的问题。说句题外话,正是由于AI中充满了很多“游戏特定”的问题,所以AI不像渲染,声音,网络等其他游戏模块,它很难做成一个“引擎”,不过也正是因为每个游戏的AI需求都不同,因此AI编程也充满了魅力和创造力 :)

虽然存在这样或者那样的不同,但就像我一开始分析的,无论是否存在一个单独的模块来收集并存储游戏世界信息,这确实是AI程序中必不可少的单元。这次我想和大家探讨一个我一直在考虑的想法,虽然在我做过的项目中还没有完全用到(零星的用到一点,没有抽象成单独的模块),不过作为一个思维笔记记下来,还是很有必要的。

假设我们写一个篮球游戏中控球队员的AI,如果我们不考虑复杂的战术配合,一般来说,控球队员要么带球突破,要么直接投篮,要么传球给会造成威胁的空位球员,考虑到篮球场上瞬息万变情况来,如果单纯的if-else,会很难罗列出全部的条件,所以我们可能会采用模糊AI的决策逻辑,比如分数系统,不过,由于今天我们讨论的是收集和存储游戏世界信息的问题,所以对于AI决策相关的东西,我们暂且不讨论。我们仅仅来看,在这个问题中,AI决策时候可能需要知道哪些游戏世界信息:

  1. 场上己方球员的分布
  2. 场上对方球员的分布
  3. 球场上哪里比较有威胁(靠近篮筐的地方)
  4. 球场上哪里比较不安全(比如有强力防守队员,或者防守队员人数很多)
  5. 场地的构成(三分线位置,三秒区)
  6. 球员的相关信息(比如球员能力,位置,当前行为等等)
  7. ……

如果把上面的信息分个类别的话,可以分成以下4种

  • 静态实体信息(比如5)
  • 静态抽象信息(比如3)
  • 动态实体信息(比如1,6)
  • 动态抽象信息(比如4)

静态和动态的概念比较好理解,“静态”就是值不随着游戏的进行而变化的信息,“动态”就是随着游戏的进行会一直改变的信息,像场地信息就是静态的,不会改变的,像场上对方球员分布就是属于动态信息,因为他们的位置是一直变动的。而我这边提到的“实体”信息,指的是“真是存在”的信息,“抽象”是指“自定义”的参考信息。像场地信息,就是实体信息,因为类似三分线位置都是实际存在的信息,但像球场上哪里有威胁,那就是我们根据需要,自己定义的信息了,可以不断的调整和修正。

我借鉴了3D渲染中“帧缓冲区”(Frame Buffer)的概念,想用一种类似的方式来存储游戏世界信息,因为我们看到上面我们需要收集的信息中,不管是静态还是动态,实体还是抽象,很多都是和游戏地图相关的(除了6),所以我们就可以用一种“图”的方式来存储信息,称之为“游戏信息图”(Game World Info Map)。

首先我们按需求将游戏地图栅格化,比10×10,当然,粒度的大小取决于对于精度和效率的平衡。每一个格子就相当于“帧缓冲区”中的像素,然后我们可以创建多个这样的“图”,和创建多个“帧缓冲区”一样。每个图都代表上述信息中的一项内容,图中的每个格子都根据信息的内容填入0.0 ~1.0的值。

例如,我们要建立一个“场地威胁图”,我们定义0表示完全没有危险,1表示薄雾浓云愁永昼威胁值最高,那我们就可以这张图的相应的格子中填入相应的值,而且因为这是静态信息,所以只需要在游戏开始时填入就可以了,当我们填完每一个格子的时候,我们就得到了这样一张“场地威胁图”。对于动态信息的情况,我们需要在每一帧(或者每几帧)对“图”中的信息做一次更新,比如“场地危险图”,就是这样的动态“信息图”,需要根据防守队员的情况来实时更新。这样当我们填完所有的“图”信息后,AI决策时就可以知道任意时刻,在地图上的任意点上的相关信息了。

小地图的情况(如上例)可以直接做栅格化,但对于地图比较大的情况,如果直接栅格化的话,更新起来性能太低,这种情况可以考虑采用层次化的图模型,先将地图分成大块的格子,在大的格子里再细分成小格子,当查看距离近的信息的,采用精细的格子信息,查看远处的时候,采用粗略的信息,这样就可以在效率上取得一些平衡。

用“图”来表示世界信息的另一个好处是,可以方便的将信息绘制出来(在地图上,或者在外部的调试工具中),而不用面对一大堆的数据,如果再将不同的值配以不同的颜色来显示的话,那将大大的降低AI调试的难度。

可以看到,其实“图”的概念,就是对于游戏世界信息中和地图有关的信息的抽象,像我前段时间提到的“势力图”(Influence Map),就是“信息图”的一种应用。“信息图”的想法并不是我的独创,其实可能大家或多或少以前在编写游戏AI的时候也用到过,但我觉得整理一下的话,可以作为一种比较通用的结构来提炼出来,在AI中加以运用。希望对大家有所帮助。

————————————————————————
作者:Finney
Blog:AI分享站(http://www.aisharing.com/)
Email:finneytang@gmail.com
本文欢迎转载和引用,请保留本说明并注明出处
————————————————————————

Read More