目录

移动系统

这一章节中,我们会介绍Entity实体的Cell部分的功能之一:移动系统!

在讲解之前,必须要提及引擎中控制器Controller的概念,因为移动系统包括下一章节的触发器系统,都是基于Controller的。那什么是Controller?

Controller介绍:

1:用于实现那些需要在后台花费很多个tick处理的任务或功能;
2:在Controller结束时会回调Python脚本的特定事件,如移动结束时回调onMoveOver等;
3:用于实现复杂的逻辑;
4:因为效率原因,用C/C++进行实现;
5:当Entity跨越Cell边界时Controller也跟着复制到新的Cell;
6:每个Entity上可以有无限多个Controller;
7:每个Controller实例都会返回一个ControllerID,用于删除它。(利用API-Entity.cancelController(id))

Ok,说完Controller,我们来说下这章节的系统-移动系统,它其实是一个Motion Controller移动控制器。

注意: 任何实体,在任意时刻只能有一个移动控制器,重复调用任何移动函数,都将终止之前的移动控制器。主动终止时,可使用API-Entity.cancelController(id)Entity.cancelController(“Movement”)


移动系统分为:非导航网格下的移动和导航网格下的移动两种。

非导航网格下的移动:

所谓非导航网格下的移动,顾名思义就是不使用导航,直接进行移动操作。

API介绍:

1. Entity.moveToPoint(self, destination, velocity, distance, userData, faceMovement, moveVertically)

描述:直线移动Entity到给定的坐标点,成功或失败会调用回调函数

参数:

destination (Vector3):要移动到的目标位置点;
velocity (float):移动速度,单位m/s;
distance (float):距离目标小于该值停止移动,如果该值为0则移动到目标位置;
userData (object):用户自定义数据,用于传给回调函数的数据;
faceMovement (bool):如果实体面向移动方向则为True。如果是其它机制则为False;一般我们都会使用True,除非是一些没有朝向的实体,如可爆炸的木桶等,可以使用False。
moveVertically (bool):设为True指移动为直线移动,设为False指贴着地面移动。如果是False,引擎会自动使移动紧贴地面,防止出现“悬空”效果。

我们来看一段代码,方便理解:(摘取Motion移动组件中的一段)

    def gotoPosition(self, position, dist=0.0):
        """
        移动到某个位置
        :param position:Vector3, 目标位置点
        :param dist: float, 达到多少距离则判定为到达
        :return:
        """
        if self.position.distTo(position) <= 0.05:  # 阈值0.05,如果小于,则不进行移动
            return

        self.isMoving = True
        speed = self.moveSpeed * 0.1

        WARNING_MSG("Motion(%s[%i])::gotoPosition: position" % (self.owner.getScriptName(), self.ownerID, dest_pos))

        self.owner.moveToPoint(position, speed, dist, None, True, False)

如果当前位置与目标位置距离太近,则不进行移动。直接调用moveToPoint让移动控制器以speed速度向目标位置positioin进行移动,并在距离<=dist时停止。


2. Entity.moveToEntity(self, destEntityID, velocity, distance, userData, faceMovement, moveVertically)

描述:直线移动实体到另一个Entity位置,成功或失败会调用回调函数

参数:
destEntityID (int):要移动到的目标Entity实体的id,引擎会查找该实体,并进行移动;
velocity (float):移动速度,单位m/s;
distance (float):距离目标小于该值停止移动,如果该值为0则移动到目标位置;
userData (object):用户自定义数据,用于传给回调函数的数据;
faceMovement (bool):如果实体面向移动方向则为True。如果是其它机制则为False;一般我们都会使用True,除非是一些没有朝向的实体,如可爆炸的木桶等,可以使用False。
moveVertically (bool):设为True指移动为直线移动,设为False指贴着地面移动。如果是False,引擎会自动使移动紧贴地面,防止出现“悬空”效果。
我们来看一段代码,方便理解:(摘取Motion移动组件中的一段)

    def gotoEntity(self, targetId, dist=0.0):
        """
        移动到entity位置
        :param targetId: 目标entityid
        :param dist:  float, 达到多少距离则判定为到达
        :return:
        """

        entity = KBEngine.entities.get(targetId)
        if entity is None:
            WARNING_MSG("Motion(%s[%i])::gotoEntity: not found targetID=%i" % (
                self.owner.getScriptName(), self.ownerID, targetId))
            return

        if entity.position.distTo(self.position) <= dist:
            return

        self.isMoving = True
        speed = self.moveSpeed * 0.1
        self.owner.moveToEntity(targetId, speed, dist, None, True, False)

先根据entityID获取到entity,检查其是否存在。如果存在则检查两者距离是否已经在dist之内。直接调用moveToEntity让移动控制器以speed速度向ID=targetId的实体进行移动,并在距离<=dist时停止。


3. Entity.cancelController(self, controllerID)

描述:停止一个控制器对Entity的影响。

注意:它只能在一个real实体上被调用。

参数controllerID(int):要取消的控制器的ID;
一个专用的控制器类型字符串也可以作为参数,例如该章节的Movement可以作为cancelController的参数。
举个例子:

    def stopMotion(self):
        """
        停止移动
        :return:
        """
        if self.isMoving:
            self.owner.cancelController("Movement")
            self.isMoving = False

调用该API就可以停止某个移动控制器或者使用Movement作为参数,可以停止所有移动控制器。


回调函数:

Entity.onMove

如果这个函数在脚本中有实现,相关的Entity.moveToPointEntity.moveToEntity还有Entity.navigate方法被调用并且成功后底层每帧移动都会回调此函数。

注意:该回调函数会每帧被调用,请谨慎函数里的代码,避免影响引擎效率。


Entity.onMoveOver

如果这个函数在脚本中有实现,移动相关的方法如Entity.moveToPoint等被调用后,在到达指定目标点时会回调此函数。

一般作为移动成功的回调来使用。


Entity.onMoveFailure

如果这个函数在脚本中有实现,移动相关的方法如Entity.moveToPoint等被调用并且失败时会回调此函数。


使用导航网格下的移动:

顾名思义就是使用导航网格navMesh,通过寻路的方式进行移动操作。

导航网格是什么?

简单说,就是Navmesh(3D情况下)或tile数据(2D情况下),这种数据可以被导航系统读取,并生成导航数据,给自动寻路成为可能。

CBE引擎可以有多个预先生成好的导航网格,不同的网格大小(会导致不同的导航路径),每个网格位于一个layer中,互相之间不影响。layer,是一个层的意思,用于区分网格所在的层。


引擎中增加一个网格数据:

1:通过工具生成Navmesh或Tile数据,这部分会在工具一章中介绍,这里不再赘述。

2:把Navmesh或Tile数据(比如新手村的一个地形数据)放在项目的资源目录下,如{项目资产库}/res/scenes/xinshoucun下,如图:

1

3:在Space空间对应的SpaceEntity(我们例子中使用Scene实体)的初始化时,加载地形的几何数据。

class Scene(KBEngine.Entity):
    """
    cell上游戏场景实体实现
    """
    def __init__(self):
        KBEngine.Entity.__init__(self)

        # 一个space代表的是一个抽象的空间,这里向这个抽象的空间添加了几何资源数据,如果数据是3D场景的
        # 该space中使用navigate寻路使用的是3D的API,如果是2D的几何数据navigate使用的是astar寻路
        res_path = 'scenes/xinshoucun'
        KBEngine.addSpaceGeometryMapping(self.spaceID, None, res_path)

        DEBUG_MSG('created scene[%d] entityID = %i, res = %s.' % (self.sceneUType, self.id, res_path))

只需调用API:addSpaceGeometryMapping即可给某个Space增加几何数据。其中res_path是相对于项目的资源文件夹(即{项目资产库}/res)的。

API: KBEngine.addSpaceGeometryMapping(spaceID, mapping, path, shouldLoadOnServer, params)
描述:关联一个给定空间的几何映射,函数调用之后服务端和客户端都会加载相应的几何体数据。

在服务端上,从给定目录里加载所有的几何数据到指定的空间。这些数据可能被分成很多区块,不同区块是异步加载的。

服务端仅加载场景的几何数据提供给导航和碰撞功能使用,客户端除了几何数据外还会加载纹理等数据。
3D场景当前默认使用的是recastnavigation插件所导出的数据,2D场景当前默认使用的是MapEditor编辑器导出的数据。

参数:
spaceID (int):空间ID,指定在哪个空间增加几何数据;
mapping :网格碰撞数据的映射值。
path (string):包含几何数据的目录路径。
shouldLoadOnServer (bool):可选参数,指定是否在服务端上加载几何。默认为True。
params (dict):可选参数,指定不同layer所使用的navmesh。如下:

KBEngine.addSpaceGeometryMapping(self.spaceID, None, resPath, True, {0 : "srv_xinshoucun_1.navmesh", 1 : "srv_xinshoucun.navmesh"}) 

注意:几何数据的加载是一个异步的过程。


4:根据回调函数,检查加载是否正确。

其中包括:
4.1:KBEngine.onSpaceGeometryLoaded(spaceID, mapping)
描述:空间所需求的网格碰撞等数据加载完毕。

参数:
spaceID (int):空间ID;
mapping (float):网格碰撞数据的映射值。

4.2:KBEngine.onAllSpaceGeometryLoaded(spaceID, isBootstrap, mapping)
描述:空间所需求的网格碰撞等数据全部加载完毕。

参数: spaceID (int):空间ID;
isBootstrap (bool):如果一个空间被分割由多个cell共同负载,那么isBootstrap描述的是是否为加载请求的发起cell。
mapping (float):网格碰撞数据的映射值。


其他API介绍:

1. Entity.canNavigate(self)

描述:通过这个方法判断当前实体是否可以使用导航功能。它只能在一个real实体上被调用。

通常,当实体所在Space使用KBEngine.addSpaceGeometryMapping加载过有效的导航用的碰撞数据(Navmesh或者2D的tile数据),并且实体在有效导航区域该功能可用。


2. Entity.navigate(self, destination, velocity, distance, maxMoveDistance, maxSearchDistance, faceMovement, layer, userData)

描述:使用导航系统来使这个Entity向一个目标点移动,成功或失败会调用回调函数

参数:
destination (Vector3):Entity移向的目标点;
velocity (float):移动速度,单位m/s;
distance (float):距离目标小于该值停止移动,如果该值为0则移动到目标位置;
maxMoveDistance (float):最大的移动距离;
maxSearchDistance (float):从导航数据中最大搜索距离;
faceMovement (bool):如果实体面向移动方向则为True。如果是其它机制则为False;一般我们都会使用True,除非是一些没有朝向的实体,如可爆炸的木桶等,可以使用False;
layer (int8):使用某个层的navmesh来寻路;
userData (object):用户自定义数据,用于传给回调函数的数据;
我们来看一段代码,方便理解:(摘取Motion移动组件中的一段,这次丰富一下刚才例子中的代码,使其自动检测是否可以使用导航系统)

    def gotoPosition(self, position, dist=0.0):
        """
        移动到某个位置
        :param position:Vector3, 目标位置点
        :param dist: float, 达到多少距离则判定为到达
        :return:
        """
        if self.position.distTo(position) <= 0.05:  # 阈值0.05,如果小于,则不进行移动
            return

        self.isMoving = True
        speed = self.moveSpeed * 0.1

        if self.owner.canNavigate():
            DEBUG_MSG("Motion(%s[%i])::gotoPosition: canNavigate=True" % (self.owner.getScriptName(), self.ownerID))
            self.owner.navigate(Math.Vector3(position), speed, dist, speed, 512.0, True, 0, None)
        else:
            WARNING_MSG("Motion(%s[%i])::gotoPosition: position=%s canNavigate=False" % (self.owner.getScriptName(), self.ownerID, position))
            self.owner.moveToPoint(position, speed, dist, None, True, False)

在这里,与之前的例子不同,我们增加了canNavigate的判断,如果可以使用导航系统,则我们使用导航系统进行导航移动。这里我们使用了512的maxSearchDistance,使其尽大可能的进行寻路搜索。

设置与获取某个Space的自定义数据:

KBEngine.setSpaceData(spaceID, key, value)

描述:设置指定key的space数据。

参数:
spaceID (int):空间ID;
key (string):一个字符串关键字。
value (string):要存放的数据。


KBEngine.getSpaceData(spaceID, key)

描述:获取指定key的space数据。

参数:
spaceID (int):空间ID;
key (string):一个字符串关键字。


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