十四:在世界中投放NPC/Monster
Space的cell创建完毕之后, 引擎会调用base上的Space实体, 告知已经获得了cell(onGetCell),那么我们确认cell部分创建好了之后就可以开始投放NPC出生点了。
(注意:这里并不是直接将NPC/Monster创建出来,而是先在对应的位置创建了一个出生点, 出生点的好处是可以根据一定规则, 当NPC/Monster在某区域减少的时候
可以在合适的时候将其创建出来,例如:一群怪被玩家清理掉了,半小时后怪刷出。)
onGetCell添加了一个刷出生点的定时器, 我们不能一次性创建出所有的出生点,因为数量可能很多, 使用定时器分批创建。
- scripts/base/space.py:
- def onGetCell(self):
- """
- KBEngine method.
- entity的cell部分实体被创建成功
- """
- self.addTimer(0.1, 0.1, SCDefine.TIMER_TYPE_SPACE_SPAWN_TICK)
出生点的数据(实体类型、坐标、朝向等)是通过配置文件给出的,script/data/d_spaces_spawns.py与script/data/spawnpoints/xinshoucun_spawnpoints.xml 关于这2个配置的由来可以参考配置章节
- kbengine_demos_assets\scripts/base/space.py:
- def spawnOnTimer(self, tid, tno):
- """
- 出生怪物
- """
- if len(self.tmpCreateEntityDatas) <= 0:
- self.delTimer(tid)
- return
- datas = self.tmpCreateEntityDatas.pop(0)
- if datas is None:
- ERROR_MSG("Space::onTimer: spawn %i is error!" % datas[0])
- KBEngine.createBaseAnywhere("SpawnPoint",
- {"spawnEntityNO" : datas[0], \
- "position" : datas[1], \
- "direction" : datas[2], \
- "modelScale" : datas[3], \
- "createToCell" : self.cell})
SpawnPoint实体被创建出来之后,其构造函数中会调用API接口创建实体的cell部分
- kbengine_demos_assets\scripts/base/spawnpoint.py:
- class SpawnPoint(KBEngine.Base, GameObject):
- def __init__(self):
- self.createCellEntity(self.createToCell)
SpawnPoint的cell部分会在当前位置根据自身被创建时所给予的参数信息来创建出真正的NPC/Monster
- kbengine_demos_assets\scripts/base/spawnpoint.py:
- def spawnTimer(self, tid, tno):
- datas = d_entities.datas.get(self.spawnEntityNO)
- if datas is None:
- ERROR_MSG("SpawnPoint::spawn:%i not found." % self.spawnEntityNO)
- return
- params = {
- "spawnID" : self.id,
- "spawnPos" : tuple(self.position),
- "uid" : datas["id"],
- "utype" : datas["etype"],
- "modelID" : datas["modelID"],
- "modelScale" : self.modelScale,
- "dialogID" : datas["dialogID"],
- "name" : datas["name"],
- "descr" : datas.get("descr", ''),
- }
- e = KBEngine.createEntity(datas["entityType"], self.spaceID, tuple(self.position), tuple(self.direction), params)
十五:Monster的AI(移动、攻击、思考)
Monster继承了一系列的接口, 每种接口对应于不同的功能。
(注意:这里使用的继承而没有用组件的原因是目前的设计def定义的远程方法只能与entity是同一个层的,可以理解为entity.xxx一级的属性,如果是组件形式则entity.component.xxx方法是无法被远程调用到的。
一定要使用组件形式也可以, 继承这些接口之后,在接口模块中实现组件, 如果有需要远程调用的接口则通过接口层向组件中转发)
复制代码
移动实体:
scripts/cell/Motion.py randomWalk : 随机走动, 通常用于怪物闲置状态时的走动
backSpawnPos: 返回出生点,如果怪物被引诱至较远距离,则返回到出生时的点,避免被玩家带到别处。
gotoEntity: 移动到目标实体的位置。
gotoPosition:移动到目标坐标点 实体继承与这个功能模块之后,实体就可以调用相关方法来移动了, 例如:monster.randomWalk()。 这些移动函数都是二次封装的,里面调用了引擎所提供的底层API函数来实现。
思考与攻击:
这里思考模块做的比较简单,只是添加了一个定时器以一定频率执行一些流程, 这些流程根据状态区分, 例如:怪物主状态为活着, 子状态为战斗时, 流程中(onThinkFight)会不断检查自己敌人列表的敌人,
根据敌人的情况决定是否攻击或者追击。 当距离敌人较远时使用“self.gotoPosition(entity.position, attackMaxDist - 0.2)”移动到离敌人较劲的可攻击距离, 当可攻击距离时对目标释放一个技能“self.spellTarget(skillID, entity.id)”
需要注意的是, 服务端上怪物成千上万, 而AI是比较耗的,如果只有一个玩家在线, 显然大量的怪物是不需要开启AI思考来白白耗掉CPU的, 这里有一个优化方法。
只有在玩家视野范围内的怪物才激活AI思考:
复制代码
Monster继承了一系列的接口, 每种接口对应于不同的功能。
(注意:这里使用的继承而没有用组件的原因是目前的设计def定义的远程方法只能与entity是同一个层的,可以理解为entity.xxx一级的属性,如果是组件形式则entity.component.xxx方法是无法被远程调用到的。
一定要使用组件形式也可以, 继承这些接口之后,在接口模块中实现组件, 如果有需要远程调用的接口则通过接口层向组件中转发)
- class Monster(KBEngine.Entity, // 每个实体都必须从引擎基本实体类型继承出来,这样引擎才可以维护,并拥有一些API特性
- NPCObject,
- Flags, // 一个管理标记信息的模块,标记如: 正在交易中、正在xx。
- State, // 状态模块, 主状态例如:死亡、活着。子状态例如:闲置状态、战斗状态
- Motion, // 关于移动的封装
- Combat, // 关于战斗公式、战斗属性等等的封装
- Spell, // 技能释放、buff/debuff维护等
- AI): // 智能思考模块
移动实体:
scripts/cell/Motion.py randomWalk : 随机走动, 通常用于怪物闲置状态时的走动
backSpawnPos: 返回出生点,如果怪物被引诱至较远距离,则返回到出生时的点,避免被玩家带到别处。
gotoEntity: 移动到目标实体的位置。
gotoPosition:移动到目标坐标点 实体继承与这个功能模块之后,实体就可以调用相关方法来移动了, 例如:monster.randomWalk()。 这些移动函数都是二次封装的,里面调用了引擎所提供的底层API函数来实现。
思考与攻击:
这里思考模块做的比较简单,只是添加了一个定时器以一定频率执行一些流程, 这些流程根据状态区分, 例如:怪物主状态为活着, 子状态为战斗时, 流程中(onThinkFight)会不断检查自己敌人列表的敌人,
根据敌人的情况决定是否攻击或者追击。 当距离敌人较远时使用“self.gotoPosition(entity.position, attackMaxDist - 0.2)”移动到离敌人较劲的可攻击距离, 当可攻击距离时对目标释放一个技能“self.spellTarget(skillID, entity.id)”
需要注意的是, 服务端上怪物成千上万, 而AI是比较耗的,如果只有一个玩家在线, 显然大量的怪物是不需要开启AI思考来白白耗掉CPU的, 这里有一个优化方法。
只有在玩家视野范围内的怪物才激活AI思考:
- def onWitnessed(self, isWitnessed):
- """
- KBEngine method.
- 此实体是否被观察者(player)观察到, 此接口主要是提供给服务器做一些性能方面的优化工作,
- 在通常情况下,一些entity不被任何客户端所观察到的时候, 他们不需要做任何工作, 利用此接口
- 可以在适当的时候激活或者停止这个entity的任意行为。
- @param isWitnessed : 为false时, entity脱离了任何观察者的观察
- """
- INFO_MSG("%s::onWitnessed: %i isWitnessed=%i." % (self.getScriptName(), self.id, isWitnessed))
- if isWitnessed:
- self.enable()