如何使用lua完成复杂AI

引言

本文分享了作者最近在实际项目中遇到一些问题,以及学到的一些新的设计方法,其内容主要关于Lua & C++

什么是lua_cfuntion?

lua_cfucnction,是指满足一定格式的C函数,其形式如下:

int  XXX (lua_State * L);

返回值为int,参数为lua_State,这样的函数,经过lua_register注册之后,就可以在lua脚本中使用的C函数。lua与C++通过一个共用的堆栈来传递参数数据。

什么是lua_pcall() and lua_call()?

就是C++调用Lua函数的接口


如何使用lua_pcall和lua_cfuntion?

本文下面讨论的实现方法,仅简单利用lua_cfucnction和lua_pcall技术来实现。所以下面介绍的方法并不是最优化的,但可以完成相对复杂的功能。

主要结构如下:

假设我在C++端,拥有一个状态机,该状态机的state拥有三个OnStart ,Update,OnExit函数。C++的OnStart 调用Lua的OnStart ,C++的Update调用lua的Update,以此类推。

这样我们可以使用Lua脚本自定义每一个状态下执行的内容。非常灵活的。

这里需要注意的是,必须向每个lua函数传递C++ 智能体的指针,这个指针在lua函数这边的类型为userdata。除了暴露状态机状态的函数,还要暴露一些控制状态转换的函数或是其他逻辑函数给lua,实际上因为向Lua函数传输了对象指针,lua函数调用C++函数时,将指针回传回来,经过lua_cfunction转换后调用C++对象的函数。这样我们可以通过脚本改变某一个对象C++ FSM的当前状态或数据,最后导致另某个对象发生行为改变。

整个感觉就像活字印刷术,C++提供字,lua提供组合而最终写成文章。

我在实际项目中发现的问题以及解决方法分析:

我自己在实际项目中发现一个问题,同一类型的NPC拥有一样的状态机逻辑和脚本逻辑,这样的行为太过单一。

简单的方法自然是改变一些逻辑数据然后产生一些不同的行为。

如果数据存在C++端,需要实现lua脚本改变C++对象的数据,OK,我们可以暴露更多的C++函数给lua,让其设置不同的数据,然后同一类型的NPC在同一状态也会有不同的变化。这样做会添加许多C++与lua交互接口。在设置和获取C++数据时,均需要来回传递C++对象的指针。

另一种方法就是避免一些数据上的传递,数据存在lua,让C++的指针做为lua表中的键值,我们为每个C++对象在lua层面上多维护额外的(但是并不冗余)一份数据。其实两者殊途同归,区别仅在于数据存在哪里,由谁负责清除。而或者显然更加灵活!

举个例子,一个boss进入战斗状态就会需要一个释放技能顺序的列表,如果不希望这一模型的boss全部按照唯一的顺序释放技能的话,就需要改变这个列表顺序,假设数据存在C++对象中,lua可以通过c++函数重排顺序。如果存在lua中,则不需要与C++进行额外的交互。

解决方法之实际应用

我给出一个实际的例子,大家可以思考一下。

一个Boss他有很多技能,每个技能都有不同的旋转模式,什么是旋转模式,简单说就是释放技能时如何旋转,举个例子,假设有三个技能,分别为360度慢速机枪扫射,又比如90度来回快速机枪扫射,在比如180度追踪目标中速机枪扫射。如何实现? 简单的想法,可以来回旋转吗 bool isloop?要旋转多少度float angle?旋转速度float rate?是否为目标为旋转方向进行最终等 bool target_clockwise?

如果这就是技能的最终版本,如果数据存在C++中,C++逻辑就是根据不同数据来进行特殊的旋转。那么lua脚本逻辑是在不同时机设置不同的旋转数据即可,由C++负责具体的旋转,这或许是个不错的选择。

如果这并不是最终版,数据以及旋转逻辑都可能改变,意味着可能需要来回修改和重编C++代码,以支持新的旋转模式。

为了更大的灵活性,我把旋转的数据和旋转的逻辑全部移动到lua,lua旋转数据以C++技能释放者的指针为索引,这样每个boss都有独立的旋转数据。一个场景出现多个boss也不会乱。每个技能可以在lua脚本中定义自己的旋转逻辑,在脚本里可以完成任意形式的旋转。 需要C++做什么,只需要它提供一个函数就是SetDirection!

那么等需求稳定后,将效率部分转换为C++代码也可。


后记考虑使用更强大的(luabridge and luabind)

luabridge,luabind提供了相当强大的Lua与C++交互功能,有想要研究lua bridge和lua bind的童鞋请自行关注github。太强大了!!!这两个实现都用神奇的模板元编程技术,所不同的是luabridge使用的是Loki库,luabind使用boost库,考虑到C++11引入了变长模板参数技术,所以使用C++11实现luabind和luabridge相关功能会比以前简单一些。但luabridge没有使用C++11也可以说是它的优势,它不依赖新编译器,并且luabridge只有两个头文件。luabridge功能有限,但其简单易用,我比较推荐。

猜你喜欢

转载自blog.csdn.net/zjq2008wd/article/details/50602063