十四:在世界中投放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方法是無法被遠程調用到的。
一定要使用組件形式也可以, 繼承這些接口之後,在接口模塊中實現組件, 如果有需要遠程調用的接口則通過接口層向組件中轉發)
- 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()
複製代碼