首先放个房间示例的资源路径https://github.com/u3dkbe/kbengine_unity3d_balls
这个例子很简单,客户端点击登录按钮后即进入场景,同时生成大量NPC。鼠标点击可移动吃分。不过这里只关心脚本涉及初始化这块。kbengine的服务器用python写脚本,各种系统回调以及自己写的回调夹杂在一起,不熟悉会相当很晕。。所以写这个也权当笔记。。。
首先客户端向服务端发送登录消息后,服务端自动检测默认设置为帐号属性的类(这个DEMO里面是Avatar)。然后自动调用Avatar BASE部分的初始化代码。初始化完成后,会调用一个回调函数:onEnitiesEnabled。这个回调是引擎接口,只需要往里面添加自己的代码就好。demo在这个回调里面,利用全局参数获得大厅HALL的指针,调用了enterRoom函数。
def onEntitiesEnabled(self):
"""
KBEngine method.
该entity被正式激活为可使用, 此时entity已经建立了client对应实体, 可以在此创建它的
cell部分。
"""
INFO_MSG("Avatar[%i] entities enable. mailbox:%s" % (self.id, self.client))
# 如果销毁玩家计时器已经开启了,此处玩家又上线了那么应该取消计时器
if self._destroyTimer > 0:
self.delTimer(self._destroyTimer)
self._destroyTimer = 0
# 如果玩家存在cell, 说明已经在地图中了, 因此不需要再次进入地图
if self.cell is None:
# 玩家上线了或者重登陆了, 此处告诉大厅,玩家请求登陆到游戏地图中
KBEngine.globalData["Halls"].enterRoom(self, self.cellData["position"], self.cellData["direction"], self.roomKey)
那这个HALL类是哪里来的呢?其实是在引擎初始化脚本里面。当启动服务器时,调用了KBEngine的base部分的引擎接口onBaseAppReady。在这个回调里面通过KBEngine.createBaseLocally函数创建了HALL类。
def onBaseAppReady(isBootstrap):
"""
KBEngine method.
baseapp已经准备好了
@param isBootstrap: 是否为第一个启动的baseapp
@type isBootstrap: BOOL
"""
INFO_MSG('onBaseAppReady: isBootstrap=%s, appID=%s, bootstrapGroupIndex=%s, bootstrapGlobalIndex=%s' % \
(isBootstrap, os.getenv("KBE_COMPONENTID"), os.getenv("KBE_BOOTIDX_GROUP"), os.getenv("KBE_BOOTIDX_GLOBAL")))
# 安装监视器
Watcher.setup()
if isBootstrap:
# 创建大厅
KBEngine.createBaseLocally( "Halls", {} )
回到HALL.enterRoom这个函数来,通过调用findRoom来寻找房间,这个函数查找是否存在已有的房间,若不存在的话则通过KBEngine.createBaseAnywhere异步创建Room类。随后判断,如果房间异步创建还未成功,则先把要加入房间的avatar信息保存下来。
def enterRoom(self, entityMailbox, position, direction, roomKey):
"""
defined method.
请求进入某个Room中
"""
roomDatas = self.findRoom(roomKey, True)
roomDatas["PlayerCount"] += 1
roomMailbox = roomDatas["roomMailbox"]
if roomMailbox is not None:
roomMailbox.enterRoom(entityMailbox, position, direction)
else:
DEBUG_MSG("Halls::enterRoom: space %i creating..., enter entityID=%i" % (roomDatas["roomKey"], entityMailbox.id))
roomDatas["enterRoomReqs"].append((entityMailbox, position, direction))
def findRoom(self, roomKey, notFoundCreate = False):
"""
查找一个指定房间,如果找不到允许创建一个新的
"""
roomDatas = self.rooms.get(roomKey)
# 如果房间没有创建,则将其创建
if not roomDatas:
if not notFoundCreate:
return FIND_ROOM_NOT_FOUND
# 如果最后创建的房间没有满员,则使用最后创建的房间key,否则产生一个新的房间唯一Key
roomDatas = self.rooms.get(self.lastNewRoomKey)
if roomDatas is not None and roomDatas["PlayerCount"] < GameConfigs.ROOM_MAX_PLAYER:
return roomDatas
self.lastNewRoomKey = KBEngine.genUUID64()
# 将房间base实体创建在任意baseapp上
# 此处的字典参数中可以对实体进行提前def属性赋值
KBEngine.createBaseAnywhere("Room", \
{
"roomKey" : self.lastNewRoomKey, \
}, \
Functor.Functor(self.onRoomCreatedCB, self.lastNewRoomKey))
roomDatas = {"roomMailbox" : None, "PlayerCount": 0, "enterRoomReqs" : [], "roomKey" : self.lastNewRoomKey}
self.rooms[self.lastNewRoomKey] = roomDatas
return roomDatas
return roomDatas
Room被异步调用创建后,首先是base部分的初始化。demo在这个初始化代码里面调用了createInNewSpace函数,异步创建Room的cell部分。Room cell部分的创建成功后,会调用Base部分的回调函数onGetCell。demo在这个回调函数里面,调用了HALL.onRoomGetCell,而这个函数利用先前保存的avatar信息,调用avatar.creatCellEntity来创建avatar的cell部分。avatar cell创建成功后同样会调用BASE部分的onGetCell回调。至此,avatar正式加入场景。看到这里,有没有觉得一堆回调让人很晕。。。
def onGetCell(self):
"""
KBEngine method.
entity的cell部分实体被创建成功
"""
DEBUG_MSG("Room::onGetCell: %i" % self.id)
KBEngine.globalData["Halls"].onRoomGetCell(self, self.roomKey)
def onRoomGetCell(self, roomMailbox, roomKey):
"""
defined method.
Room的cell创建好了
"""
self.rooms[roomKey]["roomMailbox"] = roomMailbox
# space已经创建好了, 现在可以将之前请求进入的玩家全部丢到cell地图中
for infos in self.rooms[roomKey]["enterRoomReqs"]:
entityMailbox = infos[0]
entityMailbox.createCell(roomMailbox.cell)
self.rooms[roomKey]["enterRoomReqs"] = []
def createCell(self, space):
"""
defined method.
创建cell实体
"""
self.createCellEntity(space)
avatar只是我们控制的角色,那那些NPC是怎么加入场景的呢?在ROOM的cell部分的初始化代码里,利用addTimer加入了一个定时器。这个定时器干嘛的呢?通过定期回调onTimer,调用了room cell部分的balanceMass函数,通过KBEngine.createEntity函数生成了大量的NPC。所以这里demo其实是演示了一个通过定时器,避免同时大量创建物体NPC的办法,减轻服务器压力。
def onTimer(self, id, userArg):
"""
KBEngine method.
使用addTimer后, 当时间到达则该接口被调用
@param id : addTimer 的返回值ID
@param userArg : addTimer 最后一个参数所给入的数据
"""
if TIMER_TYPE_DESTROY == userArg:
self.onDestroyTimer()
elif TIMER_TYPE_BALANCE_MASS == userArg:
self.balanceMass()