阶段目标
- 利用KBE的Event事件系统解耦客户端相应逻辑
- 匹配和房间结构调整
- 顶号和断线重连回战场
效果演示
进入战场流程调整
之前版本中,大厅和战场都是SpaceRoom,这会导致一个问题,就是我们需要给space加一个type字段,然后根据type来区分具体的战场类型,而且大厅和战场的进入逻辑并不一致,会导致在相应的接口里面做一堆的if…else判断,这明显会增加代码的复杂性和可读性。
针对以上这些问题,server端将大厅space和战场space进行拆分, 分别是GameHall和GameBattleSpace,同时匹配的功能也拆分出一个单独的Entity来完成,叫做MatchStub。
同时调整匹配和进入战场的流程,调整后的流程图如下:
其中Cell_xxx代表的是cellapp部分的名为xxx的entity实体,其余都是baseapp上的实体。
具体代码,可以搜索流程图中的函数名进行查看。
断线重连进战场
断线重连主要就两个问题,一个是网络连接的问题,另一个是恢复战场数据的问题。
- 对于已经在线的账号,我们再次登录该账号时,会触发脚本层的onLogOnAttempt方法,一些具体的业务逻辑可以在这里处理。
- proxy对象是用于和client通信的对象,主要处理网络连接。可以通过giveClientTo将当前客户端连接的proxy转交给一个entity实体。
- 玩家断线后PlayerAvatar是否需要销毁的问题。
- 如果销毁,那么每次重连就需要重新创建一个PlayerAvatar,可以复用首次登陆游戏的逻辑,但这么做会多一次连接GameHall再跳转到GameBattleSpace的流程,同时需要保证其他客户端本地的对象索引不能是Entity.id,因为重新创建的PlayerAvatar对象的唯一id是不同的。
- 如果不销毁,可以直接查找到之前的PlayerAvatar,然后将proxy进行更新即可。不过无法直接复用首次登陆流程。
- 最终游戏采用的是不销毁PlayerAvatar的方式。
针对上面两点,其实就差不多可以完成断线重连的逻辑处理了。但是,在过程中,缺遇到了一个坑,坑了我好久,有坑的代码示例如下:
def onClientEnabled(self):
"""
KBEngine method.
该entity被正式激活为可使用, 此时entity已经建立了client对应实体, 可以在此创建它的
cell部分。
"""
INFO_MSG("333333333333333333333")
self.become_player_avatar()
def onLogOnAttempt(self, ip, port, password):
INFO_MSG("[Account], %i onLogOnAttempt: ip=%s, port=%i, selfclient=%s, %s" % (self.id, ip, port, self.client, self.active_avatar))
if self.active_avatar is not None:
INFO_MSG("111111111111111111111")
if self.active_avatar.client is not None:
self.active_avatar.giveClientTo(self)
// 此处省略其他代码
INFO_MSG("222222222222222222222")
return KBEngine.LOG_ON_ACCEPT
上诉代码,模拟一次顶号重连,猜猜日志会输出啥?
我预期输出是:
111111111111111111111
222222222222222222222
333333333333333333333
而实际输出却是:
111111111111111111111
333333333333333333333
222222222222222222222
这个流程可真的是出乎我的意料,也就导致我最开始写的重连流程都是有问题的,折腾了许久,最后结合源码调试才发现了问题所在(KBEngine相关源码分析准备在后续文章中写写)。原因在于giveClientTo这个函数,会触发一次目标对象的onClientEnabled回调。
最后的顶号重连代码如下:
def onClientEnabled(self):
"""
KBEngine method.
该entity被正式激活为可使用, 此时entity已经建立了client对应实体, 可以在此创建它的
cell部分。
"""
INFO_MSG("account[%i] entities enable. entityCall:%s" % (self.id, self.client))
# 原账号在线
if self.active_avatar:
if self.active_avatar.reconnect_flag:
INFO_MSG("[Account], %i re login avatar battle space id:%s" % (self.id, self.active_avatar.battle_space_id))
self.giveClientTo(self.active_avatar)
self.active_avatar.re_connect_to_battle_space()
return
self.become_player_avatar()
def onLogOnAttempt(self, ip, port, password):
"""
KBEngine method.
客户端登陆失败时会回调到这里reloginBaseapp
"""
INFO_MSG("[Account], %i onLogOnAttempt: ip=%s, port=%i, selfclient=%s, %s" % (self.id, ip, port, self.client, self.active_avatar))
# 置空掉之前avatar的client
if self.active_avatar:
self.active_avatar.giveClientTo(self)
self.active_avatar.reconnect_flag = True
return KBEngine.LOG_ON_ACCEPT
PlayerAvatar的重连战场
def re_connect_to_battle_space(self):
if self.battle_space_id > 0:
_battle_space = KBEngine.entities.get(self.battle_space_id, None)
_battle_space_cell = _battle_space.cell
if _battle_space and _battle_space_cell:
INFO_MSG("[PlayerAvatar], %s, %i, re_connect_to_battle_space" % (self, self.id))
self.cell.teleport_space(_battle_space_cell, self.born_position, (0, 0, 0))
后续内容
到这里,关于游戏脚本层想写的其实我都写完了,对于所谓的战斗同步啥的,都是相应的游戏逻辑实现了,思想都类似,就不准备写了,以上三篇文章,完成了一个账号的创建、登录、重连回战场,以及战场内的移动同步。对于我来说,通过自己去实现一遍这个流程,对于KBEngine的基本使用目标已经达成。
后续,就开始慢慢剖析KBEngine的源码了,那才是我的主要学习和记录对象哈。以具体的游戏需求来引导自己入门上手一款引擎,然后开始剖析其背后的原理和实现。关于源码的学习记录,准备另外开个系列的标题文章来写,慢慢看,慢慢学,慢慢记录。
源码地址
github地址:地址在这里