目录

触发器系统

这一章节中,我们会介绍Entity实体的Cell部分的另一个功能:触发器系统-Proximity

概述:

触发器系统是又一个Controller的实现–ProximityController,它实现了一个无限高、与xz轴平行的立方柱形的触发器系统(为了效率,所以是方形区域)。当有实体进出触发器范围时,会有回调函数通知到实体。

同时,一个Entity可以有很多个触发器。

作用:

1:检测进出触发器的事件,可方便一些逻辑判定;
2:降低服务端的性能消耗。有些逻辑,只有当进入触发器范围时才需要运作,离开时停止运作。这样可以有效的降低服务端的性能消耗。如:

一个怪物的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的计时器,达到停止业务逻辑计算的目的。


Copyright © 2018 Yolo Technologies. Publication: 2.0-025. Built: 2018-12-07.