🌠 触发器系统
触发器系统
这一章节中,我们会介绍Entity实体的Cell部分的另一个功能:触发器系统-Proximity!
概述:
触发器系统是又一个Controller的实现–ProximityController,它实现了一个无限高、与xz轴平行的立方柱形的触发器系统(为了效率,所以是方形区域)。当有实体进出触发器范围时,会有回调函数通知到实体。
同时,一个Entity可以有很多个触发器。
作用:
检测进出触发器的事件,可方便一些逻辑判定;
降低服务端的性能消耗。有些逻辑,只有当进入触发器范围时才需要运作,离开时停止运作。这样可以有效的降低服务端的性能消耗。
提示
一个怪物的AI逻辑,需要一直检测附近是否有玩家进入,如果不做优化,服务端有N个怪物都会进行计算,大大浪费资源。此时如果给怪物增加一个触发器,只有当玩家进入怪物的警戒范围内时,才会进行一些逻辑的计算,则可以有效降低性能消耗。
增加一个Proximity
通过API-Entity.addProximity(self, rangeXZ, rangeY, userArg)的调用,就可以立即增加一个Proximity。方便吧!
描述:创建一个范围触发器,当有其它实体进入或离开这个触发器区域的时候会通知这个Entity。如果其它实体在x轴和z轴上均在给定的距离里面,则实体被视为在这个范围里面。
参数
rangeXZ
(float):给定触发器xz轴区域的大小,必须大于等于0;
rangeY
(float):给定触发器y轴高度,必须大于等于0;
该参数的生效必须在{项目资产库}/res/server/kbengine.xml中的cellapp中的coordinate_system里设置rangemgr_y的属性,详情见引擎配置一文。
开放y轴管理会带来一些消耗,因为一些游戏大量的实体都在同一y轴高度或者在差不多水平线高度,此时碰撞变得非常密集。3D太空类游戏或者小房间类实体较少的游戏比较适合开放此选项。
userArg
(Int):一个可选的整型,所有控制器共有。如果这个值不为0则传给回调函数。
我们来看一段代码,方便理解:(摘取AI组件中的一段)
__TERRITORY_AREA__ = 30.0 # 默认警戒范围为30
class AI(KBEngine.EntityComponent):
"""
负责怪物的AI计算的组件
"""
def __init__(self):
KBEngine.EntityComponent.__init__(self)
def _addTerritory(self):
"""
添加领地
进入领地范围的某些entity将视为敌人
"""
assert self.territoryControllerID == 0 and "territoryControllerID != 0"
t_range = __TERRITORY_AREA__ / 2.0
self.territoryControllerID = self.owner.addProximity(t_range, 0, 0)
if self.territoryControllerID <= 0:
ERROR_MSG("AI::addTerritory: %i, range=%i, is error!" % (self.owner.id, t_range))
else:
INFO_MSG("AI::addTerritory: %i range=%i, id=%i." % (self.owner.id, t_range, self.territoryControllerID))
def _delTerritory(self):
"""
删除领地
:return:
"""
if self.territoryControllerID > 0:
self.owner.cancelController(self.territoryControllerID)
self.territoryControllerID = 0
INFO_MSG("AI::delTerritory: %i" % self.owner.id)
直接调用API-Entity.addProximity(),就可以完成触发器的创建。t_range就是XZ的区域的大小。变量territoryControllerID是为了在销毁触发器的时候使用。如果创建成功,则territoryControllerID大于0的。通过cancelController来销毁该触发器。
利用回调函数进行逻辑处理
触发器系统中,是通过回调函数来通知脚本是否有实体进出触发器范围的。
1. Entity.onEnterTrap(self, entity, rangeXZ, rangeY, controllerID, userArg):
描述:当注册了使用Entity.addProximity注册了一个范围触发器,有其他实体进入触发器时,该回调函数被调用。
参数
entity
(Entity):进入范围的实体;
rangeXZ
(float):给定触发器xz轴区域的大小,必须大于等于0;
rangeY
(float):给定触发器y轴高度,必须大于等于0;
controllerID
(int):这个触发器的控制器id;
userArg
(int):这个参数的值由用户调用addProximity时给出,用户可以根据此参数对当前行为做一些判断。
2. Entity.onLeaveTrap(self, entity, rangeXZ, rangeY, controllerID, userArg):
描述:当注册了使用Entity.addProximity注册了一个范围触发器,有其他实体离开了触发器区域时,该回调函数被调用。
参数
entity
(Entity):进入范围的实体;
rangeXZ
(float):给定触发器xz轴区域的大小,必须大于等于0;
rangeY
(float):给定触发器y轴高度,必须大于等于0;
controllerID
(int):这个触发器的控制器id;
userArg
(int):这个参数的值由用户调用addProximity时给出,用户可以根据此参数对当前行为做一些判断。
例子:
我们还是拿上面的例子,当玩家进入触发器范围时,才其中怪物的AI逻辑,而离开时AI逻辑停止。如下:
class AI(KBEngine.EntityComponent):
"""
负责怪物的AI计算的组件
"""
def __init__(self):
KBEngine.EntityComponent.__init__(self)
self.heartBeatTimerID = 0
self.enable()
...
...
...
def enable(self):
"""
激活entity
:return:
"""
# 1秒一次心跳
self.heartBeatTimerID = self.owner.addTimer(random.randint(0, 1), 1, SCDefine.TIMER_TYPE_HEARTBEAT)
def disable(self):
"""
禁止entity做任何行为
:return:
"""
self.owner.delTimer(self.heartBeatTimerID)
self.heartBeatTimerID = 0
# --------------------------------------------------------------------------------------------
# System Callbacks
# --------------------------------------------------------------------------------------------
def onTimer(self, tid, userArg):
"""
KBEngine method.
计时器回调
:param tid:
:param userArg:
:return:
"""
# DEBUG_MSG("AI::onTimer: %i, tid:%i, arg:%i" % (self.owner.id, tid, userArg))
if SCDefine.TIMER_TYPE_HEARTBEAT == userArg:
# 这里做一些业务逻辑的判断
DEBUG_MSG("AI::DO SOME THING")
def onEnterTrap(self, entityEntering, range_xz, range_y, controllerID, userarg):
"""
有entity进入trap
:param entityEntering:
:param range_xz:
:param range_y:
:param controllerID:
:param userarg:
:return:
"""
if controllerID != self.territoryControllerID:
return
# 筛选一下,必须是Avatar,并且没有dead
if entityEntering.isDestroyed or entityEntering.getScriptName() != "Avatar" :
return
DEBUG_MSG(
"AI(%s[%i])::onEnterTrap: entityEntering=(%s)%i, range_xz=%s, range_y=%s, controllerID=%i, userarg=%i" %
(self.owner.getScriptName(), self.ownerID, entityEntering.getScriptName(), entityEntering.id,
range_xz, range_y, controllerID, userarg))
self.enable()
def onLeaveTrap(self, entityLeaving, range_xz, range_y, controllerID, userarg):
"""
有entity离开trap
:param entityLeaving:
:param range_xz:
:param range_y:
:param controllerID:
:param userarg:
:return:
"""
if controllerID != self.territoryControllerID:
return
# 筛选一下,必须是Avatar,并且没有dead
if entityLeaving.isDestroyed or entityLeaving.getScriptName() != "Avatar":
return
DEBUG_MSG("AI(%s[%i])::onLeaveTrap: entityLeaving=(%s)%i." % (
self.owner.getScriptName(), self.ownerID, entityLeaving.getScriptName(),
entityLeaving.id))
self.disable()
该例子中,有激活enable和关闭disable两个方法,主要是增加计时器和停止计时器(见脚本-计时器的介绍),计时器是1秒间隔,类似于心跳,在计时器的回调函数onTimer中计算业务逻辑的判定和处理。那什么时候激发呢?我们看到在onEnterTrap-进入触发器范围时,我们判断了目标为Avatar并且没有销毁,则使怪物的AI激活;在onLeaveTrap-离开触发器范围时,停止了AI的计时器,达到停止业务逻辑计算的目的。