赞
踩
最近公司在使用KBEngine服务器来开发网游,从接触python到开始搞KBEngine时间比较短,各种翻资料,逛论坛,自己挖了很多的坑,刚开始接触AI,网上关于这块也不多,后来慢慢摸索,使用了有限状态机的模式,总算实现了大部分的需求,今天来拿官方的demo给大家一起分享一下AI。
在官方给出的demo中,作为NPC的Monster继承了一系列的接口, 每种接口对应于不同的功能,我们说的AI就是来控制这些NPC的行为逻辑。
class Monster(KBEngine.Entity, NPCObject, // NPC父类
Flags, // 一个管理标记信息的模块,标记如: 正在交易中、正在xx。 State, // 状态模块, 主状态例如:死亡、活着。子状态例如:闲置状态、战斗状态 Motion, // 关于移动的封装 Combat, // 关于战斗公式、战斗属性等等的封装 Spell, // 技能释放、buff/debuff维护等 AI): // 智能思考模块
服务端上怪物成千上万, 而AI是比较耗的,如果只有一个玩家在线, 显然大量的怪物是不需要开启AI思考来白白耗掉CPU,于是kbe引入了AOI的概念,即规定一段范围,服务端将客户端感兴趣的区域中发生的所有事件同步给客户端。事件包括: 实体移动、客户端广播类型的属性改变、死亡销毁等等。进入AOI会触发onWitnessed()回调,demo中使用该回调让monster实体enable(),此时在enable()函数中,开启了计时器,每秒钟执行一次,类似于人的心跳,此时的Monster已经有了心跳。
在Monster有了心跳之后,心跳计时器内,每次心跳都会去执行think(),在该函数中,Monster会根据自身的状态,去思考自己的行为,官方demo中有两个状态,Free和Fight,当自身或者外界的因素改变的时候,自己的状态的迁移,也会根据状态执行相应的逻辑。
在Monster有了心跳和思维之后,需要具有感知世界环境的能力,感受到食物或者危险,在官方的demo中,空闲状态下,即在Free状态下,会在think中进行执行onThinkFree()函数,此时使用KBE提供的addProximity()给Monster加入了一个范围触发器,当有其它实体进入或离开这个触发器区域的时候会通知这个Entity。这个区域是一个方形(为了效率)。如果其它实体在x轴和z轴上均在给定的距离里面,则实体被视为在这个范围里面。这个Entity通过onEnterTrap()和onLeaveTrap()函数被通知,我们在写Monster感受食物或者敌人的时候,都是使用这两个回调进行筛选操作,如果有敌人进入到自己的触发器范围内,onEnterTrap()执行,视为自己可以进行攻击,从自由状态进入到战斗状态,因为是指在xz轴提供了触发,如果是有需要在y轴实现三维的触发器,可以在kbengine_defs.xml文件中开放y轴管理器即可。但是该操作谨慎进行,开放y轴管理器会增加消耗。<rangemgr_y>true</rangemgr_y>
在Monster加入了思维和触觉,已经可以使用触觉感应到敌人,由思维判断进入到战斗状态,执行onThinkFight(),在战斗状态,就开始进行检查自己的敌人列表,如果找到敌人,进行移动追击,此时会调用Motion类中的gotoPosition()移动到目标点,此外Motion类中还提供了gotoEntity()方法,移动到某个实体的位置,这两个函数都是对KBE引擎提供的底层API函数的实现,即moveToPoint()函数。此时已经实现了Monster的移动。
官方的Demo中,AI模块做的比较简单,只是添加了一个定时器以一定频率执行一些流程, 这些流程根据状态区分,我们在做项目的时候,简单的状态的迁移也可以使用这种方式。
在项目中,移动函数moveToPoint是使用最频繁的, 先看一下API的说明。
moveToPoint( self, destination, velocity, distance, userData, faceMovement, moveVertically )
功能说明:
直线移动实体到给定的坐标点,成功或失败会调用回调函数。
任何实体,在任意时刻只能有一个移动控制器,重复调用任何移动函数将终止之前的移动控制器。
返回一个可以用于取消这次移动的控制器ID。
参数:
destination | Vector3,Entity要移动到的目标位置点 |
velocity | float,Entity的移动速度,单位m/s |
distance | float,距离目标小于该值停止移动,如果该值为0则移动到目标位置。 |
userData | object,传给通知函数的数据 |
faceMovement | bool,如果实体面向移动方向则为true。如果是其它机制则为false。 |
moveVertically | bool,设为true指移动为直线移动,设为false指贴着地面移动。 |
返回:
int,新创建的控制器ID。 |
在使用该函数的过程中,需注意,实体一次只能有一个移动控制器,在多个移动控制器同时使用,而未使用entity.cancelController( "Movement" )取消控制器的控制的时候,会依次执行控制器的控制逻辑,所以如果是状态切换时候,移动的逻辑改变,可以先使用entity.cancelController( "Movement" )取消当前控制器,再开始新的移动控制器。
关于参数faceMovement,是否朝向移动的方向,在项目中这个基本都是要朝向的,也就是设置为True,但是有些人可能会发现此时的朝向在xz轴是朝向移动方向,如果是在三维空间内,y轴并未正常朝向移动的方向,是因为KBE在底层中是默认注释了y轴。
1. 修改底层代码
可以在底层代码中进行修改,moveto_point_handler.cpp中,打开y轴注释。
if(KBEVec3Length(&movement) < velocity_ + distance_)
{
float y = currpos.y;
currpos = dstPos;
if(distance_ > 0.0f)
{
// 单位化向量
KBEVec3Normalize(&movement, &movement);
movement *= distance_;
currpos -= movement;
}
if (!moveVertically_)
currpos.y = y;
ret = false;
}
2. 修改def文件
在Monster的def文件中加入
<Volatile>
<position/>
<yaw/>
<roll/>
<pitch/>
</Volatile>
即可实现,绕xyz轴的旋转。
这是我对官方demo中的Monster的AI的一些见解,在实际项目中,如果写一些简单的AI可以按照该框架进行实现,希望对大家有帮助。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。