这里我们先扩展下游戏AI开发中的一种比较经典的工作流。策划输出AI配置,直接在游戏内调试效果。如果现有接口不满足需求,就向程序提开发需求,程序加上新接口之后,策划可以在AI配置里面应用新的接口。这个AI配置是个比较广义的概念,既可以像很多从立项之初并没有规划AI模块的游戏那样,逐渐地、自发地形成了一套基于配表做的决策树;也可以是像腾讯的behaviac那样的,用XML文件来描述。XML天生就是描述数据的,腾讯系的组件普遍特别钟爱,tdr这种配表转数据的工具是xml,tapp tcplus什么的配置文件全是XML,倒不是说XML,而是很多问题解决起来并不直观。
配表也好,XML也好,json也好,这种描述数据的形式本身并没有错。配表帮很多团队跨过了从硬编码到数据驱动的开发模式的转变,现在国内小到创业手游团队,大到天谕这种几百人的MMO,策划的工作量除了配关卡就是配表。
但是,配表无法自我进化 http://blog.csdn.net/noslopforever/article/details/20833931 ,配表无法自己描述流程是什么样,而是流程在描述配表是什么样。
针对策划配置AI这个需求,我们希望抽象出来一个中间层,这样,基于这个中间层,开发相应的编辑器也好,直接利用这个中间层来配AI也好,都能够灵活地做到调试AI这个最终需求。如何解决?我们不妨设计一种DSL。
Domain-specific Language,领域特定语言,顾名思义,专门为特定领域设计的语言。设计一门DSL远容易于设计一门通用计算语言,我们不用考虑一些特别复杂的特性,不用加一些增加复杂度的模块,不需要care跟领域无关的一些流程。Less is more。
痛点:
- 对于游戏AI来说,需要一种语言可以描述特定类型entity的行为逻辑。
- 而对于程序员来说,只需要提供runtime即可。比如组合结点的类型、表现等等。而具体的行为决策逻辑,由其他层次的协作者来定义。
- 核心需求是做另一种/几种高级语言的目标代码生成,对于当前以及未来几年来说,对C#的支持一定是不能少的,对python/lua等服务端脚本的支持也可以考虑。
- 对语言本身的要求是足够简单易懂,declarative,这样既可以方便上层编辑器的开发,也可以在没编辑器的时候快速上手。
分析需求:
- 因为需要做目标代码生成,而且最主要的目标代码应该是C#这种强类型的,所以需要有简单的类型系统,以及编译期简单的类型检查。可以确保语言的源文件可以最终codegen成不会导致编译出错的C#代码。
- 决定行为树框架好坏的一个比较致命的因素就是对With语义的实现。根据我们之前对With语义的讨论,可以看到,这个With语义的描述其实是天然的可以转化为一个lambda的,所以这门DSL同样需要对lambda进行支持。
- 关于类型系统,需要支持一些内建的复杂类型,目前来看仅需要List,只有在seq、select等结点的构造时会用到。还是由于需要支持lambda的原因,我们需要支持Applicative Type,也就是形如A -> B应该是first class type,而一个lambda也应该是first class function。根据之前对runtime的实现讨论,我们的DSL还需要支持Generic Type,来支持IO<Result>这样的类型,以及List<IO<Result>>这样的类型。对内建primitive类型的支持只要有String、Bool、Int、Float即可。需要支持简单的类型推导,实现hindley-milner的真子集即可,这样至少我们就不需要在声明lambda的时候写的太复杂。
- 需要支持模块化定义,也就是最基本的import语义。这样的话可以方便地模块化构建AI接口,也可以比较方便地定义一些预制件。
- 模块分为两类:
- 一类是抽象的声明,只有declare。比如Prelude,seq、select等一些结点的具体实现逻辑一定是在runtime中做的,所以没必要在DSL这个层面填充这类逻辑。具体的代码转换则由一些特设的模块来做。只需要类型检查通过,目标语言的CodeGenerator生成了对应的目标代码,具体的逻辑就在runtime中直接实现了。
- 一类是具体的定义,只有define。比如定义某个具体的AIXXX中的root结点,或者定义某个通用行为结点。具体的定义就需要对外部模块的define以及declare进行组合。import语义就需要支持从外部模块导入符号。
由于原则是简单为主,所以我在语言的设计上主要借鉴的是Scheme。S表达式的好处就是代码本身即数据,也可以是我们需要的AST。同时,由于需要引入简单类型系统,需要混入一些其他语言的描述风格。我在declare类型时的语言风格借鉴了haskell,import语句也借鉴了haskell。
具体来说,declare语句可能类似于这样:
因为是以Scheme为主要借鉴对象,所以内建的复杂类型实现上本质是一个ADT,当然,有针对list构造专用的语法糖,但是其parse出来拿到的AST中一个list终究还是一个ADT。
直接拿例子来说比较直观:
可以看到,跟S-Expression没什么太大的区别,可能lambda的声明方式变了下。
然后是词法分析和语法分析,这里我选择的是Haskell的ParseC。一些更传统的选择可能是lex+yacc/flex+bison。但是这种两个工具一起混用学习成本就不用说了,也违背了simple is better的初衷。ParseC使用起来就跟PEG是一样的,PEG这种形式,是天然的结合了正则与top-down parser。haskell支持的algebraic data types,天然就是用来定义AST结构的,简单直观。haskell实现的hindly-miner类型系统,又是让你写代码基本编译通过就能直接run出正确结果,从一定程度上弥补了PEG天生不适合调试的缺陷。一个haskell的库就能解决lexical&grammar,实在方便。
先是一些AST结构的预定义:
我在这里省去了一些跟这篇文章讨论的DSL无关的语言特性,比如Pattern的定义我只保留了VarPat;Value的定义我去掉了ClosureVal,虽然语言本身仍然是支持first class function的。
algebraic data type的一个好处就是清晰易懂,定义起来不过区区二十行,但是我们一看就知道之后输出的AST会是什么样。
haskell的ParseC用起来其实跟PEG是没有本质区别的,组合子本身是自底向上描述的,而parser也是通过parse小元素的parser来构建parse大元素的parser。
例如,haskell的ParseC库就有这样几个强大的特性:
- 提供了char、string,基元的parse单个字符或字符串的parser。
- 提供了sat,传一个predicate,就可以parse到符合predicate的结果的parser。
- 提供了try,支持parse过程中的lookahead语义。
- 提供了chainl、chainr,这样就省的我们在构造parser的时候就无需考虑左递归了。不过这个我也是写完了parser才了解到的,所以基本没用上,更何况对于S-expression来说,需要我来处理左递归的情况还是比较少的。
我们可以先根据这些基本的,封装出来一些通用combinator。
比如正则规则中的star:
比如plus:
基于这些,我们可以做组装出来一个parse lambda-exp的parser(p_seperate是对char、plus这些的组装,表示形如a,b,c这样的由特定字符分隔的序列):
有了所有exp的parser,我们就可以组装出来一个通用的exp parser:
其中,listplus是一种具有优先级的lookahead:
对于parser来说,其输入是源文件其输出是AST。具体来说,其实就是parse出一个Dec数组,拿到AST,供后续的pipeline消费。
我们之前举的AI的例子,parse出来的AST大概是这副模样:
以上就是本篇文章【漫谈游戏中的人工智能】的全部内容了,欢迎阅览 ! 文章地址:http://w.yusign.com/news/7610.html
资讯
企业新闻
行情
企业黄页
同类资讯
首页
网站地图
返回首页 述古往 http://w.yusign.com/mobile/ , 查看更多
最新新闻
阳光分期全国客服电话-助力客户高效解决问题
阳光分期全国客服电话;(00861-38960-86246)—解决客户问题:{00861-57307-87089}—用户至上,用心服务。热线容易占线;请您多拨几
好借优品全国客服电话-助力客户高效解决问题
好借优品全国客服电话;(00861-38960-86246)—解决客户问题:{00861-57307-87089}—用户至上,用心服务。热线容易占线;请您多拨几
小白易购全国客服电话-助力客户高效解决问题
小白易购全国客服电话;(00861-38960-86246)—解决客户问题:{00861-57307-87089}—用户至上,用心服务。热线容易占线;请您多拨几
美兴小额贷款全国客服电话-助力客户高效解决问题
美兴小额贷款全国客服电话;(00861-38960-86246)—解决客户问题:{00861-57307-87089}—用户至上,用心服务。热线容易占线;请您多
爱施德网络小额贷款全国客服电话-助力客户高效解决问题
爱施德网络小额贷款全国客服电话;(00861-38960-86246)—解决客户问题:{00861-57307-87089}—用户至上,用心服务。热线容易占线;
拍拍分期全国客服电话-助力客户高效解决问题
拍拍分期全国客服电话;(00861-38960-86246)—解决客户问题:{00861-57307-87089}—用户至上,用心服务。热线容易占线;请您多拨几
天天分期全国客服电话-助力客户高效解决问题
天天分期全国客服电话;(00861-38960-86246)—解决客户问题:{00861-57307-87089}—用户至上,用心服务。热线容易占线;请您多拨几
中邮消费金融全国客服电话-助力客户高效解决问题
中邮消费金融全国客服电话;(00861-38960-86246)—解决客户问题:{00861-57307-87089}—用户至上,用心服务。热线容易占线;请您多
招联消费金融全国客服电话-助力客户高效解决问题
招联消费金融全国客服电话;(00861-38960-86246)—解决客户问题:{00861-57307-87089}—用户至上,用心服务。热线容易占线;请您多
铁路12306全国人工客服电话-助力客户高效解决问题
铁路12306全国人工客服电话;(00861-38960-86246)—解决客户问题:{00861-57307-87089}—用户至上,用心服务。热线容易占线;请您
本企业新闻